/* * File : FileSystem.java * Created : 20-jun-2000 9:45 * By : fbusquets * * JClic - Authoring and playing system for educational activities * * Copyright (C) 2000 - 2005 Francesc Busquets & Departament * d'Educacio de la Generalitat de Catalunya * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 (see the LICENSE file). */ package edu.xtec.jclic.fileSystem; import edu.xtec.jclic.misc.Utils; import edu.xtec.util.*; import java.awt.Component; import java.awt.Image; import java.awt.Toolkit; import java.io.*; import java.net.*; import java.util.List; import javax.swing.JFileChooser; /** * Base class for Clic filesystems. * * @author Francesc Busquets (fbusquets@xtec.cat) * @version 13.09.10 */ public class FileSystem extends Object { public static final String FS = "/", WINFS = "\\"; public static final char FSCH = '/', WINFSCH = '\\'; protected static FileChooserForFiles fileChooser; //private static final String SEP_FILE_BAK=File.separator+".."; private static final String FS_BAK = FS + ".."; public static java.util.HashMap<String, String> altFileNames = new java.util.HashMap<String, String>(); public String root; protected boolean isURL = false; protected Boolean ISURL = isURL; protected ResourceBridge rb = null; public FileSystem(ResourceBridge rb) { root = ""; this.rb = rb; } public FileSystem(String rootPath, ResourceBridge rb) { root = stdFn(rootPath); this.rb = rb; if (root == null) { root = ""; } if (root.length() > 0) { if (isStrUrl(root)) { isURL = true; ISURL = isURL; //if(root.indexOf('\\')>=0) // root=root.replace('\\', '/'); //if(!root.endsWith("/")) root = root + "/"; if (!root.endsWith(FS)) { root = root + FS; } root = getCanonicalNameOf(root); } else { File f = new File(sysFn(root)); String saveRoot = root; try { root = stdFn(f.getCanonicalPath()); } catch (Exception e) { root = saveRoot; } //if(!root.endsWith(File.separator)) // root=root+File.separator; if (!root.endsWith(FS)) { root = root + FS; } } } } public FileSystem duplicate() throws Exception { return createFileSystem(root, rb); } protected void changeBase(String newRoot, String newFileName) throws Exception { File f = new File(sysFn(newRoot)); String saveRoot = root; try { root = stdFn(f.getCanonicalPath()); } catch (Exception e) { root = saveRoot; } if (!root.endsWith(FS)) { root = root + FS; } } public static String stdFn(String s) { return s == null ? s : s.replace(WINFSCH, FSCH); } public static String sysFn(String s) { String result = s; if (result != null) { result = stdFn(result).replace(FSCH, File.separatorChar); if (result.indexOf("%20") >= 0) { result = StrUtils.replace(result, "%20", " "); } } return result; } public static FileSystem createFileSystem(String rootPath, String fileName, ResourceBridge rb) throws Exception { if (fileName == null) { return new FileSystem(rootPath, rb); } else if (fileName.endsWith(".pcc")) { return PCCFileSystem.createPCCFileSystem(rootPath, fileName, rb); } else if (fileName.endsWith(".zip")) { return ZipFileSystem.createZipFileSystem(rootPath, fileName, rb); } else { throw new Exception("unknown format " + fileName); } } public static FileSystem createFileSystem(String fullPath, ResourceBridge rb) throws Exception { fullPath = getCanonicalNameOf(fullPath, null); String fileName = null; //String rootPath=getPathOf(fullPath); String rootPath = getPathPartOf(fullPath); if (fullPath.endsWith(".pcc") || fullPath.endsWith(".zip")) { fileName = getFileNameOf(fullPath); } return createFileSystem(rootPath, fileName, rb); } public String getFullFileNamePath(String fName) { if (fName == null || fName.length() == 0) { return root.length() > 0 ? root.substring(0, root.length() - 1) : root; } String result = getCanonicalNameOf(fName); if (!isURL) { File f = new File(sysFn(result)); if (!f.isAbsolute()) { result = getCanonicalNameOf(root + result); } } else { if (!isStrUrl(result)) { result = getCanonicalNameOf(root + result); } } return result; } public String getRelativeFileNamePath(String fName) { String s = stdFn(fName); if (s == null || s.length() < root.length() || !s.substring(0, root.length()).equalsIgnoreCase(root)) { return s; } else { return s.substring(root.length()); } } public String getFullRoot() { return root; } public boolean isUrlBased() { return isURL; } public static boolean isStrUrl(String s) { return s != null && (s.startsWith("http:") || s.startsWith("https:") || s.startsWith("ftp:") || s.startsWith("mailto:")); } public String getUrl(String fileName) { String s = stdFn(fileName); if (s == null || isStrUrl(s) || s.startsWith("file:")) { return s; } if (!(s.charAt(1) == ':') && !s.startsWith(FS)) { s = getFullFileNamePath(s); } if (isURL) { return getCanonicalNameOf(s, ISURL); } else { return "file://" + sysFn(getCanonicalNameOf(s, ISURL)); } } public String getCanonicalNameOf(String fileName) { return getCanonicalNameOf(fileName, ISURL); } public static String getCanonicalNameOf(String fileName, Boolean isUrl) { String fn = stdFn(fileName); boolean flagUrl = (isUrl != null ? isUrl.booleanValue() : isStrUrl(fn)); //char sep= flagUrl ? '/' : File.separatorChar; //String sepStr= flagUrl ? "/" : File.separator; //String fn=normalizePath(fileName, sep); String prefix = ""; int cut = -1; if (fn.startsWith("file:")) { //fileName=fileName.substring(5); fn = fn.substring(5); } if (isStrUrl(fn)) { int k = fn.indexOf('@'); if (k < 0) { k = 7; } cut = fn.indexOf(FSCH, k); //cut=fn.indexOf(sep, k); } else if (fn.length() > 2 && fn.charAt(1) == ':') { cut = (fn.charAt(2) == FSCH ? 2 : 1); } //else if(fileName.startsWith("\\\\")){ else if (fn.startsWith("//")) { int i = fn.indexOf(FSCH, 2); cut = fn.indexOf(FSCH, i + 1); } else if (fn.startsWith(FS)) { cut = 0; } if (cut >= 0) { prefix = fn.substring(0, cut + 1); fn = fn.substring(cut + 1); } // interceptar '\...' //String back= flagUrl ? "/.." : SEP_FILE_BAK; int r; while ((r = fn.indexOf(FS_BAK)) >= 0) { int p; for (p = r - 1; p >= 0; p--) { if (fn.charAt(p) == FSCH) { break; } } StringBuilder newfn = new StringBuilder(); if (p >= 0) { newfn.append(fn.substring(0, p + 1)); } if (r + 4 < fn.length()) { newfn.append(fn.substring(r + 4)); } fn = newfn.substring(0); } return prefix + fn; } public static String getPathPartOf(String fullPath) { String s = stdFn(fullPath); int i = s.lastIndexOf(FS); //if((i=fullPath.lastIndexOf('\\'))<0) i=fullPath.lastIndexOf('/'); return i < 0 ? "" : s.substring(0, i + 1); } public static String getFileNameOf(String fullPath) { String s = stdFn(fullPath); int i = s.lastIndexOf(FS); //if((i=fullPath.lastIndexOf('\\'))<0) // i=fullPath.lastIndexOf('/'); return i < 0 ? s : s.substring(i + 1); } /* public static String normalizePath(String path){ return normalizePath(path, File.separatorChar); } public static String normalizePathToURL(String path){ return normalizePath(path, '/'); } */ /* public static String normalizePath(String path, char newSeparator){ if(path==null) return null; String result=new String(path); if(newSeparator!='\\') result=result.replace('\\', newSeparator); if(newSeparator!='/') result=result.replace('/', newSeparator); return result; } */ public byte[] getBytes(String fileName) throws IOException { return StreamIO.readInputStream(getInputStream(fileName)); } public Image getImageFile(String fName) throws Exception { return Toolkit.getDefaultToolkit().createImage(getBytes(fName)); } public long getFileLength(String fName) throws IOException { long length; if (isURL) { // Updated 04-Aug-2014 to solve GitHub issue #5 // https://github.com/projectestac/jclic/issues/5 URL url = new URL(getFullFileNamePath(fName).replace(" ", "%20")); URLConnection c = url.openConnection(); length = c.getContentLength(); } else { File f = new File(sysFn(getFullFileNamePath(fName))); length = f.length(); } return length; } public boolean fileExists(String fName) { boolean result; try { if (isURL) { // Updated 04-Aug-2014 to solve GitHub issue #5 // https://github.com/projectestac/jclic/issues/5 URL url = new URL(getFullFileNamePath(fName).replace(" ", "%20")); URLConnection c = url.openConnection(); result = (c.getContentLength() > 0); } else { File f = new File(sysFn(getFullFileNamePath(fName))); result = f.exists(); } } catch (Exception ex) { // eat exception result = false; } return result; } public InputStream getInputStream(String fName) throws IOException { InputStream result; int length; if (isURL) { // Updated 04-Aug-2014 to solve GitHub issue #5 // https://github.com/projectestac/jclic/issues/5 URL url = new URL(getFullFileNamePath(fName).replace(" ", "%20")); URLConnection c = url.openConnection(); length = c.getContentLength(); result = c.getInputStream(); } else { //File f=new File(getCanonicalNameOf(root + fName)); File f = new File(sysFn(getFullFileNamePath(fName))); if (!f.exists()) { String alt = (String) altFileNames.get(fName); if (alt != null) { f = new File(sysFn(getFullFileNamePath(alt))); } } length = (int) f.length(); result = new FileInputStream(f); } if (result != null && rb != null) { result = rb.getProgressInputStream(result, length, fName); } return result; } public Object getMediaDataSource(String fName) throws Exception { if (isURL) { return getExtendedByteArrayInputStream(fName); } return new StringBuilder("file:").append(getFullFileNamePath(fName)).substring(0); } public edu.xtec.util.ExtendedByteArrayInputStream getExtendedByteArrayInputStream(String fName) throws Exception { return new edu.xtec.util.ExtendedByteArrayInputStream(getBytes(fName), fName); } public static org.jdom.Document getXMLDocument(InputStream is) throws Exception { org.jdom.Document doc = JDomUtility.getSAXBuilder().build(is); edu.xtec.util.JDomUtility.clearNewLineElements(doc.getRootElement()); return doc; } public org.jdom.Document getXMLDocument(String fName) throws Exception { org.jdom.Document doc = buildDoc(fName, JDomUtility.getSAXBuilder()); edu.xtec.util.JDomUtility.clearNewLineElements(doc.getRootElement()); return doc; } protected org.jdom.Document buildDoc(String fName, org.jdom.input.SAXBuilder builder) throws Exception { return builder.build(getInputStream(fName)); } public void close() { // nothing to do } protected void open() throws Exception { // nothing to do } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } public static FileChooserForFiles getFileChooser(String root) { if (fileChooser == null) { fileChooser = new FileChooserForFiles(); if (root != null) { fileChooser.setCurrentDirectory(new File(sysFn(root))); } } return fileChooser; } /* public String chooseFile(String defaultValue, boolean save, int[] filters, edu.xtec.util.Options options, String titleKey, java.awt.Component dlgOwner, boolean proposeMove){ String result=null; FileChooserForFiles chooser; if(options!=null){ Messages msg=options.getMessages(); if(isURL){ if(save){ msg.showErrorWarning(dlgOwner, "filesystem_saveURLerror", null); } else{ result=msg.showInputDlg(dlgOwner, "filesystem_enterURL", "URL", "http://", titleKey!=null ? titleKey : "filesystem_openURL", false); } } else if((chooser=getFileChooser())!=null){ chooser.setCurrentDirectory(new File(sysFn(root))); chooser.setDialogType(save ? JFileChooser.SAVE_DIALOG : JFileChooser.OPEN_DIALOG); chooser.setDialogTitle(msg.get(titleKey!=null ? titleKey : save ? "FILE_SAVE" : "FILE_OPEN")); chooser.resetChoosableFileFilters(); if(filters!=null){ chooser.setAcceptAllFileFilterUsed(false); for(int i=0; i<filters.length; i++){ if(i==filters.length-1) chooser.setFileFilter(Utils.getFileFilter(filters[i], msg)); else chooser.addChoosableFileFilter(Utils.getFileFilter(filters[i], msg)); } } else chooser.setAcceptAllFileFilterUsed(true); String s=StrUtils.nullableString(defaultValue); boolean dummyFile=false; if(s==null){ s="."; dummyFile=true; } chooser.directSetSelectedFile(new File(sysFn(getFullFileNamePath(s)))); if(dummyFile) chooser.directSetSelectedFile(null); int retVal; boolean done=false; while(!done){ if(save) retVal = chooser.showSaveDialog(dlgOwner); else retVal = chooser.showOpenDialog(dlgOwner); if(retVal == JFileChooser.APPROVE_OPTION) { File f=chooser.getSelectedFile().getAbsoluteFile(); result=getRelativeFileNamePath(stdFn(f.getAbsolutePath())); if(save){ FileFilter filter=chooser.getFileFilter(); if(filter instanceof SimpleFileFilter){ f=((SimpleFileFilter)filter).checkFileExtension(f); result=getRelativeFileNamePath(stdFn(f.getAbsolutePath())); } done=msg.confirmOverwriteFile(dlgOwner, f); } else{ done=msg.confirmReadableFile(dlgOwner, f); if(done && proposeMove && root.length()>0 && result.indexOf(FS)>=0 && msg.showQuestionDlgObj(dlgOwner, new String[] { msg.get("filesystem_copyToRoot_1")+" "+result, msg.get("filesystem_copyToRoot_2"), msg.get("filesystem_copyToRoot_3"), msg.get("filesystem_copyToRoot_4"), }, "CONFIRM")){ String name=stdFn(f.getName()); File destFile=new File(sysFn(getFullFileNamePath(name))); if(msg.confirmOverwriteFile(dlgOwner, destFile)){ try{ OutputStream os=createSecureFileOutputStream(name); InputStream is=getInputStream(result); if(StreamIO.writeStreamDlg(is, os, (int)f.length(), msg.get("filesystem_copyFile"), dlgOwner, options)) result=name; else if(destFile.exists()) destFile.delete(); } catch(Exception ex){ msg.showErrorWarning(dlgOwner, "ERROR", ex); } } } } } else{ result=null; done=true; } } } } return result; } */ public String chooseFile(String defaultValue, boolean save, int[] filters, Options options, String titleKey, Component dlgOwner, boolean proposeMove) { String result = null; String[] files = chooseFiles(defaultValue, save, filters, options, titleKey, dlgOwner, proposeMove, false); if (files != null && files.length > 0) { result = files[0]; } return result; } public String[] chooseFiles(String defaultValue, boolean save, int[] filters, Options options, String titleKey, Component dlgOwner, boolean proposeMove, boolean multiSelection) { String[] result = null; FileChooserForFiles chooser; if (save) { multiSelection = false; } if (options != null) { Messages msg = options.getMessages(); if (isURL) { if (save) { msg.showErrorWarning(dlgOwner, "filesystem_saveURLerror", null); } else { String s = msg.showInputDlg(dlgOwner, "filesystem_enterURL", "URL", "http://", titleKey != null ? titleKey : "filesystem_openURL", false); if (s != null) { result = new String[]{s}; } } } else if ((chooser = getFileChooser(root)) != null) { chooser.setApproveButtonToolTipText(msg.get(save ? "FILE_SAVE_TOOLTIP" : "FILE_OPEN_TOOLTIP")); chooser.setDialogType(save ? JFileChooser.SAVE_DIALOG : JFileChooser.OPEN_DIALOG); chooser.setApproveButtonText(msg.get(save ? "SAVE" : "OPEN")); chooser.setMultiSelectionEnabled(multiSelection); //chooser.setCurrentDirectory(new File(sysFn(root))); chooser.setDialogTitle(msg.get(titleKey != null ? titleKey : save ? "FILE_SAVE" : "FILE_OPEN")); chooser.resetChoosableFileFilters(); if (filters != null) { chooser.setAcceptAllFileFilterUsed(false); for (int i = 0; i < filters.length; i++) { if (i == filters.length - 1) { chooser.setFileFilter(Utils.getFileFilter(filters[i], msg)); } else { chooser.addChoosableFileFilter(Utils.getFileFilter(filters[i], msg)); } } } else { chooser.setAcceptAllFileFilterUsed(true); } String s = StrUtils.nullableString(defaultValue); boolean dummyFile = false; if (s == null) { s = "."; dummyFile = true; } chooser.directSetSelectedFile(new File(sysFn(getFullFileNamePath(s)))); if (dummyFile) { chooser.directSetSelectedFile(null); } int retVal; boolean done = false; while (!done) { if (save) { retVal = chooser.showSaveDialog(dlgOwner); } else { retVal = chooser.showOpenDialog(dlgOwner); } if (retVal == JFileChooser.APPROVE_OPTION) { File[] files = multiSelection ? chooser.getSelectedFiles() : new File[]{chooser.getSelectedFile()}; result = new String[files.length]; for (int i = 0; i < files.length; i++) { File f = files[i].getAbsoluteFile(); result[i] = getRelativeFileNamePath(stdFn(f.getAbsolutePath())); if (save) { javax.swing.filechooser.FileFilter filter = chooser.getFileFilter(); if (filter instanceof SimpleFileFilter) { f = ((SimpleFileFilter) filter).checkFileExtension(f); result[i] = getRelativeFileNamePath(stdFn(f.getAbsolutePath())); } done = (msg.confirmOverwriteFile(dlgOwner, f, "yn") == Messages.YES); } else { done = msg.confirmReadableFile(dlgOwner, f); if (done && proposeMove && root.length() > 0 && result[i].indexOf(FS) >= 0 && msg.showQuestionDlgObj(dlgOwner, new String[]{msg.get("filesystem_copyToRoot_1") + " " + result[i], msg.get("filesystem_copyToRoot_2"), msg.get("filesystem_copyToRoot_3"), msg.get("filesystem_copyToRoot_4"),}, "CONFIRM", "yn") == Messages.YES) { String name = stdFn(f.getName()); File destFile = new File(sysFn(getFullFileNamePath(name))); if (msg.confirmOverwriteFile(dlgOwner, destFile, "yn") == Messages.YES) { try { OutputStream os = createSecureFileOutputStream(name); InputStream is = getInputStream(result[i]); if (StreamIO.writeStreamDlg(is, os, (int) f.length(), msg.get("filesystem_copyFile"), dlgOwner, options)) { result[i] = name; } else if (destFile.exists()) { destFile.delete(); } } catch (Exception ex) { msg.showErrorWarning(dlgOwner, "ERROR", ex); } } } } if (!done) { break; } } } else { result = null; done = true; } } } } return result; } class SecureFileOutputStream extends FileOutputStream { boolean closed; File tempFile; File destFile; /** * Creates new SecureFileOutputStream */ private SecureFileOutputStream(File tempFile, File destFile) throws FileNotFoundException { super(tempFile); this.tempFile = tempFile; this.destFile = destFile; closed = false; } @Override public void close() throws IOException { super.close(); if (!closed) { closed = true; if (destFile != null) { boolean isCurrentFs = getFullRoot().equals(stdFn(destFile.getAbsolutePath())); if (isCurrentFs) { FileSystem.this.close(); } if (destFile.exists()) { destFile.delete(); } boolean renamed = tempFile.renameTo(destFile); if (!renamed) { System.err.println("WARNING: Unable to rename " + tempFile + " to " + destFile.getName()); } if (isCurrentFs) { try { if (!renamed) { // Try to continue using the temp file as base changeBase(tempFile.getParent(), tempFile.getName()); } FileSystem.this.open(); } catch (Exception ex) { throw new IOException(ex.getMessage()); } } } } } } public FileOutputStream createSecureFileOutputStream(String fileName) throws IOException { FileOutputStream result; File file = new File(sysFn(getFullFileNamePath(fileName))); // 27-Sept-2006: Create parent directories if they don't exists file.getParentFile().mkdirs(); // --------- // Fatal errrors detected probably caused by bad info on file.exists // Try to use SecureFileOutputStream always // //if(file.exists()){ File tmp = File.createTempFile("tmp", ".tmp", file.getParentFile()); result = new SecureFileOutputStream(tmp, file); //} else{ // result=new FileOutputStream(file); //} return result; } // 16-Mar-2015: Added maxRecursion and maxFiles to prevent hangs in large file systems public static void exploreFiles(String prefix, File f, List<String> v, char pathSep, FileFilter filter, int maxRecursion, int maxFiles) { File[] files = filter == null ? f.listFiles() : f.listFiles(filter); StringBuilder sb = new StringBuilder(); for (File file : files) { sb.setLength(0); if (file.isDirectory()) { if (maxRecursion != 0) { if (prefix != null) { sb.append(prefix); } sb.append(file.getName()).append(pathSep); exploreFiles(sb.substring(0), file, v, pathSep, filter, maxRecursion - 1, maxFiles); } } else { if (prefix != null) { sb.append(prefix); } v.add(sb.append(file.getName()).substring(0)); } if (maxFiles > 0 && v.size() >= maxFiles) break; } } // Modified 10-Aug-2015 // Allow only plain ASCII characters, dot, numbers and underscore // private static final String FNAME_VALID_CHARS = "_!~0123456789abcdefghijklmnopqrstuvwxyz"; private static final String FNAME_VALID_CHARS = "_.0123456789abcdefghijklmnopqrstuvwxyz"; // Modified 26-jul0-2006 // Scope of character conversion limited to the basic ANSI (Latin1) set public static final String FNAME_CONVERTIBLE_CHARS = "\u00e1\u00e0\u00e4\u00e2\u00e3\u00e9\u00e8\u00eb\u00ea\u00ed\u00ec\u00ef\u00ee\u00f3\u00f2\u00f6\u00f4\u00f5\u00fa\u00f9\u00fc\u00fb\u00f1\u00e7\u20ac\u00ba\u00aa\u00e5\u00e6\u00f8\u00fd\u00fe\u00ff\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9"; private static final String FNAME_EQUIVALENT_CHARS = "aaaaaeeeeiiiiooooouuuunceoaaaoypyabgdezhtjklmnkoprsstufxpo"; public static String getValidFileName(String fn) { String result = null; if (fn != null) { StringBuilder sb = new StringBuilder(); for (char ch : fn.toCharArray()) { ch = Character.toLowerCase(ch); if (FNAME_VALID_CHARS.indexOf(ch) < 0) { int p = FNAME_CONVERTIBLE_CHARS.indexOf(ch); if (p >= 0) { ch = FNAME_EQUIVALENT_CHARS.charAt(p); } else { ch = '_'; } } sb.append(ch); } result = sb.substring(0); } return result; } }