/* MRBackup: 	Big File Handler 
 * Date:		12/26/87
 * Description:
 *
 *		This package contains routines which support the backup and 
 * restoration of files which are larger than the capacity of the output 
 * media.
 *
 * Design philosophy:
 *
 *		When a file is encountered whose total size exceeds the capacity
 * of the backup media, MRBackup will attempt to split the file into 
 * pieces, spanning several diskettes, if necessary.  Prior to doing this,
 * a special information file is written (name == BIGINFONAME).  This file
 * contains the following information, written as newline-terminated ASCII
 * strings:
 *
 *		file name:	1..255 characters
 *		file size:	1..NNNNNNNN
 *      sequence:   1..NNN
 *		more:		Y or N (Y => another piece follows this one)
 *
 *		When files are restored, their names are checked agains the
 * "big file" name (if any).  If the big file is encountered, RestoreBigFile
 * is called to restore it.  RestoreBigFile will request each diskette, in
 * sequence, until the full file is restored or an error occurs.
 *
 *		The user must have enabled the "Allow Big Files" and "Format
 * Destination" program options in order for multi-volume backups to occur.
 *
 * History:		(most recent change first)
 *
 */

#include "MRBackup.h"

/* 
 * Copy a "big" file, preserving date and writing the big file information.  
 * Depends upon file date routines in FileMisc.c 
 */

#include <stdio.h>
#include <exec/types.h>
#include <libraries/dos.h>
#include <exec/memory.h>
#include <functions.h>
#include ":src/lib/DiskMisc.h"

#define BIGINFONAME "MRBackup.bigfile"
#define MAXSTR 127
 
extern char fullBackPath[], fullHomePath[];
extern BOOL homeIsDevice;

extern long Chk_Abort();
extern BOOL CopyFileDate();
extern BOOL GetFileDate(), SetFileDate();
extern long IoErr();

struct {
	char 	fileName[PATH_MAX+1];
	ULONG 	size;						/* file size in bytes */
	USHORT 	seqNbr;						/* sequence number (1..n) */
	char	more[4]; 					/* last piece of file? */
	} bigFileInfo;

/* Function:
 *		BackupBigFile
 *
 * Description:
 * 		This routine splits a very large file across a sequence of diskettes.
 * 		On each of these disks, a special information file is written which
 *		specifies the name of the file, its size and its sequence number.
 *
 * Called with:
 *		fileName:	file name (less volume) to be copied
 *
 * Returns:
 *		status, where 0 => successful backup
 */

int     
BackupBigFile(fileName)
	char *fileName;
{
	long count, startCount;
	BOOL done = false;
	char *errMsg = NULL;
	struct FileHandle *fin = NULL, *fout = NULL;
	char from[PATH_MAX+1], to[PATH_MAX+1];
	struct InfoData *info;
	struct FileLock *lock = NULL;
	BOOL needNewDisk;
	long status = 0;

	/* First, check to see if this disk currently has a "big file" on it.
	 * Since we can only record 1 of these per disk, we need to know this.
	 */
	status = GetBigFileInfo(destVol);
	if (status == 0)
		needNewDisk = false;
	else if (status == 1)
		needNewDisk = true;
	else if (status == -1) {
		errMsg = "Error while attempting to get big file information.\n";
		status = ERR_ABORT;
	}
	setmem(&bigFileInfo, sizeof(bigFileInfo), 0);
	strcat(bigFileInfo.fileName, fileName);

	strcpy(from, srcVol);
	strcat(from, fileName);

	if (! (fin = Open(from, MODE_OLDFILE) ) ) {
		status = IoErr();
		goto endit;
	}
	strcpy(bigFileInfo.fileName, fileName);
	startCount = 0;

	while (! (done | status) ) {
		if (needNewDisk)
			if (status = NewDisk(true)) break;

		/* Start new segment by incrementing sequence number. */

		++bigFileInfo.seqNbr;
		bigFileInfo.size = 0;

		/* Write dummy info file.  This assures us that we will have a
		 * block reserved for the big file information file.  Since the
		 * information kept is small, 1 block is all we will need.
		 */

		if (status = PutBigFileInfo(destVol)) continue;
		strcpy(to, destVol);
		strcat(to, fileName);

		if ( !(fout = Open(to, MODE_NEWFILE)) ) {
			status = IoErr();
			sprintf(conmsg,"Failed to open %s for output; status = %ld\n",
					to, status);
			errMsg = conmsg;
			goto endit;
		}

		/* Position to the correct offset in the source file. */

		if (Seek(fin, startCount, OFFSET_BEGINNING) == -1L) {
			status = IoErr();
			errMsg = "BackupBigFile: Seek failed!\n";
			goto endit;
		}

		/* Read blocks of data from the input file and write them to the
		 * output file.  One of the following events will terminate this 
		 * loop:
		 *
		 *		1. End of file on input (count < bufSize)
		 *		2. Input error
		 *		3. Disk full on output (status == ERROR_DISK_FULL)
		 *		4. Other disk write error (non-recoverable).
		 *
		 */

		while ( ! (status || done) ) {
			if (status = CheckStop()) break;
			count = Read( fin, buffer, bufSize );
			if (count < bufSize) done = true;
			if ( count && (Write(fout, buffer, count) != count)) {
				status = IoErr();
				if (status == ERROR_DISK_FULL) {
					status = 0;
				}
				break;
			}
		}

		if (status) break;

		Close(fout);
		fout = NULL;

		if (status = CopyFileDate(from, to)) {
			errMsg = "Failed to set file date!\n";
			break;
		}

		if (! (lock = (struct FileLock *) Lock(to, ACCESS_READ))) {
			sprintf(conmsg,"Could not lock %s\n", to);
			errMsg = conmsg;
			break;
		}
		if (!Examine(lock, fibWork)) {
			status = IoErr();
			sprintf(conmsg,"Could not examine %s\n", to);
			errMsg = conmsg;
			break;
		}
		UnLock(lock);
		lock = NULL;

		startCount += fibWork->fib_Size;
		bigFileInfo.size = fibWork->fib_Size;
		bigFileInfo.more[0] = (done ? 'N' : 'Y');
		if (status = PutBigFileInfo(destVol)) break;
		needNewDisk = !done;			/* Need another disk? */
	}
endit:
	if (lock) UnLock(lock);
	if (fin) Close(fin);
	if (fout) Close(fout);
	if (errMsg) TypeAndSpeak(errMsg);
	return (int) status;
}
^L
/* Function:
 *		GetBigFileInfo
 *
 * Called with:
 *		volume:	name of volume to retrive info from
 *
 * Returns:
 *	  > 0 => sequence number of big file on this disk
 *		0 => no big file info on disk
 *	  < 0 => had trouble reading big file info
 *
 * Description:
 * 		See if the current diskette has "big file" information.
 */
BOOL
GetBigFileInfo(volume)
	char *volume;
{
	char bigFileInfoName[PATH_MAX+1];
	char *errMsg = NULL;
	FILE *f;
	int status = 0;

	MakeBigInfoName(volume, bigFileInfoName);
	if (f = fopen(bigFileInfoName, "r")) {
		if (fgets(bigFileInfo.fileName, PATH_MAX+1, f) == NULL) {
			errMsg = "Couldn't get big file name!\n";
			status = -errno;
		}
		else if (fscanf(f, "%d%ld\n", 
					&bigFileInfo.seqNbr, &bigFileInfo.size) != 2) {
			errMsg = "Couldn't get big file size or sequence number!\n";
			status = -errno;
		}
		else if (fgets(bigFileInfo.more, 3, f) == NULL) {
			errMsg = "Couldn't get big file continuation flag!\n";
			status = -errno;
		}
		else {
			status = bigFileInfo.seqNbr;
			bigFileInfo.fileName[strlen(bigFileInfo.fileName)-1] = '\0';
		}
	}
	if (f) fclose(f);
	if (errMsg) TypeAndSpeak(errMsg);
	return status;
}
^L
/* Function:
 *		ThisIsBigFile
 *
 * Called with:
 *		name:		filename to test
 *
 * Returns:
 *		true  => filename is the name of a "big file"
 *		false => this filename is not that of a "big file"
 *
 * Description:
 *		The <name> is compared against the current big file 
 *		information, if any.  If <name> matches the big file name, 
 *		true is returned, otherwise false is returned.  This routine 
 *		provides a functional interface to the BigFiles package while 
 *		allowing us to hide low-level implementation details.
 */

BOOL
ThisIsBigFile(name)
	char *name;
{
	BOOL result = false;

	if (bigFileInfo.seqNbr && !strcmp(name, bigFileInfo.fileName))
		result = true;
	return result;
}

/* Function:
 *		MakeBigInfoName
 *
 * Called with:
 *		volume:		disk volume name where big file resides
 *		name:		result name (modified)
 *
 * Description:
 *		This routine simply catenates the destination <volume> name with
 *		the root name of the information file where data about the big
 *		file is kept, returning it in <name>.
 */

MakeBigInfoName(volume, name)
	char *volume, *name;
{
	strcpy(name, volume);
	strcat(name, BIGINFONAME);
}
^L
/* Function:
 *		PutBigFileInfo
 *
 * Called with:
 *		volume:		volume name of destination
 *
 * Returns:
 *		status, where 0 => OK
 *
 * Description:
 *		This routine writes a special information file to the destination
 *		disk.  This file describes 
 *			- the name of the file, 
 *			- the sequence number of the chunk that resides on the disk, 
 *			- the size of this chunk
 *			- a flag (Y/N) indicating if more chunks exist
 */
int
PutBigFileInfo(volume)
	char *volume;
{
	FILE *f;
	char bigFileInfoName[PATH_MAX+1];
	int status = 0;

	MakeBigInfoName(volume, bigFileInfoName);

	if (! (f = fopen(bigFileInfoName, "w")) ) {
		status = errno;
		sprintf(conmsg,
				"I couldn't create the big file information block: %s\n", 
				bigFileInfoName);
		TypeAndSpeak(conmsg);
	}
	else {
		fprintf(f, "%s\n%d\n%ld\n%s\n",
				bigFileInfo.fileName,
				bigFileInfo.seqNbr,
				bigFileInfo.size,
				bigFileInfo.more
				);
		fclose(f);
	}
	return status;
}
^L
/* Function:
 *		RestoreBigFile
 *
 * Returns:
 *		status, where 0 => OK
 *
 * Description:
 *		RestoreBigFile is called by Restore() AFTER all other files on the
 *		current diskette have been restored.  This function will reassemble
 *		a "big" file (size > backup media size) onto the home device, 
 *		prompting the user as necessary for subsequent disks.
 */

int
RestoreBigFile()
{
	int compare;					/* date comparison result */
	long count;
	char savedName[PATH_MAX+1];
	BOOL done = false, eof;
	long fileByteCount;
	struct FileHandle *fin, *fout;
	USHORT seqNbr;
	int status = 0;

	strcpy(savedName, bigFileInfo.fileName);

	if ( (seqNbr = bigFileInfo.seqNbr) != 1) {
		TypeAndSpeak(
		"Oops!  RestoreBigFile was called when it shouldn't have been.\n");
		return ERR_ABORT;
	}

	strcpy(fullHomePath, homePath);
	if (!homeIsDevice) strcat(fullHomePath, "/");
	strcat(fullHomePath, savedName);

	strcpy(fullBackPath, srcVol);
	strcat(fullBackPath, savedName);

	compare = CompareFileDates(fullHomePath, fullBackPath);
	if (compare >= 0) {
		sprintf(conmsg,"Skipping %s since home file is current.\n",
				savedName);
		WriteConsole(conmsg);
		return 0;
	}

	if (! (fout = Open(fullHomePath, MODE_NEWFILE))) {
		status = IoErr();
		sprintf(conmsg,"Can't open %s for output!\n", fullHomePath);
		TypeAndSpeak(conmsg);
		return status;
	}

	done = false;
	while (! (done || status) ) {
		sprintf(conmsg, " Restoring chunk %d of %s\n ",
				bigFileInfo.seqNbr, savedName);
		WriteConsole(conmsg);
		if (! (fin = Open(fullBackPath, MODE_OLDFILE))) {
			sprintf(conmsg, "Can't open %s for input!\n", fullBackPath);
			TypeAndSpeak(conmsg);
			break;
		}
		eof = false;
		fileByteCount = 0;
		while (! (eof || status) ) {
			count = Read(fin, buffer, bufSize);
			if (count == -1L) {
				status = IoErr();
				WriteConsole("--ERROR--\n");
			}
			else {
				fileByteCount += count;

	/* The next bit of hackery is necessary since AmigaDOS apparently 
	 * doesn't do a good job of handling file size information on files
	 * which experience DISK_FULL when being written.  An Examine gives
	 * the size as of the last successful Write, while reading to EOF
	 * yields the exact number of bytes that were successfully written.
	 * Thus, we must use the file size that we recorded in the big file
	 * information file.  If this situation gets corrected in KickStart
	 * 1.3 or later, this code should still work correctly.
	 */

				if (fileByteCount > bigFileInfo.size) {
					/* Ignore "excess" data. */
					count -= (fileByteCount - bigFileInfo.size);
				}
				WriteConsole(".");
				if (count < bufSize) {
					WriteConsole("--EOF--\n");
					sprintf(conmsg," %ld bytes in chunk %d\n",
							fileByteCount, seqNbr);
					WriteConsole(conmsg);
					eof = true;
					Close(fin);
					fin = NULL;
				}
				if (count && ( Write(fout, buffer, count) != count) )
					status = IoErr();
			}
		}
		if (status) break;

		if (toupper(bigFileInfo.more[0]) == 'Y') {
			++seqNbr;
			/* Request the next floppy disk. */
			while (bigFileInfo.seqNbr != seqNbr) {
				sprintf(conmsg, "I need the disk for chunk %d of %s\n",
						seqNbr, savedName);
				TypeAndSpeak(conmsg);
				if (!RequestDisk(
						mainWindow, srcVol, "Insert next disk in ")) {
					status = ERR_ABORT;
					break;
				}
				else {
					if ((status = GetBigFileInfo(srcVol)) == seqNbr &&
						ThisIsBigFile(savedName))
						break;
				}
			}
		}
		else
			done = true;
	}
	if (fout) Close(fout);
	if (fin) Close(fin);
	if (!status) CopyFileDate(fullBackPath, fullHomePath);
	return status;
}
