/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Author: Michael Dewsnip, NZDL Project, University of Waikato
 *
 * Copyright (C) 2005 New Zealand Digital Library Project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */

package org.greenstone.gatherer.remote;

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.collection.CollectionManager;
import org.greenstone.gatherer.shell.GShell;
import org.greenstone.gatherer.util.UnzipTools;
import org.greenstone.gatherer.util.Utility;


public class RemoteGreenstoneServer
{
    // ----------------------------------------------------------------------------------------------------
    //   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;
		    queue.remove(0);
		}

		// Otherwise the queue is empty
		else {
		    progress_bar.setAction(null);

		    // 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 {
		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
	    UnzipTools.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
	    UnzipTools.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
	    UnzipTools.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
	    UnzipTools.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();
	    }
	}
    }


    static public String getUsername()
    {
	if (remote_greenstone_server_authentication != null) {
	    return remote_greenstone_server_authentication.getUserName();
	}

	return null;
    }


    // ----------------------------------------------------------------------------------------------------
    //   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);

	    while (len >= 0) {
		zip_bfos.write(buf, 0, len);
		len = dl_dbis.read(buf);
	    }

	    zip_bfos.close();
	    zip_fos.close();
	}

	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();
		}
	    }
	    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;
	int bytesAvailable = fileInputStream.available();
	int bufferSize = Math.min(bytesAvailable, maxBufferSize);
	byte[] buffer = new byte[bufferSize];

	// read file and write it into form...
	// !! This uses a lot of memory when the file being uploaded is big -- Java seems to need to keep
	//   the entire file in the DataOutputStream? (Use Runtime.getRuntime().totalMemory() to see)
	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 public 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;
    }
}
