Index: /trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java	(revision 11084)
+++ /trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java	(revision 11085)
@@ -84,9 +84,9 @@
      * @param job A previously created FileJob.
      */
-    synchronized private void addJob(FileJob job, int position) {
-	job.done = true; // Ensure that the requeued job is marked as done.
-	queue.add(position, job);
-	notify();
-    }
+//     synchronized private void addJob(FileJob job, int position) {
+// 	job.done = true; // Ensure that the requeued job is marked as done.
+// 	queue.add(position, job);
+// 	notify();
+//     }
 
     /** Add a new job to the queue, specifiying as many arguments as is necessary to complete this type of job (ie delete needs no target information).
@@ -107,14 +107,14 @@
     
     synchronized private void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean folder_level, int position) {
-	FileJob job = new FileJob(id, source, child, target, parent, type);
-	job.folder_level = folder_level;
-        DebugStream.println("Adding job: " + job);
-	if(position != -1 && position <= queue.size() + 1) {
-	    queue.add(position, job);
-	}
-	else {
+ 	FileJob job = new FileJob(id, source, child, target, parent, type);
+ 	job.folder_level = folder_level;
+	DebugStream.println("Adding job: " + job);
+ 	if(position != -1 && position <= queue.size() + 1) {
+ 	    queue.add(position, job);
+ 	}
+ 	else {
 	    queue.add(job);
-	}
-	notify();
+ 	}
+ 	notify();
     }
 
@@ -162,13 +162,13 @@
 
 
-    private int countFolderDepth(File file)
-    {
-	int depth = 0;
-	while (file != null) {
-	    depth++;
-	    file = file.getParentFile();
-	}
-	return depth;
-    }
+//     private int countFolderDepth(File file)
+//     {
+// 	int depth = 0;
+// 	while (file != null) {
+// 	    depth++;
+// 	    file = file.getParentFile();
+// 	}
+// 	return depth;
+//     }
 
 
@@ -202,4 +202,463 @@
     public GProgressBar getProgressBar() {
 	return progress;
+    }
+
+
+    synchronized private void addFileJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type)
+    {
+	queue.add(new FileJob(id, source, child, target, parent, type));
+	notify();
+    }
+
+
+    private void doEmptyDirectoryDelete(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	File source_directory = source_node.getFile();
+
+	// If the directory isn't empty then this will fail
+	if (source_directory.delete() == false) {
+	    // The source directory couldn't be deleted, so give the user the option of continuing or cancelling
+	    if (showErrorDialog(Dictionary.get("FileActions.Could_Not_Delete", source_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+
+	// Remove the node from the model
+	SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
+    }
+
+
+    private void doDirectoryDelete(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	File source_directory = source_node.getFile();
+
+	// The last thing we will do is delete this directory (which should be empty by then)
+	addFileJob(file_job.ID(), file_job.source, source_node, null, null, FileJob.DELETE_EMPTY_DIRECTORY);
+
+	// Add a new Delete job for each child of this directory (except metadata.xml files)
+	source_node.refresh();
+	for (int i = 0; i < source_node.size(); i++) {
+	    FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
+	    if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
+		addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
+	    }
+	}
+
+	// Treat metadata.xml files specially: delete them first
+	for (int i = 0; i < source_node.size(); i++) {
+	    FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
+	    if (child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
+		addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
+		break;
+	    }
+	}
+    }
+
+
+    private void doDirectoryCopy(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	FileNode target_node = file_job.getDestination();
+
+	File source_directory = source_node.getFile();
+	File target_directory = new File(target_node.getFile(), source_directory.getName());
+
+	// The target directory shouldn't already exist
+	if (target_directory.exists()) {
+	    if (showErrorDialog(Dictionary.get("FileActions.Folder_Already_Exists", target_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	target_directory.mkdirs();
+
+	// Create a node for the new directory in the collection tree
+	FileSystemModel target_model = (FileSystemModel) file_job.target.getTreeModel();
+	CollectionTreeNode new_target_node = new CollectionTreeNode(target_directory);
+	SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
+	new_target_node.setParent(target_node);
+
+	// Copy the non-folder level metadata assigned to the original directory to the new directory
+	ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(source_directory);
+	MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
+
+	// Add a new Copy job for each child of this directory (except metadata.xml files)
+	source_node.refresh();
+	for (int i = 0; i < source_node.size(); i++) {
+	    FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
+	    if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
+		addFileJob(file_job.ID(), file_job.source, child_file_node, file_job.target, new_target_node, FileJob.COPY);
+	    }
+	}
+    }
+
+
+    private void doDirectoryMove(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	FileNode target_node = file_job.getDestination();
+
+	File source_directory = source_node.getFile();
+	File target_directory = new File(target_node.getFile(), source_directory.getName());
+
+	// Check the target directory isn't the source directory
+	if (target_directory.equals(source_directory)) {
+	    DebugStream.println("Target directory is the source directory!");
+	    return;
+	}
+
+	// The target directory shouldn't already exist
+	if (target_directory.exists()) {
+	    if (showErrorDialog(Dictionary.get("FileActions.Folder_Already_Exists", target_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	target_directory.mkdirs();
+
+	// Create a node for the new directory in the collection tree
+	FileSystemModel target_model = (FileSystemModel) file_job.target.getTreeModel();
+	CollectionTreeNode new_target_node = new CollectionTreeNode(target_directory);
+	SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
+	new_target_node.setParent(target_node);
+
+	// Move the folder level metadata assigned to the original directory to the new directory
+	ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_directory);
+	MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
+	MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
+
+	// The last thing we will do is delete this directory
+	addFileJob(file_job.ID(), file_job.source, source_node, null, null, FileJob.DELETE);
+
+	// Treat metadata.xml files specially: delete them last
+	source_node.refresh();
+	for (int i = 0; i < source_node.size(); i++) {
+	    FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
+	    if (child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
+		addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
+		break;
+	    }
+	}
+
+	// Add a new Move job for each child of this directory (except metadata.xml files)
+	for (int i = 0; i < source_node.size(); i++) {
+	    FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
+	    if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
+		addFileJob(file_job.ID(), file_job.source, child_file_node, file_job.target, new_target_node, FileJob.MOVE);
+	    }
+	}
+    }
+
+
+    private void doFileDelete(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	File source_file = source_node.getFile();
+
+	// If we're deleting a metadata.xml file we must unload it
+	boolean metadata_xml_file = source_file.getName().equals(StaticStrings.METADATA_XML);
+	if (metadata_xml_file) {
+	    MetadataXMLFileManager.unloadMetadataXMLFile(source_file);
+	}
+	// Otherwise remove any metadata assigned directly to the file
+	else {
+	    ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
+	    MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
+	}
+
+	// Delete the source file
+	if (!Utility.delete(source_file)) {
+	    // The source file couldn't be deleted, so give the user the option of continuing or cancelling
+	    if (showErrorDialog(Dictionary.get("FileActions.File_Not_Deleted_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+
+	// Remove the node from the model
+	SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
+    }
+
+
+    private void doFileCopy(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	FileNode target_node = file_job.getDestination();
+
+	File source_file = source_node.getFile();
+	File target_file = new File(target_node.getFile(), source_file.getName());
+
+	// The target file shouldn't already exist
+	if (target_file.exists()) {
+	    int result = showOverwriteDialog(target_file.getName());
+	    if (result == JOptionPane.NO_OPTION) {
+		// Don't overwrite
+		return;
+	    }
+	    if (result == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+		return;
+	    }
+	}
+
+	// Copy the file
+	try {
+	    copyFile(source_file, target_file, true);
+	}
+	catch (FileAlreadyExistsException exception) {
+	    // This should not ever happen, since we've called copyFile with overwrite set
+	    DebugStream.printStackTrace(exception);
+	    return;
+	}
+	catch (FileNotFoundException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(Dictionary.get("FileActions.File_Not_Found_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    // Refresh the source tree model
+	    FileSystemModel source_model = file_job.source.getTreeModel();
+	    source_model.refresh(new TreePath(((FileNode) file_job.getOrigin().getParent()).getPath()));
+	    return;
+	}
+	catch (InsufficientSpaceException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(Dictionary.get("FileActions.Insufficient_Space_Message", exception.getMessage())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	catch (IOException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(exception.getMessage()) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	catch (ReadNotPermittedException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(Dictionary.get("FileActions.Read_Not_Permitted_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	catch (UnknownFileErrorException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(Dictionary.get("FileActions.Unknown_File_Error_Message")) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+	catch (WriteNotPermittedException exception) {
+	    DebugStream.printStackTrace(exception);
+	    if (showErrorDialog(Dictionary.get("FileActions.Write_Not_Permitted_Message", target_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+
+	CollectionTreeNode new_target_node = new CollectionTreeNode(target_file);
+	FileSystemModel target_model = file_job.target.getTreeModel();
+	SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
+	Gatherer.c_man.fireFileAddedToCollection(target_file);
+
+	// Copy the non-folder level metadata assigned to the original file to the new file
+	ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(source_file);
+	MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
+    }
+
+
+    private void doFileMove(FileJob file_job)
+    {
+	FileNode source_node = file_job.getOrigin();
+	FileNode target_node = file_job.getDestination();
+
+	File source_file = source_node.getFile();
+	File target_file = new File(target_node.getFile(), source_file.getName());
+
+	// Check the target file isn't the source file
+	if (target_file.equals(source_file)) {
+	    DebugStream.println("Target file is the source file!");
+	    return;
+	}
+
+	// The target file shouldn't already exist
+	if (target_file.exists()) {
+	    int result = showOverwriteDialog(target_file.getName());
+	    if (result == JOptionPane.NO_OPTION) {
+		// Don't overwrite
+		return;
+	    }
+	    if (result == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+		return;
+	    }
+	}
+
+	// Move the file by renaming it
+	if (!source_file.renameTo(target_file)) {
+	    String args[] = { source_file.getName(), target_file.getAbsolutePath() };
+	    if (showErrorDialog(Dictionary.get("FileActions.File_Move_Error_Message", args)) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    return;
+	}
+
+	// Remove the node from the source model and add it to the target model
+	SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
+	CollectionTreeNode new_target_node = new CollectionTreeNode(target_file);
+	FileSystemModel target_model = file_job.target.getTreeModel();
+	SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
+
+	// Move the non-folder level metadata assigned to the original file to the new file
+	ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
+	MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
+	MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
+    }
+
+
+    private void processFileJob(FileJob file_job)
+    {
+	DebugStream.println("Processing file job " + file_job + "...");
+
+	// Ensure that the source file exists
+	File source_file = file_job.getOrigin().getFile();
+	if (!source_file.exists()) {
+	    // The source file doesn't exist, so give the user the option of continuing or cancelling
+	    if (showErrorDialog(Dictionary.get("FileActions.File_Not_Found_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
+		clearJobs();  // Aborting action
+	    }
+	    // Refresh the source tree model
+	    FileSystemModel source_model = file_job.source.getTreeModel();
+	    source_model.refresh(new TreePath(((FileNode) file_job.getOrigin().getParent()).getPath()));
+	    return;
+	}
+
+	// Enable the "Stop" button
+	stop_button.setEnabled(true);
+
+	// Delete empty directory job
+	if (file_job.type == FileJob.DELETE_EMPTY_DIRECTORY) {
+	    file_status.setText(Dictionary.get("FileActions.Deleting", formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width)));
+	    doEmptyDirectoryDelete(file_job);
+	}
+
+	// Delete job
+	if (file_job.type == FileJob.DELETE) {
+	    file_status.setText(Dictionary.get("FileActions.Deleting", formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width)));
+	    if (source_file.isFile()) {
+		long source_file_size = source_file.length();
+		doFileDelete(file_job);
+		progress.addValue(source_file_size);  // Update progress bar
+	    }
+	    else {
+		doDirectoryDelete(file_job);
+	    }
+	}
+
+	// Copy job
+	if (file_job.type == FileJob.COPY) {
+	    file_status.setText(Dictionary.get("FileActions.Copying", formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width)));
+	    if (source_file.isFile()) {
+		long source_file_size = source_file.length();
+		doFileCopy(file_job);
+		progress.addValue(source_file_size);  // Update progress bar
+	    }
+	    else {
+		doDirectoryCopy(file_job);
+	    }
+	}
+
+	// Move job
+	if (file_job.type == FileJob.MOVE) {
+	    file_status.setText(Dictionary.get("FileActions.Moving", formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width)));
+	    if (source_file.isFile()) {
+		long source_file_size = source_file.length();
+		doFileMove(file_job);
+		progress.addValue(source_file_size);  // Update progress bar
+	    }
+	    else {
+		doDirectoryMove(file_job);
+	    }
+	}
+    }
+
+
+    private int showErrorDialog(String error_message)
+    {
+	Object[] options = { Dictionary.get("General.OK"), Dictionary.get("General.Cancel") };
+	int result = JOptionPane.showOptionDialog(Gatherer.g_man, error_message, Dictionary.get("General.Error"), JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[0]);
+	if (result == 0) {
+	    return JOptionPane.OK_OPTION;
+	}
+	else {
+	    return JOptionPane.CANCEL_OPTION;
+	}
+    }
+
+
+    private int showOverwriteDialog(String target_file_name)
+    {
+	// Has "yes to all" been set?
+	if (yes_to_all) {
+	    return JOptionPane.YES_OPTION;
+	}
+
+	Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
+	int result = JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Exists", target_file_name), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
+	if (result == 0) {
+	    return JOptionPane.YES_OPTION;
+	}
+	else if (result == 1) {
+	    yes_to_all = true;
+	    return JOptionPane.YES_OPTION;
+	}
+	else if (result == 2) {
+	    return JOptionPane.NO_OPTION;
+	}
+	else {
+	    return JOptionPane.CANCEL_OPTION;
+	}
+    }
+
+
+    public void run()
+    {
+	super.setName("FileQueue");
+
+	while (!Gatherer.exit) {
+	    // Retrieve the next job
+	    int position = queue.size() - 1;
+	    if (position >= 0) {
+		// We have a file job, so process it
+		processFileJob((FileJob) queue.remove(position));
+	    }
+	    else {
+		// No jobs, so reset and wait until we are notified of one
+		synchronized(this) {
+		    // Force both workspace and collection trees to refresh
+		    if (Gatherer.g_man != null) {
+			Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
+			// Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
+		    }
+
+		    // Reset status area
+		    file_status.setText(Dictionary.get("FileActions.No_Activity"));
+		    progress.reset();
+		    progress.setString(Dictionary.get("FileActions.No_Activity"));
+
+		    // Reset "yes to all"
+		    yes_to_all = false;
+
+		    // Wait for a new file job
+		    try {
+			wait();
+		    }
+		    catch (InterruptedException exception) {}
+		}
+	    }
+	}
     }
 
@@ -213,354 +672,354 @@
      * @see org.greenstone.gatherer.util.Utility
      */
-    public void run()
-    {
-	super.setName("FileQueue");
-
-	while (!Gatherer.exit) {
-	    try {
-		// Retrieve the next job
-		int position = queue.size() - 1;
-		FileJob job = null;
-		if (position >= 0) {
-		    job = (FileJob) queue.remove(position);
-		}
-
-		if (job != null) {
-		    ///ystem.err.println("Found job: " + job);
-		    // Enabled stop button
-		    stop_button.setEnabled(true);
-		    // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
-		    boolean ready = true;
-		    FileNode origin_node = job.getOrigin();
-		    FileNode destination_node = job.getDestination();
-		    FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
-		    FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
-		    if(destination_node == null) {
-			// Retrieve the root node of the target model instead. A delete, or course, has no target file so all deleted files are added to the root of the Recycle Bin model.
-			destination_node = (FileNode) target_model.getRoot();
-		    }
-
-		    // Extract common job details.
-		    File source_file = origin_node.getFile();
-		    File target_file = null;
-		    // Determine the target file for a copy or move.
-		    if (job.type == FileJob.COPY || job.type == FileJob.MOVE) {
-			// use the name of the filenode instead of the name of the file - these should be the same except for the collection directories where we want the collection name to be used, not 'import' which is the underlying name
-			target_file = new File(destination_node.getFile(), origin_node.toString());
-		    }
-		    // To copy a file, copy it then add any metadata found at the source. If this file was already in our collection then we must ensure the lastest version of its metadata.xml has been saved to disk. To copy a directory simply create the directory at the destination, then add all of its children files as new jobs.
-		    if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
-			///ystem.err.println("Copy/Move: " + origin_node);
-
-			// The number one thing to check is whether we are in a cyclic loop. The easiest way is just to check how deep we are
-			int max_folder_depth = Configuration.getInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC);
-			boolean continue_over_depth = false;
-			if (countFolderDepth(source_file) > max_folder_depth) {
-			    Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("General.No"), Dictionary.get("FileActions.Increase_Depth") };
-			    String args[] = { String.valueOf(max_folder_depth), source_file.getAbsolutePath() };
-			    int result = JOptionPane.showOptionDialog(Gatherer.g_man, Utility.formatHTMLWidth(Dictionary.get("FileActions.Possible_Cyclic_Path", args), 80), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[1]);
-			    args = null;
-			    options = null;
-			    switch(result) {
-			    case 0: // Yes
-				continue_over_depth = true;
-				break;
-			    case 2: // Continue and increase depth
-				continue_over_depth = true;
-				Configuration.setInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC, (max_folder_depth + 1));
-				break;
-			    }
-			}
-			else {
-			    continue_over_depth = true;
-			}
+//     public void run()
+//     {
+// 	super.setName("FileQueue");
+
+// 	while (!Gatherer.exit) {
+// 	    try {
+// 		// Retrieve the next job
+// 		int position = queue.size() - 1;
+// 		FileJob job = null;
+// 		if (position >= 0) {
+// 		    job = (FileJob) queue.remove(position);
+// 		}
+
+// 		if (job != null) {
+// 		    ///ystem.err.println("Found job: " + job);
+// 		    // Enabled stop button
+// 		    stop_button.setEnabled(true);
+// 		    // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
+// 		    boolean ready = true;
+// 		    FileNode origin_node = job.getOrigin();
+// 		    FileNode destination_node = job.getDestination();
+// 		    FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
+// 		    FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
+// 		    if(destination_node == null) {
+// 			// Retrieve the root node of the target model instead. A delete, or course, has no target file so all deleted files are added to the root of the Recycle Bin model.
+// 			destination_node = (FileNode) target_model.getRoot();
+// 		    }
+
+// 		    // Extract common job details.
+// 		    File source_file = origin_node.getFile();
+// 		    File target_file = null;
+// 		    // Determine the target file for a copy or move.
+// 		    if (job.type == FileJob.COPY || job.type == FileJob.MOVE) {
+// 			// use the name of the filenode instead of the name of the file - these should be the same except for the collection directories where we want the collection name to be used, not 'import' which is the underlying name
+// 			target_file = new File(destination_node.getFile(), origin_node.toString());
+// 		    }
+// 		    // To copy a file, copy it then add any metadata found at the source. If this file was already in our collection then we must ensure the lastest version of its metadata.xml has been saved to disk. To copy a directory simply create the directory at the destination, then add all of its children files as new jobs.
+// 		    if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
+// 			///ystem.err.println("Copy/Move: " + origin_node);
+
+// 			// The number one thing to check is whether we are in a cyclic loop. The easiest way is just to check how deep we are
+// 			int max_folder_depth = Configuration.getInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC);
+// 			boolean continue_over_depth = false;
+// 			if (countFolderDepth(source_file) > max_folder_depth) {
+// 			    Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("General.No"), Dictionary.get("FileActions.Increase_Depth") };
+// 			    String args[] = { String.valueOf(max_folder_depth), source_file.getAbsolutePath() };
+// 			    int result = JOptionPane.showOptionDialog(Gatherer.g_man, Utility.formatHTMLWidth(Dictionary.get("FileActions.Possible_Cyclic_Path", args), 80), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[1]);
+// 			    args = null;
+// 			    options = null;
+// 			    switch(result) {
+// 			    case 0: // Yes
+// 				continue_over_depth = true;
+// 				break;
+// 			    case 2: // Continue and increase depth
+// 				continue_over_depth = true;
+// 				Configuration.setInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC, (max_folder_depth + 1));
+// 				break;
+// 			    }
+// 			}
+// 			else {
+// 			    continue_over_depth = true;
+// 			}
 			   
-			if(continue_over_depth) {
-			    FileNode new_node = null;
-			    // Check if file exists, and action as necessary. Be aware the user can choose to cancel the action all together (where upon ready becomes false).
-			    if(target_file.exists()) {
-				// We've previously been told
-				if(yes_to_all) {
-				    // Remove the old file and tree entry.
-				    target_file.delete();
-				    ready = true;
-				}
-				else {
-				    ///atherer.println("Opps! This filename already exists. Give the user some options.");
-				    Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
-				    int result = JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Exists", target_file.getName()), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
-				    switch(result) {
-				    case 1: // Yes To All
-					yes_to_all = true;
-				    case 0: // Yes
-					// Remove the old file and tree entry.
-					if(destination_node != null) {
-					    TreePath destination_path = new TreePath(destination_node.getPath());
-					    CollectionTreeNode temp_target_node = new CollectionTreeNode(target_file);  // !!! , target_model, true);
-					    TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
-					    SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
-					    target_path = null;
-					    temp_target_node = null;
-					    destination_path = null;
-					}
-					target_file.delete();
-					ready = true;
-					break;
-				    case 3: // No To All
-					cancel_action = true;
-				    case 2: // No
-				    default:
-					ready = false;
-					// Increment progress by size of potentially copied file
-					progress.addValue(origin_node.getFile().length());
-				    }
-				}
-			    }
-			    // We proceed with the copy/move if the ready flag is still set. If it is that means there is no longer any existing file of the same name.
-			    if(ready) {
-				// update status area
-				String args[] = new String[1];
-				args[0] = "" + (queue.size() + 1) + "";
-				if(job.type == FileJob.COPY) {
-				    args[0] = formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
-				    file_status.setText(Dictionary.get("FileActions.Copying", args));
-				}
-				else {
-				    args[0] = formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
-				    file_status.setText(Dictionary.get("FileActions.Moving", args));
-				}
-				args = null;
+// 			if(continue_over_depth) {
+// 			    FileNode new_node = null;
+// 			    // Check if file exists, and action as necessary. Be aware the user can choose to cancel the action all together (where upon ready becomes false).
+// 			    if(target_file.exists()) {
+// 				// We've previously been told
+// 				if(yes_to_all) {
+// 				    // Remove the old file and tree entry.
+// 				    target_file.delete();
+// 				    ready = true;
+// 				}
+// 				else {
+// 				    ///atherer.println("Opps! This filename already exists. Give the user some options.");
+// 				    Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
+// 				    int result = JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Exists", target_file.getName()), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
+// 				    switch(result) {
+// 				    case 1: // Yes To All
+// 					yes_to_all = true;
+// 				    case 0: // Yes
+// 					// Remove the old file and tree entry.
+// 					if(destination_node != null) {
+// 					    TreePath destination_path = new TreePath(destination_node.getPath());
+// 					    CollectionTreeNode temp_target_node = new CollectionTreeNode(target_file);  // !!! , target_model, true);
+// 					    TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
+// 					    SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
+// 					    target_path = null;
+// 					    temp_target_node = null;
+// 					    destination_path = null;
+// 					}
+// 					target_file.delete();
+// 					ready = true;
+// 					break;
+// 				    case 3: // No To All
+// 					cancel_action = true;
+// 				    case 2: // No
+// 				    default:
+// 					ready = false;
+// 					// Increment progress by size of potentially copied file
+// 					progress.addValue(origin_node.getFile().length());
+// 				    }
+// 				}
+// 			    }
+// 			    // We proceed with the copy/move if the ready flag is still set. If it is that means there is no longer any existing file of the same name.
+// 			    if(ready) {
+// 				// update status area
+// 				String args[] = new String[1];
+// 				args[0] = "" + (queue.size() + 1) + "";
+// 				if(job.type == FileJob.COPY) {
+// 				    args[0] = formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
+// 				    file_status.setText(Dictionary.get("FileActions.Copying", args));
+// 				}
+// 				else {
+// 				    args[0] = formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
+// 				    file_status.setText(Dictionary.get("FileActions.Moving", args));
+// 				}
+// 				args = null;
 				
-				// If source is a file
-				if(source_file.isFile()) {
-				    // copy the file. If anything goes wrong the copy file should throw the appropriate exception. No matter what exception is thrown (bar an IOException) we display some message, perhaps take some action, then cancel the remainder of the pending file jobs. No point in being told your out of hard drive space for each one of six thousand files eh?
-				    try {
-					copyFile(source_file, target_file, false);
-					progress.addValue(source_file.length());
-				    }
-				    // If we can't find the source file, then the most likely reason is that the file system has changed since the last time it was mapped. Warn the user that the requested file can't be found, then force a refresh of the source folder involved.
-				    catch(FileNotFoundException fnf_exception) {
-					DebugStream.printStackTrace(fnf_exception);
-					cancel_action = true;
-					// Show warning.
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
-					// Force refresh of source folder.
-					source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
-				    }
-				    catch(FileAlreadyExistsException fae_exception) {
-					DebugStream.printStackTrace(fae_exception);
-					cancel_action = true;
-					// Show warning.
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Already_Exists_Message", target_file.getName()), Dictionary.get("FileActions.File_Already_Exists_Title"), JOptionPane.ERROR_MESSAGE);
-					// Nothing else can be done by the Gatherer.
-				    }
-				    catch(InsufficientSpaceException is_exception) {
-					DebugStream.printStackTrace(is_exception);
-					cancel_action = true;
-					// Show warning. The message body of the expection explains how much more space is required for this file copy.
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Insufficient_Space_Message", is_exception.getMessage()), Dictionary.get("FileActions.Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
-					// Nothing else can be done by the Gatherer. In fact if we are really out of space I'm not even sure we can quit safely.
-				    }
-				    catch (ReadNotPermittedException rnp_exception) {
-					if (DebugStream.isDebuggingEnabled()) {
-					    DebugStream.printStackTrace(rnp_exception);
-					}
-					cancel_action = true;
-					// Show warning
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Read_Not_Permitted_Message", source_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
-					// Nothing else we can do.
-				    }
-				    catch(UnknownFileErrorException ufe_exception) {
-					DebugStream.printStackTrace(ufe_exception);
-					cancel_action = true;
-					// Show warning
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Unknown_File_Error_Message"), Dictionary.get("FileActions.Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
-					// Nothing else we can do.
-				    }
-				    catch(WriteNotPermittedException wnp_exception) {
-					if (DebugStream.isDebuggingEnabled()) {
-					    DebugStream.printStackTrace(wnp_exception);
-					}
-					cancel_action = true;
-					// Show warning
-					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Write_Not_Permitted_Message", target_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
-					// Nothing else we can do.
-				    }
-				    catch(IOException exception) {
-					// Can't really do much about this.
-					DebugStream.printStackTrace(exception);
-				    }
-				    // If not cancelled
-				    if (!cancel_action) {
-					// Create a dummy FileNode with the correct structure (so getPath works)
-					new_node = new CollectionTreeNode(target_file);
-					SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_node);
-				    }
-				}
-				// Else
-				else if(source_file.isDirectory()) {
-				    // create new record
-				    CollectionTreeNode directory_record = new CollectionTreeNode(target_file);
-				    SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
-				    // Why is this not happening eh?
-				    directory_record.setParent(destination_node);
-				    if(!target_file.exists()) {
-					// make the directory
-					target_file.mkdirs();
-					new_node = directory_record;
-				    }
-
-				    // Queue non-filtered child files for copying. If this directory already existed, the child records will have to generate the undo jobs, as we don't want to entirely delete this directory if it already existed.
-				    FileNode child_record = null;
-				    // In order to have a sane copy proceedure (rather than always copying last file first as it used to) we always add the child node at the position the parent was removed from. Consider the file job 'a' at the end of the queue which generates three new jobs 'b', 'c' and 'd'. The resulting flow should look like this.
-				    // -- Starting queue                   ...[a]
-				    // remove(position) = 'a'              ...
-				    // add(position, 'b')                  ...[b]
-				    // add(position, 'c')                  ...[c][b]
-				    // add(position, 'd')                  ...[d][c][b]
-				    // Next loop
-				    // remove(position) = 'b'              ...[d][c]
-				    //for(int i = 0; i < origin_node.getChildCount(); i++) {
-				    for (int i=origin_node.getChildCount()-1; i>=0; i--) {
-					child_record = (FileNode) origin_node.getChildAt(i);
-					addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, false, position);
-				    }
-				    child_record = null;
-				    directory_record = null;
-				}
-				// The file wasn't found!
-				else {
-				    cancel_action = true;
-				    // Show warning.
-				    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
-				    // Force refresh of source folder.
-				    source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
-				}
-
-				// If we haven't been cancelled and we created a new FileNode during the above phase, now is the time to deal with metadata
-				if (!cancel_action && new_node != null) {
-				    // If the file came from inside our collection...
-				    if (job.source.toString().equals("Collection")) {
-					// Get the non-folder level metadata assigned to the origin node...
-					ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
-					// ...and remove it from the original node and assign it to the new folder
-					MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
-					MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
-				    }
-				    // If it came from the workspace search for metadata assigned to the file
-				    else if (job.source.toString().equals("Workspace")) {
-					ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(origin_node.getFile());
-					MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
-				    }
-
-				    if (job.type == FileJob.COPY && new_node.getFile().isFile()) {
-					Gatherer.c_man.fireFileAddedToCollection(new_node.getFile());
-				    }
-				}
-				new_node = null;
-			    }
-			}
-		    }
-		    // If we haven't been cancelled, and we've been asked to delete a directory/file, or perhaps as part of a move, we delete the file. This involves removing any existing metadata and then copying the file to the recycled bin (for a delete only), then deleting the file. When deleting a directory record from the tree (or from the filesystem for that matter) we must ensure that all of the descendant records have already been removed. If we fail to do this the delete will fail, or you will be bombarded with hundreds of 'Parent node of null not allowed' error messages. Also be aware that if the user has cancelled just this action, because of say a name clash, then we shouldn't do any deleting of any sort dammit.
-		    if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
-			// Update the progress bar for this job
-			if (source_file.isFile()) {
-			    progress.addValue(source_file.length());
-			}
-
-			// If the source is a file or an empty directory (but not the root node of a tree)
-			File[] child_list = source_file.listFiles();
-			if (source_file.isFile() || (child_list != null && child_list.length == 0 && origin_node.getParent() != null)) {
-			    // Update status area
-			    String args[] = new String[1];
-			    args[0] = formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
-			    file_status.setText(Dictionary.get("FileActions.Deleting", args));
-
-			    // If it is a metadata.xml file, we must unload it
-			    if (source_file.getName().equals(StaticStrings.METADATA_XML)) {
-				MetadataXMLFileManager.unloadMetadataXMLFile(source_file);
-			    }
-
-			    // Remove the metadata assigned directly to the file
-			    ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(origin_node.getFile());
-			    MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
-
-			    // Remove from model
-			    FileNode parent_record = (FileNode) origin_node.getParent();
-			    if (parent_record != null) {
-				SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
-			    }
-
-			    // Delete the source file
-			    if (!Utility.delete(source_file)) {
-				// Show message that we couldn't delete
-				JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Deleted_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Deleted_Title"), JOptionPane.ERROR_MESSAGE);
-			    }
-			}
-			// Else the source is a directory and it has children remaining
-			else if(child_list != null && child_list.length > 0) {
-			    // Don't worry about all this for true file move actions.
-			    if(job.type == FileJob.DELETE) {
-				// queue all of its children, (both filtered and non-filtered), but for deleting only. Don't queue jobs for a current move event, as they would be queued as part of copying. I have no idea way, per sec, however the children within the origin node are always invalid during deletion (there are several copies of some nodes?!?). I'll check that each child is only added once.
-				origin_node.refresh();
-				for(int i = 0; i < origin_node.size(); i++) {
-				    FileNode child_record = (FileNode) origin_node.getChildAtUnfiltered(i);
-				    ///atherer.println("Queuing: " + child_record);
-				    addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, false, position);
-				}  
-			    }
-			    // Requeue a delete job -after- the children have been dealt with. Remember I've reversed the direction of the queue so sooner is later. Te-he. Also have to remember that we have have followed this path to get here for a move job: Copy Directory -> Queue Child Files -> Delete Directory (must occur after child files) -> Queue Directory.
-			    // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
-			    if(origin_node.getParent() != null && !job.done) {
-				///atherer.println("Requeuing: " + origin_node.getFile().getAbsolutePath());
-				job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
-				addJob(job, position);
-			    }
-			    else {
-				DebugStream.println("I've already done this job twice. I refuse to requeue it again!");
-			    }
-			}
-		    }
-		    job = null;
-		    source_file = null;
-		    target_file = null;
-		    origin_node = null;
-
-		    // We only break out of the while loop if we are out of files or the action was cancelled
-		    if (cancel_action) {
-			// Empty queue
-			clearJobs();
-			cancel_action = false;
-		    }
-		}
-		else { // job == null
-		    // Disable stop button
-		    if (stop_button != null) {
-			stop_button.setEnabled(false);
-		    }
-		    synchronized(this) {
-			// Force both workspace and collection trees to refresh
-			if (Gatherer.g_man != null) {
-			    Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
-			    Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
-			}
-
-			// Reset status area
-			file_status.setText(Dictionary.get("FileActions.No_Activity"));
-			progress.reset();
-			progress.setString(Dictionary.get("FileActions.No_Activity"));
-			yes_to_all = false;
-			try {
-			    wait();
-			}
-			catch (InterruptedException exception) {}
-		    }
-		}
-	    }
-	    catch (Exception error) {
-		DebugStream.printStackTrace(error);
-	    }
-	}
-    }
+// 				// If source is a file
+// 				if(source_file.isFile()) {
+// 				    // copy the file. If anything goes wrong the copy file should throw the appropriate exception. No matter what exception is thrown (bar an IOException) we display some message, perhaps take some action, then cancel the remainder of the pending file jobs. No point in being told your out of hard drive space for each one of six thousand files eh?
+// 				    try {
+// 					copyFile(source_file, target_file, false);
+// 					progress.addValue(source_file.length());
+// 				    }
+// 				    // If we can't find the source file, then the most likely reason is that the file system has changed since the last time it was mapped. Warn the user that the requested file can't be found, then force a refresh of the source folder involved.
+// 				    catch(FileNotFoundException fnf_exception) {
+// 					DebugStream.printStackTrace(fnf_exception);
+// 					cancel_action = true;
+// 					// Show warning.
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Force refresh of source folder.
+// 					source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
+// 				    }
+// 				    catch(FileAlreadyExistsException fae_exception) {
+// 					DebugStream.printStackTrace(fae_exception);
+// 					cancel_action = true;
+// 					// Show warning.
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Already_Exists_Message", target_file.getName()), Dictionary.get("FileActions.File_Already_Exists_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Nothing else can be done by the Gatherer.
+// 				    }
+// 				    catch(InsufficientSpaceException is_exception) {
+// 					DebugStream.printStackTrace(is_exception);
+// 					cancel_action = true;
+// 					// Show warning. The message body of the expection explains how much more space is required for this file copy.
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Insufficient_Space_Message", is_exception.getMessage()), Dictionary.get("FileActions.Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Nothing else can be done by the Gatherer. In fact if we are really out of space I'm not even sure we can quit safely.
+// 				    }
+// 				    catch (ReadNotPermittedException rnp_exception) {
+// 					if (DebugStream.isDebuggingEnabled()) {
+// 					    DebugStream.printStackTrace(rnp_exception);
+// 					}
+// 					cancel_action = true;
+// 					// Show warning
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Read_Not_Permitted_Message", source_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Nothing else we can do.
+// 				    }
+// 				    catch(UnknownFileErrorException ufe_exception) {
+// 					DebugStream.printStackTrace(ufe_exception);
+// 					cancel_action = true;
+// 					// Show warning
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Unknown_File_Error_Message"), Dictionary.get("FileActions.Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Nothing else we can do.
+// 				    }
+// 				    catch(WriteNotPermittedException wnp_exception) {
+// 					if (DebugStream.isDebuggingEnabled()) {
+// 					    DebugStream.printStackTrace(wnp_exception);
+// 					}
+// 					cancel_action = true;
+// 					// Show warning
+// 					JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Write_Not_Permitted_Message", target_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
+// 					// Nothing else we can do.
+// 				    }
+// 				    catch(IOException exception) {
+// 					// Can't really do much about this.
+// 					DebugStream.printStackTrace(exception);
+// 				    }
+// 				    // If not cancelled
+// 				    if (!cancel_action) {
+// 					// Create a dummy FileNode with the correct structure (so getPath works)
+// 					new_node = new CollectionTreeNode(target_file);
+// 					SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_node);
+// 				    }
+// 				}
+// 				// Else
+// 				else if(source_file.isDirectory()) {
+// 				    // create new record
+// 				    CollectionTreeNode directory_record = new CollectionTreeNode(target_file);
+// 				    SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
+// 				    // Why is this not happening eh?
+// 				    directory_record.setParent(destination_node);
+// 				    if(!target_file.exists()) {
+// 					// make the directory
+// 					target_file.mkdirs();
+// 					new_node = directory_record;
+// 				    }
+
+// 				    // Queue non-filtered child files for copying. If this directory already existed, the child records will have to generate the undo jobs, as we don't want to entirely delete this directory if it already existed.
+// 				    FileNode child_record = null;
+// 				    // In order to have a sane copy proceedure (rather than always copying last file first as it used to) we always add the child node at the position the parent was removed from. Consider the file job 'a' at the end of the queue which generates three new jobs 'b', 'c' and 'd'. The resulting flow should look like this.
+// 				    // -- Starting queue                   ...[a]
+// 				    // remove(position) = 'a'              ...
+// 				    // add(position, 'b')                  ...[b]
+// 				    // add(position, 'c')                  ...[c][b]
+// 				    // add(position, 'd')                  ...[d][c][b]
+// 				    // Next loop
+// 				    // remove(position) = 'b'              ...[d][c]
+// 				    //for(int i = 0; i < origin_node.getChildCount(); i++) {
+// 				    for (int i=origin_node.getChildCount()-1; i>=0; i--) {
+// 					child_record = (FileNode) origin_node.getChildAt(i);
+// 					addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, false, position);
+// 				    }
+// 				    child_record = null;
+// 				    directory_record = null;
+// 				}
+// 				// The file wasn't found!
+// 				else {
+// 				    cancel_action = true;
+// 				    // Show warning.
+// 				    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
+// 				    // Force refresh of source folder.
+// 				    source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
+// 				}
+
+// 				// If we haven't been cancelled and we created a new FileNode during the above phase, now is the time to deal with metadata
+// 				if (!cancel_action && new_node != null) {
+// 				    // If the file came from inside our collection...
+// 				    if (job.source.toString().equals("Collection")) {
+// 					// Get the non-folder level metadata assigned to the origin node...
+// 					ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
+// 					// ...and remove it from the original node and assign it to the new folder
+// 					MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
+// 					MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
+// 				    }
+// 				    // If it came from the workspace search for metadata assigned to the file
+// 				    else if (job.source.toString().equals("Workspace")) {
+// 					ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(origin_node.getFile());
+// 					MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
+// 				    }
+
+// 				    if (job.type == FileJob.COPY && new_node.getFile().isFile()) {
+// 					Gatherer.c_man.fireFileAddedToCollection(new_node.getFile());
+// 				    }
+// 				}
+// 				new_node = null;
+// 			    }
+// 			}
+// 		    }
+// 		    // If we haven't been cancelled, and we've been asked to delete a directory/file, or perhaps as part of a move, we delete the file. This involves removing any existing metadata and then copying the file to the recycled bin (for a delete only), then deleting the file. When deleting a directory record from the tree (or from the filesystem for that matter) we must ensure that all of the descendant records have already been removed. If we fail to do this the delete will fail, or you will be bombarded with hundreds of 'Parent node of null not allowed' error messages. Also be aware that if the user has cancelled just this action, because of say a name clash, then we shouldn't do any deleting of any sort dammit.
+// 		    if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
+// 			// Update the progress bar for this job
+// 			if (source_file.isFile()) {
+// 			    progress.addValue(source_file.length());
+// 			}
+
+// 			// If the source is a file or an empty directory (but not the root node of a tree)
+// 			File[] child_list = source_file.listFiles();
+// 			if (source_file.isFile() || (child_list != null && child_list.length == 0 && origin_node.getParent() != null)) {
+// 			    // Update status area
+// 			    String args[] = new String[1];
+// 			    args[0] = formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
+// 			    file_status.setText(Dictionary.get("FileActions.Deleting", args));
+
+// 			    // If it is a metadata.xml file, we must unload it
+// 			    if (source_file.getName().equals(StaticStrings.METADATA_XML)) {
+// 				MetadataXMLFileManager.unloadMetadataXMLFile(source_file);
+// 			    }
+
+// 			    // Remove the metadata assigned directly to the file
+// 			    ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(origin_node.getFile());
+// 			    MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
+
+// 			    // Remove from model
+// 			    FileNode parent_record = (FileNode) origin_node.getParent();
+// 			    if (parent_record != null) {
+// 				SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
+// 			    }
+
+// 			    // Delete the source file
+// 			    if (!Utility.delete(source_file)) {
+// 				// Show message that we couldn't delete
+// 				JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Deleted_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Deleted_Title"), JOptionPane.ERROR_MESSAGE);
+// 			    }
+// 			}
+// 			// Else the source is a directory and it has children remaining
+// 			else if(child_list != null && child_list.length > 0) {
+// 			    // Don't worry about all this for true file move actions.
+// 			    if(job.type == FileJob.DELETE) {
+// 				// queue all of its children, (both filtered and non-filtered), but for deleting only. Don't queue jobs for a current move event, as they would be queued as part of copying. I have no idea way, per sec, however the children within the origin node are always invalid during deletion (there are several copies of some nodes?!?). I'll check that each child is only added once.
+// 				origin_node.refresh();
+// 				for(int i = 0; i < origin_node.size(); i++) {
+// 				    FileNode child_record = (FileNode) origin_node.getChildAtUnfiltered(i);
+// 				    ///atherer.println("Queuing: " + child_record);
+// 				    addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, false, position);
+// 				}  
+// 			    }
+// 			    // Requeue a delete job -after- the children have been dealt with. Remember I've reversed the direction of the queue so sooner is later. Te-he. Also have to remember that we have have followed this path to get here for a move job: Copy Directory -> Queue Child Files -> Delete Directory (must occur after child files) -> Queue Directory.
+// 			    // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
+// 			    if(origin_node.getParent() != null && !job.done) {
+// 				///atherer.println("Requeuing: " + origin_node.getFile().getAbsolutePath());
+// 				job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
+// 				addJob(job, position);
+// 			    }
+// 			    else {
+// 				DebugStream.println("I've already done this job twice. I refuse to requeue it again!");
+// 			    }
+// 			}
+// 		    }
+// 		    job = null;
+// 		    source_file = null;
+// 		    target_file = null;
+// 		    origin_node = null;
+
+// 		    // We only break out of the while loop if we are out of files or the action was cancelled
+// 		    if (cancel_action) {
+// 			// Empty queue
+// 			clearJobs();
+// 			cancel_action = false;
+// 		    }
+// 		}
+// 		else { // job == null
+// 		    // Disable stop button
+// 		    if (stop_button != null) {
+// 			stop_button.setEnabled(false);
+// 		    }
+// 		    synchronized(this) {
+// 			// Force both workspace and collection trees to refresh
+// 			if (Gatherer.g_man != null) {
+// 			    Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
+// 			    Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
+// 			}
+
+// 			// Reset status area
+// 			file_status.setText(Dictionary.get("FileActions.No_Activity"));
+// 			progress.reset();
+// 			progress.setString(Dictionary.get("FileActions.No_Activity"));
+// 			yes_to_all = false;
+// 			try {
+// 			    wait();
+// 			}
+// 			catch (InterruptedException exception) {}
+// 		    }
+// 		}
+// 	    }
+// 	    catch (Exception error) {
+// 		DebugStream.printStackTrace(error);
+// 	    }
+// 	}
+//     }
 
 
@@ -581,5 +1040,4 @@
      * @param source The source directory
      * @param destination The destination directory
-     * @param progress A progress bar to monitor copying progress
      * @see org.greenstone.gatherer.Gatherer
      */
