/*
 *	Copy.c - Copyright © 1991 by S.R. & P.C.
 *
 *	Created:	02 Mar 1991  15:50:36
 *	Modified:	20 Jul 1991  12:25:40
 *
 *	Make>> make
 */

#include "Global.h"
#include "FileList.h"
#include "Actions.h"
#include "ActionBack.h"
#include "Process.h"
#include "proto/Copy.h"
#include "proto/Actions.h"
#include "proto/ActionBack.h"
#include "proto/Request.h"
#include "proto/File.h"
#include "proto/FileList.h"


extern struct ExecBase *SysBase;
extern char *ReqTitle;


#define BUFFSIZEUNIT			11528
#define MAXBUFFER_MOUNTED		200000
#define MAXBUFFER_UNMOUNTED		902000


/*
 *	Compute how many buffers max will be used for this copy. If destination volume
 *	and one of sources volumes are mounted, MAXBUFFER_MOUNTED memory will be used.
 *	Else, MAXBUFFER_UNMOUNTED which is bigger will be used to minimize disk swapping.
 */

static void InitMaxBuf(struct HeadFileList *hfl)
{
	struct BrowserDir *bd, *nextbd;
	struct InfoData *info;
	struct Process *pp;
	APTR WindowPtr;

	if (!(info = AllocMem(sizeof(struct InfoData), MEMF_PUBLIC|MEMF_CLEAR)))
		return;
	pp = (struct Process *)SysBase->ThisTask;
	WindowPtr = pp->pr_WindowPtr;
	pp->pr_WindowPtr = (APTR)-1L;
	hfl->MaxBuffer = MAXBUFFER_UNMOUNTED;
	/* Info() needs volume referenced by the lock to be mounted. If Info() fails, volume is not mounted */
	if (Info(hfl->DestDir, info)) {
		bd = (struct BrowserDir *)hfl->DirList.mlh_Head;
		while(nextbd = (struct BrowserDir *)bd->Node.mln_Succ) {
			if (Info(bd->DirLock, info)) {
				hfl->MaxBuffer = MAXBUFFER_MOUNTED;
				break;
			}
			bd = nextbd;
		}
	}
	pp->pr_WindowPtr = WindowPtr;
	FreeMem(info, sizeof(struct InfoData));
}


static BOOL Mem(struct HeadFileList *hfl, long AskSize)
{
	long AfterUse;
	long Avail;

	if (hfl->ActualUse + AskSize > hfl->MaxBuffer)
		return FALSE;
	Avail = AvailMem(MEMF_PUBLIC);
	/* Never use more than 80% of initial available mem, and allways leave at least 15 KB free */
	AfterUse = hfl->StartMem - Avail - AskSize;
	if (Avail < BUFFSIZEUNIT+5000 || hfl->StartMem * 8 < AfterUse * 10)
		return FALSE;
	return TRUE;
}


/* Copy Quick handling routine. Skip file if older than existing one. If return TRUE, file must be copied */

static BOOL CheckDest(BPTR DestDir, struct SuperFileInfo *sfi)
{
	struct FileInfoBlock *fib;
	BPTR CD;
	BOOL ret = TRUE;

	if (!(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC|MEMF_CLEAR)))
		return FALSE;
	CD = CurrentDir(DestDir);
	if (GetFib(sfi->FileInfo.fi_Name, fib, FALSE) == A_RETRY) {
		if (Date2Secs(&fib->fib_Date) >= sfi->FileInfo.fi_Secs) {
		 	sfi->Flags |= SFI_REMOVE|SFI_NOSELECT;
		 	ret = FALSE;
		}
	}
	CurrentDir(CD);
	FreeMem(fib, sizeof(struct FileInfoBlock));
	return ret;
}


/* Obtain lock on destination directory if exists */

static short GetDestDir(BPTR DestDir, struct SuperFileInfo *sfi)
{
	struct FileInfoBlock *fib;
	BPTR CD, DirLock;
	char *Name;
	short Ok = A_RETRY;

	if (!(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC|MEMF_CLEAR)))
		return A_STOP;
	CD = CurrentDir(DestDir);
	Name = sfi->FileInfo.fi_Name;
	while(Ok == A_RETRY && (DirLock = Lock(Name, ACCESS_READ))) {
		while (Ok == A_RETRY && !Examine(DirLock, fib))
			Ok = ThreeGadRequest("Retry", "Skip", "Couldn't get info for \"%s\"\n%s.", Name, StrIoErr());
		if (Ok == A_RETRY) {
			if (fib->fib_DirEntryType > 0) {
				Fib2Fi(&sfi->FileInfo, fib);	/* get info from dest dir for action back */
				break;
			}				
			else {
				UnLock(DirLock);
				Ok = ThreeGadRequest("Retry", "Skip", "Can't create dir \"%s\"\nA file already exists.", Name);
				if (Ok != A_RETRY) {
					sfi->Flags |= SFI_REMOVE;
					DirLock = NULL;
				}
			}
		}
	}
	FreeMem(fib, sizeof(struct FileInfoBlock));
	sfi->FileDir.Dir.DestDir = DirLock;
	CurrentDir(CD);
	return Ok;
}


static short HandleCopyFlags(struct SuperFileInfo *sfi, struct FileInfoBlock *fib, UBYTE CopyFlags)
{
	char *Name;
	short Ok = A_RETRY;

	Name = sfi->FileInfo.fi_Name;
	if (CopyFlags & (CF_CLONE|CF_DATE))
		Ok = Touch(Name, &sfi->FileInfo.fi_Date);
	if (Ok == A_RETRY && CopyFlags & (CF_CLONE|CF_PROTECT))
		Ok = Protect(Name, sfi->FileInfo.fi_Protection & ~FIBF_ARCHIVE);
	if (Ok == A_RETRY && CopyFlags & (CF_CLONE|CF_COMMENT))
		Ok = Comment(Name, sfi->FileInfo.fi_Comment);
	if (Ok == A_RETRY && (Ok = GetFib(Name, fib, TRUE) == A_RETRY))
		Fib2Fi(&sfi->FileInfo, fib);
	if (Ok == A_RETRY)
		sfi->ActionBack |= AB_NEW_ENTRY;
	return Ok;
}


/* Try to obtain destination directory. If doesn't exists, create it */

static short MakeDestDir(struct SuperFileInfo *sfi, struct FileInfoBlock *fib, UBYTE CopyFlags, UBYTE CopyMode)
{
	BPTR CurrentDir, DirLock;
	char *Name;
	short Ok = A_RETRY;

	if (sfi->FileDir.Dir.DestDir)
		return A_RETRY;
	CurrentDir = ((struct Process *)SysBase->ThisTask)->pr_CurrentDir;
	if (sfi->FileInfo.fi_Type == DLX_DIR && (CopyMode & CM_COPY_HIERARCHY)) {
		if ((Ok = GetDestDir(CurrentDir, sfi)) == A_RETRY) { 
			if (!sfi->FileDir.Dir.DestDir) {
				/* dir doesn't exist so, create it */
				Name = sfi->FileInfo.fi_Name;
				while(Ok == A_RETRY && !(DirLock = CreateDir(Name)))
					Ok = ThreeGadRequest("Retry", "Skip", "Couldn't create dir \"%s\"\n%s.", Name, StrIoErr());
				if (Ok == A_RETRY) {
					/* replace the ACCESS_WRITE lock given by CreateDir() by an ACCESS_READ lock so people can access it */
					UnLock(DirLock);
					DirLock = NULL;
					Ok = HandleCopyFlags(sfi, fib, CopyFlags);
					while(Ok == A_RETRY && !(DirLock = Lock(Name, ACCESS_READ)))
						Ok = ThreeGadRequest("Retry", "Skip", "Couldn't access \"%s\"\n%s.", Name, StrIoErr());
				}
				sfi->FileDir.Dir.DestDir = DirLock;
			}
		}
	}
	else /* volumes, devices, assigns, or don't copy hierarchy */
		sfi->FileDir.Dir.DestDir = DupLock(CurrentDir);
	return Ok;
}


static short PurgeBuffer(struct SuperFileInfo *sfi)
{
	struct BufferList *Cur;
	char *Buffer;
	long ToBeWritten;
	short Ok = A_RETRY;

	while(Ok == A_RETRY && (Cur = sfi->FileDir.File.BufferList)) {
		sfi->FileDir.File.BufferList = Cur->Next;
		ToBeWritten = Cur->Size;
		Buffer = Cur->Memory;
		while(Ok == A_RETRY && (ToBeWritten -= Write(sfi->FileDir.File.FH_D, Buffer, ToBeWritten)) != 0) {
			Buffer = Cur->Memory + Cur->Size - ToBeWritten;
			Ok = ThreeGadRequest("Retry", "Skip", "Error writing \"%s\"\n%s.", sfi->FileInfo.fi_Name, StrIoErr());
		}
		FreeMem(Cur, Cur->Size+sizeof(struct BufferList));
	}
	return Ok;
}


static short WriteFile(struct SuperFileInfo *sfi, struct FileInfoBlock *fib, UBYTE CopyFlags, UBYTE CopyMode)
{
	BPTR FileLock;
	char *Name;
	short Ok = A_RETRY;

	Name = sfi->FileInfo.fi_Name;
	if ((sfi->ActionBack & AB_DELETE_DEST) && sfi->FileDir.File.FH_D) {
		Close(sfi->FileDir.File.FH_D);
		sfi->FileDir.File.FH_D = NULL;
		DeleteDest(Name);
	}
	if (sfi->FileDir.File.BufferList) {
		if (!sfi->FileDir.File.FH_D) {
			if ((CopyMode & (CM_ASK_OVERWRITE|CM_DONT_OVERWRITE)) && (FileLock = Lock(Name, ACCESS_READ))) {
				UnLock(FileLock);
				if (CopyMode & CM_DONT_OVERWRITE) {
					Ok = A_SKIP;
					sfi->Flags |= SFI_REMOVE|SFI_NOSELECT;
				}
				else if (!TwoGadRequest(ReqTitle, "File \"%s\" already exists\nOverwrite it ?", Name))
					Ok = A_SKIP;
			}
			while (Ok == A_RETRY && !(sfi->FileDir.File.FH_D = Open(Name, MODE_NEWFILE)))
				Ok = ThreeGadRequest("Retry", "Skip", "Couldn't open \"%s\" for writing\n%s.", Name, StrIoErr());
		}
		if (Ok != A_RETRY)
			sfi->Flags |= SFI_REMOVE;
		else if ((Ok = PurgeBuffer(sfi)) != A_RETRY) {
			sfi->FileDir.File.ToBeRead = 0;	/* something wrong happened, so don't continue reading ! */
			Close(sfi->FileDir.File.FH_D);
			sfi->FileDir.File.FH_D = NULL;
			sfi->Flags |= SFI_REMOVE;
			if (TwoGadRequest(ReqTitle, "File \"%s\" couldn't be copied\nDelete bad copy ?", Name))
				DeleteDest(Name);
		}
		else if (sfi->Flags & SFI_READ_FINISHED) {
			Close(sfi->FileDir.File.FH_D);	/* close if nothing else must be read */
			sfi->FileDir.File.FH_D = NULL;
			Ok = HandleCopyFlags(sfi, fib, CopyFlags);
		}
		FreeFileBuffers(sfi);
	}
	return Ok;
}


static short WriteDir(struct HeadFileList *hfl, struct SuperFileInfo *StartSFI, struct FileInfoBlock *fib);

static short WriteEntry(struct HeadFileList *hfl, struct SuperFileInfo *sfi, struct FileInfoBlock *fib)
{
	short Ok = A_RETRY;
	BPTR CD;

	if ((sfi->FileInfo.fi_Type != DLX_FILE) && (sfi->Flags & (SFI_READING|SFI_READ_FINISHED))) {
		if ((Ok = MakeDestDir(sfi, fib, hfl->CopyFlags, hfl->CopyMode)) == A_RETRY) {
			CD = CurrentDir(sfi->FileDir.Dir.DestDir);
			Ok = WriteDir(hfl, sfi, fib);
			CurrentDir(CD);
		}
		else
			sfi->Flags |= SFI_REMOVE;
	}
	else
		Ok = WriteFile(sfi, fib, hfl->CopyFlags, hfl->CopyMode);
	return Ok;
}


static short WriteDir(struct HeadFileList *hfl, struct SuperFileInfo *StartSFI, struct FileInfoBlock *fib)
{
	struct SuperFileInfo *sfi, *nextsfi;
	short Ok = A_RETRY;

	sfi = (struct SuperFileInfo *)StartSFI->FileDir.Dir.SuperFileList.mlh_Head;
	while(Ok != A_STOP && (nextsfi = (struct SuperFileInfo *)sfi->Node.mln_Succ)) {
		Ok = WriteEntry(hfl, sfi, fib);
		if (sfi->Flags & (SFI_READ_FINISHED|SFI_REMOVE)) {
			if (sfi->Flags & SFI_REMOVE && !(sfi->Flags & SFI_NOSELECT))
				sfi->ActionBack = (sfi->ActionBack & ~AB_DESELECT) | AB_SELECT;
			SendActionBack(sfi, StartSFI->FileDir.Dir.SrcDir, StartSFI->FileDir.Dir.DestDir);
			if (!(sfi->Flags & SFI_READING)) {
				Remove((struct Node *)sfi);
				FreeSuperFileInfo(sfi, NULL, NULL);
			}
		}
		if (Ok != A_RETRY)
			hfl->CopySuccessfull = FALSE;
		sfi = nextsfi;
	}
	if ((StartSFI->Flags & SFI_READ_FINISHED) && (hfl->CopyMode & CM_COPY_HIERARCHY))
		SendUpdateDir(StartSFI->FileDir.Dir.DestDir);
	return Ok;
}


static short WriteFileList(struct HeadFileList *hfl)
{
	struct FileInfoBlock *fib;
	struct SuperFileInfo *sfi, *nextsfi;
	struct BrowserDir *bd;
	short Ok = A_RETRY;
	BPTR CD;
	BOOL Duplicate;

	/* if hfl->NewName isn't empty, the action is Duplicate, else Copy */
	Duplicate = (hfl->ActionArgs.NewName[0]) ? TRUE : FALSE;
	/* Allocate a fib for WriteFile() to examine dest file */
	if (!(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC|MEMF_CLEAR)))
		return A_STOP;
	CD = CurrentDir(hfl->DestDir);
	bd = (struct BrowserDir *)hfl->DirList.mlh_Head;
	while(Ok != A_STOP && bd->Node.mln_Succ) {
		if (Duplicate && bd->DirLock)
			CurrentDir(bd->DirLock);
		sfi = (struct SuperFileInfo *)bd->SuperFileList.mlh_Head;
		while(Ok != A_STOP && (nextsfi = (struct SuperFileInfo *)sfi->Node.mln_Succ)) {
			if ((Ok = WriteEntry(hfl, sfi, fib)) != A_RETRY)
				hfl->CopySuccessfull = FALSE;
			if ((sfi->Flags & SFI_REMOVE) || (sfi->Flags & SFI_READ_FINISHED) && !(hfl->CopyMode & CM_ALLWAYS_MOVE)) {
				if (sfi->Flags & SFI_REMOVE && !(sfi->Flags & SFI_NOSELECT))
					sfi->ActionBack = (sfi->ActionBack & ~AB_DESELECT) | AB_SELECT;
				SendActionBack(sfi, bd->DirLock, (Duplicate) ? bd->DirLock : hfl->DestDir);
			}
			if ((sfi->Flags & SFI_REMOVE) && !(sfi->Flags & SFI_READING)) {
				Remove((struct Node *)sfi);
				FreeSuperFileInfo(sfi, NULL, NULL);
			}
			sfi = nextsfi;
		}
		if (Duplicate)
			SendUpdateDir(bd->DirLock);
		bd = (struct BrowserDir *)bd->Node.mln_Succ;
	}
	FreeMem(fib, sizeof(struct FileInfoBlock));
	CurrentDir(CD);
	/* ok, memory released now */
	hfl->StartMem = AvailMem(MEMF_PUBLIC);	/* reset the base mem */
	hfl->ActualUse = 0;
	return Ok;
}


static short Read2Buffer(struct HeadFileList *hfl, struct SuperFileInfo *sfi, struct BufferList **bl)
{
	char *Buffer;
	long ToBeRead, size;
	short Ok = A_RETRY;

	size = (sfi->FileDir.File.ToBeRead > BUFFSIZEUNIT) ? BUFFSIZEUNIT : sfi->FileDir.File.ToBeRead;
	if (!Mem(hfl, size+sizeof(struct BufferList)))
		return -1;	/* Mem() say there is not enough memory */
	if (!(*bl = AllocMem(size+sizeof(struct BufferList), MEMF_PUBLIC)))
		return -1;	/* Can't alloc buffer, so return a mem error */
	ToBeRead = (*bl)->Size = size;
	(*bl)->Next = NULL;
	Buffer = (*bl)->Memory;
	while(Ok == A_RETRY && (ToBeRead -= Read(sfi->FileDir.File.FH_S, Buffer, ToBeRead)) != 0) {
		Buffer = (*bl)->Memory + size - ToBeRead;
		Ok = ThreeGadRequest("Retry", "Skip", "Error reading \"%s\"\n%s.", sfi->FileInfo.fi_Name, StrIoErr());
	}
	sfi->FileDir.File.ToBeRead -= size;
	hfl->ActualUse += size+sizeof(struct BufferList);
	return Ok;
}


static short CopyFile(struct HeadFileList *hfl, struct SuperFileInfo *sfi)
{
	struct BufferList **CBL = NULL;
	char *Name;
	short Ok = A_RETRY;

	Name = sfi->OldName;
	while (Ok == A_RETRY && !(sfi->FileDir.File.FH_S = Open(Name, MODE_OLDFILE)))
		Ok = ThreeGadRequest("Retry", "Skip", "Couldn't open \"%s\" for reading\n%s.", Name, StrIoErr());
	if (Ok == A_RETRY) {
		sfi->Flags |= SFI_READING;
		sfi->FileDir.File.ToBeRead = sfi->FileInfo.fi_Size;
		CBL = &sfi->FileDir.File.BufferList;
		while(Ok == A_RETRY && sfi->FileDir.File.ToBeRead) {
			switch(Ok = Read2Buffer(hfl, sfi, CBL)) {
			case -1:
				if (hfl->ActualUse == 0)
					Ok = A_STOP;	/* Not enough memory for Copy */
				else
					Ok = WriteFileList(hfl);
				CBL = &(sfi->FileDir.File.BufferList);	/* reset the mem ptr: return to beginning */
				break;
			case A_RETRY:
				CBL = &(*CBL)->Next;
				break;
			default:	/* Skip or Cancel */
				sfi->ActionBack = AB_SELECT|AB_DELETE_DEST;
				FreeFileBuffers(sfi);		/* free memory used by this file */
				/* now recompute ActualUse. This not really valid since number_of_buffer * sizeof(struct BufferList) is lost,
				 * but as we don't know the number of buffers, it better than loosing everything! */
				hfl->ActualUse -= sfi->FileInfo.fi_Size - sfi->FileDir.File.ToBeRead;
				hfl->CopySuccessfull = FALSE;
			}
		}
		Close(sfi->FileDir.File.FH_S);
		sfi->FileDir.File.FH_S = NULL;
		sfi->Flags = (sfi->Flags & ~SFI_READING) | SFI_READ_FINISHED;
	}
	else
		sfi->Flags |= SFI_REMOVE;
	return Ok;
}


static short CopyDir(struct HeadFileList *hfl, struct SuperFileInfo *StartSFI, struct FileInfoBlock *StartFib)
{
	struct SuperFileInfo *sfi;
	struct FileInfoBlock *fib;
	BPTR CD, DirLock;
	char *Name;
	BOOL NoCheck = FALSE;	/* Copy Quick handling. If dest dir don't exists yet, no use to check for presence of a file in it! */
	short err, Ok = A_RETRY;

	if (!(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC|MEMF_CLEAR)))
		return A_STOP;
	if ((hfl->CopyMode & CM_UPDATE) && !StartSFI->FileDir.Dir.DestDir)
		NoCheck = TRUE;
	Name = StartSFI->OldName;
	while(Ok == A_RETRY && !(DirLock = Lock(Name, ACCESS_READ)))
		Ok = ThreeGadRequest("Retry", "Skip", "Couldn't access \"%s\"\n%s.", Name, StrIoErr());
	if (Ok != A_RETRY) {
		StartSFI->Flags |= SFI_REMOVE;
		StartSFI->ActionBack = AB_SELECT;
	}
	else {
		StartSFI->FileDir.Dir.SrcDir = DirLock;
		CD = CurrentDir(DirLock);
		NewMinList(&StartSFI->FileDir.Dir.SuperFileList);
		*fib = *StartFib;
		StartSFI->Flags |= SFI_READING|SFI_DIREMPTY;
		while (Ok != A_STOP && ExNext(DirLock, fib)) {
			if (sfi = AllocMem(sizeof(struct SuperFileInfo), MEMF_PUBLIC|MEMF_CLEAR)) {
				Ok = A_RETRY;
				Fib2Fi(&sfi->FileInfo, fib);
				if (!(hfl->Select.si_Flags & SI_AFFECT_SUBDIRS) || MatchFilters(&sfi->FileInfo, &hfl->Select)) {
					/* link file to current dir filelist */
					AddTail((struct List *)&StartSFI->FileDir.Dir.SuperFileList, (struct Node *)sfi);
					strcpy(sfi->OldName, sfi->FileInfo.fi_Name);
					if (sfi->FileInfo.fi_Type == DLX_FILE) {
						if (!(hfl->CopyMode & CM_UPDATE) || NoCheck || CheckDest(StartSFI->FileDir.Dir.DestDir, sfi))
							Ok = CopyFile(hfl, sfi);
					}
					else {
						if (hfl->CopyMode & CM_UPDATE)
							Ok = GetDestDir(StartSFI->FileDir.Dir.DestDir, sfi);
						if (Ok == A_RETRY && !(sfi->Flags & SFI_REMOVE))
							Ok = CopyDir(hfl, sfi, fib);
					}
					if (Ok == A_RETRY && !(sfi->Flags & SFI_DIREMPTY))
						StartSFI->Flags &= ~SFI_DIREMPTY;
					if (sfi->Flags & SFI_REMOVE) {
						/* no dest dir for action back here, file wasn't written */
						if (!(sfi->Flags & SFI_NOSELECT))
							sfi->ActionBack = (sfi->ActionBack & ~AB_DESELECT) | AB_SELECT;
						Remove((struct Node *)sfi);
						FreeSuperFileInfo(sfi, DirLock, NULL);
					}
				}
				else {
					CleanFileInfo(&sfi->FileInfo);
					FreeMem(sfi, sizeof(struct SuperFileInfo));
				}
			}
			else
				Ok = A_STOP;
		}
		if (Ok == A_RETRY && (err = IoErr()) != ERROR_NO_MORE_ENTRIES)
			Ok = ThreeGadRequest("Skip", NULL, "Error reading directory \"%s\"\n%s.", Name, DosError(err)) << 1;
		if (!(hfl->CopyMode & CM_COPY_EMPTYDIRS) && (StartSFI->Flags & SFI_DIREMPTY))
			StartSFI->Flags |= SFI_REMOVE;
		CurrentDir(CD);
	}
	if (Ok != A_RETRY)
		hfl->CopySuccessfull = FALSE;
	StartSFI->Flags = (StartSFI->Flags & ~SFI_READING) | SFI_READ_FINISHED;
	FreeMem(fib, sizeof(struct FileInfoBlock));
	return Ok;
}


static short MoveFile(struct SuperFileInfo *sfi, BPTR DestDir)
{
	short Ok = A_RETRY;
	char *OldName, *NewName;
	char Buffer[300];

	OldName = sfi->FileInfo.fi_Name;
	PathName(DestDir, Buffer, 255);
	/* remove volume name from path to avoid disk confusion if two volumes have the same name */
	NewName = Buffer;
	while(*NewName && *NewName != ':') NewName++;
	TackOn(NewName, OldName);
	while(Ok == A_RETRY && !Rename(OldName, NewName))
		Ok = ThreeGadRequest("Retry", "Skip", "Couldn't move \"%s\"\n%s.", OldName, StrIoErr());
	if (Ok == A_RETRY)
		sfi->ActionBack |= (AB_DELETE_SOURCE|AB_NEW_ENTRY);
	else
		sfi->ActionBack = AB_SELECT;
	return Ok;
}


void CopyMove(struct HeadFileList *hfl)
{
	struct FileInfoBlock *fib;
	struct SuperFileInfo *sfi, *nextsfi;
	struct BrowserDir *bd, *nextbd;
	BOOL Duplicate;
	short Ok = A_RETRY;

	if (!(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC|MEMF_CLEAR)))
		return;
	Duplicate = (hfl->ActionArgs.NewName[0]) ? TRUE : FALSE;
	/* init some global var for test mem use */
	hfl->StartMem = AvailMem(MEMF_PUBLIC);	/* init base mem */
	hfl->ActualUse = 0;
	hfl->CopySuccessfull = TRUE;
	InitMaxBuf(hfl);

	bd = (struct BrowserDir *)hfl->DirList.mlh_Head;
	while(Ok != A_STOP && (nextbd = (struct BrowserDir *)bd->Node.mln_Succ)) {
		if (bd->DirLock)
			CurrentDir(bd->DirLock);
		sfi = (struct SuperFileInfo *)bd->SuperFileList.mlh_Head;
		if (!(hfl->CopyMode & CM_ALLWAYS_COPY) && bd->RootLock && !CompareLock(bd->RootLock, hfl->DestRoot)) {
			while(Ok != A_STOP && (nextsfi = (struct SuperFileInfo *)sfi->Node.mln_Succ)) {
				Ok = MoveFile(sfi, hfl->DestDir);
				SendActionBack(sfi, bd->DirLock, hfl->DestDir);
				sfi = nextsfi;
			}
			Remove((struct Node *)bd);
			FreeBrowserDir(bd, NULL);
		}
		else {
			while(Ok != A_STOP && (nextsfi = (struct SuperFileInfo *)sfi->Node.mln_Succ)) {
				if ((Ok = GetFib(sfi->FileInfo.fi_Name, fib, TRUE)) == A_RETRY) {
					/* Don't Fib2Fi() for a device, volume or assign. It removes
					 * ending colon from name */
					if (sfi->FileInfo.fi_Type == DLX_FILE || sfi->FileInfo.fi_Type == DLX_DIR)
						Fib2Fi(&sfi->FileInfo, fib);
				}
				if (Ok == A_RETRY && Duplicate)
					Ok = MakeNewName(sfi->FileInfo.fi_Name, hfl->ActionArgs.NewName, sfi->OldName);
				if (Ok == A_RETRY) {
					switch(sfi->FileInfo.fi_Type) {
					case DLX_FILE:
						if (!(hfl->CopyMode & CM_UPDATE) || CheckDest((Duplicate) ? bd->DirLock : hfl->DestDir, sfi))
							Ok = CopyFile(hfl, sfi);
						break;
					case DLX_DIR:
						if (hfl->CopyMode & CM_UPDATE)
							Ok = GetDestDir(hfl->DestDir, sfi);
						if (Ok == A_RETRY && !(sfi->Flags & SFI_REMOVE))
							Ok = CopyDir(hfl, sfi, fib);
						break;
					default:		/* Volume, Device, or Assign */
						Ok = CopyDir(hfl, sfi, fib);
					}
				}
				if (sfi->Flags & SFI_REMOVE) {
					/* no dest dir for action back here, file wasn't written */
					if (!(sfi->Flags & SFI_NOSELECT))
						sfi->ActionBack = (sfi->ActionBack & ~AB_DESELECT) | AB_SELECT;
					Remove((struct Node *)sfi);
					FreeSuperFileInfo(sfi, bd->DirLock, NULL);
				}
				sfi = nextsfi;
			}
		}
		bd = nextbd;
	}
	FreeMem(fib, sizeof(struct FileInfoBlock));
	if (Ok != A_STOP)
		Ok = WriteFileList(hfl);
	if (Ok != A_STOP && (hfl->CopyMode & CM_ALLWAYS_MOVE)) {
		if (!hfl->CopySuccessfull)
			Ok = TwoGadRequest(ReqTitle, "One or more file(s) not copied\nDelete source(s) anyway ?");
		if (Ok != A_STOP)
			DoAction(hfl, BROWSERACTION_DELETE);
	}
	CurrentDir(((struct TaskData *)SysBase->ThisTask->tc_UserData)->td_InitialDir);
}


