/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (c) 1993-1994.  The Regents of the University of California.  All     */
/*   rights reserved.                                                      */
/*                                                                         */
/*   This work was produced at the University of California, Lawrence      */
/*   Livermore National Laboratory (UC LLNL) under contract no.            */
/*   W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy     */
/*   (DOE) and The Regents of the University of California (University)    */
/*   for the operation of UC LLNL.  Copyright is reserved to the           */
/*   University for purposes of controlled dissemination,                  */
/*   commercialization through formal licensing, or other disposition      */
/*   under terms of Contract 48; DOE policies, regulations and orders;     */
/*   and U.S. statutes.  The rights of the Federal Government are          */
/*   reserved under Contract 48 subject to the restrictions agreed upon    */
/*   by the DOE and University.                                            */
/*                                                                         */
/*                                                                         */
/*                              DISCLAIMER                                 */
/*                                                                         */
/*   This software was prepared as an account of work sponsored by an      */
/*   agency of the United States Government.  Neither the United States    */
/*   Government nor the University of California nor any of their          */
/*   employees, makes any warranty, express or implied, or assumes any     */
/*   liability or responsibility for the accuracy, completeness, or        */
/*   usefulness of any information, apparatus, product, or process         */
/*   disclosed, or represents that its specific commercial products,       */
/*   process, or service by trade name, trademark, manufacturer, or        */
/*   otherwise, does not necessarily constitute or imply its               */
/*   endorsement, recommendation, or favoring by the United States         */
/*   Government or the University of California. The views and opinions    */
/*   of the authors expressed herein do not necessarily state or reflect   */
/*   those of the United States Government or the University of            */
/*   California, and shall not be used for advertising or product          */
/*   endorsement purposes.                                                 */
/*                                                                         */
/*   Permission to use, copy, modify and distribute this software and its  */
/*   documentation for any non-commercial purpose, without fee, is         */
/*   hereby granted, provided that the above copyright notice and this     */
/*   permission notice appear in all copies of the software and            */
/*   supporting documentation, and that all UC LLNL identification in      */
/*   the user interface remain unchanged.  The title to copyright LLNL     */
/*   XFTP shall at all times remain with The Regents of the University     */
/*   of California and users agree to preserve same. Users seeking the     */
/*   right to make derivative works with LLNL XFTP for commercial          */
/*   purposes may obtain a license from the Lawrence Livermore National    */
/*   Laboratory's Technology Transfer Office, P.O. Box 808, L-795,         */
/*   Livermore, CA 94550.                                                  */
/*                                                                         */
/***************************************************************************/
/***************************************************************************/

#include <sys/time.h>
#include <ctype.h>
#include <Xm/Xm.h>
#include "xftp.h"
#include "xfer.h"
#include "list.h"
#include "str.h"

struct xfer_ctrl_block xfer_ctrl;

int init_local_copy();
int do_local_copy();
int abort_local_copy();

int init_put();
int do_put();
int abort_put();

int init_get();
int do_get();
int abort_get();

int init_remote_copy();
int do_remote_copy();
int abort_remote_copy();

int (*init_fn[2][2])() = {
	{ init_local_copy,  init_put         },
	{ init_get,         init_remote_copy }
};

int (*do_fn[2][2])() = {
	{ do_local_copy,    do_put         },
	{ do_get,           do_remote_copy }
};

int (*abort_fn[2][2])() = {
	{ abort_local_copy, abort_put         },
	{ abort_get,        abort_remote_copy }
};

char *merge_paths();
char *links_to_path();

static struct timeval start_time;
static struct timeval stop_time;

extern int beep_when_ops_done;
extern int diagnostics;
extern int max_ftp_retries;
extern int preserve_vms_ver_nums;

char *unix_rel_dir_path_to_native();
char *unix_rel_file_path_to_native();
char *unix_links_to_path();
double stop_timer();


/*
 * cb_xfer_files - Work proc for transferring files.
 */
cb_xfer_files()
{
	double ftime;
	struct entry_link *ptr;
	int src_host = xfer_ctrl.src_host;
	int snk_host = xfer_ctrl.snk_host;
	int src_host_type = xfer_ctrl.src_host_type;
	int snk_host_type = xfer_ctrl.snk_host_type;
	int src_host_system = xfer_ctrl.src_host_system;
	int snk_host_system = xfer_ctrl.snk_host_system;
	char *src_host_wd = xfer_ctrl.src_host_wd;
	char *snk_host_wd = xfer_ctrl.snk_host_wd;
	int operation = xfer_ctrl.operation;
	struct sl_struct *list;
	int retval;
	int lost_src_host = False;
	int lost_snk_host = False;
	int i;
	int is_a_directory;
	char msg[MAXPATHLEN+40];
	char **rel_path_links;
	char *q;

	switch (xfer_ctrl.state) {
	case 0:   /* State 0: No transfer in progress */
		xfer_ctrl.rel_path = NULL;
		xfer_ctrl.src_path = NULL;
		xfer_ctrl.snk_path = NULL;
		xfer_ctrl.src_rel_path = NULL;
		xfer_ctrl.snk_rel_path = NULL;
		if (xfer_ctrl.head[xfer_ctrl.level] == NULL) {
			if (xfer_ctrl.level == 0)
				goto done;
			else {
				xfer_ctrl.level--;
				ptr = xfer_ctrl.head[xfer_ctrl.level];
				xfer_ctrl.head[xfer_ctrl.level] = ptr->next;
				XtFree(ptr->entry);
				XtFree((char *)ptr);
				return False;
			}
		}
		if (xfer_ctrl.abort_requested)
			goto done;

		/* Build relative path name (in Unix format) */
	    rel_path_links = (char **)XtMalloc(sizeof(char *)*(xfer_ctrl.level+2));
	    for (i=0; i<=xfer_ctrl.level; i++) {
	        rel_path_links[i] = XtNewString(xfer_ctrl.head[i]->entry);
			if (src_host_system == SYS_VMS) {  /* Shave off ".dir;n" */
				if ((q = strstr(rel_path_links[i], ".dir")))
					*q = '\0';
			}
		}
		rel_path_links[xfer_ctrl.level+1] = NULL;
	    xfer_ctrl.rel_path = unix_links_to_path(rel_path_links,
			xfer_ctrl.level+1);
		release_path_links(rel_path_links);

		if (xfer_ctrl.operation == RCOPY) {
			/* Build relative source path (and assume it is a directory) */
			xfer_ctrl.src_rel_path = unix_rel_dir_path_to_native(
				src_host_system, xfer_ctrl.rel_path);
			/* Build source path (and assume it is a directory) */
	    	xfer_ctrl.src_path = merge_paths(src_host_system,
				src_host_wd, xfer_ctrl.src_rel_path);
			/* Test for directory by trying to cd to source path */
			is_a_directory = False;
			if (src_host_type == LOCAL) {
				if (local_cd(src_host, xfer_ctrl.src_path, False) == 0)
					is_a_directory = True;
			} else
				switch (remote_cd(src_host, xfer_ctrl.src_path, True, False)) {
				case -3:
					lost_src_host = True;
					goto lost;
				case 0:
					is_a_directory = True;
				}
			if (is_a_directory) {
				/* Build relative sink path */
				xfer_ctrl.snk_rel_path = unix_rel_dir_path_to_native(
					snk_host_system, xfer_ctrl.rel_path);
				/* Build sink path */
		    	xfer_ctrl.snk_path = merge_paths(snk_host_system,
					snk_host_wd, xfer_ctrl.snk_rel_path);
				/* Make sink directory */
				if (snk_host_type == LOCAL) {
					if (local_mkdir(snk_host, xfer_ctrl.snk_path, 0777) != 0) {
						warning_error("Unable to make directory");
						goto done;
					}
				} else
					switch (remote_mkdir(snk_host, xfer_ctrl.snk_path)) {
					case -3:
						lost_snk_host = True;
						goto lost;
					case -1:
						sprintf(msg, "Unable to make directory %s",
									 xfer_ctrl.snk_path);
						warning_error(msg);
						goto done;
					}
				if (diagnostics >= NORMAL) {
					sprintf(msg, "*** Successfully created directory: %s\n",
							xfer_ctrl.snk_rel_path);
					write_log(msg);
				}
				/* Get source directory list */
				if (src_host_type == LOCAL) {
					if (local_ls(xfer_ctrl.src_path, &list, False) != 0) {
						warning_error("Unable to get source directory list");
						goto done;
					}
				} else {
					switch (remote_ls(src_host, xfer_ctrl.src_path, &list,
						False)) {
					case -3:
						lost_src_host = True;
						goto lost;
					case -1:
						warning_error("Unable to get source directory list");
						goto done;
					}
				}
				/* Add source directory list to new level */
				xfer_ctrl.level++;
				if (xfer_ctrl.level == MAXLEVELS) {
					warning_error(
						"File transfer recurses too deeply - aborted.");
					goto done;
				}
				xfer_ctrl.head[xfer_ctrl.level] = NULL;
				for (i=list->nentries-1; i>=0; i--)
					add_to_linked_list(&xfer_ctrl.head[xfer_ctrl.level],
						list->entries[i]);
				release_array_list(list);
				XtFree(xfer_ctrl.rel_path);
				XtFree(xfer_ctrl.src_path);
				XtFree(xfer_ctrl.snk_path);
				XtFree(xfer_ctrl.src_rel_path);
				XtFree(xfer_ctrl.snk_rel_path);
				return False;
			}
			XtFree(xfer_ctrl.src_path);
			XtFree(xfer_ctrl.src_rel_path);
		}

		/* Build relative and full source paths (and assume it is a file) */
		xfer_ctrl.src_rel_path = unix_rel_file_path_to_native(
			src_host_system, xfer_ctrl.rel_path);
    	xfer_ctrl.src_path = merge_paths(src_host_system,
			src_host_wd, xfer_ctrl.src_rel_path);

        /* Build relative and full sink paths (and assume it is a file) */
		xfer_ctrl.snk_rel_path = unix_rel_file_path_to_native(
			snk_host_system, xfer_ctrl.rel_path);
   		xfer_ctrl.snk_path = merge_paths(snk_host_system,
			snk_host_wd, xfer_ctrl.snk_rel_path);
		if (src_host_system == SYS_VMS &&
				(operation == VIEW || !preserve_vms_ver_nums)) {
			if ((q = strrchr(xfer_ctrl.snk_path, ';')))
				*q = '\0';
		}

		/* Initialize file transfer */
		initialize_monitor1();
		retval = (*init_fn[src_host_type][snk_host_type])();
		switch (retval) {
		case -5:
			lost_src_host = True;
			lost_snk_host = True;
			goto lost;
		case -4:
			lost_snk_host = True;
			goto lost;
		case -3:
			lost_src_host = True;
			goto lost;
		case -1:
			sprintf(msg, "Unable to initialize transfer of %s",
						 xfer_ctrl.head[xfer_ctrl.level]->entry);
			warning_error(msg);
			if (operation == VIEW)
				delete_file_and_its_dir(xfer_ctrl.snk_path);
			xfer_ctrl.state = 0;
			xfer_ctrl.nretries = 0;
			ptr = xfer_ctrl.head[xfer_ctrl.level];
			xfer_ctrl.head[xfer_ctrl.level] = ptr->next;
			XtFree(xfer_ctrl.rel_path);
			XtFree(xfer_ctrl.src_path);
			XtFree(xfer_ctrl.snk_path);
			XtFree(xfer_ctrl.src_rel_path);
			XtFree(xfer_ctrl.snk_rel_path);
			XtFree(ptr->entry);
			XtFree((char *)ptr);
			return False;
		case 0:
			initialize_monitor2();
			start_timer();
			xfer_ctrl.state = 1;
		}

	case 1:   /* State 1: transfer in progress */
		if (xfer_ctrl.abort_requested)
			goto abort;
		retval = (*do_fn[src_host_type][snk_host_type])();
		switch (retval) {
		case -5:
			lost_src_host = True;
			lost_snk_host = True;
			goto lost;
		case -4:
			lost_snk_host = True;
			goto lost;
		case -3:
			lost_src_host = True;
			goto lost;
		case -6:    /* LLNL Archive retry logic */
			if (xfer_ctrl.nretries++ < max_ftp_retries)  {
				xfer_ctrl.state = 0;
				sprintf(msg, "*** Transfer of %s failed.  Will retry.\n",
					xfer_ctrl.head[xfer_ctrl.level]->entry);
				write_log(msg);
				return False;
			}
		case -1:
			sprintf(msg, "Unable to complete transfer of %s",
						 xfer_ctrl.head[xfer_ctrl.level]->entry);
			warning_error(msg);
			if (operation == VIEW)
				delete_file_and_its_dir(xfer_ctrl.snk_path);
			ptr = xfer_ctrl.head[xfer_ctrl.level];
			xfer_ctrl.head[xfer_ctrl.level] = ptr->next;
			XtFree(xfer_ctrl.rel_path);
			XtFree(xfer_ctrl.src_path);
			XtFree(xfer_ctrl.snk_path);
			XtFree(xfer_ctrl.src_rel_path);
			XtFree(xfer_ctrl.snk_rel_path);
			XtFree(ptr->entry);
			XtFree((char *)ptr);
			xfer_ctrl.state = 0;
			xfer_ctrl.nretries = 0;
			return False;
		case 0:   /* Transfer complete */
			update_monitor();
			ftime = stop_timer();
			if (diagnostics >= NORMAL) {
				sprintf(msg, "*** Successfully transferred file:  %s\n",
						xfer_ctrl.src_rel_path);
				write_log(msg);
				print_xfer_stats(ftime, xfer_ctrl.file_len);
			}
			if (operation == VIEW)
				view_file(src_host, xfer_ctrl.snk_path);
			ptr = xfer_ctrl.head[xfer_ctrl.level];
			xfer_ctrl.head[xfer_ctrl.level] = ptr->next;
			XtFree(xfer_ctrl.rel_path);
			XtFree(xfer_ctrl.src_path);
			XtFree(xfer_ctrl.snk_path);
			XtFree(xfer_ctrl.src_rel_path);
			XtFree(xfer_ctrl.snk_rel_path);
			XtFree(ptr->entry);
			XtFree((char *)ptr);
			xfer_ctrl.state = 0;
			xfer_ctrl.nretries = 0;
			return False;
		case 1:   /* More to transfer */
			update_monitor();
			return False;
		}
	}

done:
	
	if ((operation == VIEW) && xfer_ctrl.snk_path)
		delete_file_and_its_dir(xfer_ctrl.snk_path);
	clean_up_xfer_ctrl();
	hide_monitor();
	enable_controls(True);
	switch (operation) {
	case COPY:
	case RCOPY:
		if (update_dir_displays(snk_host, False) < 0) {
			lost_snk_host = True;
			goto lost;
		}
	}
	clear_selected_entries(src_host);
	update_xfer_controls();
	update_host_controls(src_host);
	if (beep_when_ops_done)
		beep();
	restore_prev_cursor();
	if (xfer_ctrl.abort_requested) {
		xfer_ctrl.abort_requested = False;
		hide_abort_dialog();
		warning_error("File transfer(s) aborted");
	}
	return True;

abort:

	retval = (*abort_fn[src_host_type][snk_host_type])();
	switch (retval) {
	case -5:
		lost_src_host = True;
		lost_snk_host = True;
		goto lost;
	case -4:
		lost_snk_host = True;
		goto lost;
	case -3:
		lost_src_host = True;
		goto lost;
	default:
		if ((operation == VIEW) && xfer_ctrl.snk_path)
			delete_file_and_its_dir(xfer_ctrl.snk_path);
		clean_up_xfer_ctrl();
		enable_controls(True);
		switch (operation) {
		case COPY:
		case RCOPY:
			if (update_dir_displays(snk_host, False) < 0) {
				lost_snk_host = True;
				goto lost;
			}
		}
		clear_selected_entries(src_host);
		update_xfer_controls();
		update_host_controls(src_host);
		if (beep_when_ops_done)
			beep();
		restore_prev_cursor();
		xfer_ctrl.abort_requested = False;
		hide_abort_dialog();
		warning_error("File transfer(s) aborted");
		return True;
	}

lost:

	if ((operation == VIEW) && xfer_ctrl.snk_path)
		delete_file_and_its_dir(xfer_ctrl.snk_path);
	clean_up_xfer_ctrl();
	hide_monitor();
	enable_controls(True);
	clear_selected_entries(src_host);
	if (xfer_ctrl.abort_requested) {
		xfer_ctrl.abort_requested = False;
		hide_abort_dialog();
	}
	warning_error("File transfer(s) aborted");
	if (lost_src_host) {
		restore_prev_cursor();
		lost_connection(src_host);
		use_busy_cursor();
	}
	if (lost_snk_host) {
		restore_prev_cursor();
		lost_connection(snk_host);
		use_busy_cursor();
	} else {
		switch (operation) {
		case COPY:
		case RCOPY:
			if (update_dir_displays(snk_host, False) < 0) {
				restore_prev_cursor();
				lost_connection(snk_host);
				use_busy_cursor();
			}
		}
	}
	update_xfer_controls();
	update_host_controls(src_host);
	if (beep_when_ops_done)
		beep();
	restore_prev_cursor();
	return True;
}


/*
 * extract_file_len - Extracts the file length usually returned in a "150"
 *                    ftp reply.  This routine looks for a pattern of the
 *                    form "(xxx bytes)", "(xxx)", or "(xxxk) near the end
 *                    of the reply, where the "xxx" represent digits.  If
 *                    this pattern is found, the file length is returned,
 *                    otherwise -1 is returned.  "reply" is the "150"
 *                    ftp reply to be scanned.
 */
long
extract_file_len(reply)
char *reply;
{
    static char *pat1 = " bytes)";
    int lenpat1 = strlen(pat1);
	static char *pat2 = ")";
	int lenpat2 = strlen(pat2);
	static char *pat3 = "k)";
	int lenpat3 = strlen(pat3);
	char *ptr;
	long len;

    if ((ptr = strrchr(reply, '(')) && isdigit(*++ptr)) {
        len = 0;
        while (*ptr && isdigit(*ptr))
            len = (len*10)+(*ptr++-'0');
        if ((int)(strlen(reply)-strlen(ptr)) >= lenpat1)
            if (strncmp(ptr, pat1, lenpat1) == 0)
                return len;
        if ((int)(strlen(reply)-strlen(ptr)) >= lenpat2)
            if (strncmp(ptr, pat2, lenpat2) == 0)
                return len;
        if ((int)(strlen(reply)-strlen(ptr)) >= lenpat3)
            if (strncmp(ptr, pat3, lenpat3) == 0)
                return len*1024;
    }

	return -1;
}


/*
 * start_timer - Starts the microsecond timer.  Needs to be called at
 *               start of interval to be timed.  Call stop_timer() at
 *               end of interval.
 *
 */
start_timer()
{
    gettimeofday(&start_time, NULL);
}


/*
 * stop_timer - Returns the number of microseconds that have elapsed
 *              since start_timer() was last called.  Undefined results
 *              if start_timer() is not called first.
 */
double
stop_timer()
{
    double fstart_time;
    double fstop_time;

    gettimeofday(&stop_time, NULL);
    fstart_time = (double)start_time.tv_sec+(double)start_time.tv_usec/1000000.;
    fstop_time = (double)stop_time.tv_sec+(double)stop_time.tv_usec/1000000.;
    return fstop_time-fstart_time;
}


/*
 * print_xfer_stats - Prints statistics about a file transfer.  "ftime"
 *                    is the number of seconds the transfer took.  "nbytes"
 *                    is the number of bytes transferred.  Set "nbytes"
 *                    to -1 if file length not known.
 */
print_xfer_stats(ftime, nbytes)
double ftime;
long nbytes;
{
	char msg[120];

	write_log("      ");
	if (nbytes == -1)
		write_log("?");
	else {
		sprintf(msg, "%ld", nbytes);
		write_log(msg);
	}
	sprintf(msg, " bytes transferred in %3.1f seconds", ftime);
	write_log(msg);
	if (nbytes != -1) {
		if (ftime <= 0)
			write_log(" (Very Fast)");
		else {
			sprintf(msg, " (%3.1f Kbytes/s)", (double)nbytes/(ftime*1000.));
			write_log(msg);
		}
	}
	write_log("\n");
}


/*
 * clean_up_xfer_ctrl - Cleans up the control structure used by the file
 *                      transfer work proc.
 */
clean_up_xfer_ctrl()
{
	int i;

	if (xfer_ctrl.rel_path != NULL) {
		XtFree(xfer_ctrl.rel_path);
		xfer_ctrl.rel_path = NULL;
	}

	if (xfer_ctrl.src_rel_path != NULL) {
		XtFree(xfer_ctrl.src_rel_path);
		xfer_ctrl.src_rel_path = NULL;
	}

	if (xfer_ctrl.snk_rel_path != NULL) {
		XtFree(xfer_ctrl.snk_rel_path);
		xfer_ctrl.snk_rel_path = NULL;
	}

	if (xfer_ctrl.src_path != NULL) {
		XtFree(xfer_ctrl.src_path);
		xfer_ctrl.src_path = NULL;
	}

	if (xfer_ctrl.snk_path != NULL) {
		XtFree(xfer_ctrl.snk_path);
		xfer_ctrl.snk_path = NULL;
	}

	for(i=0; i<=xfer_ctrl.level; i++)
		release_linked_list(&xfer_ctrl.head[i]);

	XtFree(xfer_ctrl.src_host_wd);
	XtFree(xfer_ctrl.snk_host_wd);
}

