Index: trunk/gli/src/org/greenstone/gatherer/Gatherer.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/Gatherer.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/Gatherer.java	(revision 10726)
@@ -47,4 +47,5 @@
 import org.greenstone.gatherer.gui.URLField;
 import org.greenstone.gatherer.gui.WarningDialog;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.JarTools;
 import org.greenstone.gatherer.util.StaticStrings;
@@ -93,5 +94,4 @@
     static public boolean isApplet = false;
     static public boolean isGsdlRemote = false;
-    static public String cgiBase = null;
 
     // feedback stuff
@@ -293,5 +293,4 @@
 		if (Configuration.gliserver_url != null) {
 		    gliserver_url_string = Configuration.gliserver_url.toString();
-		    cgiBase = gliserver_url_string.substring(0, gliserver_url_string.lastIndexOf('/') + 1);
 		}
 	    }
@@ -508,4 +507,9 @@
 	if (LocalLibraryServer.isRunning() == true) {
 	    LocalLibraryServer.stop();
+	}
+
+	// If we're using a remote Greenstone server we need to make sure that all jobs have completed first
+	if (isGsdlRemote) {
+	    RemoteGreenstoneServer.exit();
 	}
 
Index: trunk/gli/src/org/greenstone/gatherer/GathererApplet.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/GathererApplet.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/GathererApplet.java	(revision 10726)
@@ -123,4 +123,5 @@
 			  "-gliserver_url", gliserver_url_string,
 			  "-library_url", library_url_string,
+			  "-debug",
 	                };
 
@@ -133,5 +134,5 @@
 		//   for documented example collections so "base this on
 		//   existing collection" works
-		ZipTools.unzipFromJar("collect.zip", Gatherer.getGLIUserDirectoryPath());
+		unzipFromJar("collect.zip", Gatherer.getGLIUserDirectoryPath());
 	    }
 	    else {
@@ -146,5 +147,5 @@
 	if (!metadata_directory.exists()) {
 	    // dig out metadata.zip from JAR file and unzip it
-	    ZipTools.unzipFromJar("metadata.zip", Gatherer.getGLIDirectoryPath());
+	    unzipFromJar("metadata.zip", Gatherer.getGLIDirectoryPath());
 	}
 
@@ -159,5 +160,4 @@
 
 
-
     public void start() 
     {
@@ -166,5 +166,4 @@
 
 
-
     public void stop()
     {
@@ -172,4 +171,5 @@
 
     }
+
 
     public void destroy()
@@ -186,5 +186,34 @@
 
 	// If there was an open collection last session, reopen it
-	Gatherer.c_man.openCollectionFromLastTime();
+	new OpenCollectionFromLastTimeTask().start();
+    }
+
+
+    /** This is in it's own thread because this action cannot happen on the GUI thread. */
+    private class OpenCollectionFromLastTimeTask
+	extends Thread
+    {
+	public void run()
+	{
+	    Gatherer.c_man.openCollectionFromLastTime();
+	}
+    }
+
+
+    /** 
+     * Method which unzips a given metadata resoure.
+     * @param jar_zip_fname The name of the jar file as a <strong>String</strong>.
+     * @param dst_dir The destination directory for unzipping, also as a <strong>String</strong>.
+     * @see JarTools.extractFromJar(String, String, boolean)
+     */
+    static public void unzipFromJar(String jar_zip_fname, String dst_dir)
+    {
+	if (!dst_dir.endsWith(File.separator)) {
+	    dst_dir += File.separator;
+	}
+
+	// !! TO DO: Test this !!
+	JarTools.extractFromJar(jar_zip_fname, dst_dir, true);
+	ZipTools.unzipFile(dst_dir + jar_zip_fname, dst_dir);
     }
 }
Index: trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java	(revision 10726)
@@ -43,4 +43,5 @@
 import org.greenstone.gatherer.gui.GComboBox;
 import org.greenstone.gatherer.gui.GLIButton;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.JarTools;
 import org.greenstone.gatherer.util.StaticStrings;
@@ -311,15 +312,6 @@
 	    StringBuffer xml = null;
 	    if (Gatherer.isGsdlRemote) {
-		String launch  = Gatherer.cgiBase + "launch";
-		launch += "?cmd=classinfo.pl";
-		launch += "&xml=&language="+lang;
-		launch += "&class=" + getClassifierName(classifier);
-		
-		System.err.println("*** launch = " + launch);
-		
-		URL launch_url = new URL(launch);
-		URLConnection launch_connection = launch_url.openConnection();
-		input_stream = launch_connection.getInputStream();
-		xml = Utility.readXMLStream(input_stream);
+		String classinfo_output = RemoteGreenstoneServer.getScriptOptions("classinfo.pl", "&classifier=" + classifier);
+		xml = new StringBuffer(classinfo_output);
 	    }
 	    else {
@@ -386,29 +378,9 @@
 
 	    if (Gatherer.isGsdlRemote) {
-
-		String lang = Configuration.getLanguage();
-
-		String launch  = Gatherer.cgiBase + "launch";
-		launch += "?cmd=classinfo.pl";
-		launch += "&xml=&language="+lang;
-		launch += "&listall=";
-
-		System.err.println("*** launch = " + launch);
-
-		try {
-		    URL launch_url = new URL(launch);
-		    URLConnection launch_connection = launch_url.openConnection();
-		    InputStream input_stream = launch_connection.getInputStream();
-		    StringBuffer xml = Utility.readXMLStream(input_stream);
-		    loadClassifiers(xml);
-		}
-		catch (Exception error) {
-		    System.err.println("Failed when trying to connect to : " + launch);
-		    error.printStackTrace();
-		}
-
-	    }
+		String classinfo_output = RemoteGreenstoneServer.getScriptOptions("classinfo.pl", "&listall=");
+		loadClassifiers(new StringBuffer(classinfo_output));
+	    }
+
 	    else {
-
 		// Retrieve the gsdl home directory...
 		String directory = LocalGreenstone.getDirectoryPath();
Index: trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 10726)
@@ -511,6 +511,5 @@
 		if (Gatherer.isGsdlRemote) {
 		    String collection_name = Gatherer.c_man.getCollection().getName();
-		    ZipTools.zipup(Gatherer.getCollectDirectoryPath(), collection_name, "collect.cfg", null, "", "");
-		    RemoteGreenstoneServer.upload_url_zip(collection_name, "etc", "", null);
+		    RemoteGreenstoneServer.uploadCollectionFile(collection_name, collect_cfg_file);
 		}
 	    }
Index: trunk/gli/src/org/greenstone/gatherer/cdm/PluginManager.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/cdm/PluginManager.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/cdm/PluginManager.java	(revision 10726)
@@ -47,4 +47,5 @@
 import org.greenstone.gatherer.gui.ModalDialog;
 import org.greenstone.gatherer.gui.WarningDialog;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.JarTools;
 import org.greenstone.gatherer.util.StaticStrings;
@@ -443,15 +444,6 @@
 	    StringBuffer xml = null;
 	    if (Gatherer.isGsdlRemote) {
-		String launch  = Gatherer.cgiBase + "launch";
-		launch += "?cmd=pluginfo.pl";
-		launch += "&xml=&language="+lang;
-		launch += "&plug=" + getPluginName(plugin);
-		
-		System.err.println("*** launch = " + launch);
-		
-		URL launch_url = new URL(launch);
-		URLConnection launch_connection = launch_url.openConnection();
-		input_stream = launch_connection.getInputStream();
-		xml = Utility.readXMLStream(input_stream);
+		String pluginfo_output = RemoteGreenstoneServer.getScriptOptions("pluginfo.pl", "&plugin=" + plugin);
+		xml = new StringBuffer(pluginfo_output);
 	    }
 	    else {
@@ -514,28 +506,10 @@
 	if(library == null) {
 	    library = new ArrayList();
+
 	    if (Gatherer.isGsdlRemote) {
-
-		String lang = Configuration.getLanguage();
-
-		String launch  = Gatherer.cgiBase + "launch";
-		launch += "?cmd=pluginfo.pl";
-		launch += "&xml=&language="+lang;
-		launch += "&listall=";
-
-		System.err.println("*** launch = " + launch);
-
-		try {
-		    URL launch_url = new URL(launch);
-		    URLConnection launch_connection = launch_url.openConnection();
-		    InputStream input_stream = launch_connection.getInputStream();
-		    StringBuffer xml = Utility.readXMLStream(input_stream);
-		    loadPlugins(xml);
-		}
-		catch (Exception error) {
-		    System.err.println("Failed when trying to connect to : " + launch);
-		    error.printStackTrace();
-		}
-
-	    }
+		String pluginfo_output = RemoteGreenstoneServer.getScriptOptions("pluginfo.pl", "&listall=");
+		loadPlugins(new StringBuffer(pluginfo_output));
+	    }
+
 	    else {
 		// Retrieve the gsdl home directory...
Index: trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java	(revision 10726)
@@ -100,4 +100,7 @@
     private GShellProgressMonitor import_monitor = null;
 
+    /** Whether we have downloaded the collection configurations in this session, when using a remote Greenstone. */
+    static public boolean downloaded_collection_configurations = false;
+
     /** The name of the standard lock file. */
     static final public String LOCK_FILE = "gli.lck";
@@ -117,7 +120,10 @@
 	MetadataXMLFileManager.addMetadataChangedListener(this);
 
-	// If using a remote Greenstone server, download the collection configurations
+	// If using a remote Greenstone server, delete the local collect directory because it will be out of date
 	if (Gatherer.isGsdlRemote) {
-	    RemoteGreenstoneServer.downloadCollectionConfigurations();
+	    System.err.println("Deleting user's local collect directory...");
+	    Utility.delete(new File(Gatherer.getCollectDirectoryPath()));
+	    System.err.println("Done.");
+	    new File(Gatherer.getCollectDirectoryPath()).mkdirs();
 	}
     }
@@ -142,5 +148,6 @@
     public void buildCollection(boolean incremental_build)
     {
-	DebugStream.println("In CollectionManager.buildCollection(), incremental_build: " + incremental_build);
+	System.err.println("In CollectionManager.buildCollection(), incremental_build: " + incremental_build);
+	System.err.println("Is event dispatch thread: " + SwingUtilities.isEventDispatchThread());
 	building = true;
 
@@ -175,5 +182,5 @@
 	shell.addGShellListener(Gatherer.g_man.create_pane);
 	shell.start();
-	DebugStream.println("CollectionManager.buildCollection().return");
+	System.err.println("CollectionManager.buildCollection().return");
     }
 
@@ -217,4 +224,9 @@
 	if (lock_file.exists()) {
 	    System.err.println("Warning: Lockfile was not successfully deleted.");
+	}
+
+	// Remove the lock file on the server
+	if (Gatherer.isGsdlRemote) {
+	    RemoteGreenstoneServer.deleteCollectionFile(collection.getName(), lock_file);
 	}
 
@@ -665,5 +677,6 @@
 	}
 
-	DebugStream.println("CollectionManager.importCollection()");
+	System.err.println("CollectionManager.importCollection()");
+	System.err.println("Is event dispatch thread: " + SwingUtilities.isEventDispatchThread());
 	//check that we can remove the old index before starting import
 	File index_dir = new File(getCollectionIndexDirectoryPath());
@@ -709,5 +722,5 @@
 	shell.addGShellListener(Gatherer.g_man.create_pane);
 	shell.start();
-	DebugStream.println("CollectionManager.importCollection().return");
+	System.err.println("CollectionManager.importCollection().return");
 
 	importing = false;
@@ -798,5 +811,5 @@
 	if (Gatherer.isGsdlRemote) {
 	    String collection_name = location.substring(location.lastIndexOf(File.separator) + 1, location.length() - ".col".length());
-	    if (!RemoteGreenstoneServer.downloadCollection(collection_name)) {
+	    if (RemoteGreenstoneServer.downloadCollection(collection_name).equals("")) {
 		return;
 	    }
@@ -994,9 +1007,14 @@
     {
 	// If there was an open collection last session, reopen it
-	if (Gatherer.open_collection_file_path != null) {
+	if (Gatherer.open_collection_file_path != null && !Gatherer.isGsdlRemote) {
 	    // If we're using a remote Greenstone server we must download the collection configurations first
-	    if (Gatherer.isGsdlRemote) {
-		RemoteGreenstoneServer.downloadCollectionConfigurations();
-	    }
+	    // if (Gatherer.isGsdlRemote && !CollectionManager.downloaded_collection_configurations) {
+	    // if (RemoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
+		    // Something went wrong downloading the collection configurations
+		    // return;
+	    // }
+
+	    // CollectionManager.downloaded_collection_configurations = true;
+	    // }
 
 	    // Load the collection now
@@ -1025,5 +1043,5 @@
      */
     public synchronized void processComplete(GShellEvent event) {
-	DebugStream.println("CollectionManager.processComplete(" + event.getType() + ")");
+	System.err.println("CollectionManager.processComplete(" + event.getType() + ")");
 	Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), false);
 	///ystem.err.println("Recieved process complete event - " + event);
@@ -1267,4 +1285,9 @@
 			throw new Exception(Dictionary.get("CollectionManager.Index_Not_Deleted"));
 		    }
+		}
+
+		if (Gatherer.isGsdlRemote) {
+		    RemoteGreenstoneServer.deleteCollectionFile(collection.getName(), new File(getCollectionIndexDirectoryPath()));
+		    RemoteGreenstoneServer.moveCollectionFile(collection.getName(), new File(getCollectionBuildingDirectoryPath()), new File(getCollectionIndexDirectoryPath()));
 		}
 
Index: trunk/gli/src/org/greenstone/gatherer/collection/ScriptOptions.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/collection/ScriptOptions.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/collection/ScriptOptions.java	(revision 10726)
@@ -11,4 +11,5 @@
 import org.greenstone.gatherer.LocalGreenstone;
 import org.greenstone.gatherer.cdm.Argument;
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.ArrayTools;
 import org.greenstone.gatherer.util.Codec;
@@ -291,14 +292,6 @@
 	    Document document;
 	    if (Gatherer.isGsdlRemote) {
-		String launch  = Gatherer.cgiBase + "launch";
-		launch += "?cmd=" + filename;
-		launch += "&xml=&language="+lang;
-
-		System.err.println("*** launch = " + launch);
-
-		URL launch_url = new URL(launch);
-		URLConnection launch_connection = launch_url.openConnection();
-		input_stream = launch_connection.getInputStream();
-		document = XMLTools.parseXML(input_stream);
+		String script_output = RemoteGreenstoneServer.getScriptOptions(filename, "");
+		document = XMLTools.parseXML(new StringReader(script_output));
 	    }
 	    else {
Index: trunk/gli/src/org/greenstone/gatherer/file/FileManager.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/file/FileManager.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/file/FileManager.java	(revision 10726)
@@ -174,4 +174,5 @@
 		    // Perform the appropriate action based on the job type (RemoteGreenstoneServer will queue)
 		    if (type == FileJob.COPY) {
+			// Copies: upload all the files at once in one zip file
 			File[] source_files = new File[source_nodes.length];
 			for (int i = 0; i < source_nodes.length; i++) {
@@ -181,8 +182,14 @@
 		    }
 		    else if (type == FileJob.DELETE) {
-			RemoteGreenstoneServer.deleteCollectionFile(collection_name, source_nodes[0].getFile());
+			// Deletes: delete each top-level file/directory one at a time
+			for (int i = 0; i < source_nodes.length; i++) {
+			    RemoteGreenstoneServer.deleteCollectionFile(collection_name, source_nodes[i].getFile());
+			}
 		    }
 		    else if (type == FileJob.MOVE) {
-			RemoteGreenstoneServer.moveCollectionFile(collection_name, source_nodes[0].getFile(), target_node.getFile());
+			// Moves: move each top-level file/directory one at a time
+			for (int i = 0; i < source_nodes.length; i++) {
+			    RemoteGreenstoneServer.moveCollectionFile(collection_name, source_nodes[i].getFile(), target_node.getFile());
+			}
 		    }
 		}
@@ -218,5 +225,8 @@
 		// ... but only if it is inside the collection and we haven't already downloaded it
 		if (file.getAbsolutePath().startsWith(Gatherer.getCollectDirectoryPath()) && file.length() == 0) {
-		    RemoteGreenstoneServer.downloadCollectionFile(Gatherer.c_man.getCollection().getName(), file);
+		    if (RemoteGreenstoneServer.downloadCollectionFile(Gatherer.c_man.getCollection().getName(), file).equals("")) {
+			// Something has gone wrong downloading the file
+			return;
+		    }
 		}
 	    }
@@ -239,55 +249,84 @@
 
     protected void newFolderOrDummyDoc(DragTree tree, CollectionTreeNode parent_node, int type) {
-	// Ask the user for the directories name.
-	String extension = "";
-	if (type == FILE_TYPE) {
-	    extension = ".nul";
-	}
-	NewFolderOrFilePrompt new_folder_prompt = new NewFolderOrFilePrompt(parent_node, type, extension);
-	String name = new_folder_prompt.display();
-	new_folder_prompt.dispose();
-	new_folder_prompt = null;
-	// And if the name is non-null...
-	if(name != null) {
-	    FileSystemModel model = (FileSystemModel) tree.getModel();
-	    File folder_file = new File(parent_node.getFile(), name);
-	    //... check if it already exists.
-	    if(folder_file.exists()) { 
-		if (type == FILE_TYPE) {
-		    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Already_Exists_No_Create", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
-		} else {
-		    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Folder_Already_Exists", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
-		}
-	    }
-	    // Otherwise create it.
-	    else {
-		try {
+	(new NewFolderOrDummyDocumentTask(tree, parent_node, type)).start();
+    }
+
+
+    private class NewFolderOrDummyDocumentTask
+	extends Thread
+    {
+	private DragTree tree = null;
+	private CollectionTreeNode parent_node = null;
+	private int type;
+
+	public NewFolderOrDummyDocumentTask(DragTree tree, CollectionTreeNode parent_node, int type)
+	{
+	    this.tree = tree;
+	    this.parent_node = parent_node;
+	    this.type = type;
+	}
+
+	public void run()
+	{
+	    // Ask the user for the directories name.
+	    String extension = "";
+	    if (type == FILE_TYPE) {
+		extension = ".nul";
+	    }
+
+	    NewFolderOrFilePrompt new_folder_prompt = new NewFolderOrFilePrompt(parent_node, type, extension);
+	    String name = new_folder_prompt.display();
+	    new_folder_prompt.dispose();
+	    new_folder_prompt = null;
+
+	    // And if the name is non-null...
+	    if (name != null) {
+		FileSystemModel model = (FileSystemModel) tree.getModel();
+		File folder_file = new File(parent_node.getFile(), name);
+
+		//... check if it already exists.
+		if (folder_file.exists()) { 
 		    if (type == FILE_TYPE) {
-			folder_file.createNewFile();
-		    } else {
-			folder_file.mkdirs();
-			if (Gatherer.isGsdlRemote) {
-			    long id = System.currentTimeMillis();
-			    RemoteGreenstoneServer.newCollectionDirectory(Gatherer.c_man.getCollection().getName(), folder_file);
-			}
-		    }
-		    // Update the parent node to show the new folder
-		    parent_node.refresh();
-		    
-		    // Refresh workspace tree (collection tree is done automatically)
-		    Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
-		} catch (Exception e) {
-		    if (type == FILE_TYPE) {
-			JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Create_Error", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
-		    } else {
-			JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Folder_Create_Error", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
-		    }
-		}
-	    }
+			JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Already_Exists_No_Create", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
+		    }
+		    else {
+			JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Folder_Already_Exists", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
+		    }
+		}
+		// Otherwise create it.
+		else {
+		    try {
+			if (type == FILE_TYPE) {
+			    folder_file.createNewFile();
+			}
+			else {
+			    folder_file.mkdirs();
+			    if (Gatherer.isGsdlRemote) {
+				long id = System.currentTimeMillis();
+				RemoteGreenstoneServer.newCollectionDirectory(Gatherer.c_man.getCollection().getName(), folder_file);
+			    }
+			}
+
+			// Update the parent node to show the new folder
+			parent_node.refresh();
+
+			// Refresh workspace tree (collection tree is done automatically)
+			Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
+		    }
+		    catch (Exception exception) {
+			if (type == FILE_TYPE) {
+			    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Create_Error", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
+			}
+			else {
+			    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Folder_Create_Error", name), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
+			}
+		    }
+		}
 		
-	    folder_file = null;
-	    model = null;
-	}
-	name = null;
+		folder_file = null;
+		model = null;
+	    }
+	    name = null;
+	}
     }
 }
Index: trunk/gli/src/org/greenstone/gatherer/gui/CreatePane.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/CreatePane.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/CreatePane.java	(revision 10726)
@@ -537,4 +537,5 @@
      */
     public synchronized void processComplete(GShellEvent event) {
+	System.err.println("In CreatePane::processComplete(" + event.getType() + ")...");
 	if(event.getStatus() == GShell.OK) {
 	    if(event.getType() == GShell.BUILD) {
Index: trunk/gli/src/org/greenstone/gatherer/gui/DeleteCollectionPrompt.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/DeleteCollectionPrompt.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/DeleteCollectionPrompt.java	(revision 10726)
@@ -50,4 +50,5 @@
 import org.greenstone.gatherer.collection.BasicCollectionConfiguration;
 import org.greenstone.gatherer.file.WorkspaceTree;  // !!! Don't like this here
+import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.ArrayTools;
 import org.greenstone.gatherer.util.StaticStrings;
@@ -293,4 +294,5 @@
 	public void actionPerformed(ActionEvent event) {
 	    // Delete the selected collection.
+	    // !! This should all go into CollectionManager !!
 
 	    // First we must release it from the local library
@@ -300,39 +302,5 @@
 
 	    if (Gatherer.isGsdlRemote) {
-				
-		String launch_cgi  = Gatherer.cgiBase + "launch?cmd=deldir";
-
-		String col_name = collection.getShortName();
-		String dir = ".";
-
-		try {
-
-		    String col_name_encoded = URLEncoder.encode(col_name,"UTF-8");
-		    String dir_encoded = URLEncoder.encode(dir,"UTF-8");
-
-		    launch_cgi += "&c=" + col_name_encoded;
-		    launch_cgi += "&dir=" + dir_encoded;
-		
-		    URL launch_url = new URL(launch_cgi);
-		    URLConnection launch_connection = launch_url.openConnection();
-		    InputStream stdis = launch_connection.getInputStream();
-		    InputStreamReader stdisr = new InputStreamReader(stdis, "UTF-8"  );
-		    
-		    BufferedReader stdbr = new BufferedReader(stdisr);
-		    
-		    while(true) {
-			String line = stdbr.readLine();
-			// ignore output of running lauch command
-			
-			if (line == null) { break; }
-		    }		
-		    
-		    stdbr.close();
-		}
-		// Exception
-		catch (Exception exception) {
-		    DebugStream.println("Exception in GShell.runRemove() - unexpected");
-		    DebugStream.printStackTrace(exception);
-		}
+		RemoteGreenstoneServer.deleteCollection(collection.getShortName());
 	    }
 
Index: trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 10726)
@@ -54,4 +54,5 @@
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.collection.Collection;
+import org.greenstone.gatherer.collection.CollectionManager;
 import org.greenstone.gatherer.file.FileOpenActionListener;
 import org.greenstone.gatherer.file.WorkspaceTree;
@@ -394,4 +395,15 @@
 	    content_pane.add(tab_pane, BorderLayout.CENTER);
 
+	    // Add an extra progress bar at the bottom of every screen when using a remote Greenstone server
+	    if (Gatherer.isGsdlRemote) {
+		JPanel remote_greenstone_server_progress_panel = new JPanel();
+		JLabel remote_greenstone_server_progress_label = new JLabel(Dictionary.get("RemoteGreenstoneServer.Progress"));
+		remote_greenstone_server_progress_panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		remote_greenstone_server_progress_panel.setLayout(new BorderLayout());
+		remote_greenstone_server_progress_panel.add(remote_greenstone_server_progress_label, BorderLayout.WEST);
+		remote_greenstone_server_progress_panel.add(RemoteGreenstoneServer.getProgressBar(), BorderLayout.CENTER);
+		content_pane.add(remote_greenstone_server_progress_panel, BorderLayout.SOUTH);
+	    }
+
 	    // Call refresh to update all controls to reflect current collection status.
 	    refresh(-1, Gatherer.c_man.ready());
@@ -641,12 +653,11 @@
 	{
 	    // If using a remote Greenstone check if the collection configurations have been downloaded; do so if not
-	    if (Gatherer.isGsdlRemote) {
-		File collect_directory = new File(Gatherer.getCollectDirectoryPath());
-		if (!collect_directory.exists() || collect_directory.listFiles().length == 0) {
-		    if (!RemoteGreenstoneServer.downloadCollectionConfigurations()) {
-			// Something went wrong downloading the collection configurations
-			return;
-		    }
-		}
+	    if (Gatherer.isGsdlRemote && !CollectionManager.downloaded_collection_configurations) {
+		if (RemoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
+		    // Something went wrong downloading the collection configurations
+		    return;
+		}
+
+		CollectionManager.downloaded_collection_configurations = true;
 	    }
 
@@ -712,6 +723,16 @@
 	public void run()
 	{
+	    // If using a remote Greenstone check if the collection configurations have been downloaded; do so if not
+	    if (Gatherer.isGsdlRemote && !CollectionManager.downloaded_collection_configurations) {
+		if (RemoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
+		    // Something went wrong downloading the collection configurations
+		    return;
+		}
+
+		CollectionManager.downloaded_collection_configurations = true;
+	    }
+
+	    // Create the collection details prompt from new collection prompt
 	    NewCollectionMetadataPrompt ncm_prompt = null;
-	    // Create the collection details prompt from new collection prompt
 	    NewCollectionDetailsPrompt ncd_prompt = new NewCollectionDetailsPrompt();
 	    // If no previous collection was indicated as a model design, then show the metadata selection prompt from new collection prompt
Index: trunk/gli/src/org/greenstone/gatherer/gui/GatherPane.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/GatherPane.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/GatherPane.java	(revision 10726)
@@ -602,4 +602,5 @@
 		    explode_metadata_database = new JMenuItem(Dictionary.get("Menu.Explode_Metadata_Database"), KeyEvent.VK_E);
 		    explode_metadata_database.addActionListener(this);
+		    explode_metadata_database.setEnabled(!Gatherer.isGsdlRemote);
 		    add(explode_metadata_database);
 		}
Index: trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/MenuBar.java	(revision 10726)
@@ -97,4 +97,5 @@
 	file_cdimage = new JMenuItem();
 	file_cdimage.addActionListener(Gatherer.g_man);
+	file_cdimage.setEnabled(!Gatherer.isGsdlRemote);
 	file_cdimage.setMnemonic(KeyEvent.VK_I);
 	Dictionary.registerText(file_cdimage, "Menu.File_CDimage");
@@ -118,4 +119,5 @@
 	file_exportas = new JMenuItem();
 	file_exportas.addActionListener(Gatherer.g_man);
+	file_exportas.setEnabled(!Gatherer.isGsdlRemote);
 	file_exportas.setMnemonic(KeyEvent.VK_E);
 	Dictionary.registerText(file_exportas, "Menu.File_ExportAs");
Index: trunk/gli/src/org/greenstone/gatherer/gui/NewCollectionDetailsPrompt.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/NewCollectionDetailsPrompt.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/NewCollectionDetailsPrompt.java	(revision 10726)
@@ -221,11 +221,4 @@
 	// Retrieve the first 8 non-whitespace characters of title_final.
 	StringBuffer name_buffer = new StringBuffer("");
-
-	if (Gatherer.isGsdlRemote) {
-	    String username = System.getProperty("user.name");
-	    if ((username != null) && (username != "")) {
-		name_buffer.append(username + "-");
-	    }
-	}
 
 	int i = 0;
Index: trunk/gli/src/org/greenstone/gatherer/gui/Preferences.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/gui/Preferences.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/gui/Preferences.java	(revision 10726)
@@ -86,4 +86,5 @@
     private JTextArea mode_description_textarea;
     private JTextField font_field;
+    private JTextField gliserver_url_field;
     private JTextField library_path_field;
     private JTextField program_field;
@@ -197,5 +198,21 @@
 	    Dictionary.registerTooltip(library_path_field, "Preferences.Connection.Library_Path_Tooltip");
 	}
-	
+
+	JPanel gliserver_url_pane = null;
+	JLabel gliserver_url_label = null;
+	if (Gatherer.isGsdlRemote) {
+	    gliserver_url_pane = new JPanel();
+	    gliserver_url_pane.setPreferredSize(ROW_SIZE);
+	    gliserver_url_label = new JLabel();
+	    gliserver_url_label.setPreferredSize(LABEL_SIZE);
+	    String gliserver_url_string = "";
+	    if (Configuration.gliserver_url != null) {
+		gliserver_url_string = Configuration.gliserver_url.toString();
+	    }
+	    gliserver_url_field = new JTextField(gliserver_url_string);
+	    gliserver_url_field.setCaretPosition(0);
+	    Dictionary.registerText(gliserver_url_label, "Preferences.Connection.GLIServer_URL");
+	    Dictionary.registerTooltip(gliserver_url_field, "Preferences.Connection.GLIServer_URL_Tooltip");
+	}
 
 	JPanel site_pane = null;
@@ -291,4 +308,5 @@
 	    site_combobox.addActionListener(new SiteComboboxListener());
 	}
+
 	// Layout
 	program_pane.setLayout(new BorderLayout());
@@ -300,4 +318,10 @@
 	library_path_pane.add(library_path_field, BorderLayout.CENTER);
 
+	if (Gatherer.isGsdlRemote) {
+	    gliserver_url_pane.setLayout(new BorderLayout());
+	    gliserver_url_pane.add(gliserver_url_label, BorderLayout.WEST);
+	    gliserver_url_pane.add(gliserver_url_field, BorderLayout.CENTER);
+	}
+
 	if (Gatherer.GS3) {
 	    site_pane.setLayout(new BorderLayout());
@@ -309,4 +333,5 @@
 	    servlet_pane.add(servlet_combobox, BorderLayout.CENTER);
 	}
+
 	proxy_host_pane.setLayout(new BorderLayout());
 	proxy_host_pane.add(proxy_host_label, BorderLayout.WEST);
@@ -321,4 +346,7 @@
 	connection_pane.add(program_pane);
 	connection_pane.add(library_path_pane);
+	if (Gatherer.isGsdlRemote) {
+	    connection_pane.add(gliserver_url_pane);
+	}
 	if (Gatherer.GS3) {
 	    connection_pane.add(site_pane);
@@ -636,4 +664,20 @@
 	    Configuration.setString("general.library_url", true, library_url_string);
 
+	    if (Gatherer.isGsdlRemote) {
+		String gliserver_url_string = gliserver_url_field.getText();
+		if (gliserver_url_string.equals("")) {
+		    Configuration.gliserver_url = null;
+		}
+		else {
+		    try {
+			Configuration.gliserver_url = new URL(gliserver_url_string);
+		    }
+		    catch (MalformedURLException exception) {
+			DebugStream.printStackTrace(exception);
+		    }
+		}
+		Configuration.setString("general.gliserver_url", true, gliserver_url_string);
+	    }
+
 	    boolean site_changed = false;
 	    if (Gatherer.GS3) {
Index: trunk/gli/src/org/greenstone/gatherer/remote/RemoteGreenstoneServer.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/remote/RemoteGreenstoneServer.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/remote/RemoteGreenstoneServer.java	(revision 10726)
@@ -6,5 +6,5 @@
  * University of Waikato, New Zealand.
  *
- * Author: David Bainbridge, Greenstone Digital Library, University of Waikato
+ * Author: Michael Dewsnip, NZDL Project, University of Waikato
  *
  * Copyright (C) 2005 New Zealand Digital Library Project
@@ -30,265 +30,1109 @@
 import java.io.*;
 import java.net.*;
+import java.util.*;
+import java.util.zip.*;
+import javax.swing.*;
 import org.greenstone.gatherer.Configuration;
 import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.GAuthenticator;
 import org.greenstone.gatherer.Gatherer;
-import org.greenstone.gatherer.file.FileNode;
+import org.greenstone.gatherer.collection.CollectionManager;
 import org.greenstone.gatherer.shell.GShell;
+import org.greenstone.gatherer.util.Utility;
+import org.greenstone.gatherer.util.ZipTools;
 
 
 public class RemoteGreenstoneServer
 {
-    static public boolean deleteCollectionFile(String collection_name, File collection_file)
-    {
-	return true;
-    }
-
-
-    static public boolean downloadCollection(String collection_name)
-    {
-	return true;
-    }
-
-
-    static public boolean downloadCollectionConfigurations()
-    {
-	return true;
-    }
-
-
-    static public boolean downloadCollectionFile(String collection_name, File collection_file)
-    {
-	return true;
-    }
-
-
-    static public Boolean moveCollectionFile(String collection_name, File source_collection_file, File target_collection_file)
-    {
-	return null;
-    }
-
-
-    static public Boolean newCollectionDirectory(String collection_name, File new_collection_directory)
-    {
-	return null;
-    }
-
-
-    static public boolean uploadCollectionFile(String collection_name, File collection_file)
-    {
- 	return true;
-    }
-
-
-    static public boolean uploadCollectionFiles(String collection_name, File[] collection_files)
-    {
- 	return true;
-    }
-
-
-    static public Boolean uploadFilesIntoCollection(String collection_name, File[] source_files, File target_collection_directory)
-    {
- 	return null;
-    }
-
-
-    static public void download_url_zip(String col_name, String dir, GShell source, String accept_expr, String reject_expr)
-    {
-	try {
-	    String download_cgi  = Gatherer.cgiBase + "download";
-
-	    // Send col_name and dir arguments to download cgi
-	    String col_name_encoded = URLEncoder.encode(col_name,"UTF-8");
-	    String dir_encoded = URLEncoder.encode(dir,"UTF-8");
-	    String cgi_args = "c=" + col_name_encoded;
-	    cgi_args += "&dir=" + dir_encoded + "&a=" + accept_expr + "&r=" + reject_expr;
-
-	    URL download_url = new URL(download_cgi);
-
-	    System.err.println("**** download cgi = '" + download_cgi + "'");
-	    System.err.println("**** cgi args = '" + cgi_args + "'");
-
-	    URLConnection dl_connection = download_url.openConnection();
-	    dl_connection.setDoOutput(true);	    
-	    OutputStream dl_os = dl_connection.getOutputStream();
-
-	    PrintWriter dl_out = new PrintWriter(dl_os);
-	    dl_out.println(cgi_args);
-	    dl_out.close();
-
-	    // Download result from running cgi script
-	    InputStream dl_is = dl_connection.getInputStream();
-	    BufferedInputStream dl_bis = new BufferedInputStream(dl_is); 
-	    DataInputStream dl_dbis = new DataInputStream(dl_bis);
-		    
-	    // set up output stream for zip download
-	    String col_dir;
-	    if (col_name.startsWith("/")) {
-		col_dir = Configuration.gsdl_path;
+    // ----------------------------------------------------------------------------------------------------
+    //   PUBLIC LAYER
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static public String deleteCollection(String collection_name)
+    {
+	return performAction(new RemoteGreenstoneServerDeleteCollectionAction(collection_name));
+    }
+
+
+    static public String deleteCollectionFile(String collection_name, File collection_file)
+    {
+	return performAction(new RemoteGreenstoneServerDeleteCollectionFileAction(collection_name, collection_file));
+    }
+
+
+    static public String downloadCollection(String collection_name)
+    {
+	return performAction(new RemoteGreenstoneServerDownloadCollectionAction(collection_name));
+    }
+
+
+    static public String downloadCollectionArchives(String collection_name)
+    {
+	return performAction(new RemoteGreenstoneServerDownloadCollectionArchivesAction(collection_name));
+    }
+
+
+    static public String downloadCollectionConfigurations()
+    {
+	return performAction(new RemoteGreenstoneServerDownloadCollectionConfigurationsAction());
+    }
+
+
+    static public String downloadCollectionFile(String collection_name, File collection_file)
+    {
+	return performAction(new RemoteGreenstoneServerDownloadCollectionFileAction(collection_name, collection_file));
+    }
+
+
+    static public String getScriptOptions(String script_name, String script_arguments)
+    {
+	return performAction(new RemoteGreenstoneServerGetScriptOptionsAction(script_name, script_arguments));
+    }
+
+
+    static public String moveCollectionFile(String collection_name, File source_collection_file, File target_collection_file)
+    {
+	return performAction(new RemoteGreenstoneServerMoveCollectionFileAction(collection_name, source_collection_file, target_collection_file));
+    }
+
+
+    static public String newCollectionDirectory(String collection_name, File new_collection_directory)
+    {
+	return performAction(new RemoteGreenstoneServerNewCollectionDirectoryAction(collection_name, new_collection_directory));
+    }
+
+
+    static public String runScript(String collection_name, String script_name, String script_arguments, GShell shell)
+    {
+	return performAction(new RemoteGreenstoneServerRunScriptAction(collection_name, script_name, script_arguments, shell));
+    }
+
+
+    static public String uploadCollectionFile(String collection_name, File collection_file)
+    {
+	return performAction(new RemoteGreenstoneServerUploadCollectionFilesAction(collection_name, new File[] { collection_file }));
+    }
+
+
+    static public String uploadCollectionFiles(String collection_name, File[] collection_files)
+    {
+	return performAction(new RemoteGreenstoneServerUploadCollectionFilesAction(collection_name, collection_files));
+    }
+
+
+    static public String uploadFilesIntoCollection(String collection_name, File[] source_files, File target_collection_directory)
+    {
+	return performAction(new RemoteGreenstoneServerUploadFilesIntoCollectionAction(collection_name, source_files, target_collection_directory));
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static public void exit()
+    {
+	System.err.println("Exiting, number of jobs on queue: " + remote_greenstone_server_action_queue.size());
+
+	// If there are still jobs on the queue we must wait for the jobs to finish
+	while (remote_greenstone_server_action_queue.size() > 0) {
+	    synchronized (remote_greenstone_server_action_queue) {
+		try {
+		    DebugStream.println("Waiting for queue to become empty...");
+		    remote_greenstone_server_action_queue.wait(500);
+		}
+		catch (InterruptedException exception) {}
+	    }
+	}
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   QUEUE LAYER
+    // ----------------------------------------------------------------------------------------------------
+
+
+    /** Returns null if we cannot wait for the action to finish, "" if the action failed, or the action output. */
+    static private String performAction(RemoteGreenstoneServerAction remote_greenstone_server_action)
+    {
+	// Add the action to the queue
+	remote_greenstone_server_action_queue.addAction(remote_greenstone_server_action);
+
+	// If we're running in the GUI thread we must return immediately
+	// We cannot wait for the action to complete because this will block any GUI updates
+	if (SwingUtilities.isEventDispatchThread()) {
+	    System.err.println("WARNING: In event dispatch thread, returning immediately...");
+	    return null;
+	}
+
+	// Otherwise wait until the action is processed
+	while (!remote_greenstone_server_action.processed) {
+	    synchronized (remote_greenstone_server_action) {
+		try {
+		    DebugStream.println("Waiting for action to complete...");
+		    remote_greenstone_server_action.wait(500);
+		}
+		catch (InterruptedException exception) {}
+	    }
+	}
+
+	// Return "" if the action failed
+	if (!remote_greenstone_server_action.processed_successfully) {
+	    return "";
+	}
+	// Otherwise return the action output
+	return remote_greenstone_server_action.action_output;
+    }
+
+
+    static private RemoteGreenstoneServerActionQueue remote_greenstone_server_action_queue = new RemoteGreenstoneServerActionQueue();
+
+
+    static private class RemoteGreenstoneServerActionQueue
+	extends Thread
+    {
+	/** The queue of waiting jobs. */
+	private ArrayList queue = null;
+
+
+	public RemoteGreenstoneServerActionQueue()
+	{
+	    if (Gatherer.isGsdlRemote) {
+		queue = new ArrayList();
+		start();
+	    }
+	}
+
+
+	synchronized public void addAction(RemoteGreenstoneServerAction remote_greenstone_server_action)
+	{
+	    queue.add(remote_greenstone_server_action);
+	    notifyAll();
+	}
+
+
+	public int size()
+	{
+	    return queue.size();
+	}
+
+
+	public void run()
+	{
+	    while (true) {
+		// If there are jobs on the queue, get the next in line and process it
+		if (queue.size() > 0) {
+		    RemoteGreenstoneServerAction remote_greenstone_server_action = (RemoteGreenstoneServerAction) queue.get(0);
+
+		    try {
+			remote_greenstone_server_action.perform();
+
+			// No exceptions were thrown, so the action was successful
+			remote_greenstone_server_action.processed_successfully = true;
+		    }
+		    catch (RemoteGreenstoneServerActionCancelledException exception) {
+			remote_greenstone_server_action.processed_successfully = false;
+		    }
+		    catch (Exception exception) {
+			DebugStream.printStackTrace(exception);
+			JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Error", exception.getMessage()), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
+			remote_greenstone_server_action.processed_successfully = false;
+		    }
+
+		    // We're done with this action, for better or worse
+		    remote_greenstone_server_action.processed = true;
+		    progress_bar.setAction(null);
+		    queue.remove(0);
+		}
+
+		// Otherwise the queue is empty
+		else {
+		    // Wait until we are notify()ed by addAction that there is a new job on the queue
+		    synchronized (this) {
+			try {
+			    wait();
+			}
+			catch (InterruptedException exception) { }
+		    }
+		}
+	    }
+	}
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   PROGRESS BAR
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static private RemoteGreenstoneServerProgressBar progress_bar = new RemoteGreenstoneServerProgressBar();
+
+
+    static private class RemoteGreenstoneServerProgressBar
+	extends JProgressBar
+    {
+	public RemoteGreenstoneServerProgressBar()
+	{
+	    setBackground(Configuration.getColor("coloring.collection_tree_background", false));
+	    setForeground(Configuration.getColor("coloring.collection_tree_foreground", false));
+	    setString(Dictionary.get("FileActions.No_Activity"));
+	    setStringPainted(true);
+	}
+
+
+	public void setAction(String action)
+	{
+	    if (action != null) {
+		DebugStream.println(action);
+	    }
+
+	    // We cannot call this from the GUI thread otherwise the progress bar won't start
+	    if (SwingUtilities.isEventDispatchThread()) {
+		System.err.println("ERROR: RemoteGreenstoneServerProgressBar.setAction() called from event dispatch thread!");
+		return;
+	    }
+
+	    // Set the string on the progress bar, and start or stop it
+	    if (action == null) {
+		setString(Dictionary.get("FileActions.No_Activity"));
+		setIndeterminate(false);
 	    }
 	    else {
-		col_dir = Gatherer.getCollectDirectoryPath();
-	    }
-
-	    String zip_fname = col_dir + col_name + ".zip";
-	    FileOutputStream zip_fos = new FileOutputStream(zip_fname);
+		setString(action);
+		setIndeterminate(true);
+	    }
+	}
+    }
+
+
+    static public RemoteGreenstoneServerProgressBar getProgressBar()
+    {
+	return progress_bar;
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   ACTIONS
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static private abstract class RemoteGreenstoneServerAction
+    {
+	public String action_output = null;
+	public boolean processed = false;
+	public boolean processed_successfully;
+
+	abstract public void perform()
+	    throws Exception;
+    }
+
+
+    static private class RemoteGreenstoneServerActionCancelledException
+	extends Exception
+    {
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DELETE COLLECTION
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDeleteCollectionAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+
+	public RemoteGreenstoneServerDeleteCollectionAction(String collection_name)
+	{
+	    this.collection_name = collection_name;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Deleting collection " + collection_name + "...");
+
+	    String delete_collection_command = "cmd=delete-collection";
+	    delete_collection_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    action_output = sendCommandToServer(delete_collection_command, null);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DELETE COLLECTION FILE
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDeleteCollectionFileAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File collection_file;
+
+	public RemoteGreenstoneServerDeleteCollectionFileAction(String collection_name, File collection_file)
+	{
+	    this.collection_name = collection_name;
+	    this.collection_file = collection_file;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String collection_file_relative_path = getPathRelativeToDirectory(collection_file, collection_directory_path);
+	    collection_file_relative_path = collection_file_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    progress_bar.setAction("Deleting collection file " + collection_file_relative_path + "...");
+
+	    String delete_collection_file_command = "cmd=delete-collection-file";
+	    delete_collection_file_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    delete_collection_file_command += "&file=" + URLEncoder.encode(collection_file_relative_path, "UTF-8");
+	    action_output = sendCommandToServer(delete_collection_file_command, null);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DOWNLOAD COLLECTION
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDownloadCollectionAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+
+	public RemoteGreenstoneServerDownloadCollectionAction(String collection_name)
+	{
+	    this.collection_name = collection_name;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Downloading remote collection " + collection_name + "...");
+
+	    String download_collection_command = "cmd=download-collection";
+	    download_collection_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    String zip_file_path = Gatherer.getCollectDirectoryPath() + collection_name + ".zip";
+	    action_output = downloadFile(download_collection_command, zip_file_path);
+
+	    // Delete the existing (local) collection
+	    Gatherer.c_man.deleteCollection(collection_name);
+
+	    // Unzip the collection just downloaded
+	    ZipTools.unzipFile(zip_file_path, Gatherer.getCollectDirectoryPath());
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DOWNLOAD COLLECTION ARCHIVES
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDownloadCollectionArchivesAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+
+	public RemoteGreenstoneServerDownloadCollectionArchivesAction(String collection_name)
+	{
+	    this.collection_name = collection_name;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Downloading collection archives for " + collection_name + "...");
+
+	    String download_collection_archives_command = "cmd=download-collection-archives";
+	    download_collection_archives_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    String zip_file_path = Gatherer.getCollectDirectoryPath() + collection_name + "-archives.zip";
+	    action_output = downloadFile(download_collection_archives_command, zip_file_path);
+
+	    // Delete the existing (local) collection archives
+	    Utility.delete(new File(Gatherer.c_man.getCollectionArchivesDirectoryPath()));
+
+	    // Unzip the collection archives just downloaded
+	    ZipTools.unzipFile(zip_file_path, Gatherer.getCollectDirectoryPath());
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DOWNLOAD COLLECTION CONFIGURATIONS
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDownloadCollectionConfigurationsAction
+	extends RemoteGreenstoneServerAction
+    {
+	public RemoteGreenstoneServerDownloadCollectionConfigurationsAction()
+	{
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Downloading collection configurations...");
+
+	    // Delete the existing (local) collect directory
+	    Utility.delete(new File(Gatherer.getCollectDirectoryPath()));
+	    new File(Gatherer.getCollectDirectoryPath()).mkdirs();
+
+	    String download_collection_configurations_command = "cmd=download-collection-configurations";
+	    String zip_file_path = Gatherer.getCollectDirectoryPath() + "collections.zip";
+	    action_output = downloadFile(download_collection_configurations_command, zip_file_path);
+
+	    // Unzip the collection configurations just downloaded
+	    ZipTools.unzipFile(zip_file_path, Gatherer.getCollectDirectoryPath());
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    DOWNLOAD COLLECTION FILE
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerDownloadCollectionFileAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File collection_file;
+
+	public RemoteGreenstoneServerDownloadCollectionFileAction(String collection_name, File collection_file)
+	{
+	    this.collection_name = collection_name;
+	    this.collection_file = collection_file;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String collection_file_relative_path = getPathRelativeToDirectory(collection_file, collection_directory_path);
+	    collection_file_relative_path = collection_file_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    progress_bar.setAction("Downloading collection file " + collection_file_relative_path + "...");
+
+	    String download_collection_file_command = "cmd=download-collection-file";
+	    download_collection_file_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    download_collection_file_command += "&file=" + URLEncoder.encode(collection_file_relative_path, "UTF-8");
+	    String zip_file_name = collection_name + "-" + collection_file.getName() + ".zip";
+	    String zip_file_path = collection_directory_path + zip_file_name;
+	    action_output = downloadFile(download_collection_file_command, zip_file_path);
+
+	    // Unzip the collection file just downloaded
+	    ZipTools.unzipFile(zip_file_path, collection_directory_path);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    GET SCRIPT OPTIONS
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerGetScriptOptionsAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String script_name;
+	private String script_arguments;
+
+	public RemoteGreenstoneServerGetScriptOptionsAction(String script_name, String script_arguments)
+	{
+	    this.script_name = script_name;
+	    this.script_arguments = script_arguments;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Getting options for " + script_name + "...");
+
+	    String get_script_options_command = "cmd=get-script-options";
+	    get_script_options_command += "&script=" + script_name;
+	    get_script_options_command += "&xml=";
+	    get_script_options_command += "&language=" + Configuration.getLanguage();
+	    get_script_options_command += script_arguments;
+	    action_output = sendCommandToServer(get_script_options_command, null);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    MOVE COLLECTION FILE
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerMoveCollectionFileAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File source_collection_file;
+	private File target_collection_file;
+
+	public RemoteGreenstoneServerMoveCollectionFileAction(String collection_name, File source_collection_file, File target_collection_file)
+	{
+	    this.collection_name = collection_name;
+	    this.source_collection_file = source_collection_file;
+	    this.target_collection_file = target_collection_file;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String source_collection_file_relative_path = getPathRelativeToDirectory(source_collection_file, collection_directory_path);
+	    source_collection_file_relative_path = source_collection_file_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    String target_collection_file_relative_path = getPathRelativeToDirectory(target_collection_file, collection_directory_path);
+	    target_collection_file_relative_path = target_collection_file_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    progress_bar.setAction("Moving file " + source_collection_file_relative_path + " -> " + target_collection_file_relative_path + "...");
+
+	    String move_collection_file_command = "cmd=move-collection-file";
+	    move_collection_file_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    move_collection_file_command += "&source=" + URLEncoder.encode(source_collection_file_relative_path, "UTF-8");
+	    move_collection_file_command += "&target=" + URLEncoder.encode(target_collection_file_relative_path, "UTF-8");
+	    action_output = sendCommandToServer(move_collection_file_command, null);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    NEW COLLECTION DIRECTORY
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerNewCollectionDirectoryAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File new_collection_directory;
+
+	public RemoteGreenstoneServerNewCollectionDirectoryAction(String collection_name, File new_collection_directory)
+	{
+	    this.collection_name = collection_name;
+	    this.new_collection_directory = new_collection_directory;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String new_collection_directory_relative_path = getPathRelativeToDirectory(new_collection_directory, collection_directory_path);
+	    new_collection_directory_relative_path = new_collection_directory_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    progress_bar.setAction("Creating new directory " + new_collection_directory_relative_path + "...");
+
+	    String new_collection_directory_command = "cmd=new-collection-directory";
+	    new_collection_directory_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    new_collection_directory_command += "&directory=" + URLEncoder.encode(new_collection_directory_relative_path, "UTF-8");
+	    action_output = sendCommandToServer(new_collection_directory_command, null);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    RUN SCRIPT
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerRunScriptAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private String script_name;
+	private String script_arguments;
+	private GShell shell;
+
+	public RemoteGreenstoneServerRunScriptAction(String collection_name, String script_name, String script_arguments, GShell shell)
+	{
+	    this.collection_name = collection_name;
+	    this.script_name = script_name;
+	    this.script_arguments = script_arguments;
+	    this.shell = shell;
+	}
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Running " + script_name + "...");
+
+	    String run_script_command = "cmd=run-script";
+	    run_script_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    run_script_command += "&script=" + script_name;
+	    run_script_command += "&language=" + Configuration.getLanguage();
+	    run_script_command += script_arguments;
+	    action_output = sendCommandToServer(run_script_command, shell);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    UPLOAD COLLECTION FILE
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerUploadCollectionFilesAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File[] collection_files;
+
+
+	public RemoteGreenstoneServerUploadCollectionFilesAction(String collection_name, File[] collection_files)
+	{
+	    this.collection_name = collection_name;
+	    this.collection_files = collection_files;
+	}
+
+
+	public void perform()
+	    throws Exception
+	{
+	    progress_bar.setAction("Uploading collection files...");
+
+	    // Determine the file paths relative to the collection directory
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String[] collection_file_relative_paths = new String[collection_files.length];
+	    for (int i = 0; i < collection_files.length; i++) {
+		collection_file_relative_paths[i] = getPathRelativeToDirectory(collection_files[i], collection_directory_path);
+	    }
+
+	    // Zip up the files to send to the server
+	    String zip_file_name = collection_name + "-" + System.currentTimeMillis() + ".zip";
+	    String zip_file_path = collection_directory_path + zip_file_name;
+	    ZipTools.zipFiles(zip_file_path, collection_directory_path, collection_file_relative_paths);
+
+	    // Upload the zip file
+	    String upload_collection_file_command = "cmd=upload-collection-file";
+	    upload_collection_file_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    upload_collection_file_command += "&file=" + URLEncoder.encode(zip_file_name, "UTF-8");
+	    upload_collection_file_command += "&directory=";
+	    upload_collection_file_command += "&zip=true";
+	    action_output = uploadFile(upload_collection_file_command, zip_file_path);
+	}
+    }
+
+
+    /**
+     * --------------------------------------------------------------------------------------------
+     *    UPLOAD FILES INTO COLLECTION
+     * --------------------------------------------------------------------------------------------
+     */
+    static private class RemoteGreenstoneServerUploadFilesIntoCollectionAction
+	extends RemoteGreenstoneServerAction
+    {
+	private String collection_name;
+	private File[] source_files;
+	private File target_collection_directory;
+
+
+	public RemoteGreenstoneServerUploadFilesIntoCollectionAction(String collection_name, File[] source_files, File target_collection_directory)
+	{
+	    this.collection_name = collection_name;
+	    this.source_files = source_files;
+	    this.target_collection_directory = target_collection_directory;
+	}
+
+
+	public void perform()
+	    throws Exception
+	{
+	    String collection_directory_path = CollectionManager.getCollectionDirectoryPath(collection_name);
+	    String target_collection_directory_relative_path = getPathRelativeToDirectory(target_collection_directory, collection_directory_path);
+	    target_collection_directory_relative_path = target_collection_directory_relative_path.replaceAll((Utility.isWindows() ? "\\\\" : "\\/"), "|");
+	    progress_bar.setAction("Uploading files into collection...");
+
+	    String zip_file_name = collection_name + "-" + System.currentTimeMillis() + ".zip";
+	    String zip_file_path = Gatherer.getCollectDirectoryPath() + zip_file_name;
+	    DebugStream.println("Zip file path: " + zip_file_path);
+
+	    String base_directory_path = source_files[0].getParentFile().getAbsolutePath();
+	    DebugStream.println("Base directory path: " + base_directory_path);
+	    String[] source_file_relative_paths = new String[source_files.length];
+	    for (int i = 0; i < source_files.length; i++) {
+		DebugStream.println("Source file path: " + source_files[i]);
+		source_file_relative_paths[i] = getPathRelativeToDirectory(source_files[i], base_directory_path);
+	    }
+
+	    ZipTools.zipFiles(zip_file_path, base_directory_path, source_file_relative_paths);
+
+	    String upload_collection_file_command = "cmd=upload-collection-file";
+	    upload_collection_file_command += "&c=" + URLEncoder.encode(collection_name, "UTF-8");
+	    upload_collection_file_command += "&file=" + URLEncoder.encode(zip_file_name, "UTF-8");
+	    upload_collection_file_command += "&directory=" + URLEncoder.encode(target_collection_directory_relative_path, "UTF-8");
+	    upload_collection_file_command += "&zip=true";
+	    action_output = uploadFile(upload_collection_file_command, zip_file_path);
+	}
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   AUTHENTICATION LAYER
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static private PasswordAuthentication remote_greenstone_server_authentication = null;
+    // static private PasswordAuthentication remote_greenstone_server_authentication = new PasswordAuthentication(System.getProperty("user.name"), new char[] { });
+
+
+    static private class RemoteGreenstoneServerAuthenticateTask
+	extends Thread
+    {
+	public void run()
+	{
+	    remote_greenstone_server_authentication = new RemoteGreenstoneServerAuthenticator().getAuthentication();
+	}
+
+
+	static private class RemoteGreenstoneServerAuthenticator
+	    extends GAuthenticator
+	{
+	    public PasswordAuthentication getAuthentication()
+	    {
+		return getPasswordAuthentication();
+	    }
+
+	    protected String getMessageString()
+	    {
+		return Dictionary.get("RemoteGreenstoneServer.Authentication_Message");
+	    }
+ 	}
+    }
+
+
+    static private void authenticateUser()
+	throws RemoteGreenstoneServerActionCancelledException
+    {
+	// If we don't have any authentication information then ask for it now
+	if (remote_greenstone_server_authentication == null) {
+	    try {
+		// We have to do this on the GUI thread
+		SwingUtilities.invokeAndWait(new RemoteGreenstoneServerAuthenticateTask());
+	    }
+	    catch (Exception exception) {
+		DebugStream.printStackTrace(exception);
+	    }
+
+	    // If it is still null then the user has cancelled the authentication, so the action is cancelled
+	    if (remote_greenstone_server_authentication == null) {
+		throw new RemoteGreenstoneServerActionCancelledException();
+	    }
+	}
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   REQUEST LAYER
+    // ----------------------------------------------------------------------------------------------------
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String downloadFile(String gliserver_args, String file_path)
+	throws Exception
+    {
+	while (true) {
+	    // Check that Configuration.gliserver_url is set
+	    if (Configuration.gliserver_url == null) {
+		throw new Exception("Empty gliserver URL: please set this in Preferences before continuing.");
+	    }
+
+	    // Ask for authentication information (if necessary), then perform the action
+	    authenticateUser();
+	    String gliserver_url_string = Configuration.gliserver_url.toString();
+	    String command_output = downloadFileInternal(gliserver_url_string, gliserver_args, file_path);
+
+	    // Check the first line to see if authentication has failed; if so, go around the loop again
+	    if (command_output.startsWith("ERROR: Authentication failed:")) {
+		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Error", command_output.substring("ERROR: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
+		remote_greenstone_server_authentication = null;
+		continue;
+	    }
+	    // Check if the collection is locked by someone else; if so, see if the user wants to steal the lock
+	    else if (command_output.startsWith("ERROR: Collection is locked by: ")) {
+		if (JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Steal_Lock_Message", command_output.substring("ERROR: Collection is locked by: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
+		    // The user has decided to cancel the action
+		    throw new RemoteGreenstoneServerActionCancelledException();
+		}
+
+		// The user has decided to steal the lock... rerun the command with "&steal_lock="
+		gliserver_args += "&steal_lock=";
+		continue;
+	    }
+	    // Handle other types of errors by throwing an exception
+	    else if (command_output.startsWith("ERROR: ")) {
+		throw new Exception(command_output.substring("ERROR: ".length()));
+	    }
+
+	    // There were no exceptions thrown so the action must have succeeded
+	    return command_output;
+	}
+    }
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String sendCommandToServer(String gliserver_args, GShell shell)
+	throws Exception
+    {
+	while (true) {
+	    // Check that Configuration.gliserver_url is set
+	    if (Configuration.gliserver_url == null) {
+		throw new Exception("Empty gliserver URL: please set this in Preferences before continuing.");
+	    }
+
+	    // Ask for authentication information (if necessary), then perform the action
+	    authenticateUser();
+	    String gliserver_url_string = Configuration.gliserver_url.toString();
+	    String command_output = sendCommandToServerInternal(gliserver_url_string, gliserver_args, shell);
+	    // System.err.println("Command output: " + command_output);
+
+	    // Check the first line to see if authentication has failed; if so, go around the loop again
+	    if (command_output.startsWith("ERROR: Authentication failed:")) {
+		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Error", command_output.substring("ERROR: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
+		remote_greenstone_server_authentication = null;
+		continue;
+	    }
+	    // Check if the collection is locked by someone else; if so, see if the user wants to steal the lock
+	    else if (command_output.startsWith("ERROR: Collection is locked by: ")) {
+		if (JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Steal_Lock_Message", command_output.substring("ERROR: Collection is locked by: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
+		    // The user has decided to cancel the action
+		    throw new RemoteGreenstoneServerActionCancelledException();
+		}
+
+		// The user has decided to steal the lock... rerun the command with "&steal_lock="
+		gliserver_args += "&steal_lock=";
+		continue;
+	    }
+	    // Handle other types of errors by throwing an exception
+	    else if (command_output.startsWith("ERROR: ")) {
+		throw new Exception(command_output.substring("ERROR: ".length()));
+	    }
+
+	    // There were no exceptions thrown so the action must have succeeded
+	    return command_output;
+	}
+    }
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String uploadFile(String gliserver_args, String file_path)
+	throws Exception
+    {
+	while (true) {
+	    // Check that Configuration.gliserver_url is set
+	    if (Configuration.gliserver_url == null) {
+		throw new Exception("Empty gliserver URL: please set this in Preferences before continuing.");
+	    }
+
+	    // Ask for authentication information (if necessary), then perform the action
+	    authenticateUser();
+	    String gliserver_url_string = Configuration.gliserver_url.toString();
+	    String command_output = uploadFileInternal(gliserver_url_string, gliserver_args, file_path);
+	    // System.err.println("Command output: " + command_output);
+
+	    // Check the first line to see if authentication has failed; if so, go around the loop again
+	    if (command_output.startsWith("ERROR: Authentication failed:")) {
+		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Error", command_output.substring("ERROR: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
+		remote_greenstone_server_authentication = null;
+		continue;
+	    }
+	    // Check if the collection is locked by someone else; if so, see if the user wants to steal the lock
+	    else if (command_output.startsWith("ERROR: Collection is locked by: ")) {
+		if (JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("RemoteGreenstoneServer.Steal_Lock_Message", command_output.substring("ERROR: Collection is locked by: ".length())), Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
+		    // The user has decided to cancel the action
+		    throw new RemoteGreenstoneServerActionCancelledException();
+		}
+
+		// The user has decided to steal the lock... rerun the command with "&steal_lock="
+		gliserver_args += "&steal_lock=";
+		continue;
+	    }
+	    // Handle other types of errors by throwing an exception
+	    else if (command_output.startsWith("ERROR: ")) {
+		throw new Exception(command_output.substring("ERROR: ".length()));
+	    }
+
+	    // There were no exceptions thrown so the action must have succeeded
+	    return command_output;
+	}
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   NETWORK LAYER
+    // ----------------------------------------------------------------------------------------------------
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String downloadFileInternal(String download_cgi, String cgi_args, String file_path)
+	throws Exception
+    {
+	DebugStream.println("gliserver URL: " + download_cgi);
+	System.err.println("gliserver args: " + cgi_args);
+
+	// Add username and password
+	cgi_args += "&un=" + remote_greenstone_server_authentication.getUserName();
+	cgi_args += "&pw=" + new String(remote_greenstone_server_authentication.getPassword());
+
+	URL download_url = new URL(download_cgi);
+	URLConnection dl_connection = download_url.openConnection();
+	dl_connection.setDoOutput(true);
+	OutputStream dl_os = dl_connection.getOutputStream();
+
+	PrintWriter dl_out = new PrintWriter(dl_os);
+	dl_out.println(cgi_args);
+	dl_out.close();
+
+	// Download result from running cgi script
+	InputStream dl_is = dl_connection.getInputStream();
+	BufferedInputStream dl_bis = new BufferedInputStream(dl_is);
+	DataInputStream dl_dbis = new DataInputStream(dl_bis);
+
+	String first_line = "";
+	byte[] buf = new byte[1024];
+	int len = dl_dbis.read(buf);
+	if (len >= 0) {
+	    String first_chunk = new String(buf, 0, len);
+	    first_line = first_chunk.substring(0, ((first_chunk.indexOf("\n") != -1) ? first_chunk.indexOf("\n") : len));
+
+	    // Save the data to file
+	    FileOutputStream zip_fos = new FileOutputStream(file_path);
 	    BufferedOutputStream zip_bfos = new BufferedOutputStream(zip_fos);
-	    
-	    byte[] buf = new byte[1024];
-	    int len;
-
-	    while ((len = dl_dbis.read(buf)) >= 0 && !source.hasSignalledStop()) {
-		zip_bfos.write(buf,0,len);
-	    }
-
-	    dl_dbis.close();
-	    dl_bis.close();
-	    dl_is.close();
+
+	    while (len >= 0) {
+		zip_bfos.write(buf, 0, len);
+		len = dl_dbis.read(buf);
+	    }
 
 	    zip_bfos.close();
 	    zip_fos.close();
-
-	    if(source.hasSignalledStop()) {
-		//A cancel has been called. Delete the zip file.
-		DebugStream.println("download_url_zip() cancelled. Cleaning up.");
-		if(new File(zip_fname).delete()) {
-		    DebugStream.println("Zip file " + zip_fname + " deleted");
+	}
+
+	dl_dbis.close();
+	dl_bis.close();
+	dl_is.close();
+	return first_line;
+    }
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String sendCommandToServerInternal(String gliserver_url_string, String cgi_args, GShell shell)
+	throws Exception
+    {
+	DebugStream.println("gliserver URL: " + gliserver_url_string);
+	System.err.println("gliserver args: " + cgi_args);
+
+	// Add username and password
+	cgi_args += "&un=" + remote_greenstone_server_authentication.getUserName();
+        cgi_args += "&pw=" + new String(remote_greenstone_server_authentication.getPassword());
+
+	URL gliserver_url = new URL(gliserver_url_string + "?" + cgi_args);
+	URLConnection gliserver_connection = gliserver_url.openConnection();
+
+	// Read the output of the command from the server, and return it
+	StringBuffer command_output_buffer = new StringBuffer(2048);
+	InputStream gliserver_is = gliserver_connection.getInputStream();
+	BufferedReader gliserver_in = new BufferedReader(new InputStreamReader(gliserver_is, "UTF-8"));
+	String gliserver_output_line = gliserver_in.readLine();
+	while (gliserver_output_line != null) {
+	    if (shell != null) {
+		shell.fireMessage(gliserver_output_line);
+		if (shell.hasSignalledStop()) {
+		    throw new RemoteGreenstoneServerActionCancelledException();
 		}
-		else {
-		    DebugStream.println("Zip file " + zip_fname + " NOT deleted (no big deal). Does it exist?");
-		}
-	    }
-	}
-	catch (Exception error) {
-	    error.printStackTrace();
-	}
-	DebugStream.println("Exited download_url_zip");
-    }
-
-
-    static public void upload_url_zip(String col_name, String dir, String delete_type, GShell source)
-    {
-	final String lineEnd = "\r\n";
-	final String twoHyphens = "--";
-	final String boundary =  "*****";
+	    }
+	    command_output_buffer.append(gliserver_output_line + "\n");
+	    gliserver_output_line = gliserver_in.readLine();
+	}
+	gliserver_in.close();
+
+	return command_output_buffer.toString();
+    }
+
+
+    /** Returns the command output if the action completed, throws some kind of exception otherwise. */
+    static private String uploadFileInternal(String upload_cgi, String cgi_args, String file_path)
+	throws Exception
+    {
+	DebugStream.println("gliserver URL: " + upload_cgi);
+	System.err.println("gliserver args: " + cgi_args);
+
+	// Add username and password
+	cgi_args += "&un=" + remote_greenstone_server_authentication.getUserName();
+	cgi_args += "&pw=" + new String(remote_greenstone_server_authentication.getPassword());
+
+	// Open a HTTP connection to the URL
+	URL url = new URL(upload_cgi);
+	HttpURLConnection gliserver_connection = (HttpURLConnection) url.openConnection();
+
+	gliserver_connection.setDoInput(true);         // Allow Inputs
+	gliserver_connection.setDoOutput(true);        // Allow Outputs
+	gliserver_connection.setUseCaches(false);      // Don't use a cached copy.
+
+	gliserver_connection.setRequestProperty("Connection", "Keep-Alive");	    
+
+	DataOutputStream dos = new DataOutputStream(gliserver_connection.getOutputStream());
+	dos.writeBytes(cgi_args + "\n");
+
+	// Send zip file to server
+	File file = new File(file_path);
+	FileInputStream fileInputStream = new FileInputStream(file);
+
+	// create a buffer of maximum size
 	final int maxBufferSize = 1024;
-
-	String col_dir;
-	if (col_name.startsWith("/")) {
-	    col_dir = Configuration.gsdl_path;
-	}
-	else {
-	    col_dir = Gatherer.getCollectDirectoryPath();
-	}
-
-	String zip_fname = col_dir + col_name + ".zip";
-	String upload_cgi = Gatherer.cgiBase + "upload";
-
-	HttpURLConnection conn = null;
-
-	try {
-	    // Send zip file to server
-	    File file = new File(zip_fname);
-	    FileInputStream fileInputStream = new FileInputStream(file);
-
-	    // open a URL connection to the Servlet 
-	    URL url = new URL(upload_cgi);
-	    System.err.println("**** Uploading to: " + upload_cgi);
-
-	    // Open a HTTP connection to the URL
-	    conn = (HttpURLConnection) url.openConnection();
-
-	    conn.setDoInput(true);         // Allow Inputs
-	    conn.setDoOutput(true);        // Allow Outputs
-	    conn.setUseCaches(false);      // Don't use a cached copy.
-	    conn.setRequestMethod("POST"); // Use a post method.
-
-	    conn.setRequestProperty("Connection", "Keep-Alive");	    
-	    conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);
-
-	    DataOutputStream dos = new DataOutputStream( conn.getOutputStream() );
-
-	    dos.writeBytes(twoHyphens + boundary + lineEnd);
-	    dos.writeBytes("Content-Disposition: form-data; name=\"c\"" + lineEnd + lineEnd);
-	    dos.writeBytes(col_name + lineEnd);
-	    System.err.println("**** c="+col_name);
-
-	    dos.writeBytes(twoHyphens + boundary + lineEnd);
-	    dos.writeBytes("Content-Disposition: form-data; name=\"dir\"" + lineEnd + lineEnd);
-	    dos.writeBytes(dir + lineEnd);	
-	    System.err.println("**** dir="+dir);
-    
-	    dos.writeBytes(twoHyphens + boundary + lineEnd);
-	    dos.writeBytes("Content-Disposition: form-data; name=\"del\"" + lineEnd + lineEnd);
-	    dos.writeBytes(delete_type + lineEnd);	
-	    System.err.println("**** del="+delete_type);
-
-	    dos.writeBytes(twoHyphens + boundary + lineEnd);
-	    dos.writeBytes("Content-Disposition: form-data; name=\"zip\";"
-			   + " filename=\"" + zip_fname +"\"" + lineEnd);
-	    dos.writeBytes(lineEnd);
-	    System.err.println("**** zip (filename)="+zip_fname);
-
-	    // create a buffer of maximum size	           	    
-	    int bytesAvailable = fileInputStream.available();
-	    int bufferSize = Math.min(bytesAvailable, maxBufferSize);
-	    byte[] buffer = new byte[bufferSize];
-	    
-	    // read file and write it into form...
-	    int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
-
-	    while (bytesRead > 0) {
-		// Check to see if action has been cancelled.
-		if (source != null && source.hasSignalledStop()) {
-		    break;
-		}
-		dos.write(buffer, 0, bufferSize);
-		bytesAvailable = fileInputStream.available();
-		bufferSize = Math.min(bytesAvailable, maxBufferSize);
-		bytesRead = fileInputStream.read(buffer, 0, bufferSize);
-	    }
-	    
-	    // send multipart form data necesssary after file data...
-	    
-	    dos.writeBytes(lineEnd);
-	    dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
-	    
-	    // close streams
-	    
-	    fileInputStream.close();
-	    dos.flush();
-	    dos.close();
-
-	    // delete zip file
-	    boolean file_deleted = file.delete();
-	    if (file_deleted) {
-		System.err.println("Zip file " + file.toString() + " deleted");
-	    }
-	    else {
-		System.err.println("Zip file " + file.toString() + " NOT deleted");
-	    }
-	}
-	catch (MalformedURLException ex) {
-	    System.err.println("Failed on: "+ex);
-	}
-	
-	catch (IOException ioe) {
-	    System.err.println("Failed on: "+ioe);
-	}
-	
-	// Read server response
-	try {
-	    InputStreamReader isr = new InputStreamReader(conn.getInputStream() );
-	    BufferedReader bisr = new BufferedReader (isr);
-	    String str;
-	    while (( str = bisr.readLine()) != null) {
-		System.err.println(str);
-	    }
-	    bisr.close();
-	    
-	}
-	catch (IOException ioex) {
-	    System.err.println("From (ServerResponse): "+ioex);
-	}
+	int bytesAvailable = fileInputStream.available();
+	int bufferSize = Math.min(bytesAvailable, maxBufferSize);
+	byte[] buffer = new byte[bufferSize];
+
+	// read file and write it into form...
+	int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+	while (bytesRead > 0) {
+	    dos.write(buffer, 0, bufferSize);
+	    bytesAvailable = fileInputStream.available();
+	    bufferSize = Math.min(bytesAvailable, maxBufferSize);
+	    bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+	}
+
+	// close streams
+	fileInputStream.close();
+	dos.flush();
+	dos.close();
+
+	// Read the output of the command from the server, and return it
+	String command_output = "";
+	InputStream gliserver_is = gliserver_connection.getInputStream();
+	BufferedReader gliserver_in = new BufferedReader(new InputStreamReader(gliserver_is, "UTF-8"));
+	String gliserver_output_line = gliserver_in.readLine();
+	while (gliserver_output_line != null) {
+	    command_output += gliserver_output_line + "\n";
+	    gliserver_output_line = gliserver_in.readLine();
+	}
+	gliserver_in.close();
+
+	return command_output;
+    }
+
+
+    // ----------------------------------------------------------------------------------------------------
+    //   UTILITIES
+    // ----------------------------------------------------------------------------------------------------
+
+
+    static private String getPathRelativeToDirectory(File file, String directory_path)
+    {
+	String file_path = file.getAbsolutePath();
+	if (!file_path.startsWith(directory_path)) {
+	    System.err.println("ERROR: File path " + file_path + " is not a child of " + directory_path);
+	    return file_path;
+	}
+
+	String relative_file_path = file_path.substring(directory_path.length());
+	if (relative_file_path.startsWith(File.separator)) {
+	    relative_file_path = relative_file_path.substring(File.separator.length());
+	}
+	return relative_file_path;
     }
 }
Index: trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionArchives.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionArchives.java	(revision 10726)
+++ trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionArchives.java	(revision 10726)
@@ -0,0 +1,148 @@
+package org.greenstone.gatherer.remote;
+
+import java.io.*;
+import java.util.zip.*;
+import org.greenstone.gatherer.util.ZipTools;
+
+
+/**
+ */
+public class ZipCollectionArchives
+{
+    static public void main(String[] args)
+    {
+	if (args.length != 3) {
+	    System.err.println("Usage: ZipCollectionArchives <zip-file> <collect-directory-path> <collection-name>");
+	    return;
+	}
+
+	String zip_file_path = args[0];
+	String collect_directory_path = args[1];
+	String collection_name = args[2];
+
+	if (!collect_directory_path.endsWith(File.separator)) {
+	    collect_directory_path += File.separator;
+	}
+
+ 	try {
+	    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip_file_path));
+
+	    // Add archives directory, with doc.xml files only
+	    String archives_relative_path = collection_name + File.separator + "archives";
+	    ZipTools.ZipFilter collection_archives_zip_filter = new CollectionArchivesZipFilter();
+	    ZipTools.addFileToZip(zos, collect_directory_path, archives_relative_path, collection_archives_zip_filter);
+
+	    zos.close();
+	}
+	catch (Exception exception) {
+	    exception.printStackTrace();
+	}
+    }
+
+
+    static public class CollectionArchivesZipFilter
+	extends ZipTools.NullZipFilter
+    {
+	private byte[] bytes_remaining = new byte[0];
+	private int section_depth = 0;
+	private boolean in_section_content = false;
+
+
+	public boolean shouldIncludeFile(String relative_file_path)
+	{
+	    // Only doc.xml files are included
+	    return (relative_file_path.equals("doc.xml") || relative_file_path.endsWith(File.separator + "doc.xml"));
+	}
+
+
+	public int filterFileContent(BufferedInputStream bis, byte[] data, int bytes_to_read)
+	{
+	    try {
+		// Read the next chunk of bytes from the file
+		byte[] bytes_read_this_time = new byte[bytes_to_read];
+		int num_bytes_read_this_time = bis.read(bytes_read_this_time, 0, bytes_to_read);
+		if (num_bytes_read_this_time == -1) {
+		    // We must be at the end of the file
+		    return -1;
+		}
+
+		// Add this chunk to the bytes remaining from last time
+		byte[] bytes_unprocessed = new byte[bytes_remaining.length + num_bytes_read_this_time];
+		for (int i = 0; i < bytes_remaining.length; i++) {
+		    bytes_unprocessed[i] = bytes_remaining[i];
+		}
+		for (int i = 0; i < num_bytes_read_this_time; i++) {
+		    bytes_unprocessed[bytes_remaining.length + i] = bytes_read_this_time[i];
+		}
+
+		// Split the chunk into lines and filter them
+		int num_bytes_processed = 0;
+		int num_bytes_unfiltered = 0;
+		int last_line_start = 0;
+		for (int i = 0; (i < bytes_unprocessed.length && num_bytes_unfiltered < bytes_to_read); i++) {
+		    // Found the end of a line
+		    if (bytes_unprocessed[i] == '\n') {
+			int line_length = i - last_line_start + 1;
+			String line = new String(bytes_unprocessed, last_line_start, line_length);
+
+			// If this line isn't filtered, write it to the data array
+			if (!filterFileLine(line)) {
+			    for (int j = 0; (j < line_length && num_bytes_unfiltered < bytes_to_read); j++) {
+				data[num_bytes_unfiltered] = bytes_unprocessed[last_line_start + j];
+				num_bytes_unfiltered++;
+				num_bytes_processed++;
+			    }
+			}
+			// Otherwise ignore it
+			else {
+			    num_bytes_processed = i + 1;
+			}
+
+			last_line_start = i + 1;
+		    }
+		}
+
+		// Create a new byte array containing any bytes remaining (these will be processed next time)
+		int num_bytes_remaining = bytes_unprocessed.length - num_bytes_processed;
+		bytes_remaining = new byte[num_bytes_remaining];
+		for (int i = 0; i < num_bytes_remaining; i++) {
+		    bytes_remaining[i] = bytes_unprocessed[num_bytes_processed + i];
+		}
+
+		return num_bytes_unfiltered;
+	    }
+	    catch (Exception exception) {
+		exception.printStackTrace();
+	    }
+
+	    return -1;
+	}
+
+
+	private boolean filterFileLine(String line)
+	{
+	    boolean filter_line = false;
+
+	    if (line.indexOf("<Section>") != -1) {
+		section_depth++;
+	    }
+	    if (line.indexOf("<Content>") != -1) {
+		in_section_content = true;
+	    }
+
+	    // If we're in a subsection or in a content element, filter this line
+	    if (section_depth > 1 || in_section_content) {
+		filter_line = true;
+	    }
+
+	    if (line.indexOf("</Content>") != -1) {
+		in_section_content = false;
+	    }
+	    if (line.indexOf("</Section>") != -1) {
+		section_depth--;
+	    }
+
+	    return filter_line;
+	}
+    }
+}
Index: trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionConfigurations.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionConfigurations.java	(revision 10726)
+++ trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionConfigurations.java	(revision 10726)
@@ -0,0 +1,72 @@
+package org.greenstone.gatherer.remote;
+
+import java.io.*;
+import java.util.zip.*;
+import org.greenstone.gatherer.util.ZipTools;
+
+
+/**
+ */
+public class ZipCollectionConfigurations
+{
+    static public void main(String[] args)
+    {
+	if (args.length != 2) {
+	    System.err.println("Usage: ZipCollectionConfigurations <zip-file> <collect-directory-path>");
+	    return;
+	}
+
+	String zip_file_path = args[0];
+	String collect_directory_path = args[1];
+
+	if (!collect_directory_path.endsWith(File.separator)) {
+	    collect_directory_path += File.separator;
+	}
+	File collect_directory = new File(collect_directory_path);
+
+ 	try {
+	    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip_file_path));
+	    ZipTools.ZipFilter collection_etc_zip_filter = new CollectionEtcZipFilter();
+	    ZipTools.ZipFilter collection_metadata_zip_filter = new CollectionMetadataZipFilter();
+
+	    // Add the etc and metadata directories from each of the collections
+	    File[] collection_directories = collect_directory.listFiles();
+	    for (int i = 0; i < collection_directories.length; i++) {
+		if (collection_directories[i].isDirectory()) {
+		    String etc_relative_path = collection_directories[i].getName() + File.separator + "etc";
+		    ZipTools.addFileToZip(zos, collect_directory_path, etc_relative_path, collection_etc_zip_filter);
+
+		    String metadata_relative_path = collection_directories[i].getName() + File.separator + "metadata";
+		    ZipTools.addFileToZip(zos, collect_directory_path, metadata_relative_path, collection_metadata_zip_filter);
+		}
+	    }
+
+	    zos.close();
+	}
+	catch (Exception exception) {
+	    exception.printStackTrace();
+	}
+    }
+
+
+    static private class CollectionEtcZipFilter
+	extends ZipTools.NullZipFilter
+    {
+	public boolean shouldIncludeFile(String relative_file_path)
+	{
+	    // Only collect.cfg files are included
+	    return (relative_file_path.equals("collect.cfg") || relative_file_path.endsWith(File.separator + "collect.cfg"));
+	}
+    }
+
+
+    static private class CollectionMetadataZipFilter
+	extends ZipTools.NullZipFilter
+    {
+	public boolean shouldIncludeFileContent(String relative_file_path)
+	{
+	    // We don't include content for any of the metadata set files
+	    return false;
+	}
+    }
+}
Index: trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionShell.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionShell.java	(revision 10726)
+++ trunk/gli/src/org/greenstone/gatherer/remote/ZipCollectionShell.java	(revision 10726)
@@ -0,0 +1,74 @@
+package org.greenstone.gatherer.remote;
+
+import java.io.*;
+import java.util.zip.*;
+import org.greenstone.gatherer.util.ZipTools;
+
+
+/**
+ */
+public class ZipCollectionShell
+{
+    static public void main(String[] args)
+    {
+	if (args.length != 3) {
+	    System.err.println("Usage: ZipCollectionShell <zip-file> <collect-directory-path> <collection-name>");
+	    return;
+	}
+
+	String zip_file_path = args[0];
+	String collect_directory_path = args[1];
+	String collection_name = args[2];
+
+	if (!collect_directory_path.endsWith(File.separator)) {
+	    collect_directory_path += File.separator;
+	}
+
+ 	try {
+	    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip_file_path));
+	    ZipTools.ZipFilter null_zip_filter = new ZipTools.NullZipFilter();
+
+	    // Add archives directory, with doc.xml files only
+	    String archives_relative_path = collection_name + File.separator + "archives";
+	    ZipTools.ZipFilter collection_archives_zip_filter = new ZipCollectionArchives.CollectionArchivesZipFilter();
+	    ZipTools.addFileToZip(zos, collect_directory_path, archives_relative_path, collection_archives_zip_filter);
+
+	    // Add etc/collect.cfg file
+	    String collect_cfg_relative_path = collection_name + File.separator + "etc" + File.separator + "collect.cfg";
+	    ZipTools.addFileToZip(zos, collect_directory_path, collect_cfg_relative_path, null_zip_filter);
+
+	    // Add images directory
+	    String images_relative_path = collection_name + File.separator + "images";
+	    ZipTools.addFileToZip(zos, collect_directory_path, images_relative_path, null_zip_filter);
+
+	    // Add import directory, with zero-length files except for metadata.xml
+	    String import_relative_path = collection_name + File.separator + "import";
+	    ZipTools.ZipFilter collection_import_zip_filter = new CollectionImportZipFilter();
+	    ZipTools.addFileToZip(zos, collect_directory_path, import_relative_path, collection_import_zip_filter);
+
+	    // Add index/build.cfg file
+	    String build_cfg_relative_path = collection_name + File.separator + "index" + File.separator + "build.cfg";
+	    ZipTools.addFileToZip(zos, collect_directory_path, build_cfg_relative_path, null_zip_filter);
+
+	    // Add metadata directory
+	    String metadata_relative_path = collection_name + File.separator + "metadata";
+	    ZipTools.addFileToZip(zos, collect_directory_path, metadata_relative_path, null_zip_filter);
+
+	    zos.close();
+	}
+	catch (Exception exception) {
+	    exception.printStackTrace();
+	}
+    }
+
+
+    static private class CollectionImportZipFilter
+	extends ZipTools.NullZipFilter
+    {
+	public boolean shouldIncludeFileContent(String relative_file_path)
+	{
+	    // We only include file content for metadata.xml files
+	    return (relative_file_path.equals("metadata.xml") || relative_file_path.endsWith(File.separator + "metadata.xml"));
+	}
+    }
+}
Index: trunk/gli/src/org/greenstone/gatherer/remote/ZipFiles.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/remote/ZipFiles.java	(revision 10726)
+++ trunk/gli/src/org/greenstone/gatherer/remote/ZipFiles.java	(revision 10726)
@@ -0,0 +1,29 @@
+package org.greenstone.gatherer.remote;
+
+import java.io.File;
+import java.util.ArrayList;
+import org.greenstone.gatherer.util.ZipTools;
+
+
+/**
+ */
+public class ZipFiles
+{
+    static public void main(String[] args)
+    {
+	if (args.length < 3) {
+	    System.err.println("Usage: ZipFiles <zip-file> <base-directory-path> (<files> ...)");
+	    return;
+	}
+
+	String zip_file_path = args[0];
+	String base_directory_path = args[1];
+	ArrayList relative_file_paths_list = new ArrayList();
+	for (int i = 2; i < args.length; i++) {
+	    relative_file_paths_list.add(args[i]);
+	}
+
+	String[] relative_file_paths = (String[]) relative_file_paths_list.toArray(new String[0]);
+	ZipTools.zipFiles(zip_file_path, base_directory_path, relative_file_paths);
+    }
+}
Index: trunk/gli/src/org/greenstone/gatherer/shell/GShell.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/shell/GShell.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/shell/GShell.java	(revision 10726)
@@ -66,4 +66,5 @@
     /** A flag used to determine if this process has been asked to cancel. */
     private boolean cancel = false;
+    private BufferedOutputStream buffered_output_stream = null;
     /** The list of listeners associated with this class. */
     private EventListenerList listeners = null;
@@ -158,30 +159,30 @@
 
 
-    protected boolean got_stream_char(InputStreamReader isr, StringBuffer line_buffer,
-				      BufferedOutputStream bos) throws IOException 
-    {
-	// Hopefully this doesn't block if the process is trying to write to STDOUT/STDERR.
-
-	boolean input_status = false;
-
-	if(isr.ready()) {
-	    input_status = true;
-	    int c = isr.read();
-	    if(c == '\n' || c == '\r') {
-		if(line_buffer.length() > 0) {
-		    String line = line_buffer.toString();
-		    DebugStream.println("* " + line + " *");
-		    fireMessage(type, typeAsString(type) + "> " + line, status, bos);
-		    line = null;
-		    line_buffer = new StringBuffer();
-		}
-	    }
-	    else {
-		line_buffer.append((char)c);
-	    }
-	}
-
-	return input_status;
-    }
+//     protected boolean got_stream_char(InputStreamReader isr, StringBuffer line_buffer,
+// 				      BufferedOutputStream bos) throws IOException 
+//     {
+// 	// Hopefully this doesn't block if the process is trying to write to STDOUT/STDERR.
+
+// 	boolean input_status = false;
+
+// 	if(isr.ready()) {
+// 	    input_status = true;
+// 	    int c = isr.read();
+// 	    if(c == '\n' || c == '\r') {
+// 		if(line_buffer.length() > 0) {
+// 		    String line = line_buffer.toString();
+// 		    DebugStream.println("* " + line + " *");
+// 		    fireMessage(type, typeAsString(type) + "> " + line, status, bos);
+// 		    line = null;
+// 		    line_buffer = new StringBuffer();
+// 		}
+// 	    }
+// 	    else {
+// 		line_buffer.append((char)c);
+// 	    }
+// 	}
+
+// 	return input_status;
+//     }
 
 
@@ -221,184 +222,44 @@
 	return error_count;
     }
-	    
-
-    protected void runRemote(String[] args, BufferedOutputStream bos) 
+
+
+    private void runRemote(String[] args, BufferedOutputStream bos)
     {
-	System.err.println("The level of design processing needed is: " + CollectionDesignManager.getRebuildTypeRequired()); //Will add smart design processing
-	if(hasSignalledStop()) { return; }
-	int error_count = 0;
-	StringBuffer error_list = new StringBuffer();
+	// Make sure the process hasn't been cancelled
+	if (hasSignalledStop()) {
+	    return;
+	}
+
 	try {
-	    if (type == IMPORT) {
-		String col_name = args[args.length-1];
-		if (progress!=null) {
-		    progress.messageOnProgressBar("Uploading data to server");
-		}
-
-		String collect_directory_path = Gatherer.getCollectDirectoryPath();
-		
-		//Only upload the parts of the import folder (files/metadata) that has been changed.
-		//Or upload the lot if we are doing a complete rebuild.
-		if(Gatherer.c_man.getCollection().getFilesChanged() || !Gatherer.g_man.create_pane.isIncremental()) {
-		    // zip up import folder, but exclude metadata
-		    ZipTools.zipup(collect_directory_path, col_name, "import", this, "", ".*metadata\\.xml");
-
-		    if(hasSignalledStop()) { return; }
-		    // upload it to gsdl server
-		    RemoteGreenstoneServer.upload_url_zip(col_name, "import", "files", this);
-
-		    Gatherer.c_man.getCollection().setFilesChanged(false);
-		    System.err.println("Finished uploading files");
-		}
-		if(Gatherer.c_man.getCollection().getMetadataChanged() || !Gatherer.g_man.create_pane.isIncremental()) {
-		    // zip up metadata from import folder
-		    ZipTools.zipup(collect_directory_path, col_name, "import", this, ".*metadata\\.xml", "");
-
-		    if(hasSignalledStop()) { return; }
-		    // upload it to gsdl server
-		    RemoteGreenstoneServer.upload_url_zip(col_name, "import", "metadata", this);
-		    Gatherer.c_man.getCollection().setMetadataChanged(false);
-		    System.err.println("Finished uploading metadata.");
-		}
-		
-		if(hasSignalledStop()) { return; }
-		// upload etc folder to server (need collect.cfg and any hfiles)
-		ZipTools.zipup(collect_directory_path, col_name, "etc", this, "", "");
-
-		if(hasSignalledStop()) { return; }
-		RemoteGreenstoneServer.upload_url_zip(col_name, "etc", "", this);
-
-		String collection_directory_path = Gatherer.c_man.getCollectionDirectoryPath(col_name);
-		File img_dir = new File(Gatherer.c_man.getCollectionImagesDirectoryPath());
-		if (img_dir.exists() && img_dir.list().length > 0) {
-		    // upload images/ directory to server
-		    if(hasSignalledStop()) { return; }
-		    ZipTools.zipup(collect_directory_path, col_name, "images", this, "", "");
-
-		    if(hasSignalledStop()) { return; }
-		    RemoteGreenstoneServer.upload_url_zip(col_name, "images", "", this);
-
-		    System.err.println("collect_directory_path: " + collect_directory_path);
-		    System.err.println("col_name: " + col_name);
-		}
-
-		if(hasSignalledStop()) { return; }
-		// see if collection specific image needs uploading
-		CollectionMetaManager cmm 
-		    = CollectionDesignManager.collectionmeta_manager;
-		
-		CollectionMeta icon_collection_collectionmeta 
-		    = cmm.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_ICONCOLLECTION_STR);
-		CollectionMeta icon_collection_small_collectionmeta 
-		    = cmm.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_ICONCOLLECTIONSMALL_STR);
-		
-		String ics_text = icon_collection_collectionmeta.getValue(CollectionMeta.TEXT);
-		String icsc_text = icon_collection_small_collectionmeta.getValue(CollectionMeta.TEXT);
-		if ((ics_text != null) && (ics_text != "")) {
-		    // Stub code for detecting when collectoin image changed
-		    // => need to upload images
-		    // System.err.println("**** ics_text = " + ics_text);
-		}
-		
-		if (progress!=null) {
-		    progress.messageOnProgressBar("");
-		}
-	    }
-	    
-	    if(hasSignalledStop()) { return; }
-	    String perl_cmd = args[0];
-	    int perl_cmd_root = perl_cmd.lastIndexOf(File.separator);
-	    
-	    if (perl_cmd_root > 0) {
-		String perl_cmd_cut = perl_cmd.substring(perl_cmd_root+1);
-		perl_cmd = perl_cmd_cut;
-	    }
-
-	    String cols_concat = null;
-	    String launch  = Gatherer.cgiBase + "launch";
-	    launch = launch + "?cmd=" + perl_cmd;
-	    
-	    for(int i = 1; i<args.length; i++) {
-		
-		String arg = args[i];
-		
-		if (arg.equals("-collectdir") || arg.equals("-importdir")
-		    || arg.equals("-builddir")) {
-		    // skip it
+	    int directory_name_end = args[0].lastIndexOf(File.separator);
+	    String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
+	    System.err.println("Script name: " + script_name);
+	    String collection_name = args[args.length - 1];
+	    System.err.println("Collection name: " + collection_name);
+
+	    String script_args = "";
+	    for (int i = 1; i < (args.length - 1); i++) {
+		// Skip arguments that won't make sense on the server
+		if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir")) {
 		    i++;
 		    continue;
 		}
-				
-		if(arg.startsWith(StaticStrings.MINUS_CHARACTER)) {
-		    String name = arg.substring(1);
-		    launch = launch + "&" + name + "=";
-		    if (i+1<args.length-1) {
-			if (!args[i+1].startsWith(StaticStrings.MINUS_CHARACTER)) {
-			    i++;
-			    String val = URLEncoder.encode(args[i],"UTF-8");
-			    launch = launch + val;
-			}
+
+		// Script arguments get changed to CGI arguments
+		if (args[i].startsWith("-")) {
+		    script_args += "&" + args[i].substring(1) + "=";
+		    if ((i + 1) < (args.length - 1) && !args[i + 1].startsWith("-")) {
+			script_args += URLEncoder.encode(args[i + 1], "UTF-8");
+			i++;
 		    }
 		}
-		else {
-		    if (cols_concat==null) {
-			cols_concat = arg;
-		    }
-		    else {
-			cols_concat = cols_concat + ":" + arg;
-		    }
-		}
-	    }
-
-	    if (cols_concat!=null) {
-		launch = launch + "&c=" + cols_concat;
-	    }
-
-	    System.err.println("**** launch url = " + launch);
-	    // fireMessage(type, Dictionary.get("GShell.Command") + ": " + launch, status, null); // ****
-	    
-	    URL launch_url = new URL(launch);
-	    URLConnection launch_connection = launch_url.openConnection();
-	    InputStream stdis = launch_connection.getInputStream();
-	    InputStreamReader stdisr = new InputStreamReader(stdis, "UTF-8");
-	    
-	    BufferedReader stdbr = new BufferedReader(stdisr);
-	    
-	    if (type == GShell.NEW) {
-		while(true) {
-		    String line = stdbr.readLine();
-		    if (line == null) { break; }
-		    error_count = check_for_error(line,error_list,error_count);
-		}
-	    }
-	    else {
-		while(!hasSignalledStop()) {
-		    String line = stdbr.readLine();
-		    if (line == null) { break; }
-		    error_count = check_for_error(line,error_list,error_count);
-		    fireMessage(type, typeAsString(type) + "> " + line, status, bos);
-		}
-	    }
-	    stdbr.close();
-	    
-	    if (error_count>0) {
-		status = ERROR;
-		System.err.println(error_list);
-		if (type != GShell.NEW) {
-		    fireMessage(type, typeAsString(type) + "> " + error_list, status, null);
-		}
-	    }
-	    else if(hasSignalledStop()) {
-		//User pressed the cancel button.
-		//Status already set in hasSignalledStop method.
-	    }
-	    else {
-		status = OK;
-		fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
-	    }
-	}
-	// Exception
+	    }
+
+	    System.err.println("Script args: " + script_args);
+	    buffered_output_stream = bos;
+	    String command_output = RemoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
+	    status = (command_output.equals("") ? CANCELLED : OK);
+	}
 	catch (Exception exception) {
-	    System.err.println("Exception in GShell.runRemote() - unexpected");
 	    DebugStream.printStackTrace(exception);
 	    status = ERROR;
@@ -407,5 +268,5 @@
 
 
-    protected void runLocal(String[] args, BufferedOutputStream bos) 
+    private void runLocal(String[] args, BufferedOutputStream bos) 
     {
 	try {
@@ -415,5 +276,5 @@
 	    }
 	    
-	    DebugStream.println("Command: "+command);
+	    System.err.println("Command: "+command);
 	    ///ystem.err.println("Command: " + command);
 	    fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
@@ -543,5 +404,7 @@
 
 	// Issue a processBegun event
+	System.err.println("\nFiring process begun for " + type + "...");
 	fireProcessBegun(type, status);
+	System.err.println("Done process begun.");
 	if (Gatherer.isGsdlRemote) {
 	    runRemote(args,bos);
@@ -550,12 +413,10 @@
 	    runLocal(args,bos);
 	}
+	System.err.println("Done runLocal().");
 
 	if(status == OK) {
 	    if (type == NEW) {
 		if (Gatherer.isGsdlRemote) {
-		    RemoteGreenstoneServer.download_url_zip(col_name, ".", this, "", "");
-		    if (!hasSignalledStop()) {
-			ZipTools.unzip(Gatherer.getCollectDirectoryPath(), col_name);
-		    }
+		    RemoteGreenstoneServer.downloadCollection(col_name);
 		}
 	    }
@@ -569,24 +430,6 @@
 		    }
 
-		    Utility.delete(Gatherer.c_man.getCollectionArchivesDirectoryPath()); // remove current archives
-		    //new File(Gatherer.c_man.getCollectionArchivesDirectoryPath()).mkdir(); //Make a clean dir
-		    RemoteGreenstoneServer.download_url_zip(col_name, "archives", this, ".*doc\\.xml", "");
-		    if (hasSignalledStop()) {
-			// Clean up, then exit
-			fireProcessComplete(type, status);
-			// Close bos
-			if(bos != null) {
-			    try {
-				bos.close();
-				bos = null;
-			    }
-			    catch(Exception error) {
-				DebugStream.printStackTrace(error);
-			    }
-			}
-			return;
-		    }
-		    ZipTools.unzip(Gatherer.getCollectDirectoryPath(), col_name);
-		    
+		    RemoteGreenstoneServer.downloadCollectionArchives(col_name);
+
 		    if (progress!=null) {
 			progress.messageOnProgressBar("");
@@ -608,11 +451,7 @@
  		    }
 
-		    File buildDir = new File(Gatherer.c_man.getCollectionBuildingDirectoryPath());
- 		    Utility.delete(buildDir); // remove current build dir
-		    //Only need build.cfg
-		    RemoteGreenstoneServer.download_url_zip(col_name, "building", this, ".*build\\.cfg", "");
-		    if (!hasSignalledStop()) {
-			ZipTools.unzip(Gatherer.getCollectDirectoryPath(), col_name);
-		    }
+		    // Only need to download build.cfg file
+		    File build_cfg_file = new File(Gatherer.c_man.getCollectionBuildingDirectoryPath(), "build.cfg");
+		    RemoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
 		    
  		    if (progress!=null) {
@@ -638,4 +477,5 @@
 
 	// We're done.	
+	System.err.println("Firing process complete for " + type + "...");
 	fireProcessComplete(type, status);
 	// Close bos
@@ -650,4 +490,12 @@
 	}
     }
+
+
+    public void fireMessage(String message)
+    {
+	fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
+    }
+
+
     /** Method for firing a message to all interested listeners.
      * @param type An <strong>int</strong> indicating the process type.
@@ -697,5 +545,7 @@
 	// Start the progres monitor if available
 	if(progress != null) {
+	    System.err.println("About to call progress.start().");
 	    progress.start();
+	    System.err.println("Called progress.start().");
 	}
 	// Fire an event
Index: trunk/gli/src/org/greenstone/gatherer/util/ZipTools.java
===================================================================
--- trunk/gli/src/org/greenstone/gatherer/util/ZipTools.java	(revision 10691)
+++ trunk/gli/src/org/greenstone/gatherer/util/ZipTools.java	(revision 10726)
@@ -31,6 +31,4 @@
 import java.util.*;
 import java.util.zip.*;
-import org.greenstone.gatherer.DebugStream;
-import org.greenstone.gatherer.shell.GShell;
 import org.w3c.dom.*;
 
@@ -47,5 +45,5 @@
 		ZipEntry zip_entry = (ZipEntry) e.nextElement();
 		File zip_entry_file = new File(base_directory_path + zip_entry.getName());
-		DebugStream.println("    Unzipping: " + zip_entry_file.getAbsolutePath());
+		// System.err.println("    Unzipping: " + zip_entry_file.getAbsolutePath());
 
 		// Directory case
@@ -61,5 +59,5 @@
 		    // Write out file to disk
 
-		    // Make sure it's parent directory exists.
+		    // Make sure its parent directory exists.
 		    File dir = new File(zip_entry_file.getParent());
 		    dir.mkdirs();
@@ -92,5 +90,5 @@
 	} 
 	catch (Exception exception) {
-	    DebugStream.printStackTrace(exception);
+	    exception.printStackTrace();
 	}
     }
@@ -112,5 +110,5 @@
 	}
 	catch (Exception exception) {
-	    DebugStream.printStackTrace(exception);
+	    exception.printStackTrace();
 	}
     }
@@ -131,5 +129,6 @@
 	    if (file.isDirectory()) {
 		// Add a zip entry for this directory
-		zos.putNextEntry(new ZipEntry(relative_file_path + File.separator));
+		// Actually, don't -- this messes things up completely on Windows and doesn't seem to matter on Linux
+		// zos.putNextEntry(new ZipEntry(relative_file_path + File.separator));
 
 		// Apply recursively to each of the children of the directory
@@ -144,5 +143,6 @@
 		// Add a zip entry for this file
 		if (zip_filter.shouldIncludeFile(relative_file_path)) {
-		    zos.putNextEntry(new ZipEntry(relative_file_path));
+		    // Always use Unix style paths in zip files, even on Windows
+		    zos.putNextEntry(new ZipEntry(relative_file_path.replace('\\', '/')));
 
 		    if (zip_filter.shouldIncludeFileContent(relative_file_path)) {
@@ -150,5 +150,5 @@
 			byte[] data = new byte[1024];
 			int bytes_read;
-			while ((bytes_read = bis.read(data, 0, 1024)) > -1) {
+			while ((bytes_read = zip_filter.filterFileContent(bis, data, 1024)) > -1) {
 			    zos.write(data, 0, bytes_read);
 			}
@@ -159,5 +159,5 @@
 	}
 	catch (Exception exception) {
-	    DebugStream.printStackTrace(exception);
+	    exception.printStackTrace();
 	}
     }
@@ -169,4 +169,6 @@
 
 	public boolean shouldIncludeFileContent(String relative_file_path);
+
+	public int filterFileContent(BufferedInputStream bis, byte[] data, int bytes_to_read);
     }
 
@@ -187,406 +189,16 @@
 	    return true;
 	}
-    }
-
-
-    // ----------------------------------------------------------------------------------------------------
-
-
-    static private String unixStylePath(String path)
-    {
-	String unix_path = path.replace('\\','/');
-	return unix_path;
-    }
-
-
-    /**
-     * Method to handle zipping up of a file (not a directory).
-     * Called by {@link zipup(String, String, String, GShell, String, String)} and {@link dirFunc(ZipOutputStream, String, int, GShell, boolean, String, String)}
-     *
-     * @param zos the zip output stream, as a <strong>ZipOutputStream</strong>
-     * @param file_path the <strong>String</strong> containing the path to the file/directory tozip up
-     * @param prefix_strip the <strong>int</strong> used to substring the file_path
-     * @param source the <strong>GShell</strong> object that is calling this method, so that we can check to see if the process has been cancelled.
-     *               This is null if GShell is not the calling object.
-     * @param accept_expr a <strong>String</strong> containing a regular expression of files to include in the archive. All other files will be excluded.
-     *                    There is a special case of ".*doc.xml", where the doc.xml files will be parsed to only include their metadata.
-     * @param reject_expr a <strong>String</strong> containing a regular expression of files not to include in the archive.
-     * @return boolean - true if the zip was created successfully(?).
-     * @see dirFunc(ZipOutputStream, String, int, GShell, boolean, String, String) 
-     * @see zipup(String, String, String, GShell, String, String)
-     */
-    static private boolean zipFunc(ZipOutputStream zos, String file_path, int prefix_strip, GShell source, boolean encountered_file, String accept_expr, String reject_expr) 
-    {
-	String new_file_path = file_path;
-
-	if((reject_expr != "") && (file_path.matches(reject_expr))) {
-	    // matches reject expression
-	    //DebugStream.println("File \'" + file_path + "\' matches the reject expression \'" + reject_expr + "\'");
-	    return encountered_file;
-	}
-
-	if ((accept_expr != "") && (!file_path.matches(accept_expr))) {
-	    // does not match accept expression
-	    //DebugStream.println("File \'" + file_path + "\' doesn't match accept expression \'" + accept_expr + "\'");
-	    return encountered_file;
-	}
-
-	//Special case: we only want the metadata. Parse the xml files.
-	if(accept_expr.compareTo(".*doc.xml") == 0) {
-	    DebugStream.println("Only want to get metadata");
-	    Document old_document = XMLTools.parseXMLFile(new File(file_path));
-	    NodeList content = old_document.getElementsByTagName("Content");
-
+
+
+	public int filterFileContent(BufferedInputStream bis, byte[] data, int bytes_to_read)
+	{
 	    try {
-		for(int i=content.getLength()-1; i >= 0; i--) {
-		    //Remove all content - just leave the metadata.
-		    content.item(i).getParentNode().removeChild(content.item(i));
-		}
-	    }
-	    catch (Exception ex) {
-		ex.printStackTrace();
-	    }
-
-	    new_file_path = file_path.substring(0, file_path.lastIndexOf(File.separator) + 1) + "new.xml";
-	    //Is there any better way than writing out as a new file, then reading back in??
-	    XMLTools.writeXMLFile(new File(new_file_path), old_document);
-	}
-
-	// Using try is required because of file io.
-	try {
-	    // Exit if pressed cancel.
-	    if (source != null && source.hasSignalledStop()) {
-		return false;
-	    }
-
-	    // Create a Zip Entry and put it into the archive (no data yet).
-
-	    // Strip off col_dir prefix
-	    String zip_path = file_path.substring(prefix_strip);
-	    // Zip files use '/' for directory separator
-	    String unix_style_path = unixStylePath(zip_path);
-
-	    ZipEntry fileEntry = new ZipEntry(unix_style_path);
-	    zos.putNextEntry(fileEntry);
-
-	    // Create a file input stream and a buffered input stream.
-	    FileInputStream fis = new FileInputStream(new_file_path);
-	    BufferedInputStream bis = new BufferedInputStream(fis);
-	    
-	    // Create a byte array object named data and declare byte count variable.
-	    byte[] data = new byte[1024];
-	    int byteCount;
-	    // Create a loop that reads from the buffered input stream and writes
-	    // to the zip output stream until the bis has been entirely read.
-	    while ((byteCount = bis.read(data, 0, 1024)) > -1) {
-		//Check for cancell
-		if(source != null) {
-		    if(source.hasSignalledStop()) {
-			break;
-		    }
-		}
-		zos.write(data, 0, byteCount);
-	    }
-	    encountered_file = true;
-	} 
-	catch (IOException e) {
-	    e.printStackTrace();
-	}
-	
-	DebugStream.println("Zipping up: " + file_path);
-	return encountered_file;
-    }
-
-    /**
-     * Method to handle ziping up of a directory. This is called by {@link zipup(String, String, String, GShell, String, String)}
-     *
-     * @param zos the <strong>ZipOutputStream</strong> to output to.
-     * @param dir_name the directory name to zip, as a <strong>String</strong>
-     * @param prefix_strip the <strong>int</strong> used to substring the file_path
-     * @param source the <strong>GShell</strong> object that is calling this method, so that we can check to see if the process has been cancelled.
-     *               This is null if GShell is not the calling object.
-     * @param encountered_file ??What's this??
-     * @param accept_expr a <strong>String</strong> containing a regular expression of files to include in the archive. All other files will be excluded.
-     * @param reject_expr a <strong>String</strong> containing a regular expression of files not to include in the archive.
-     *
-     * @see zipup(String, String, String, GShell, String, String)
-     * @see zipFunc(ZipOutputStream, String, int, GShell, boolean, String, String) 
-     */
-    static private boolean dirFunc (ZipOutputStream zos, String dir_name, int prefix_strip, GShell source, boolean encountered_file, String accept_expr, String reject_expr) 
-    {
-	File dirObj = new File(dir_name);
-
-	if (dirObj.exists() == true) {
-	    if (dirObj.isDirectory() == true) {
-		// Create an array of File objects, one for each file or directory in dirObj.
-		File [] fileList = dirObj.listFiles();
-		
-		// Loop through File array and display.
-		for (int i = 0; i < fileList.length; i++) {
-		    if(source != null && source.hasSignalledStop()) { break; }
-		    File file = fileList[i];
-		    if (file.isDirectory()) {
-			String dir_path = file.getPath();
-			String zip_path = dir_path.substring(prefix_strip);
-			// Zip files use '/' for directory separator
-			String unix_style_path 
-			    = unixStylePath(zip_path+File.separator);
-			ZipEntry dirEntry = new ZipEntry(unix_style_path);
-
-			try {
-			    zos.putNextEntry(dirEntry);
-			} 
-			catch (IOException e) {
-			    e.printStackTrace();
-			}
-
-			encountered_file = dirFunc(zos, dir_path, prefix_strip, source, encountered_file, accept_expr, reject_expr);
-		    } else if (file.isFile()) {
-			// Call the zipFunc function
-			String file_path = fileList[i].getPath();
-			encountered_file = zipFunc(zos,file_path,prefix_strip, source, encountered_file, accept_expr, reject_expr);
-		    }
-		}
-	    } 
-	    else { 
-		System.err.println (dir_name+" is not a directory."); 
-	    }
-	} 
-	else { 
-	    System.err.println ("Directory "+dir_name+" does not exist."); 
-	}
-	return encountered_file;
-    }
-    
-    /**
-     * Method to zip up a given file or directory associated with a particular collection.
-     * This method mainly called from {@link GShell}, with a few exceptions. The zip files produced are used
-     * to pass files (new files to add, metadata, etc) between the GLI applet and the Greenstone server.
-     * The actual zipping of files and directories is handled by {@link zipFunc(ZipOutputStream, String, int, GShell, boolean, String, String) and
-     * {@link dirFunc(ZipOutputStream, String, int, GShell, boolean, String, String)} respectfully.
-     *
-     * @param col_dir the path to the collect directory, as a <strong>String</strong>
-     * @param col_name the collection name, as as <strong>String</strong>
-     * @param dir_or_file the directory or file within the collection to zip up, as a <strong>String</strong>
-     * @param source the <strong>GShell</strong> object that is calling this method, so that we can check to see if the process has been cancelled.
-     *               This is null if GShell is not the calling object.
-     * @param accept_expr a <strong>String</strong> containing a regular expression of files to include in the archive. All other files will be excluded.
-     * @param reject_expr a <strong>String</strong> containing a regular expression of files not to include in the archive.
-     *
-     * @see zipFunc(ZipOutputStream, String, int, GShell, boolean, String, String) 
-     * @see dirFunc(ZipOutputStream, String, int, GShell, boolean, String, String) 
-     */
-    static public boolean zipup(String col_dir, String col_name, String dir_or_file, GShell source, String accept_expr, String reject_expr)
-    {
-	if (!col_dir.endsWith(File.separator)) {
-	    col_dir += File.separator;
-	}
-
-	int prefix_strip = col_dir.length();
-	boolean encountered_file = false;
-
-	String zip_fname = col_dir + col_name + ".zip";
-	String zip_dir_or_file = col_dir + col_name + File.separator + dir_or_file;
-	File zip_dof = new File(zip_dir_or_file);
-
-	try {
-	    FileOutputStream fos = new FileOutputStream(zip_fname);
-	    ZipOutputStream zos = new ZipOutputStream(fos);
-	    
-	    if (zip_dof.exists()) {
-		if (zip_dof.isDirectory()) {
-		    String zip_dir = zip_dir_or_file;
-		    encountered_file = dirFunc(zos, zip_dir, prefix_strip, source, encountered_file, accept_expr, reject_expr);
-		}
-		else {
-		    String zip_full_file = zip_dir_or_file; 
-		    String zip_path = zip_full_file.substring(prefix_strip);
-
-		    for (int i=1; i<zip_path.length(); i++) {
-			String ch = String.valueOf(zip_path.charAt(i));
-
-			if (ch.equals(File.separator)) {
-			    String dir_path = zip_path.substring(0,i);
-			    // Zip files use '/' for directory separator
-			    String unix_style_path 
-				= unixStylePath(dir_path+File.separator);
-			    ZipEntry dirEntry = new ZipEntry(unix_style_path);
-			    zos.putNextEntry(dirEntry);
-			}
-		    }
-		    encountered_file = zipFunc(zos, zip_full_file, prefix_strip, source, encountered_file, accept_expr, reject_expr);
-		}
-	    }
-	    else {
-		System.err.println("Warning: " + zip_dir_or_file + " does not exist!");
-	    }
-	    
-	    // Close the file output streams for both the file and the zip.
-	    zos.flush();
-	    zos.close();
-	    fos.close();
-	} 
-	catch (IOException exception) {
-	    DebugStream.printStackTrace(exception);
-	    return false;
-	}
-	return encountered_file;
-    } 
-
-
-    /**
-     * Method to unzip a zip file associated with a particular collection.
-     * The location and name of the zip file should be derrived from the collection. Please see below for details.
-     *
-     * @param col_dir the path to the collection directory, as a <strong>String</strong>. This is where the zip file should be located.
-     * @param col_name the name of the collection, as a <strong>String</strong>. This should be the name of the zip file (with a  .zip extension).
-     */
-    static public void unzip(String col_dir, String col_name)
-    {
-	if (!col_dir.endsWith(File.separator)) {
-	    col_dir += File.separator;
-	}
-
-	String zip_fname = col_dir + col_name + ".zip";
-	// int zip_mode = ZipFile.OPEN_READ | ZipFile.OPEN_DELETE;
-	int zip_mode = ZipFile.OPEN_READ; 
-
-	try {
-	    File cfile = new File(zip_fname);
-	    ZipFile zipfile = new ZipFile(cfile, zip_mode);
-	    
-	    Enumeration e = zipfile.entries(); 
-	    while (e.hasMoreElements()) {
-		ZipEntry zipentry = (ZipEntry) e.nextElement();
-		String zentryname = col_dir + zipentry.getName();
-		File zfile = new File(zentryname);
-		DebugStream.println("    Unzipping: " + zentryname);
-
-		if (zipentry.isDirectory()) {
-		    // Create named directory, if it doesn't already exist
-		    if (!zfile.exists() && !zfile.mkdirs()) {
-			System.err.println("Error: unable to create directory " + zfile);
-		    }
-		}
-		else {
-		    // Write out file to disk
-
-		    //Make sure it's parent directory exists.
-		    File dir = new File(zfile.getParent());
-		    dir.mkdirs();
-
-		    // set up input stream
-		    InputStream zis = zipfile.getInputStream(zipentry); 
-		    BufferedInputStream bzis = new BufferedInputStream(zis); 
-		    DataInputStream dbzis = new DataInputStream(bzis);
-		    
-		    // set up output stream
-		    FileOutputStream fzos = new FileOutputStream(zentryname);
-		    BufferedOutputStream bfzos = new BufferedOutputStream(fzos);
-		    
-		    byte[] buf = new byte[1024];
-		    int len;
-		    while ((len = dbzis.read(buf)) >= 0) {
-			bfzos.write(buf,0,len);
-		    }
-		    
-		    dbzis.close();
-		    bzis.close();
-		    zis.close();
-
-		    bfzos.close();
-		    fzos.close();
-		}
-	    }
-
-	    zipfile.close();
-
-	    // delete zip file
-	    boolean file_deleted = cfile.delete();
-	    if (file_deleted) {
-		System.err.println("Zip file " + cfile.toString() + " deleted");
-	    }
-	    else {
-		System.err.println("Zip file " + cfile.toString() + " NOT deleted");
-	    }
-
-
-	} 
-	catch (ZipException error) {
-	    System.err.println("Error: Unable to open '"+zip_fname+"'");
-	    System.err.println("This maybe caused by the zip file being empty.");
-	    DebugStream.printStackTrace(error);
-	}
-
-	catch (Exception error) {
-	    error.printStackTrace();
-	}
-    }
-
-
-    /** 
-     * Method which unzips a given metadata resoure.
-     * @param jar_zip_fname The name of the jar file as a <strong>String</strong>.
-     * @param dst_dir The destination directory for unzipping, also as a <strong>String</strong>.
-     * @see JarTools.extractFromJar(String, String, boolean)
-     */
-    static public void unzipFromJar(String jar_zip_fname, String dst_dir) {
-
-	File file = null;
-	if (!dst_dir.endsWith(File.separator)) {
-	    dst_dir += File.separator;
-	}
-
-	JarTools.extractFromJar(jar_zip_fname,dst_dir,true);
-
-	String zip_ofname = dst_dir + jar_zip_fname;
-	File zip_file = new File(zip_ofname);
-
-	try {
-	    ZipFile zipfile = new ZipFile(zip_file);
-	    
-	    Enumeration e = zipfile.entries(); 
-	    while (e.hasMoreElements()) {
-		ZipEntry zipentry = (ZipEntry) e.nextElement();
-		String zentryname = dst_dir + zipentry.getName();
-		DebugStream.println("    Unzipping: " + zentryname);
-
-		if (zipentry.isDirectory()) {
-		    // Create named directory, if it doesn't already exist
-		    File zip_entry_file = new File(zentryname);
-		    if (!zip_entry_file.exists() && !zip_entry_file.mkdirs()) {
-			System.err.println("Error: unable to create directory " + zip_entry_file);
-		    }
-		}
-		else {
-		    // Write out file to disk
-
-		    // set up input stream
-		    InputStream zis = zipfile.getInputStream(zipentry); 
-		    BufferedInputStream bzis = new BufferedInputStream(zis); 
-		    DataInputStream dbzis = new DataInputStream(bzis);
-		    
-		    // set up output stream
-		    FileOutputStream fzos = new FileOutputStream(zentryname);
-		    BufferedOutputStream bfzos = new BufferedOutputStream(fzos);
-		    
-		    byte[] buf = new byte[1024];
-		    int len;
-		    while ((len = dbzis.read(buf)) >= 0) {
-			bfzos.write(buf,0,len);
-		    }
-		    
-		    dbzis.close();
-		    bfzos.close();
-		}
-	    }
-	} 
-	catch (ZipException error) {
-	    System.err.println("Error: Unable to open '"+zip_file.getAbsolutePath()+"'");
-	    DebugStream.printStackTrace(error);
-	}
-
-	catch (Exception error) {
-	    error.printStackTrace();
+		return bis.read(data, 0, bytes_to_read);
+	    }
+	    catch (Exception exception) {
+		exception.printStackTrace();
+	    }
+
+	    return -1;
 	}
     }
