/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * FileManager.java * * Created on September 16, 2003, 9:31 AM * * The software contained in this file is copyright 2003 by Mark J. Norton, all rights reserved. */ package tufts.oki.localFiling; import java.io.*; import java.util.*; import javax.swing.filechooser.FileSystemView; import tufts.oki.shared.*; /** * The Local Filing Manager manages a local filing system. It allows manipulation of * both directories and files via Cabinets and ByteStores. The LocalFilingManager * initializes a set of roots based on drive letters (if PC) or slash (if Unix or Mac). * In addition, an addRoot() method is provided to take an arbitrary path and treat it * as another root. This allows a GUI to avoid showing unneeded level to deeply nexted * directories. * <p> * The system defines a current working directory, which is initially the root (or C:\), but can * be changed to any directory added to this filing system. To avoid an excessive amount * of information in memory at one time, directories below the working directory can be * opened using LocalCabinet.entries(). This adds CabinetEntry's to the current directory * corresponding to sub-directories and files. * * @author Mark Norton * */ public class LocalFilingManager extends tufts.oki.OsidManager implements osid.filing.FilingManager { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(LocalFilingManager.class); private static final int BUFFER_SIZE = 1024; // Size of buffer to use. public boolean trace = false; // Set this to true to trace operations. private SortedSet rootCabinets = null; //private LocalCabinet root = null; // The root cabinet for a client-session. private LocalCabinet cwd = null; // The current working directory. /** * Creates a new instance of the LocalFileManager */ public LocalFilingManager() throws osid.filing.FilingException { super(); rootCabinets = new TreeSet(new LocalCabinetEntryComparator()); initializeRoots(); } /** * Add the path as a root of the local file system. This root need not be a disk drive * root. It can be any directory on the local file system. This subsequently * serves as a virtual root. * * @author Mark Norton */ public void addRoot (String path) throws osid.filing.FilingException { LocalCabinet root = null; try { osid.shared.Agent agent = new Agent("unknown", new AgentPersonType()); if (Log.isDebugEnabled()) Log.debug(this + "; addRoot " + path); root = LocalCabinet.instance(path, agent, null); rootCabinets.add(root); cwd = root; } catch (osid.shared.SharedException ex1) { throw new osid.filing.FilingException (osid.filing.FilingException.OPERATION_FAILED); } catch (osid.OsidException ex3) { throw new osid.filing.FilingException (osid.filing.FilingException.OPERATION_FAILED); } /* This code is obsolete now that rootBase is kept by the root Cabinet itself. // Remove final node from root path name. String[] parts = this.explodePath(path, root.separatorChar()); if (trace) System.out.println ("setRoot - rootBase part count: " + parts.length); if (parts.length > 1) { String[] baseParts = new String[parts.length]; for (int i = 0; i < parts.length; i++) baseParts[i] = parts[i]; //rootBase = this.implodePath (baseParts, root.separator()); } //else //rootBase = parts[0]; */ //openDirectory(); // Open the root directory. root.entries(); // Initialize the entries in this directory. } /** * Gets the root cabinets for the local file system. */ // private static final ArrayList list = new ArrayList(); // { // Log.debug("File.listRoots..."); // File[] roots = File.listRoots(); // for (int i=0;i<roots.length;i++) // list.add(roots[i]); // Log.debug("File.listRoots: " + list); // } private void initializeRoots() throws osid.filing.FilingException { final String[] drives = {"C","D","E","F","G","H","I","J","K","L","M","N", "O","P","Q","R","S","T","U","V","W","X","Y","Z"}; Log.debug("initializeRoots; in " + tufts.Util.tags(this) + "..."); // Create dummy owner. osid.shared.Agent agent = null; try { agent = new Agent("unknown", new AgentPersonType()); } catch (osid.shared.SharedException ex) { throw new osid.filing.FilingException (osid.filing.FilingException.OPERATION_FAILED); } // If there are no roots, then scan the PC drive letters and try to open each. // If files exist, then add that drive as a root. if (tufts.Util.isWindowsPlatform()) { if (rootCabinets.size() == 0) { File[] f = File.listRoots(); for (int i=0;i<f.length;i++) { File file = f[i]; LocalCabinet newRoot = LocalCabinet.instance(file.toString(), agent, null); rootCabinets.add (newRoot); if (drives[i].compareTo("C:") == 0) cwd = newRoot; } } } else { if (rootCabinets.size() == 0) { for (int i = 0; i < drives.length; i++) { //----------------------------------------------------------------------------- // TODO: Why are we looking for drives on non-window platforms? // Are there cases where such files could ever exists? SMF 2008-04-16 //----------------------------------------------------------------------------- File file = new File(drives[i]+":" + java.io.File.separator); // * Trying out a test for removable disks here... boolean isRemovableDisk = false; //isRemovableDisk = view.getSystemTypeDescription(file).equals("Removable Disk"); // System.out.println("is removable disk : " + isRemovableDisk); //} //isRemovableDisk = !FileSystemView.getFileSystemView().getSystemTypeDescription(file).equals("Removable Disk"); if (!isRemovableDisk && file.exists()) { if (Log.isDebugEnabled()) Log.debug(this + "; checking " + file); String idStr = drives[i]+":" + java.io.File.separator; //this.rootCabinets.put(idStr, new LocalCabinet(this, null, idStr)); LocalCabinet newRoot = LocalCabinet.instance(idStr, agent, null); // LocalCabinet newRoot = new LocalCabinet (idStr, agent, null); rootCabinets.add (newRoot); if (drives[i].compareTo("C:") == 0) cwd = newRoot; } } } } // If this is not a PC environment, drive letters won't likely work, so try to // open a Unix root, "/". This is likely to work for Mac OS-X as well. // 2004-10-11 SMF: If this is Mac OS X, we should grab the items // in /Volumes, which is all mounted drives (including network), and // a symlink to /, named as whatever the user wanted to name their hard-drive // (e.g., "Macintosh HD") if (rootCabinets.size() == 0) { File file = new File ("/"); if (file.exists()) { rootCabinets.add (new LocalCabinet ("/", agent, null)); } } // If the current working directory has not been set, set it to the first root. if ((cwd == null) && rootCabinets.size() > 0) cwd = (LocalCabinet) rootCabinets.first(); if (Log.isDebugEnabled()) Log.debug("initializeRoots; in " + tufts.Util.tags(this) + ": completed."); } /** * No mention is made if the entry in question is a cabinet or not. Since only * root cabinets exist at FilingManager level, we can probably assume that the * search for this entry should recurse to sub-cabinents. * * @author Mark Norton * */ public osid.filing.CabinetEntry getCabinetEntry(osid.shared.Id id) throws osid.filing.FilingException { osid.filing.CabinetEntry found = null; osid.filing.CabinetEntryIterator it = listRoots(); while (it.hasNext()) { /* Search the root cabinets for the entry with id. */ LocalCabinet entry = (LocalCabinet) it.next(); //LocalCabinet entry = root; osid.shared.Id entry_id = entry.getId(); /* Check to see if the entry we are looking for is a root cabinet. */ try { if (entry_id.isEqual(id)) found = entry; /* Otherwise, check sub-cabinets to see if it's in there. */ else { try { found = entry.getCabinetEntryById(id); } catch (osid.filing.FilingException ex) { /* An UNKNOWN_ID exception is expected. Continue search. */ } } } catch (osid.shared.SharedException ex) { /* isEqual doesn't really throw an exception. */ } } /* If found is null at this point, we didn't find it. */ if (found == null) throw new osid.filing.FilingException (osid.filing.FilingException.ITEM_DOES_NOT_EXIST); return found; } /** * The documentation indicates that the cabinet being deleted must be empty and * that the owner must have permissions to do this. Permissions are not implemented * at this time. * <br> * The cabinet entry being deleted is assumed to be the root cabinet. To delete * sub-cabinets, see Cabinet.remove(); If the entry is not found, nothing happens. * * @author Mark Norton * */ public void delete(osid.shared.Id cabinetEntryId) throws osid.filing.FilingException { throw new osid.filing.FilingException (osid.filing.FilingException.UNIMPLEMENTED); } /** * Return an iterator over all root cabinets. The osid.filing interface supports * multiple file roots. The LocalFilingManager only supports a single root. As * such, this creates a interator which lists a single root. * * @author Mark Norton * * return A CabinetEntryIterator which the root cabinet. */ public osid.filing.CabinetEntryIterator oldlistRoots() throws osid.filing.FilingException { Vector vect = new Vector(10); SortedSet set = new TreeSet(); //vect.add (root); osid.filing.CabinetEntryIterator it = (osid.filing.CabinetEntryIterator) new LocalCabinetEntryIterator(set); return it; } public osid.filing.CabinetEntryIterator listRoots() throws osid.filing.FilingException { return new LocalCabinetEntryIterator (rootCabinets); } /* Local File System Operations */ /* ---------------------------- */ /** * Open and intialized the LocalCabinet given if not done previously. */ public void openDirectory (LocalCabinet dir) throws osid.filing.FilingException { dir.entries(); // executed for the side effect of opening it. } public void XXopenDirectory (LocalCabinet dir) throws osid.filing.FilingException { // Check to see if previuosly initialized. Force open and return. if (dir.isInitialized()) { dir.setOpen (true); return; } // Initialize the directory by getting all entries contained in it. String[] files = null; //System.out.println ("Open Directory: " + this.cwd.getDisplayName()); files = dir.getFile().list(); if (trace) System.out.println ("openDirectory - files to open: " + files.length + " in " + dir.getFile().getAbsoluteFile()); // Iterate over the files returned and create CabinetEntries for them. // Note that there is a lot of other information in the FTPFile objects which // could be added to the entries being created here. In particular, creation // date. //String path = rootBase + dir.getFullName(); String rootBase = dir.getRootBase(); String path = rootBase + dir.getFullName(); if (trace) System.out.println ("openDirectory - path name: " + path); for (int i = 0; i < files.length; i++) { //File temp = new File (cwd.getPath(), files[i]); File temp = new File (rootBase+dir.getFullName(), files[i]); if (trace) System.out.println ("openDirectory - new file: " + rootBase + dir.getFullName() + files[i]); String absolute = null; if (dir.isRootCabinet()) absolute = path + temp.getName(); else absolute = path + dir.separator() + temp.getName(); if (temp.isDirectory()) { if (trace) System.out.println ("\tDir " + i + ": " + temp.getName() + "\t" + absolute); //cwd.createCabinet (temp.getName()); cwd.createCabinet (absolute); } else if (temp.isFile()) { if (trace) System.out.println ("\tFile " + i + ": " + temp.getName() + "\t" + absolute); //cwd.createByteStore (temp.getName()); cwd.createByteStore (absolute); } // Unknown cases are ignored. } // The current working directory is now set to open and intialized. dir.setOpen (true); dir.setInitialized (true); } /** * Open directory without an argument causes the current working directory to be * opened and intialized, if not done previously. */ public void openDirectory () throws osid.filing.FilingException { this.openDirectory (cwd); } /** * Close the current working directory. Note that this does not deallocate entries * included in the directory. It merely marks it as closed. * * @author Mark Norton */ /* Obsolete. public void closeDirectory() { cwd.setOpen(false); } */ /** * Set the working directory to the Cabinet indicated. * * @author Mark Norton */ public void setWorkingDirectory (LocalCabinet cabinet) throws osid.filing.FilingException { if (trace) System.out.println ("setWorkingDirectory.1 - cabinet to open: " + cabinet.getDisplayName()); this.cwd = cabinet; openDirectory(); // Open it up. } /** * Set the working directory to the Cabinet name indicated. * <p> * Three cases are supported by this command. If an isolated cabinet name is given, that * cabinet is assumed to be in the current working directory. This is like the Unix * command pushd. If the cabinet name is given as "..", the parent of the current * working directory is assumed. This is like the Unix command popd. Finally, if * a full path name is given, the current working directory is set to the cabinet * specified by the path. Note that this path name is relative to the local root * and which means it is not prefixed by rootBase. * <p> * Examples, assume the local root is c:/dir1 and the cwd is c:/dir1/dir2:<br> * 1. setWorkingDirectory ("dir3") --> c:/dir1/dir2/dir3<br> * 2. setWorkingDirectory ("..") --> c>/dir1<br> * 3. setWorkingDirectory ("/dir2/dir3") --> c:/dir1/dir2/dir3<br> * 4. setWorkingDirectory ("/dir2/dir3/dir4") --> c:/dir1/dir2/dir3/dir4 (dir3 is opened) * * @author Mark Norton */ public void setWorkingDirectory (String cabName) throws osid.filing.FilingException { osid.filing.CabinetEntry entry = null; if (trace) System.out.println ("setWorkingDirectory.2 - cabinet to open: " + cabName); String parts[] = this.explodePath(cabName, java.io.File.separatorChar); if (trace) System.out.println ("setWorkingDirectory.2 - node count is: " + parts.length); // Check the popd case where name is given as "..". if (cabName.compareTo("..") == 0) { entry = cwd.getParent(); } // Check for the local cabinet case. else if (parts.length == 1) { entry = cwd.getCabinetEntryByName(cabName); } // Otherwise, we have a path to unravel. else { entry = walkPath (cabName); } // Check to make sure that the entry found is indeed a LocalCabinet. if (!(entry instanceof LocalCabinet)) { throw new osid.filing.FilingException (osid.filing.FilingException.NOT_A_CABINET); } // Set the new entry to be the cached current working directory. cwd = (LocalCabinet) entry; // Make sure it is open. openDirectory(); } /** * Return the active root as a LocalCabinet. The active root is defined to be * the root directory associated with the current working directory. */ public LocalCabinet getRoot () { return (LocalCabinet) cwd.getRootCabinet(); } /** * List the contents of the current working directory by returning a * CabinetEntryIterator for entries in the working directory. Note that the entries * in this iterator are RemoteCabinetEntry's. * * @author Mark Norton */ public osid.filing.CabinetEntryIterator list () throws osid.filing.FilingException { return cwd.entries(); } /** * Get the current working directory as a RemoteCabinet object. * * @author Mark Norton */ public LocalCabinet getWorkingDirectory () { return this.cwd; } /** * Open the name passed for input on the local file system. The name must be * an entry in the current working directory. * * @author Mark Norton */ public InputStream openForInput (String name) throws osid.filing.FilingException { // Find the entry for name. osid.filing.CabinetEntry theEntry = null; osid.filing.CabinetEntryIterator it = cwd.entries(); while (it.hasNext()) { osid.filing.CabinetEntry entry = it.next(); if (name.compareTo(entry.getDisplayName()) == 0) { theEntry = entry; break; } } // Open the file for input access. FileInputStream stream = null; File file = ((LocalByteStore)theEntry).getFile(); if (trace) System.out.println ("openForInput - file to open: " + file.getAbsolutePath()); try { stream = new FileInputStream (file); } catch (java.io.IOException ex1) { throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR); } return stream; } public void closeInput (InputStream stream) throws java.io.IOException { stream.close(); } /** * Open the name passed for output on the local file system. If the file named * exists, an exception is thrown. If it doesn't exist, it is created in the * current working directory. * * @author Mark Norton */ public OutputStream openForOutput (String name) throws osid.filing.FilingException { FileOutputStream stream = null; // Get the path to the current working directory. String rootBase = cwd.getRootBase(); String path = rootBase + cwd.getFullName(); // Make a temporary file. File temp = new File (rootBase + cwd.getFullName(), name); if (trace) System.out.println ("openForOutput - new file: " + rootBase + cwd.getFullName() + name); // Create the absolute file name. String absolute = null; if (cwd.isRootCabinet()) absolute = path + temp.getName(); else absolute = path + cwd.separator() + temp.getName(); // Check to see if the file already exists. try { cwd.getCabinetEntryByName(absolute); new osid.filing.FilingException (osid.filing.FilingException.ITEM_ALREADY_EXISTS); } catch (osid.filing.FilingException ex1) { // If the file doesn't exist, that's good! } // Open the file for output access. Add it to the working directory. try { LocalByteStore bs = (LocalByteStore)cwd.createByteStore (absolute); File file = bs.getFile(); if (trace) System.out.println ("openForInput - file to open: " + file.getAbsolutePath()); stream = new FileOutputStream (file); } catch (java.io.IOException ex1) { throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR); } return stream; } public void closeOutput (OutputStream stream) throws java.io.IOException { stream.close(); } /** * Copy the input stream to the output stream. * <p> * This is currently implemented as a byte by byte copy, but it's reasonably * efficient, since the copying is all done in memory with periodic flushing out * to disk. * * @author Mark Norton */ public void copy (InputStream in, OutputStream out) throws osid.filing.FilingException { //int data[] = new int[RemoteFilingManager.BUFFER_SIZE/4]; BufferedInputStream bin = new BufferedInputStream (in, LocalFilingManager.BUFFER_SIZE); BufferedOutputStream bout = new BufferedOutputStream (out, LocalFilingManager.BUFFER_SIZE); try { while (true) { int datum = bin.read(); if (datum == -1) break; bout.write(datum); } bout.flush(); } catch (java.io.IOException ex1) { throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR); } } /** * Rename the cabinet entry given to a new name. * Works for both cabinets and byte stores. * * @author Mark Norton */ public void rename (osid.filing.CabinetEntry old, String absolute, String newName) throws osid.filing.FilingException { if (old instanceof LocalByteStore) { ((LocalByteStore)old).rename (absolute, newName); } else if (old instanceof LocalCabinet) { ((LocalCabinet)old).rename (absolute, newName); } else throw new osid.filing.FilingException (osid.filing.FilingException.IO_ERROR); } /** * Find the oldName given in the current working directory and rename it to newName. * Works for both cabinets and byte stores. * * @author Mark Norton */ public void rename (String oldName, String newName) throws osid.filing.FilingException { osid.filing.CabinetEntry entry = cwd.getCabinetEntryByName(oldName); String rootBase = cwd.getRootBase(); String absolute = rootBase + cwd.getFullName() + newName; if (trace) System.out.println ("rename - new name: " + absolute); this.rename (entry, absolute, newName); } /** * Delete the entry given from the current working directory. */ public void delete (osid.filing.CabinetEntry entry) throws osid.filing.FilingException { cwd.remove (entry); } /** * Create a directory of the given name in the current working directory. * * @author Mark Norton */ public void createDirectory (String name) throws osid.filing.FilingException { String rootBase = cwd.getRootBase(); String path = rootBase + cwd.getFullName(); if (trace) System.out.println ("createDirectory - path: " + path); File temp = new File (cwd.getFullName(), name); String absolute = null; if (cwd.isRootCabinet()) absolute = path + temp.getName(); else absolute = path + cwd.separator() + temp.getName(); if (trace) System.out.println ("createDirectory - named " + ": " + temp.getName() + "\t" + absolute); File fin = new File (absolute); if (fin.mkdir()) { cwd.createCabinet (absolute); if (trace) System.out.println ("createDirectory - mkdir result: false"); } else { if (trace) System.out.println ("createDirectory - mkdir result: false"); } } /* Utility Methods */ /* --------------- */ /** * Get the path of the root cabinet. * * @author Mark Norton */ public String getRootPath () { LocalCabinet root = (LocalCabinet) cwd.getRootCabinet(); String rootBase = cwd.getRootBase(); return rootBase + root.getFullName(); } /** * Get the rootBase. */ public String getRootbase() { String rootBase = cwd.getRootBase(); return rootBase; } /** * Parse a path using the separator character provided. The parts of the * path are returned as an array of strings. * <p> * The path provided must be constructed with the separator character given. */ public String[] explodePath (String path, char separator) { ArrayList parts = new ArrayList(100); //char[] node = new char[256]; StringBuffer node = new StringBuffer(256); // Parse the pathname according to separator provided. for (int i = 0; i < path.length(); i++) { // If not at the end of a node, copy the characters of the node. if (path.charAt(i) != separator) { node.append (path.charAt(i)); } // Otherwise, add this node to the node list. else { parts.add (node.toString()); node.setLength(0); } } // Add the end of the path. if (node.length() != 0) parts.add(node.toString()); // Copy the ListArray into a String array. I shouldn't have to do this, but // ListArray.toArray() would cast into String[] properly. String[] strParts = new String[parts.size()]; for (int j = 0; j < parts.size(); j++) strParts[j] = (String)parts.get(j); return strParts; } /** * Assemble a path string from the parts and separator given. */ public String implodePath (String[] parts, String separator) { StringBuffer path = new StringBuffer(1024); if (parts.length > 0) { path.append(parts[0]); for (int i = 1; i < parts.length; i++) { path.append(separator); path.append(parts[i]); } } return path.toString(); } /** * Given a path to a directory name, walk the path and return the LocalCabinet * corresponding to the terminal node. If a path node is not currently open, * open it and proceed. * <p> * Please note that the path name given should be relative to the local root. * That means that the first node in the path should correspond to the root * cabinet held by this filing manager. */ public LocalCabinet walkPath (String path) throws osid.filing.FilingException { LocalCabinet root = (LocalCabinet) cwd.getRootCabinet(); // Parse out the path. String parts[] = this.explodePath(path, cwd.separatorChar()); // Check to make sure that the first node is indeed the root node. if (root.getDisplayName().compareTo(parts[0]) != 0) { throw new osid.filing.FilingException (osid.filing.FilingException.ITEM_DOES_NOT_EXIST); } // Starting at the root, work down the path to the directory indicated. osid.filing.Cabinet ptr = (osid.filing.Cabinet) root; for (int i = 1; i < parts.length; i++) { // Make sure that the directory we are at is open. if (! ((LocalCabinet)ptr).isOpen()) openDirectory((LocalCabinet)ptr); // Find the next node and set path pointer to that cabinet. ptr = (osid.filing.Cabinet) ptr.getCabinetEntryByName(parts[i]); } return (LocalCabinet) ptr; } }