//	Zinc Application Framework - Z_STORE.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_cset.hpp>
#include <z_list.hpp>
#include <z_store.hpp>
#include <z_string.hpp>
#include <z_utils.hpp>
#if defined(ZAF_MOTIF)
#	include <sys/stat.h>
#endif

#define ZAF_COPY_BUFFER_SIZE	4096

static ZafIChar DEFAULT_EXTENSION[] = ZAF_ITEXT(".znc");
static ZafIChar BACKUP_EXTENSION[] = ZAF_ITEXT(".bk?");

// ----- ZafSignature -------------------------------------------------------

struct ZafSignature
{
	char copyrightNotice[64];
  	ZafUInt8 majorVersion;
	ZafUInt8 minorVersion;
	ZafUInt16 magicNumber;
};

// Change these constants and zafSignature.copyrightNotice below during a
// version change. The following versions have been used in shipping product
// so far.
//
// ZAF  ZafStorage ISO/Unicode copyrightNotice ISO/Unicode
// ---- ---------------------- ---------------------------
// 5.0         5.0/5.5                  5.0/5.5
// 5.01        5.0/5.5                  5.0/5.5
// 5.1         5.1/5.6                  5.1/5.1U
//
const ZafUInt16 ZAF_MAGIC_NUMBER = 0x05AF;
const ZafUInt8 ZAF_MAJOR_FILE_VERSION = 5;
#if defined(ZAF_UNICODE)
	const ZafUInt8 ZAF_MINOR_FILE_VERSION = 6;
#else
	const ZafUInt8 ZAF_MINOR_FILE_VERSION = 1;
#endif

const struct ZafSignature zafSignature =
{
#	if defined(ZAF_UNICODE)
	"Zinc Data File Version 5.1U\032",
#	else
	"Zinc Data File Version 5.1\032",
#	endif
	ZAF_MAJOR_FILE_VERSION, ZAF_MINOR_FILE_VERSION,
	ZAF_MAGIC_NUMBER
};

// ----- ZafDirectoryElement ------------------------------------------------
// For private use by ZafStorage and ZafStorageFile.

class ZafDirectoryElement : public ZafElement
{
public:
	// --- Name and ID ---
	ZafIChar *name;

	// --- File Information ---
	ZafFile *ioFile;
	ZafOffset offset;
	ZafOffset length;
	ZafFileAttribute attributes;

	// --- Directory Information ---
	ZafDirectoryElement *parentDirectory;

	ZafDirectoryElement(const ZafIChar *name, ZafFileAttribute attributes = 0,
		ZafStringID stringID = ZAF_NULLP(ZafIChar), ZafNumberID numberID = 0);
	virtual ~ZafDirectoryElement(void);

	// --- List members ---
	void Add(ZafDirectoryElement *directoryElement);
	void Subtract(ZafDirectoryElement *directoryElement) { fileList.Subtract(directoryElement); };
	ZafDirectoryElement *GetElement(const ZafIChar *name,
		ZafStringID stringID = ZAF_NULLP(ZafIChar), ZafNumberID numberID = 0);
	ZafDirectoryElement *First() { return ((ZafDirectoryElement *)fileList.First()); };
	ZafDirectoryElement *Next() { return ((ZafDirectoryElement *)ZafElement::Next()); };
	ZafDirectoryElement *Next(const ZafIChar *searchString, ZafStringID stringID, ZafNumberID numberID);

	// --- I/O Members ---
	ZafDirectoryElement(const ZafIChar *name, ZafFile *file);
	void WriteDirectories(ZafFile *file);
	void WriteFiles(ZafFile *file);
	void ResetIOFile(ZafFile *file);

protected:
	// --- Directory Contents ---
	ZafList fileList;
};

ZafDirectoryElement::ZafDirectoryElement(const ZafIChar *setName,
	ZafFileAttribute setAttributes, const ZafIChar *setStringID,
	ZafInt16 setNumberID) :
	ioFile(ZAF_NULLP(ZafFile)), offset(-1), length(0),
	attributes(setAttributes),
	parentDirectory(ZAF_NULLP(ZafDirectoryElement)), fileList()
{
	name = strdup(setName);
	SetStringID(setStringID);
	SetNumberID(setNumberID);
}

ZafDirectoryElement::~ZafDirectoryElement(void)
{
	delete []name;
}

void ZafDirectoryElement::Add(ZafDirectoryElement *newElement)
{
	fileList.Add(newElement);
	newElement->parentDirectory = this;
}

ZafDirectoryElement *ZafDirectoryElement::Next(const ZafIChar *searchString,
	ZafStringID searchStringID, ZafNumberID searchNumberID)
{
	ZafDirectoryElement *nextElement = Next();
	while (nextElement)
	{
		if ((WildStrcmp(nextElement->name, searchString) != 0) ||
			(searchStringID && (!nextElement->StringID() ||
				strcmp(nextElement->StringID(), searchStringID) != 0)) ||
			(searchNumberID && nextElement->NumberID() != searchNumberID))
			nextElement = nextElement->Next();
		else
			break;
	}
	return (nextElement);
}

ZafDirectoryElement::ZafDirectoryElement(const ZafIChar *setName, ZafFile *file) :
	ioFile(ZAF_NULLP(ZafFile)), offset(-1), length(0), attributes(0),
	parentDirectory(ZAF_NULLP(ZafDirectoryElement)), fileList()
{
	name = strdup(setName);

	// Read information for this directory.
	ZafInt32 value;
	file->Read((ZafIChar **)&stringID);
	file->Read(numberID);
	file->Read(value); offset = value;
	file->Read(value); length = value;
	file->Read(attributes);
	ioFile = file;

	// Read child directories.
	ZafIChar *childName;
	file->Read(&childName);
	while (childName)
	{
		Add(new ZafDirectoryElement(childName, file));
		delete []childName;
		file->Read(&childName);
	}
}

void ZafDirectoryElement::WriteDirectories(ZafFile *file)
{
	// Write information for this direcotry.
	file->Write(name);
	file->Write(stringID);
	file->Write(numberID);
	
	ZafInt32 value;
	value = offset; file->Write(value);
	value = length;	file->Write(value);

	file->Write(attributes);

	// Write child directories.
	for (ZafDirectoryElement *directory = First(); directory; directory = directory->Next())
		directory->WriteDirectories(file);

	// Mark end of children.
	file->Write(ZAF_NULLP(ZafIChar));
}

void ZafDirectoryElement::WriteFiles(ZafFile *file)
{
	if (length)
	{
		// Write the file associated with this directory.

		// Seek to the data in the old file.
		ioFile->Seek(offset, ZAF_SEEK_START);

		// Save the new file offset.
		offset = file->Tell();

		// Read the data from the old file.
		char *buffer = new char[(int)length];
		ioFile->ReadData(buffer, (int)length);

		// Write the data to the new file.
		file->WriteData(buffer, (int)length);

		// Update the file pointer.
		ioFile = file;

		// Clean up.
		delete []buffer;
	}

	// Write the files associated with child directories.
	for (ZafDirectoryElement *directory = First(); directory; directory = directory->Next())
		directory->WriteFiles(file);
}

void ZafDirectoryElement::ResetIOFile(ZafFile *file)
{
	// Reset the file pointer for this directory.
	if (ioFile)
		ioFile = file;

	// Reset the file pointer for child directories.
	for (ZafDirectoryElement *directory = First(); directory; directory = directory->Next())
		directory->ResetIOFile(file);
}

ZafDirectoryElement *ZafDirectoryElement::GetElement(const ZafIChar *searchName,
	ZafStringID searchStringID, ZafNumberID searchNumberID)
{
	// This function is called alot when traversing directories, so
	// speed optimization is important!

	ZafDirectoryElement *element = First();
	while (element)
	{
		// Skip matching characters.
		int index = 0;
		while (searchName[index] && searchName[index] == element->name[index])
			index++;

		// Test for name match.  Only call WildStrcmp() if necessary 
		// for speed optimization.
		if (element->name[index] == searchName[index] ||
			((searchName[index] == '*' || searchName[index] == '?') &&
			WildStrcmp(&element->name[index], &searchName[index]) == 0))
		{
			// Test for stringID and numberID match.
			if ((!searchStringID || (element->stringID && strcmp(searchStringID, element->stringID) == 0)) &&
				(!searchNumberID || searchNumberID == element->numberID))
				break;
		}

		element = element->Next();
	}

	return element;
}

// --------------------------------------------------------------------------
// ----- ZafStorage ---------------------------------------------------------
// --------------------------------------------------------------------------

ZafStorage::ZafStorage(void) :
	ZafFileSystem(), mode(ZAF_FILE_READWRITE), version(0),
	readFile(ZAF_NULLP(ZafFile)), diskFileSystem(),
	fileName(ZAF_NULLP(ZafIChar)),inMemStorage(false)
	//, callback()
{
	// Create a temporary file for writing.
	tempFileName = new ZafIChar[ZAF_MAXPATHLEN];
	tempFileName[0] = 0;
	diskFileSystem.GetCWD(tempFileName, ZAF_MAXPATHLEN);
	diskFileSystem.TempName(tempFileName);
	tempFile = diskFileSystem.Open(tempFileName, ZAF_FILE_CREATE | ZAF_FILE_READWRITE | ZAF_FILE_BINARY);
	writeFile = tempFile;
	if (!writeFile)
		error = ZAF_ERROR_FILE_OPEN;
	else if (writeFile->Error() != ZAF_ERROR_NONE)
		error = writeFile->Error();

	// Create the root directory and set the current directory.
	rootDirectory = new ZafDirectoryElement(ZafFileSystem::rootDirectoryName, ZAF_FATTRIB_DIRECTORY);
	currentDirectory = rootDirectory;

	// Set the version used for the file.
	SetVersion((ZafVersion)((zafSignature.majorVersion << 8) | zafSignature.minorVersion));
}

ZafStorage::ZafStorage(const ZafIChar *tPathName, ZafFileMode setMode) :
	ZafFileSystem(),
	mode(setMode), version(0), readFile(ZAF_NULLP(ZafFile)),
	tempFile(ZAF_NULLP(ZafFile)), writeFile(ZAF_NULLP(ZafFile)),
	diskFileSystem(), tempFileName(ZAF_NULLP(ZafIChar)),
	currentDirectory(ZAF_NULLP(ZafDirectoryElement)),
	rootDirectory(ZAF_NULLP(ZafDirectoryElement)),inMemStorage(false)
	//, callback()
{
	if (Create() || OpenCreate())
		mode |= ZAF_FILE_READWRITE;

	// Get the full path name.
	fileName = new ZafIChar[ZAF_MAXPATHLEN];
	strcpy(fileName, tPathName);

	// Add the default extension if appropriate.
	int i = (int)(strlen(fileName) - 1);
	while (i >= 0 && fileName[i] != '.' && fileName[i] != ZafOSDirSepStr()[0])
		--i;
	if (i < 0 || fileName[i] == ZafOSDirSepStr()[0])
		strcat(fileName, DEFAULT_EXTENSION);

	ZafIChar tStringID[ZAF_MAXNAMELEN];
	ZafDiskFileSystem::StripFullPath(fileName, ZAF_NULLP(ZafIChar), tStringID);
	SetStringID(tStringID);

	if (Create())
	{
		// Create the root directory.
		rootDirectory = new ZafDirectoryElement(ZafFileSystem::rootDirectoryName, ZAF_FATTRIB_DIRECTORY);

		// Set the version used for the file.
		SetVersion((ZafVersion)((zafSignature.majorVersion << 8) | zafSignature.minorVersion));
	}
	else
	{
		// Open the requested file.
		readFile = diskFileSystem.Open(fileName,
		 (ZafFileMode)((ReadWrite() ? ZAF_FILE_READWRITE : ZAF_FILE_READ) | ZAF_FILE_BINARY));
		ZafError fileError = readFile->Error();

		if (fileError == ZAF_ERROR_NONE)
		{
			// Read the file signature.
			ZafSignature signature;
			readFile->Read(signature.copyrightNotice, 64, 1);
			readFile->Read(signature.majorVersion);
			readFile->Read(signature.minorVersion);
			readFile->Read(signature.magicNumber);

			// Test version for compatibility.
			if (signature.magicNumber != ZAF_MAGIC_NUMBER ||
				signature.majorVersion != ZAF_MAJOR_FILE_VERSION)
					fileError = ZAF_ERROR_STORAGE_VERSION;

			// Set the version used for the file.
			SetVersion((ZafVersion)((signature.majorVersion << 8) | signature.minorVersion));
		}
		else if (OpenCreate())
		{
			// Clean up.
			diskFileSystem.Close(readFile);
			readFile = ZAF_NULLP(ZafFile);
			ZafFile *testFile = diskFileSystem.Open(fileName, ZAF_FILE_CREATE | ZAF_FILE_BINARY);
			fileError = testFile->Error();
			diskFileSystem.Close(testFile);
			diskFileSystem.Remove(fileName);

			// Create the root directory.
			rootDirectory = new ZafDirectoryElement(ZafFileSystem::rootDirectoryName, ZAF_FATTRIB_DIRECTORY);

			// Set the version used for the file.
			SetVersion((ZafVersion)((zafSignature.majorVersion << 8) | zafSignature.minorVersion));
		}

		if (fileError != ZAF_ERROR_NONE)
		{
			// Clean up.
			diskFileSystem.Close(readFile);
			readFile = ZAF_NULLP(ZafFile);
			currentDirectory = rootDirectory = new ZafDirectoryElement(ZafFileSystem::rootDirectoryName, ZAF_FATTRIB_DIRECTORY);
			error = fileError;
			return;
		}

		if (readFile)
		{
			// Seek to the directory tree location.
			ZafOffset directoryTreeOffset;
			ZafInt32 value;
			readFile->Read(value); directoryTreeOffset = value;
			readFile->Seek(directoryTreeOffset, ZAF_SEEK_START);

			// Read the directory tree.
			ZafIChar *rootName;
			readFile->Read(&rootName);
			rootDirectory = new ZafDirectoryElement(rootName, readFile);
			delete []rootName;
		}
	}

	if (ReadWrite())
	{
		// Create a temporary file for writing.
		tempFileName = new ZafIChar[ZAF_MAXPATHLEN];
		diskFileSystem.StripFullPath(fileName, tempFileName);
		if (tempFileName[0] == '\0')
			diskFileSystem.GetCWD(tempFileName, ZAF_MAXPATHLEN);
		diskFileSystem.TempName(tempFileName);
		tempFile = diskFileSystem.Open(tempFileName, ZAF_FILE_CREATE | ZAF_FILE_READWRITE | ZAF_FILE_BINARY);
		writeFile = tempFile;
		if (!writeFile)
			error = ZAF_ERROR_FILE_OPEN;
		else if (writeFile->Error() != ZAF_ERROR_NONE)
			error = writeFile->Error();
	}

	// Set the current directory.
	currentDirectory = rootDirectory;
}

ZafStorage::ZafStorage(ZafResource &inMemoryStorage) :
	ZafFileSystem(),
	mode(ZAF_FILE_READ), version(0), readFile(ZAF_NULLP(ZafFile)),
	tempFile(ZAF_NULLP(ZafFile)), writeFile(ZAF_NULLP(ZafFile)),
	diskFileSystem(), tempFileName(ZAF_NULLP(ZafIChar)),
	currentDirectory(ZAF_NULLP(ZafDirectoryElement)),
	rootDirectory(ZAF_NULLP(ZafDirectoryElement)),
	inMemStorage(true)
{
	// perform a rudimentary check 

	if (inMemoryStorage.DataSize()<=0 || !inMemoryStorage.Data())
	{
		error= ZAF_ERROR_FILE_OPEN;
		return;
	}

	SetStringID(inMemoryStorage.ResourceName());

	readFile= new ZafStorageFile(ZAF_FILE_READ | ZAF_FILE_BINARY, 
								(char*)inMemoryStorage.Data(), inMemoryStorage.DataSize());
	if (readFile)
	{
		ZafError fileError = readFile->Error();
		if (fileError!=ZAF_ERROR_NONE)
		{
			delete readFile;
			readFile = ZAF_NULLP(ZafFile);
			error = fileError;
			return;
		}
	}
	else
	{
		error= ZAF_ERROR_FILE_OPEN;
		return;
	}

	// must not let ZafStorageFile destructor
	// delete inMemoryStorage.data, so ...

	((ZafStorageFile *)readFile)->SetDestroyable(false); 

	// Read the file signature.

	ZafSignature signature;
	readFile->Read(signature.copyrightNotice, 64, 1);
	readFile->Read(signature.majorVersion);
	readFile->Read(signature.minorVersion);
	readFile->Read(signature.magicNumber);

	// Test version for compatibility.

	if (signature.magicNumber != ZAF_MAGIC_NUMBER ||
		signature.majorVersion != ZAF_MAJOR_FILE_VERSION)
	{
		error = ZAF_ERROR_STORAGE_VERSION;
		return;
	}

	// Set the version used for the file.

	SetVersion((ZafVersion)((signature.majorVersion << 8) | signature.minorVersion));

	// Seek to the directory tree location, read the tree, and set current directory.

	ZafOffset directoryTreeOffset;
	ZafInt32 value;
	readFile->Read(value); 
	directoryTreeOffset = value;
	readFile->Seek(directoryTreeOffset, ZAF_SEEK_START);

	ZafIChar *rootName;
	readFile->Read(&rootName);
	rootDirectory = new ZafDirectoryElement(rootName, readFile);
	delete []rootName;

	currentDirectory = rootDirectory;
}

ZafStorage::~ZafStorage(void)
{
	if (!inMemStorage)
	{
		// Close/Remove files & clean up.

		if (readFile)
			diskFileSystem.Close(readFile);
		if (tempFile)
		{
			diskFileSystem.Close(tempFile);
			diskFileSystem.Remove(tempFileName);
		}

		delete []fileName;
		delete []tempFileName;
	}
	else
	{
		// Clean up connection to the compiled image;
		// Note that fileName, tempFile, & tempFileName are never used
		// with readonly storage

		if (readFile)
			delete readFile;
	}
	
	// Delete the directory tree.

	delete rootDirectory;
}

/* START BLOCK COMMENT
**	const ZafStorageFunctor &ZafStorage::Callback(void) const
**	{
**		return (callback);
**	}
END BLOCK COMMENT */

int ZafStorage::Save(int backups)
{
	if (!ReadWrite())
		return (ZAF_ERROR_INVALID_TARGET);

	if (fileName)
	{
/* START BLOCK COMMENT
**			if (callback)
**				callback.Execute(0, ZAF_FILE_SAVE);
END BLOCK COMMENT */

		// Create the final ouput file.
		ZafIChar saveFileName[ZAF_MAXPATHLEN];
		diskFileSystem.StripFullPath(fileName, saveFileName);
		diskFileSystem.TempName(saveFileName);
		ZafFile *saveFile = diskFileSystem.Open(saveFileName, ZAF_FILE_CREATE | ZAF_FILE_READWRITE | ZAF_FILE_BINARY);
		if (!saveFile)
			return (ZAF_ERROR_FILE_OPEN);
		writeFile = saveFile;

		// Write the file signature.
		saveFile->Write(zafSignature.copyrightNotice, 64, 1);
		saveFile->Write(zafSignature.majorVersion);
		saveFile->Write(zafSignature.minorVersion);
		saveFile->Write(zafSignature.magicNumber);

		// Save the jump location for seeking to the directory tree.
		// (The direcory tree must be saved last because its information
		//  is modified when the storage files are saved.)
		ZafOffset jumpOffset = saveFile->Tell();
		ZafInt32 value = jumpOffset;
		saveFile->Write(value);

		// Save the Storage files.
		rootDirectory->WriteFiles(saveFile);

		// Save the offset of the directory tree.
		ZafOffset dirTreeOffset = saveFile->Tell();

		// Write the directory tree.
		rootDirectory->WriteDirectories(saveFile);

		// Write the jump information for the directory tree.
		saveFile->Seek(jumpOffset, ZAF_SEEK_START);
		value = dirTreeOffset;
		saveFile->Write(value);

		// Close the save file.
		diskFileSystem.Close(saveFile);

		// Close the read file.
		if (readFile)
			diskFileSystem.Close(readFile);

		// Rename backup file(s).
		if (backups > 0)
		{
			ZafIChar extension[5];
			ZafIChar oldFileName[ZAF_MAXPATHLEN];
			strcpy(oldFileName, fileName);
			strcpy(extension, BACKUP_EXTENSION);
			*strchr(extension, '?') = (ZafIChar)('0' + backups - 1);
			diskFileSystem.ChangeExtension(oldFileName, extension);

			ZafIChar newFileName[ZAF_MAXPATHLEN];
			strcpy(newFileName, fileName);
			strcpy(extension, BACKUP_EXTENSION);
			*strchr(extension, '?') = (ZafIChar)('0' + backups);
			diskFileSystem.ChangeExtension(newFileName, extension);
			diskFileSystem.Remove(newFileName);

			while (backups > 1)
			{
				diskFileSystem.Rename(oldFileName, newFileName);
				strcpy(newFileName, oldFileName);
				backups--;
				strcpy(extension, BACKUP_EXTENSION);
				*strchr(extension, '?') = (ZafIChar)('0' + backups);
				diskFileSystem.ChangeExtension(oldFileName, ZAF_ITEXT(".bak"));
			}

			diskFileSystem.Rename(fileName, newFileName);
		}

#if defined(ZAF_MOTIF)
		// In Motif we need to get the file stats before the file is lost.
		struct stat statBuf;
		bool invalidFile = false;
#	if defined(ZAF_UNICODE)
		char *osPath = ZafConvertToOSString(fileName);
		if (stat(osPath, &statBuf))
			invalidFile = true;
		delete []osPath;
#	else
		if (stat(fileName, &statBuf))
			invalidFile = true;
#	endif
#endif

		// Rename the saved file.
		if (diskFileSystem.Rename(saveFileName, fileName) != ZAF_ERROR_NONE)
		{
			// Rename didn't work, so copy the file.

			// Open the files.
			ZafFile *newReadFile = diskFileSystem.Open(saveFileName, ZAF_FILE_READ | ZAF_FILE_BINARY);
			ZafFile *newWriteFile = diskFileSystem.Open(fileName, ZAF_FILE_CREATE | ZAF_FILE_READWRITE | ZAF_FILE_BINARY);

			if (!newReadFile || !newWriteFile)
			{
				if (newReadFile)
					diskFileSystem.Close(newReadFile);
				else if (newWriteFile)
					diskFileSystem.Close(newWriteFile);

				return (ZAF_ERROR_FILE_OPEN);
			}

			if (newReadFile->Error() || newWriteFile->Error())
			{
				if (newReadFile)
					diskFileSystem.Close(newReadFile);
				else if (newWriteFile)
					diskFileSystem.Close(newWriteFile);

				return (ZAF_ERROR_FILE_OPEN);
			}

			// Copy the data.
			char *copyBuffer = new char[ZAF_COPY_BUFFER_SIZE];
			int bytesRead;
			while ((bytesRead = newReadFile->ReadData(copyBuffer, ZAF_COPY_BUFFER_SIZE)) > 0)
			{
				int bytesWritten = newWriteFile->WriteData(copyBuffer, bytesRead);
				if (bytesWritten != bytesRead)
					 error = ZAF_ERROR_FILE_WRITE;
			}
			delete []copyBuffer;

			// Close the files.
			diskFileSystem.Close(newReadFile);
			diskFileSystem.Close(newWriteFile);

			// Remove the temporary file.
			diskFileSystem.Remove(saveFileName);
		}

#if defined(ZAF_MOTIF)
		// After the file has been renamed or copied set the original stats 
		// on the file.
		if (!invalidFile)
		{
#	if defined(ZAF_UNICODE)
			char *osPath = ZafConvertToOSString(fileName);
			chmod(osPath, statBuf.st_mode);
			delete []osPath;
#	else
 			chmod(fileName, statBuf.st_mode);
#	endif
		}
#endif
		// Re-open the saved file.
		readFile = diskFileSystem.Open(fileName, ZAF_FILE_READWRITE | ZAF_FILE_BINARY);

		// Reset the directory information to point to the new file.
		rootDirectory->ResetIOFile(readFile);

		// Reset the temporary file.
		diskFileSystem.Close(tempFile);
		tempFile = diskFileSystem.Open(tempFileName, ZAF_FILE_CREATE | ZAF_FILE_READWRITE | ZAF_FILE_BINARY);
		writeFile = tempFile;

		return (error ? -1 : 0);
	}

	error = ZAF_ERROR_FILE_OPEN;
	return (-1);
}

int ZafStorage::SaveAs(const ZafIChar *newName, int backups)
{
	if (inMemStorage)
	{
		error= ZAF_ERROR_INVALID_TARGET;
		return -1;
	}

	// Create the fileName array if this is a temporary object (created by the default constructor).
	if (!fileName)
		fileName = new ZafIChar[ZAF_MAXPATHLEN];

	// Create the full path name.
	// Add the default extension if appropriate.
	strcpy(fileName, newName);
	int i = (int)(strlen(fileName) - 1);
	while (i >= 0 && fileName[i] != '.' && fileName[i] != ZafOSDirSepStr()[0])
		--i;
	if (i < 0 || fileName[i] == ZafOSDirSepStr()[0])
		strcat(fileName, DEFAULT_EXTENSION);

	ZafIChar tStringID[ZAF_MAXNAMELEN];
	ZafDiskFileSystem::StripFullPath(fileName, ZAF_NULLP(ZafIChar), tStringID);
	SetStringID(tStringID);

	// Save the file.
	if (!ReadWrite())
	{
		// Create a temporary file for writing.
		tempFileName = new ZafIChar[ZAF_MAXPATHLEN];
		diskFileSystem.StripFullPath(fileName, tempFileName);
		if (tempFileName[0] == '\0')
			diskFileSystem.GetCWD(tempFileName, ZAF_MAXPATHLEN);
		diskFileSystem.TempName(tempFileName);
	}
	mode = ZAF_FILE_READWRITE;
	int retVal = Save(backups);
	return (retVal);
}

int ZafStorage::ChDir(const ZafIChar *path, ZafStringID tStringID,
	ZafNumberID tNumberID)
{
	// Set the current directory.
	ZafDirectoryElement *newCurrentDirectory = WalkPath(path);
	if (newCurrentDirectory)
	{
		if ((tStringID && strcmp(tStringID, newCurrentDirectory->StringID())) ||
			tNumberID && tNumberID != newCurrentDirectory->NumberID())
		{
			SetError(ZAF_ERROR_INVALID_ID);
			return (-1);
		}

		currentDirectory = newCurrentDirectory;
/* START BLOCK COMMENT
**			if (callback)
**				callback.Execute(path, ZAF_FILE_READ);
END BLOCK COMMENT */
		return (0);
	}

	SetError(ZAF_ERROR_INVALID_NAME);
	return (-1);
}

int ZafStorage::FindFirst(ZafFileInfoStruct &fileInfo,
	const ZafIChar *searchName, ZafStringID tStringID, ZafNumberID tNumberID)
{
	// Get the find directory and searchName.
	ZafDirectoryElement *directory = WalkPath(searchName, &searchName);

	// Test for error.
	if (!directory)
	{
		error = ZAF_ERROR_FILE_READ;
		return (-1);
	}

	// Get the first element matching the searchName.
	ZafDirectoryElement *findElement = directory->GetElement(searchName, tStringID, tNumberID);

	if (findElement)
	{
		// Fill the fileInfo structure.
		fileInfo.name = findElement->name;
		fileInfo.stringID = findElement->StringID();
		fileInfo.numberID = findElement->NumberID();
		fileInfo.length = findElement->length;
		fileInfo.attributes = findElement->attributes;
		fileInfo.internalHandle = findElement;
		return (0);
	}

	// The error member is not set here, since not finding an object is not an error condition.
	return (-1);
}

int ZafStorage::FindNext(ZafFileInfoStruct &fileInfo,
	const ZafIChar *searchName, ZafStringID tStringID, ZafNumberID tNumberID)
{
	// Get the find directory and searchName.
	ZafDirectoryElement *directory = WalkPath(searchName, &searchName);

	// Test for error.
	if (!directory)
	{
		error = ZAF_ERROR_FILE_READ;
		return (-1);
	}

	// Get the element found during the previous search.
	ZafDirectoryElement *findElement = (ZafDirectoryElement *)fileInfo.internalHandle;

	// Make sure the search directory is the same directory used
	// for the previous search.
	if (directory != findElement->parentDirectory)
	{
		error = ZAF_ERROR_FILE_READ;
		return(-1);
	}

	// Get the next element.
	findElement = findElement->Next(searchName, tStringID, tNumberID);

	if (findElement)
	{
		// Fill the fileInfo structure.
		fileInfo.name = findElement->name;
		fileInfo.stringID = findElement->StringID();
		fileInfo.numberID = findElement->NumberID();
		fileInfo.length = findElement->length;
		fileInfo.attributes = findElement->attributes;
		fileInfo.internalHandle = findElement;
		return (0);
	}

	// The error member is not set here, since not finding an object is not an error condition.
	return (-1);
}

int ZafStorage::FindClose(ZafFileInfoStruct &)
{
	return (0);
}

int ZafStorage::GetCWD(ZafIChar *pathName, int pathLength)
{
	ZafIChar cwd[ZAF_MAXPATHLEN];

	// Build the current directory path.
	ZafDirectoryElement *directory = currentDirectory;
	if (directory == rootDirectory)
	{
		// Return the root directory.
		cwd[0] = ZAF_DIRECTORY_SEPARATOR;
		cwd[1] = 0;
	}
	else
	{
		cwd[0] = 0;

		// Append directory names from the current directory up to the root.
		while (directory != rootDirectory)
		{
			ZafIChar tempBuffer[ZAF_MAXPATHLEN];
			tempBuffer[0] = ZAF_DIRECTORY_SEPARATOR;
			tempBuffer[1] = 0;
			strcat(tempBuffer, directory->name);
			strcat(tempBuffer, cwd);
			strcpy(cwd, tempBuffer);
			directory = directory->parentDirectory;
		}
	}

	// Copy only as many characters as will fit.
#if defined(ZAF_MACINTOSH)
	strncpy(pathName, cwd, (unsigned long)pathLength);
#else
	strncpy(pathName, cwd, pathLength);
#endif

	return (0);
}

int ZafStorage::MkDir(const ZafIChar *dirName, ZafStringID tStringID,
	ZafNumberID tNumberID)
{
	// Get the creation directory and new directory name.
	ZafDirectoryElement *parentDirectory = WalkPath(dirName, &dirName);

	// If the directory already exists don't make another one.
	if (parentDirectory && parentDirectory->GetElement(dirName))
		return (-1);

	if (parentDirectory)
	{
		// Create the new directory.
		ZafDirectoryElement *newDirectory = new ZafDirectoryElement(dirName,
			ZAF_FATTRIB_DIRECTORY, tStringID, tNumberID);
		parentDirectory->Add(newDirectory);

/* START BLOCK COMMENT
**			if (callback)
**				callback.Execute(dirName, ZAF_FILE_READWRITE);
END BLOCK COMMENT */

		return (0);
	}

	return (-1);
}

void ZafStorage::Close(ZafFile *file)
{
	ZafStorageFile *storageFile = (ZafStorageFile *)file;
	if (storageFile->Changed())
	{
		// Seek to the end of the write file.
		writeFile->Seek(0, ZAF_SEEK_END);

		// Update the directory information.
		ZafDirectoryElement *directoryElement = storageFile->directoryElement;
		directoryElement->ioFile = writeFile;
		directoryElement->offset = writeFile->Tell();
		directoryElement->length = storageFile->Length();

		// Write the data.
		writeFile->WriteData(storageFile->Data(), (int)storageFile->Length());
		if (writeFile->Error() != ZAF_ERROR_NONE)
			error = writeFile->Error();
	}

	// Clean up.
	delete storageFile;
}

ZafFile *ZafStorage::Open(const ZafIChar *newFileName,
	const ZafFileMode fileMode, ZafStringID searchStringID,
	ZafNumberID searchNumberID)
{
	ZafStorageFile *storageFile = ZAF_NULLP(ZafStorageFile);

	// Get the directory and file name.
	ZafDirectoryElement *directory = WalkPath(newFileName, &newFileName);
	bool found = true;

	if (directory)
	{
		// Get the requested directory element.
		ZafDirectoryElement *directoryElement = directory->GetElement(newFileName, searchStringID, searchNumberID);

		// Test for a valid file.
		if (directoryElement && directoryElement->attributes & ZAF_FATTRIB_DIRECTORY)
			return (ZAF_NULLP(ZafFile));

		// Delete old directory element if creation was requested.
		if (directoryElement && (fileMode & ZAF_FILE_CREATE))
		{
			directory->Subtract(directoryElement);
			delete directoryElement;
			directoryElement = ZAF_NULLP(ZafDirectoryElement);
		}

		// Create a new directory element if creation is required.
		if (!directoryElement && (fileMode & (ZAF_FILE_CREATE | ZAF_FILE_OPENCREATE)))
		{
			directoryElement = new ZafDirectoryElement(newFileName, 0, searchStringID, searchNumberID);
			directory->Add(directoryElement);
			found = false;
		}

		if (directoryElement)
		{
			if (directoryElement->length > 0)
			{
				// Read the files data.
				char *buffer = new char[(int)directoryElement->length];
				directoryElement->ioFile->Seek(directoryElement->offset, ZAF_SEEK_START);
				directoryElement->ioFile->ReadData(buffer, (int)directoryElement->length);

				// Create the file.
				storageFile = new ZafStorageFile(fileMode, buffer, (int)directoryElement->length);
			}
			else
				// Create a new file.
				storageFile = new ZafStorageFile(fileMode);

			// If the object doesn't exist in the file and we don't have write
			// privileges, then set an error.
			if (!(mode & ZAF_FILE_READWRITE) && !found)
				storageFile->SetError(ZAF_ERROR_INVALID_NAME);
			
				// Attach the directory element to the new file.
			storageFile->directoryElement = directoryElement;
			if (storageFile->Error() != ZAF_ERROR_NONE)
				error = storageFile->Error();
		}

/* START BLOCK COMMENT
**			if (callback && !storageFile->Error())
**				callback.Execute(newFileName, fileMode);
END BLOCK COMMENT */
	}

	return (storageFile);
}

int ZafStorage::Remove(const ZafIChar *rmFileName)
{
	// Get the directory and file name.
	ZafDirectoryElement *directory = WalkPath(rmFileName, &rmFileName);
	if (directory)
	{
		// Get the requested directory element.
		ZafDirectoryElement *directoryElement = directory->GetElement(rmFileName);

		// Test for a valid file.
		if (directoryElement && !(directoryElement->attributes & ZAF_FATTRIB_DIRECTORY))
		{
/* START BLOCK COMMENT
**				if (callback)
**					callback.Execute(rmFileName, ZAF_FILE_DELETE);
END BLOCK COMMENT */

			directory->Subtract(directoryElement);
			delete directoryElement;
			return (ZAF_ERROR_NONE);
		}
	}

	return (ZAF_ERROR_INVALID_NAME);
}

int ZafStorage::Rename(const ZafIChar *oldName, const ZafIChar *newName,
	ZafStringID tStringID, ZafNumberID tNumberID)
{
	// Get the directories and file names.
	ZafDirectoryElement *oldDirectory = WalkPath(oldName, &oldName);
	ZafDirectoryElement *newDirectory = WalkPath(newName, &newName);

	if (oldDirectory && newDirectory)
	{
		// Get the directory elements.
		ZafDirectoryElement *oldElement = oldDirectory->GetElement(oldName);
		ZafDirectoryElement *newElement = newDirectory->GetElement(newName);

		// Test for a valid operation.
		if (oldElement && !newElement)
		{
			oldDirectory->Subtract(oldElement);
			delete []oldElement->name;
			oldElement->name = strdup(newName);
			if (tStringID)
				oldElement->SetStringID(tStringID);
			if (tNumberID)
				oldElement->SetNumberID(tNumberID);
			newDirectory->Add(oldElement);
/* START BLOCK COMMENT
**				if (callback)
**				{
**					callback.Execute(oldName, ZAF_FILE_DELETE);
**					callback.Execute(newName, ZAF_FILE_READWRITE);
**				}
END BLOCK COMMENT */

			return (ZAF_ERROR_NONE);
		}
		else if (oldElement && oldElement == newElement)
		{
			if (tStringID)
				oldElement->SetStringID(tStringID);
			if (tNumberID)
				oldElement->SetNumberID(tNumberID);

/* START BLOCK COMMENT
**				if (callback)
**				{
**					callback.Execute(oldName, ZAF_FILE_DELETE);
**					callback.Execute(newName, ZAF_FILE_READWRITE);
**				}
END BLOCK COMMENT */

			return (ZAF_ERROR_NONE);
		}
	}

	return (ZAF_ERROR_INVALID_NAME);
}

int ZafStorage::RmDir(const ZafIChar *pathName, bool deleteContents)
{
	// Get the directory to be removed.
	ZafDirectoryElement *deleteDirectory = WalkPath(pathName);

	if (deleteDirectory && (deleteContents || !deleteDirectory->First()))
	{
		// Make sure directory removal is allowed.
		if (deleteDirectory == rootDirectory)
			return (-1);
		if (deleteContents)
		{
			// Make sure the current directory won't be deleted.
			ZafDirectoryElement *directory = currentDirectory;
			while (directory && directory != deleteDirectory)
				directory = directory->parentDirectory;
			if (directory)
				return (-1);

			//??? Test for open files?
		}
		else if (deleteDirectory == currentDirectory)
			return (-1);

/* START BLOCK COMMENT
**			if (callback)
**				callback.Execute(pathName, ZAF_FILE_DELETE);
END BLOCK COMMENT */

		// Remove the directory.
		// (File will be garbage collected when saved.)
		deleteDirectory->parentDirectory->Subtract(deleteDirectory);
		delete deleteDirectory;

		return (0);
	}
	else
		return (-1);
}

/* START BLOCK COMMENT
**	ZafStorageFunctor &ZafStorage::SetCallback(ZafStorageFunctor &tCallback)
**	{
**		callback = tCallback;
**		return (callback);
**	}
END BLOCK COMMENT */

bool ZafStorageFile::SetDestroyable(bool tDestroyable) 
{ 
	destroyable = tDestroyable; 
	return (destroyable); 
}

ZafDirectoryElement *ZafStorage::WalkPath(const ZafIChar *path)
{
	ZafIChar buffer[ZAF_MAXPATHLEN];
	strcpy(buffer, path);

	// Initialize the directory and next directory name.
	ZafIChar *dirName = buffer;
	ZafDirectoryElement *directory;
	if (*dirName == ZAF_DIRECTORY_SEPARATOR)
	{
		directory = rootDirectory;
		dirName++;
	}
	else
		directory = currentDirectory;

	// Walk the path.
	while (*dirName)
	{
		// Find the next directory name.
		ZafIChar *dirSep = strchr(dirName, ZAF_DIRECTORY_SEPARATOR);
		if (dirSep)
			*dirSep = 0;

		// Change the directory.
		if (strcmp(dirName, ZafFileSystem::parentDirectoryName) == 0)
			directory = directory->parentDirectory;
		else if (strcmp(dirName, ZafFileSystem::currentDirectoryName) != 0)
		{
			directory = directory->GetElement(dirName);
			if (!directory)
				return (ZAF_NULLP(ZafDirectoryElement));
		}

		// Continue.
		if (dirSep)
			dirName = dirSep + 1;
		else
			break;
	}

	return (directory);
}

ZafDirectoryElement *ZafStorage::WalkPath(const ZafIChar *walkPath, const ZafIChar **walkFileName)
{
	ZafDirectoryElement *directory = currentDirectory;

	// See if a new directory is specified in the path.
	if (strchr(walkPath, ZAF_DIRECTORY_SEPARATOR))
	{
		ZafIChar directoryPath[ZAF_MAXPATHLEN];
		strcpy(directoryPath, walkPath);

		// Walk the directory path.
		ZafIChar *dirSep = strrchr(directoryPath, ZAF_DIRECTORY_SEPARATOR);
		if (dirSep == directoryPath)
			directory = rootDirectory;
		else
		{
			*dirSep = 0;
			directory = WalkPath(directoryPath);
		}

		// Set the file name.
		if (directory)
			*walkFileName = &walkPath[(int)(dirSep  - directoryPath + 1)];
		else
			*walkFileName = ZAF_NULLP(ZafIChar);
	}
	else
		*walkFileName = walkPath;

	return (directory);
}

ZafResource::ZafResource(unsigned char *_data, int _dataSize, ZafIChar *_resourceName):
	dataSize(_dataSize), data(_data)
{
	resourceName = strdup(_resourceName);
}

ZafResource::~ZafResource()
{
	if (resourceName)
		delete resourceName;
}

/* START BLOCK COMMENT
**	ZafStorageFunctor::ZafStorageFunctor(): body(0)
**	{
**	}
**	
**	ZafStorageFunctor::ZafStorageFunctor(const ZafStorageFunctor &copy)
**	{
**		body = copy.body;
**		if (body)
**			body->IncrementCount();
**	}
**	
**	ZafStorageFunctor::~ZafStorageFunctor()
**	{
**		if (body && body->DecrementCount() < 1)
**			delete body;
**	}
**	
**	ZafStorageFunctor &ZafStorageFunctor::operator=(ZafStorageFunctor &copy)
**	{
**		if (body && body->DecrementCount() < 1)
**			delete body;
**	
**		body = copy.body;
**		if (body)
**			body->IncrementCount();
**	
**		return (*this);
**	}
**	
**	ZafStorageFunctor::ZafStorageFunctor(ZafStorageFunctorBody *tBody) : body(0)
**	{
**		AssignBody(tBody);
**	}
**	
**	void ZafStorageFunctor::AssignBody(ZafStorageFunctorBody *tBody)
**	{
**		if (body && body->DecrementCount() < 1)
**			delete body;
**	
**		body = tBody;
**		if (body)
**			body->IncrementCount();
**	}
**	
**	ZafStorageFunctor::operator void *()
**	{
**		return (body ? (void *)1 : (void *)0);
**	}
**	
**	void ZafStorageFunctor::Execute(const ZafIChar *string, ZafFileMode mode)
**	{
**		if (!body)
**			return;
**	
**		body->Execute(string, mode);
**	}
**	
**	ZafStorageFunctorBody::ZafStorageFunctorBody(): count(0)
**	{
**	}
**	
**	ZafStorageFunctorBody::~ZafStorageFunctorBody()
**	{
**	}
**	
**	int ZafStorageFunctorBody::Count(void) const
**	{
**		return (count);
**	}
**	
**	int ZafStorageFunctorBody::DecrementCount(void)
**	{
**		return (--count);
**	}
**	
**	int ZafStorageFunctorBody::IncrementCount(void)
**	{
**		return (++count);
**	}
END BLOCK COMMENT */

