/* Filename:	restore.c
 * Author:		Mark R. Rinfret
 * Date:		08/02/87
 * Description:	Restore processing module for MRBackup
 *
 * History:		(most recent change first)
 *
 * 12/18/87 -MRR- Restore was using function IsCompressed() to determine
 *                candidates for decompression.  Unfortunately, it also
 *                tried to decompress .ARC and .ZOO files.
 *
 *				  Restore now requests the next disk to be restored from.
 *
 * 11/22/87 -MRR- Modifications for version 2.0.
 *
 * 09/19/87 -MRR- Added NewHomeDir() which creates subdirectories as
 *                necessary when the initial backup path specifies a
 *                subdirectory.
 */


#include "MRBackup.h"
#include ":src/lib/DiskMisc.h"

char fullBackPath[PATH_MAX+1], fullHomePath[PATH_MAX+1];
static BOOL bigFileSeqNbr;		/* >0 => big file sequence number */
BOOL homeIsDevice;		/* true => home is "DH<N>:" */

^L
/* Create a new directory on the home device.
 * Called with:
 *		name:		directory pathname
 * Returns:
 *		false => success
 *		true  => failure
 */
int
NewHomeDir(name)
	char   *name;
{
	char c;
	struct Lock *dirLock;
	int dirLeng;
	int errnum;
	char dirname[256];
	int nameindx = 0, nameleng;

	*dirname = '\0';
	dirLeng = 0;
	nameleng = strlen(name);

	/* Parse the pathname, one directory node at a time, creating
	 * directories as needed.
	 */

	while (nameindx < nameleng) {
		if (nameindx)				/* 2nd - nth pass? */
			dirname[dirLeng++] = '/'; /* directory separator */
		while ((c = name[nameindx++]) && c != '/')
			dirname[dirLeng++] = c;
		dirname[dirLeng] = '\0';	/* terminate with null */
		if (dirLock = (struct Lock *)
			Lock(dirname,SHARED_LOCK)) /* subdir exists? */
			UnLock(dirLock);
		else {						/* create subdirectory */
			if ((dirLock = (struct Lock *) CreateDir(dirname))== NULL){
				if ((errnum = IoErr())== ERROR_DIRECTORY_NOT_EMPTY){
					sprintf(conmsg,
						"Directory %s already exists!\n",dirname);
					TypeAndSpeak(conmsg);
				}
				else {
					sprintf(conmsg,
						"ERROR %d: Unable to create directory %s\n",
						errnum,dirname);
					TypeAndSpeak(conmsg);
					return errnum;
				}
			}
			else
				UnLock(dirLock);
		}
	}								/* endwhile */
	return 0;
}
/* Restore files from floppy disk. */

int
Restore()
{
	int disks=0, status = 0;
	char volumeName[31];

	Speak("And away we go!");

	BreakPath(backPath, srcVol, srcPath);
	strcat(srcVol, ":");

	homeIsDevice = (homePath[strlen(homePath)-1] == ':');
	BreakPath(homePath, destVol, destPath);
	strcat(destVol, ":");

	while ( !status ) {
		TypeAndSpeak("I am ready to read the next disk.\n");
		if (!RequestDisk(mainWindow, srcVol, 
						 "Insert the next disk to be restored in ")) 
			break;

		if (!IsDir(backPath)) {
			TypeAndSpeak(
				"Backup path must be a device or directory name!\n");
			return ERR_ABORT;
		}
		GetVolumeName(srcVol, volumeName);
		SetCurVolumeGadget(volumeName);
		if ( (totalSize = TotalDiskBlocks(destVol)) < 0)
			status = -totalSize;
		else if ((bigFileSeqNbr = GetBigFileInfo(srcVol)) < 0)
			status = -bigFileSeqNbr;
		else {
			status = RestoreFile(srcPath);
		}
		if (bigFileSeqNbr) {		/* we have a big file to restore? */
			status = RestoreBigFile();
		}
/*!!! Need GetErrOpt right here... */

		++disks;
	}
	if (status == 0) {
		if (disks)
			TypeAndSpeak("Your restoration project is completed.\n");
		else
			TypeAndSpeak("Maybe you will let me do it next time.\n");
	}
	else {
		sprintf(conmsg,"Restore terminated with status %d.\n",status);
		TypeAndSpeak(conmsg);
		TypeAndSpeak(
			"Perhaps you should check things out and try it again.\n");
	}

	SetCurVolumeGadget("");
	return status;
}
^L
/* Restore all the files in a directory.
 * Called with:
 *		lock:		lock on the directory
 *		fib:		pointer to file info block
 *		path:		directory pathname (without volume)
 * Returns:
 *		status (0 => success)
 */
int
RestoreDir(lock, fib, path)
	struct Lock *lock; struct FileInfoBlock *fib; char *path;
{
	struct Lock *dirLock = NULL, *filelock = NULL;
	char newpath[256];
	int status = 0;

	strcpy(temp, homePath);
	if (*path) {
		if (!homeIsDevice) strcat(temp, "/");
		strcat(temp, path);
	}
#ifdef DEBUG
	sprintf(debugMsg,"Checking for directory %s\n",temp);
	DebugWrite(debugMsg);
#endif
	if (!(dirLock = (struct Lock *) Lock(temp, SHARED_LOCK))) {
		if ((status = IoErr()) == ERROR_OBJECT_NOT_FOUND) {
#ifdef DEBUG
			sprintf(debugMsg,"Creating directory %s\n",temp);
			DebugWrite(debugMsg);
#endif
			if (status = NewHomeDir(temp))
				return status;
		}
		else {
			sprintf(conmsg,"RestoreDir cannot lock %s: %d\n",temp, status);
			TypeAndSpeak(conmsg);
			return status;
		}
	}
	if (dirLock) UnLock(dirLock);

	while (ExNext(lock,fib)) {
		strcpy(newpath, path);
		if (*newpath)
			strcat(newpath, "/");
		strcat(newpath, fib->fib_FileName);
		if (status = RestoreFile(newpath)) {

			/* filter out "permissable:" errors */

			if (status == ERROR_OBJECT_IN_USE ||
				status == ERROR_WRITE_PROTECTED)
				status = 0;
			else
				break;
		}
	}
done:
	return status;
}
^L
/* Restore one or more files according to the calling pathname.
 * The path argument does not contain the backup volume name.
 */
int
RestoreFile(path)
	char *path;
{
	struct FileInfoBlock *fib = NULL, *fib2 = NULL;
	UBYTE exists = FALSE, ignore = FALSE;
	unsigned isCompressed;
	struct Lock *lock = NULL;
	USHORT nameLength;
	char *s;
	UBYTE savechar;
	int status = 0;

	if (status = CheckStop()) return status;

	/* Don't restore the big file here...it's done last. */

	if (ThisIsBigFile(path)) return status;

	if (!(fib = (struct FileInfoBlock *)
		AllocMem((long) sizeof (struct FileInfoBlock), 
					MEMF_PUBLIC | MEMF_CHIP))) {
		TypeAndSpeak("RestoreFile could not allocate FIB!\n");
		return ERROR_NO_FREE_STORE;
	}

	sprintf(fullBackPath, "%s%s",srcVol,path);
	strcpy(fullHomePath, homePath);
	if (*path) {
		if (!homeIsDevice) strcat(fullHomePath, "/");
		strcat(fullHomePath, path);
	}

#ifdef DEBUG
	sprintf(conmsg,"fullBackPath = %s\n",fullBackPath);
	DebugWrite(conmsg);
	sprintf(conmsg,"fullHomePath = %s\n",fullHomePath);
	DebugWrite(conmsg);
#endif

	if ((sizeLeft = DiskBlocksLeft(destVol)) < 0) {
		status = -sizeLeft;
		sprintf(conmsg,
			"Can't determine disk blocks left on %s; error %d.\n",
			destVol, status);
		TypeAndSpeak(conmsg);
		goto done;
	}
	SetGauge(sizeLeft, totalSize);

	do {
		status = 0;
		if (!(lock = (struct Lock *) Lock(fullBackPath, SHARED_LOCK))) {
			status = IoErr();
			sprintf(conmsg, "RestoreFile can't lock %s; error %d\n",
					fullBackPath, status);
			TypeAndSpeak(conmsg);
			goto checkStatus;
		}

		if (!Examine(lock, fib)) {
			status = IoErr();
			sprintf(conmsg, "RestoreFile can't examine %s; error %d\n", 
					fullBackPath, status);
			TypeAndSpeak(conmsg);
			goto checkStatus;
		}

		if (fib->fib_DirEntryType > 0) {	/* path is a directory */
			status = RestoreDir(lock, fib, path);
			UnLock(lock);
			lock = NULL;
		}
		else {
			UnLock(lock);
			lock = NULL;

			/* Note: though we can use the function IsCompressed to test
			 * for potentially compressed files in Backup(), ONLY files
			 * compressed with LZW (ending in .z) are candidates for
			 * decompression.  The following test looks for these files
			 * only.
			 */
			isCompressed = false;
			if (doCompress && (s = rindex(fullHomePath,'.'))) {
				/* look for ".z" ONLY! */
				if (!strcmpc(s, ".z")) {
					isCompressed = true;

					/* truncate the destination pathname (remove ".z") */

					nameLength = strlen(fullHomePath);
					fullHomePath[nameLength-2] = '\0';
				}
			}
	/*#define NOCOPY*/
	#ifndef NOCOPY
			/* If this file exists, then check its modification date.  If
			 * it's newer than the backup, don't replace it.
			 */

			if ((lock = (struct Lock *) Lock(fullHomePath, SHARED_LOCK))) {
				if (!(fib2 = (struct FileInfoBlock *)
					AllocMem((long) sizeof (struct FileInfoBlock), 
								MEMF_PUBLIC | MEMF_CHIP))) {
					TypeAndSpeak("RestoreFile could not allocate FIB!\n");
					status = ERROR_NO_FREE_STORE;
					goto done;
				}
				Examine(lock, fib2);
				UnLock(lock);
				lock = NULL;
				if (CompareDS(&fib2->fib_Date, &fib->fib_Date) >= 0)
					ignore = TRUE;
			}

			if (ignore) {
				sprintf(conmsg,
						"Skipping %s, since home file is current.\n",
						path);
				WriteConsole(conmsg);
			}
			else {
				if (! (doCompress && isCompressed) ) {
	copyfile:
					sprintf(conmsg,"Copying %s\n", fullBackPath);
					WriteConsole(conmsg);
					status = CopyFile(fullBackPath, fullHomePath);
				}
				else {
					sprintf(conmsg, "Decompressing %s\n", fullBackPath);
					WriteConsole(conmsg);
					if (status = decompress(fullBackPath, fullHomePath)) {
						sprintf(conmsg, 
							"Decompression of %s failed;  status is %d.\n",
							fullBackPath, status);
						TypeAndSpeak(conmsg);
						TypeAndSpeak(
							"I will try to copy the file, instead.\n");
						/* restore ".z" to name */
						fullHomePath[nameLength-2] = '.'; 
						goto copyfile;
					}
					CopyFileDate(fullBackPath, fullHomePath);
				}
			}
	#endif
		}
checkStatus:
		if (status && status != ERR_ABORT) {
			++errorCount;
			SetErrorGadget();
			if (status == ERROR_DISK_FULL) {
					TypeAndSpeak("The destination disk is full.\n");
	/* The following test is pretty kludgy.  It boils down to
	 * "If the drive is a DF<x> device, it's removable, therefore the
	 *  user can recover from this error by inserting a new diskette."
	 */
				if (toupper(destVol[0]) == 'D' && 
				    toupper(destVol[1]) == 'F') {
					TypeAndSpeak(
			"Put a new disk in the destination drive and try again.\n");
				}
				else {
					TypeAndSpeak(
						"You may have to delete some files to continue.");
				}
			}
			status = GetErrOpt(ERR_ABORT|ERR_RETRY_FILE|ERR_IGNORE);
			if (status == ERR_IGNORE) status = 0;
		}
	} while (status == ERR_RETRY_FILE);
done:
	if (lock) UnLock(lock);
	if (fib) FreeMem(fib, (long) sizeof(struct FileInfoBlock));
	if (fib2) FreeMem(fib2, (long) sizeof(struct FileInfoBlock));
	return status;
}
