/*
* 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;
}
}