/*
** 1998-05-21 -	This module implements the built-in command "Copy". Very handy stuff!
** 1998-05-23 -	Added some error handling/reporting. Very far from bullet-proof, though. :(
** 1998-05-31 -	Added capability to copy device files (recreates them at destination using
**		mknod(), of course). Nice.
** 1998-06-04 -	Copied directories now try to have the same protection flags, rather than
**		trying to turn on as many bits as possible (as the old code did!).
** 1998-06-07 -	Added support for copying soft links. Originally wrote the code in cmd_move,
**		then moved it here and made the cmd_move code call it here.
** 1998-09-12 -	Changed all destination arguments to be full (with path and filename). This
**		makes the implementation of cmd_copys SO much easier.
** 1998-09-19 -	Now uses the new overwrite protection/confirmation module. Kinda cool.
** 1998-12-23 -	Now supports copying the source's access and modification dates, too.
** 1999-01-03 -	After complaints, I altered the copying command slightly; it no longer attempts
**		to avoid doing a new stat() call on the file being copied. This assures that
**		the size used for the copy is the most recent one known to the file system (if
**		you pretend there's no other programs running, that is).
** 1999-03-05 -	Moved over to new selection method, and it's semi-abstracted view of dir rows.
** 1999-04-06 -	Modified to use the new command configuration system.
*/

#include "gentoo.h"

#include <fcntl.h>
#include <utime.h>

#include "errors.h"
#include "fileutil.h"
#include "dirpane.h"
#include "strutil.h"
#include "overwrite.h"
#include "progress.h"
#include "cmdseq_config.h"

#include "cmd_copy.h"

#define	CMD_ID	"copy"

/* ----------------------------------------------------------------------------------------- */

typedef struct {			/* Options used by the "Copy" command (and relatives). */
	gboolean	modified;
	gboolean	copy_dates;		/* Copy access and modification dates? */
	gboolean	leave_fullsize;		/* If destination has same size as source, leave it even if error occured. */
	gsize		buf_size;		/* Buffer size. */
} OptCopy;

static OptCopy	copy_options;
static CmdCfg	*copy_cmc = NULL;

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-21 -	Copy a single regular file from <from> to <to>. The destination name should not
**		include the source name (i.e., it should end with a directory or a slash). The
**		<sstat> structure should contain the result of a stat() call on the source file,
**		or NULL.
** 1998-05-30 -	Now uses the passed in stat to change protection flags on the destination file
**		(otherwise it has no bits at all, which is boring).
** 1998-06-07 -	Now removes the destination file if copying fails in mid-copy.
** 1998-09-01 -	Now uses the new fut_copy() function, saving lots of code. Also opens the
**		destination file with the source's mode directly, rather than setting the
**		flags afterward.
** 1998-09-12 -	Modified. The <full_to> parameter now gives the complete absolute destination
**		name directly; there is no need to append anything. Also removed a HUGE silly
**		bug: the files weren't being closed!!! Shoot me.
** 1999-03-02 -	Added a new (as of yet non-configurable) option (leave_fullsize): if set, when
**		an error occurs during copy, we will check if the destination file has the same
**		size as the source, and if so ignore the error.
** 1999-03-15 -	Cleaned up the implementation of the leave_fullsize feature a great deal.
*/
gboolean copy_file(MainInfo *min, const gchar *from, const gchar *full_to, struct stat *sstat)
{
	gint		fd_in, fd_out;
	gsize		put = 0;
	struct stat	stat;

	if(sstat == NULL)
	{
		if(lstat(from, &stat) == 0)
			sstat = &stat;
		else
		{
			err_set(min, errno, CMD_ID, from);
			return FALSE;
		}
	}
	pgs_progress_item_begin(min, full_to, sstat->st_size);
	if((fd_in = open(from, O_RDONLY)) >= 0)
	{
		if((fd_out = open(full_to, O_CREAT | O_WRONLY | O_TRUNC, sstat->st_mode | S_IWUSR)) >= 0)
		{
			put = fut_copy(fd_in, fd_out, sstat->st_size, copy_options.buf_size);
			close(fd_out);
		}
		close(fd_in);
		if((put == sstat->st_size) && copy_options.copy_dates)
		{
			struct utimbuf	tim;

			memcpy(&tim.actime,  &sstat->st_atime, sizeof tim.actime);
			memcpy(&tim.modtime, &sstat->st_mtime, sizeof tim.modtime);
			utime(full_to, &tim);
		}
	}
	pgs_progress_item_end(min);
	if(errno)
	{
		if((put == sstat->st_size) && (copy_options.leave_fullsize))	/* Leave fullsized copy? */
		{
			errno = 0;
			return TRUE;
		}
		err_set(min, errno, CMD_ID, full_to);
		if(fut_exists(full_to))				/* Preserves errno. */
			unlink(full_to);			/* Attempt to remove failed destination file. */
	}
	return (errno == 0) ? TRUE : FALSE;
}

/* 1998-05-31 -	Copy a device special file. Pretty simple, since there are no contents
**		to worry about. Note that non-root users rarely have enough power for this.
** 1998-09-12 -	Modified; the <full_to> parameter now gives the complete destination name.
*/
gboolean copy_device(MainInfo *min, const gchar *from, const gchar *full_to, struct stat *sstat)
{
	struct stat	stat;

	if(sstat == NULL)
	{
		if(lstat(from, &stat) == 0)
			sstat = &stat;
		else
		{
			err_set(min, errno, CMD_ID, from);
			return FALSE;
		}
	}

	pgs_progress_item_begin(min, full_to, stat.st_size);
	mknod(full_to, sstat->st_mode, sstat->st_rdev);
	pgs_progress_item_update(min, sstat->st_size);
	pgs_progress_item_end(min);
	if(errno)
		err_set(min, errno, CMD_ID, from);
	return (errno == 0) ? TRUE : FALSE;
}

/* 1998-06-07 -	Copy a symbolic (soft) link. Copies the actual link, meaning that
**		there will be a new link created at the destination. Inspects the
**		contents of the link; if it begins with its "own" path, that part
**		is replaced with the destination path. I feel that this might
**		be the right thing to do.
**		Note: Copying links is considered a rare event, and as such need
**		      not be manically optimized.
** 1998-09-12 -	Modified; the <full_to> param now gives the complete destination name.
** 1998-09-23 -	Completely rewritten. Now is a lot more complex, but sometimes works
**		better. Still, I must say I would sleep better if there were no symbolic
**		links. They're great in theory (and in /usr/local/lib), but a pain in
**		the butt to handle in programs like this... :(
*/
gboolean copy_link(MainInfo *min, const gchar *from, const gchar *full_to)
{
	gchar	buf[PATH_MAX], newbuf[PATH_MAX], here[PATH_MAX];
	gchar	dbuf[PATH_MAX], *dslash;
	gint	len;

	if((getcwd(here, sizeof here)) != NULL)
	{
		if((len = readlink(from, buf, sizeof buf - 1)) > 0)
		{
			buf[len] = '\0';
			if(buf[0] == '/' || (!strncmp(buf, "..", 2)))	/* Absolute or "parent-relative"? */
				strcpy(newbuf, buf);
			else						/* Nope, so relative. */
			{
				strcpy(dbuf, full_to);
				if((dslash = strrchr(dbuf, '/')) != NULL)
					*dslash = '\0';
				strcat(dbuf, "/");
				strcat(dbuf, buf);
				if(fut_exists(dbuf))			/* Does link target exist? */
					strcpy(newbuf, dbuf);		/* Then create relative link to it. */
				else
					sprintf(newbuf, "%s/%s", here, buf);	/* Otherwise create absolute link to original. */

			}
			symlink(newbuf, full_to);
		}
	}
	if(errno)
		err_set(min, errno, CMD_ID, from);
	return (errno == 0) ? TRUE : FALSE;
}

/* 1998-05-21 -	Copy a directory recursively, by first creating the appropriate
**		destination directory, and then copying all the source's contents
**		there. When directories are found in the source, this function is
**		called again to copy them.
** 1998-06-07 -	Added more error checking (now aborts on first failure, returning
**		0). Does not remove partially failed directories.
** 1998-09-12 -	Modified; the <dest_name> is now the COMPLETE destination; it is
**		created directly, without appending anything. Also changed the order of
**		the fut_cd() and mkdir() calls, for better handling of unreable dirs.
*/
gboolean copy_dir(MainInfo *min, const gchar *from, const gchar *dest_name)
{
	gchar		old_dir[PATH_MAX];
	DIR		*dir;
	struct dirent	*de;
	struct stat	stat;

	if(lstat(from, &stat) != 0)
	{
		err_set(min, errno, CMD_ID, from);
		return FALSE;
	}

	pgs_progress_item_begin(min, dest_name, stat.st_size);
	if(fut_cd(from, old_dir, sizeof old_dir))
	{
		if(mkdir(dest_name, stat.st_mode | S_IWUSR) == 0)
		{
			if((dir = opendir(".")) != NULL)
			{
				while(!errno && (de = readdir(dir)) != NULL)
				{
					if(!min->cfg.dir_filter(de->d_name))
						continue;
					if(lstat(de->d_name, &stat) == 0)
					{
						char	dest2[PATH_MAX];

						sprintf(dest2, "%s/%s", dest_name, de->d_name);
						if(S_ISDIR(stat.st_mode))
							copy_dir(min, de->d_name, dest2);
						else if(S_ISREG(stat.st_mode))
							copy_file(min, de->d_name, dest2, &stat);
						else if(S_ISLNK(stat.st_mode))
							copy_link(min, de->d_name, dest2);
						else if(S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode))
							copy_device(min, de->d_name, dest2, &stat);
					}
				}
				closedir(dir);
			}
		}
		fut_cd(old_dir, NULL, 0);
	}
	pgs_progress_item_end(min);
	if(errno)
		err_set(min, errno, CMD_ID, from);
	return (errno == 0) ? TRUE : FALSE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-30 -	Debugged this one. It was poking the selection flag directly, which is not
**		legal. The correct way is to call dp_unselect(). Fixing this removed the
**		need to rescan (via dp_rescan_dir()) the *source* directory after the copy
**		is complete, which of course boosts performance. Neat.
** 1998-09-06 -	Now only rescans the dest if some file(s) were actually selected to begin with.
** 1998-09-17 -	Added overwrite protection.
*/
int cmd_copy(MainInfo *min, DirPane *src, DirPane *dst, CmdArg *ca)
{
	gchar	old_path[PATH_MAX], dest[PATH_MAX];
	guint	num = 0;
	mode_t	mode;
	OvwRes	res;
	GSList	*slist, *iter;

	err_clear(min);

	if((src == NULL) || (dst == NULL))
		return 1;
	if(!fut_cd(src->dir.path, old_path, sizeof old_path))
		return 0;

	if((slist = dp_get_selection(src)) == NULL)
		return 1;

	ovw_overwrite_begin(min, "\"%s\" exists - proceed with copy?", 0UL);
	pgs_progress_begin(min, "Copy", PFLG_COUNT_RECURSIVE | PFLG_ITEM_VISIBLE | PFLG_BYTE_VISIBLE);
	for(iter = slist; !errno && (iter != NULL); iter = g_slist_next(iter))
	{
		sprintf(dest, "%s/%s", dst->dir.path, DP_SEL_NAME(iter));
		mode = DP_SEL_LSTAT(iter).st_mode;
		res = ovw_overwrite_file(min, dest, dp_full_name(src, DP_SEL_INDEX(src, iter)));
		if(res == OVW_SKIP)
			continue;
		else if(res == OVW_CANCEL)
			break;
		if(S_ISLNK(mode))
			copy_link(min, DP_SEL_NAME(iter), dest);
		else if(S_ISDIR(mode))
			copy_dir(min, DP_SEL_NAME(iter), dest);
		else if(S_ISREG(mode))
			copy_file(min, DP_SEL_NAME(iter), dest, NULL);
		else if(S_ISCHR(mode) || S_ISBLK(mode))
			copy_device(min, DP_SEL_NAME(iter), dest, NULL);
		if(errno == 0)
			dp_unselect(src, DP_SEL_INDEX(src, iter));
		num++;
	}
	if(num)
		dp_rescan_dir(dst);
	pgs_progress_end(min);
	ovw_overwrite_end(min);
	dp_free_selection(slist);

	fut_cd(old_path, NULL, 0);

	err_show(min);
	return errno == 0;
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-04-06 -	Configuration initialization. */
void cfg_copy(MainInfo *min)
{
	if(copy_cmc == NULL)
	{
		/* Set the default values for module's options. */
		copy_options.modified	= FALSE;
		copy_options.copy_dates	= TRUE;
		copy_options.leave_fullsize = TRUE;
		copy_options.buf_size	= (1 << 18);

		copy_cmc = cmc_config_new("Copy", &copy_options);
		cmc_field_add_boolean(copy_cmc, "modified", NULL, offsetof(OptCopy, modified));
		cmc_field_add_boolean(copy_cmc, "copy_dates", "Preserve Dates During Copy?", offsetof(OptCopy, copy_dates));
		cmc_field_add_boolean(copy_cmc, "leave_fullsize", "Leave Failed Destination if Full Size?", offsetof(OptCopy, leave_fullsize));
		cmc_field_add_size(copy_cmc, "buf_size", "Buffer Size", offsetof(OptCopy, buf_size), 1024, (1<<20), 1024);
		cmc_config_register(copy_cmc);
	}
}
