#include "ftptool.h"

#pragma ident   "@(#)change_dir.c 1.8     94/06/27"

int dirlastmtime;

#ifdef USE_PROTOTYPES
int	checkdir(char *dirname)
#else
int	checkdir(dirname)
char	*dirname;
#endif
{
	struct stat buf;

	if (lstat(dirname, &buf) == -1) {
		if (errno == ENOENT)
			return (ENOENT);
		return (1);
	}

	if (buf.st_mtime > local_dircache.first->mtime) {
		return (1);
	}
	return (0);
}

#ifdef USE_PROTOTYPES
int dirwasmodified(void)
#else
int dirwasmodified()
#endif
{
	int	error;
	char	*dirname = local_dircache.first->name;

	if (cache_lookup(&local_dircache, dirname) == NULL)
		return (0);
	/* first now */

	error = checkdir(dirname);
	if (error == ENOENT) {
		if (xv_get(local_window.frame, XV_SHOW) == FALSE) {
			xv_set(local_window.frame,
				XV_SHOW, TRUE,
				NULL);
		}
		local_footer_message(
		    "Current directory was deleted. Changing to /tmp.");
		clear_slist(local_window.list);
		xv_set(local_window.list,
			XV_SHOW, TRUE,
			NULL);
		dircache_delete(&local_dircache, dirname);
		change_local_dir("/tmp", 0);
		return (1);
	} else if (error != 0) {
		local_footer_message(
		    "Current directory was modified. Rereading.");
		if (xv_get(local_window.frame, XV_SHOW) == FALSE) {
			xv_set(local_window.frame,
				XV_SHOW, TRUE,
				NULL);
		}
		clear_slist(local_window.list);
		xv_set(local_window.list,
			XV_SHOW, TRUE,
			NULL);
		dircache_delete(&local_dircache, dirname);
		change_local_dir(dirname, 0);
		return (1);
	}
	return (0);
}

#ifdef USE_PROTOTYPES
int change_local_dir(char *s, int force)
#else
int change_local_dir(s, force)
char	*s;
int		force;
#endif
{
	static char	cld[MAXPATHLEN + 2];
#ifndef __FreeBSD__
	extern char *sys_errlist[];
#endif
	struct dirlist *head = NULL;
	int		wasinactive;
	int		rval = 0;
	int		error;
	char	*dir = NULL;
#ifndef	ultrix
#if defined(SYSV) || defined(SYSV386)
	struct statvfs fsbuf;
#else
	struct statfs fsbuf;
#endif
#else
	struct	fs_data	fsbuf;
#endif

	wasinactive = xv_get(local_window.list, PANEL_INACTIVE);
	if (wasinactive == FALSE) {
		xv_set(local_window.list,
			PANEL_INACTIVE, TRUE,
			NULL);
	}
	cursor_busy();
	local_footer_message("Reading directory...");
	local_right_footer_message("");
	dir = s;
	if (dir == NULL) {
		fprintf(stderr, "out of memory\n");
		goto out;
	}
	if (chdir(dir) == -1) {
		if (errno == ENOENT) {
			if ((rval = ask_make_dir(dir)) != 0) {
				if (rval == -1) {
					local_footer_message("");
					/* user cancelled. */
					goto out;
				}
				local_footer_message(
				    "Could not make directory: %s",
				    sys_errlist[rval]);
				goto out;
			}
			if (chdir(dir) == -1) {
				rval = errno;
				local_footer_message(
				    "Could not change to directory: %s",
				    sys_errlist[rval]);
				goto out;
			}
		} else {
			rval = errno;
			local_footer_message(
			    "Could not change to directory: %s",
			    sys_errlist[rval]);
			goto out;
		}
	}

	clear_slist(local_window.list);

	local_list_ndirs = 0;
	local_list_nfiles = 0;
	local_list_nothers = 0;
	change_local_list_menu();

	if (getcwd(cld, sizeof (cld)) == NULL) {
		/* Failure */
		goto out;
	}

	xv_set(local_window.directory,
		PANEL_VALUE, cld,
		NULL);

#if defined(SYSV) || defined(SYSV386)
	if (statvfs(cld, &fsbuf) == -1) {
		sprintf(scratch, "statvfs failed: %s", sys_errlist[errno]);
	}
#else
	if (statfs(cld, &fsbuf) == -1) {
		sprintf(scratch, "statfs failed: %s", sys_errlist[errno]);
	}
#endif
	else {
#ifndef	ultrix
		sprintf(scratch, "%d Kbytes (%d%% free)",
			(int)(fsbuf.f_bsize / 1024.0 * fsbuf.f_bavail),
			(int)(100.0 * fsbuf.f_bavail /
			    (fsbuf.f_blocks -
				(fsbuf.f_bfree - fsbuf.f_bavail))));
#else
		sprintf(scratch, "%d Kbytes (%d%% free)",
			(int)(fsbuf.fd_req.bfreen),
			(int)(100.0 * fsbuf.fd_req.bfreen /
			    (fsbuf.fd_req.btot -
				(fsbuf.fd_req.bfree - fsbuf.fd_req.bfreen))));
#endif
	}
	xv_set(local_window.space,
		PANEL_LABEL_STRING, scratch,
		NULL);

	if (force)
		dircache_delete(&local_dircache, cld);

	head = cache_lookup(&local_dircache, cld);
	if (head == NULL) {
		/* cache miss */
		head = read_local_dir(cld);
		if (head == NULL) {
			fprintf(stderr, "Out of memory\n");
			rval = 1;
			goto out;
		}
		/* add to cache */
		dircache_add(&local_dircache, cld, head);
		local_dircache.first->mtime = dirlastmtime;
	} else if ((error = checkdir(cld)) != 0) {
		dircache_delete(&local_dircache, cld);
		if (error == ENOENT) {
			local_footer_message("%s does not exist.", cld);
			goto out;
		}
		/* else reread */
		head = read_local_dir(cld);
		if (head == NULL) {
			fprintf(stderr, "Out of memory\n");
			rval = 1;
			goto out;
		}
		/* add to cache */
		dircache_add(&local_dircache, cld, head);
		local_dircache.first->mtime = dirlastmtime;
	}


	dirlist_to_slist(local_window.list, head);

	local_show_items();
	local_footer_message("");
out:
	cursor_normal();

	if (wasinactive == FALSE) {
		xv_set(local_window.list,
			PANEL_INACTIVE, FALSE,
			NULL);
	}
	XFlush(dpy);
	return (rval);
}

#ifdef USE_PROTOTYPES
int change_remote_dir(char *s, int force)
#else
int change_remote_dir(s, force)
char	*s;
int	force;
#endif
{
	char	*ftperr;
	char	crd[MAXPATHLEN + 1];
	struct dirlist *head = NULL;
	int		wasinactive;
	char	*dir = NULL;
	int	rval = 0;
	int	pwdfailed = 0;
	char	*quote;

	wasinactive = xv_get(base_window.list, PANEL_INACTIVE);
	if (wasinactive == FALSE) {
		xv_set(base_window.list,
			PANEL_INACTIVE, TRUE,
			NULL);
	}
	if (ping_server())
		goto out;
	cursor_busy();
	/* send cd command */
	footer_message("Reading directory...");
	right_footer_message("");
	dir = strdup(s);
	if (dir == NULL)
		goto out;
	if (!strcmp(dir, "..")) {
		if (up_one_level())
			goto out;
	} else if (strcmp(dir, ".")) {
		extern int code;

		code = -1;
		command("CWD %s", dir);
		/* success */
		/*
		 * 250 CWD command successful.
		 * 200 CWD command successful (OS9)
		 */
		/* failure */
		/*
		 * 451 error changing directory to <name>: 000:214 {000:###} (OS9) 
		 * 550 k: No such file or directory
		 */
		if (code == 451) {
			ftperr = ftp_error(' ', response_line);
			footer_message("Error changing to %s. (Not a Directory)", dir);
			rval = ENOTDIR;
			goto out;
		}

		if (code == 550) {
			ftperr = index(response_line, ':');
			if (ftperr == NULL) {
				rval = 1;
				goto out;
			}
			if (!strncmp(ftperr, ": Not a directory.", 18)) {
				footer_message("%s is not a directory.", dir);
				rval = ENOTDIR;
				goto out;
			}
			if ((rval = ask_make_remote_dir(dir)) != 0) {
				if (rval == -1) {
					footer_message("");
					/* user cancelled */
					goto out;
				}
				footer_message("Could not make directory.");
				goto out;
			}
			command("CWD %s", dir);
			if (code == 550) {
				sprintf(scratch,
				    "%s: No such file or directory.", dir);
				ftperr = ftp_error(' ', scratch);
				footer_message(ftperr);
				rval = 1;
				goto out;
			}
		}
	}

	clear_slist(base_window.list);

	remote_list_ndirs = 0;
	remote_list_nfiles = 0;
	remote_list_nothers = 0;

	change_remote_list_menu();

	/* set current directory */
	if (command("PWD") == ERROR && code == 500) {
		/*
		footer_message("pwd not recognized.");
		*/
		/* try quote xpwd */
		if (command("XPWD") == ERROR && code == 500) {
			footer_message("pwd and xpwd not recognized.");
			pwdfailed = 1;
		}
	}
	/* response */
	/*
	 * 25[17] "/" is current directory.
	 * 257 PWD: "/Print_Output" is current directory.
	 *
	 * Skip to first double-quote, since they seem to have that.
	 */
	crd[0] = '\0';
	if (pwdfailed) {
		force = 1;
	} else if (!strncmp(response_line, "257", 3) ||
	    !strncmp(response_line, "251", 3)) {
		quote = strchr(response_line, '\"');
		if (quote != NULL)
			sscanf (quote, "\"%[^\"]\"", crd);
	} else {
		footer_message("pwd or xpwd returned bad response.");
		force = 1;
	}
	xv_set(base_window.directory,
		PANEL_VALUE, crd,
		NULL);

	if (force)
		dircache_delete(&remote_dircache, crd);
	head = cache_lookup(&remote_dircache, crd);
	if (head == NULL) {
		/* cache miss */
		head = read_remote_dir();
		if (head == NULL) {
			rval = ETIMEDOUT;
			goto out;
		}
		dircache_add(&remote_dircache, crd, head);
	}


	dirlist_to_slist(base_window.list, head);

	remote_show_items();
out:
	if (rval == 0)
		footer_message("");
	cursor_normal();
	if (dir)
		free(dir);
/*
	if (head)
		free_dirlist(head);
*/
	if (wasinactive == FALSE) {
		xv_set(base_window.list,
			PANEL_INACTIVE, FALSE,
			NULL);
	}
	if (timedout)
		timeout_disconnect();
	XFlush(dpy);
	return (rval);
}

#ifdef USE_PROTOTYPES
char *expand_dirname(char *arg)
#else
char *expand_dirname(arg)
char	*arg;
#endif
{
	char 	*slash;
	char	*lastpart = "";
	char	*path;
	struct passwd *pwd;
	char	*firstpart = "";
	char	*s;

	if (arg[0] == '/' || (arg[0] != '~' && arg[0] != '$')) {
		path = strdup(arg);
		return (path);
	}
	s = strdup(arg);
	if (s == NULL)
		return (NULL);
	if ((slash = index(s, '/')) != NULL) {
		*slash = 0;
		lastpart = slash + 1;
	}
	switch (s[0]) {
	case '~': /* ~ or ~user */
		if (s[1] == '\0') {
			pwd = getpwuid(getuid());
			if (pwd == NULL) {
				footer_message(
				    "You are unknown to the system.");
				free(s);
				return (NULL);
			}
		} else {
			pwd = getpwnam(&s[1]);
			if (pwd == NULL) {
				footer_message("Unknown user %s.", &s[1]);
				free(s);
				return (NULL);
			}
		}
		firstpart = pwd->pw_dir;
		break;
	case '$': /* Environment variable */
		firstpart = getenv(&s[1]);
		if (firstpart == NULL) {
			footer_message("Unknown variable %s.", &s[1]);
			free(s);
			return (NULL);
		}
		break;
	}
	path = (char *)malloc((unsigned int)(strlen(firstpart) +
	    1 + strlen(lastpart) + 1));
	if (path == NULL) {
		footer_message("Memory allocation failed.");
		free(s);
		return (NULL);
	}
	if (lastpart[0] != '\0')
		sprintf(path, "%s/%s", firstpart, lastpart);
	else
		strcpy(path, firstpart);
	free(s);
	return (path);
}

#ifdef USE_PROTOTYPES
int	delete_local_dir(char *dir)
#else
int	delete_local_dir(dir)
char	*dir;
#endif
{
	struct dirlist *head = NULL;
	struct dirlist *tmp;
#ifndef __FreeBSD__
	extern char *sys_errlist[];
#endif
	int	rval = 0;

	if (chdir(dir) == -1) {
		local_footer_message("Can not change to %s: %s",
			dir, sys_errlist[errno]);
		return (errno);
	}
	head = read_local_dir(".");
	if (head == NULL) {
		fprintf(stderr, "Out of memory\n");
		rval = ENOMEM;
		goto out;
	}
	for (tmp = head->next; tmp != NULL; tmp = tmp->next)
		if (S_ISDIR(tmp->mode)) {
			if ((rval = delete_local_dir(tmp->name)) != 0)
				goto out;
		} else if ((rval = delete_local_file(tmp->name, unlink)) != 0)
				goto out;
out:
	if (head)
		free_dirlist(head);
	if (chdir("..") == -1) {
		local_footer_message("Can not cd ..: %s", sys_errlist[errno]);
		return (errno);
	}
	/* delete parent */
	if (rval == 0)
		rval = delete_local_file(dir, rmdir);
	return (rval);
}

#ifdef USE_PROTOTYPES
int delete_local_file(char *filename, int (*deletefunc)(const char *filename))
#else
int delete_local_file(filename, deletefunc)
char	*filename;
int		(*deletefunc)();
#endif
{
	int	answer;
#ifndef __FreeBSD__
	extern char *sys_errlist[];
#endif
#ifdef XVIEW3
	Xv_notice notice;
#endif

	if (confirmdeletes) {
		sprintf(scratch, "Really delete %s?", filename);
#ifdef XVIEW3
		notice = xv_create(base_window.panel, NOTICE,
			NOTICE_MESSAGE_STRINGS,
				scratch,
				NULL,
			NOTICE_BUTTON_YES, "Cancel",
			NOTICE_BUTTON_NO, "Delete",
			NOTICE_STATUS, &answer,
			XV_SHOW, TRUE,
			NULL);
		xv_destroy_safe(notice);
#else
		answer =  notice_prompt(base_window.panel, NULL,
			NOTICE_MESSAGE_STRINGS,
				scratch,
				NULL,
			NOTICE_BUTTON_YES, "Cancel",
			NOTICE_BUTTON_NO, "Delete",
			NULL);
#endif
	} else
		answer = NOTICE_NO;

	if (answer == NOTICE_NO) {
		local_footer_message("Deleting %s", filename);
		if ((*deletefunc)(filename) == -1) {
			local_footer_message("delete %s failed: %s", filename,
				sys_errlist[errno]);
			return (1);
		}
		return (0);
	}
	return (1); /* deletion aborted */
}

#ifdef USE_PROTOTYPES
int	delete_remote_dir(char *dir)
#else
int	delete_remote_dir(dir)
char	*dir;
#endif
{
	struct dirlist *head = NULL;
	struct dirlist *tmp;
	int	rval = 0;
	char	*ftperr;

	if (command("CWD %s", dir) == ERROR && code == 550) {
		sprintf(scratch, "Can not change to %s", dir);
		ftperr = ftp_error(' ', scratch);
		footer_message(ftperr);
		return (1);
	}
	head = read_remote_dir();
	if (head == NULL) {
		rval = ENOMEM;
		goto out;
	}
	for (tmp = head->next; tmp != NULL; tmp = tmp->next)
		if (S_ISDIR(tmp->mode)) {
			if ((rval = delete_remote_dir(tmp->name)) != 0)
				goto out;
		} else if ((rval = delete_remote_file(tmp->name, "DELE")) != 0)
				goto out;
out:
	if (head)
		free_dirlist(head);
	if (up_one_level())
		footer_message("Can not cd ..");

	/* delete parent */
	if (rval == 0)
		rval = delete_remote_file(dir, "RMD");
	return (rval);
}

#ifdef USE_PROTOTYPES
int delete_remote_file(char *filename, char *deletecmd)
#else
int delete_remote_file(filename, deletecmd)
char	*filename;
char	*deletecmd;
#endif
{
	char	*ftperr;
	int	answer;
#ifdef XVIEW3
	Xv_notice notice;
#endif

	if (confirmdeletes) {
		sprintf(scratch, "Really delete %s?", filename);
#ifdef XVIEW3
		notice = xv_create(base_window.panel, NOTICE,
			NOTICE_MESSAGE_STRINGS,
				scratch,
				NULL,
			NOTICE_BUTTON_YES, "Cancel",
			NOTICE_BUTTON_NO, "Delete",
			NOTICE_STATUS, &answer,
			XV_SHOW, TRUE,
			NULL);
		xv_destroy_safe(notice);
#else
		answer = notice_prompt(base_window.panel, NULL,
			NOTICE_MESSAGE_STRINGS,
				scratch,
				NULL,
			NOTICE_BUTTON_YES, "Cancel",
			NOTICE_BUTTON_NO, "Delete",
			NULL);
#endif
	} else
		answer = NOTICE_NO;

	if (answer == NOTICE_NO) {
		footer_message("Deleting %s", filename);
		if (command("%s %s", deletecmd, filename) == ERROR) {
			sprintf(scratch, "delete %s failed", filename);
			ftperr = ftp_error(' ', scratch);
			footer_message(ftperr);
			return (1);
		}
		return (0);
	}
	return (1); /* deletion aborted */
}

#ifdef USE_PROTOTYPES
int up_one_level(void)
#else
int up_one_level()
#endif
{
	static struct {
		char	*cmd;
		char	*msg;
	} method[] = {
		{ "CDUP", "CDUP failed, trying XCUP" },
		{ "XCUP", "CDUP and XCUP failed, trying 'CD ..'" },
		{ "CWD ..", "CDUP, XCUP, and 'CD ..' failed.  Try manually." },
		{ NULL, NULL }
	};

	if (which_up_cmd != -1) {
		if (command(method[which_up_cmd].cmd) == ERROR &&
		    code >= 500) {
			footer_message(method[which_up_cmd].msg);
			which_up_cmd = -1;
		} else
			return (0);
	}
	for (which_up_cmd = 0; method[which_up_cmd].cmd; which_up_cmd++) {
		if (command(method[which_up_cmd].cmd) == ERROR &&
		    code >= 500)
			footer_message(method[which_up_cmd].msg);
		else
			return (0);
	}
	which_up_cmd = -1;
	return (1);

}


syntax highlighted by Code2HTML, v. 0.9.1