//----------------------------------------------------------------------------------
// <copyright file="PIB_FileTransfer.c" company="LiquidRobotics">
//	Copyright (c) Liquid Robotics Corporation.  All rights reserved.
// </copyright>
//
// <summary>
// 	This is the block of code for the module that controls file transfers with the
//  Wave Glider
// </summary>
//
// <owner>Jim Kirklin</owner>
//----------------------------------------------------------------------------------

#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "includes.h"
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
//#include "app_init.h"
#include "uart.h"
#include "pib.h"
#include "timer.h"
#include "lrstat.h"
#include "spidebug.h"
#include "wdtrig.h"
#include "pib_apps.h"
#include "User_app_init.h"
#include "fltPyldComms.h"



extern uint16_t CalculateCRC16(uint8_t* pBuffer, uint16_t size, uint16_t initValue);

//#include "common.h"
//#include "kernel.h"
//#include "FloatCommon.h"
//#include "FloatManagers.h"
//#include "FloatManager_Alarm.h"
//#include "FloatManager_XBeeLevel3.h"
//#include "debug.h"
#define FILE_TRANSFER_LOCAL
#include "PIB_FileTransfer.h"
//#include "FloatManager_Communication.h"
#include "sd_ff.h"
#include "md5.h"

#ifdef FILE_TRANSFER

#ifndef MIN
#define MIN(p1,p2) (((p1)>(p2))?(p2):(p1))
#endif

//#define INCLUDE_DEBUG_STATEMENTS

//Finds the offset of a member within a structure
#define OFFSET_OF(structType,member)	((unsigned)(&((structType*) 0)->member))

//Zero's an object
#define ZERO_OBJ(obj)					(memset(&obj, '\0', sizeof(obj)))

//Copies one object to another
#define	COPY_OBJ(dest,src)				(memcpy(&dest, &src, sizeof(dest)))

//Determines the size (dimension 0) of an array
#define	DIM(obj)						(sizeof(obj)/sizeof((obj)[0]))


#define GET_BUFFER_CHAR(b,i)		(i += 2, b[i-2])
#define GET_BUFFER_STR(b,i,s,l)		(getstr(s, &b[i], l), i += l+1)
#define GET_BUFFER_STR_FIXED(b,i,s,l) (strncpy(s, &b[i], l), i += l+1)
#define GET_BUFFER_HEX(b,i,u,t)		(u = (t) gethex(&b[i], 2*sizeof(u)), i += 2*sizeof(u)+1)
#define GET_BUFFER_HEX_BYTES(b,i,v) (gethexbuffer(&b[i], (uint8_t*) &v, sizeof(v)), i += 2*sizeof(v)+1)
#define PUT_BUFFER_CHAR(b,i,c)		(b[i] = c, i += 2)
#define PUT_BUFFER_STR(b,i,s,l)		(putstr(&b[i],s), i += l+1)
#define PUT_BUFFER_STR_FIXED(b,i,s,l) (strncpy(&b[i],s,l), i += l+1)
#define PUT_BUFFER_HEX(b,i,u)		(puthex(&b[i], (uint32_t) (u), 2*sizeof(u)), i += 2*sizeof(u)+1)
#define PUT_BUFFER_HEX_BYTES(b,i,v)	(puthexbuffer(&b[i], (uint8_t*) &v, sizeof(v)), i += 2*sizeof(v)+1)

// Local Data
//static BOOL filesystemMounted = FALSE;
//static BOOL callbackScheduled;
static uint16_t totalIndexedFiles;
static PFT_TASK unallocatedTasks;
static PFT_TASK allocatedTasks;
static FT_TASK allTasks[FT_MAX_FILE_OPS];
//JGK static uint32_t lastTaskMessage = 0;
static PFT_TASK nextTaskToSchedule;
static FT_OPERATION_LOCK operationLocks[FT_MAX_FILE_OPS];
static int8_t dirFileHandle;
static BOOL dirFileHandleOpen = FALSE;

static prog_char tempHeader[] = "$TEMP";
static prog_char tempSuffix[] = "$.$$$";
static prog_char dirFile[] = "$DIR$.$$$";
static prog_char eepromName[] = "EEPROM";
static prog_char programMemoryName[] = "PRGMEM";
static prog_char ramName[] = "RAM";


// Local Function Prototypes
uint8_t InitializeFileTransferManager();
//static uint8_t InitializeFileTransferManager2();
static uint8_t GetFileIndexByID(uint32_t fileID, uint16_t* pFileIndex);
static uint8_t GetFileIndexByName(const char* lpszFileName, uint16_t* pFileIndex);
static uint8_t PermuteFilename(char* lpszFileName);
static uint8_t CreateOrTruncate(uint16_t* pFileIndex, PFT_FILE_ENTRY pNewFileEntry);
static uint8_t GetOpenIndex(uint16_t* fileEntryIndex);
static uint8_t DeleteFileByIndex(uint16_t fileIndex, BOOL anyType, BOOL overrideReadOnly);
static void PaddedNameToName(const char* pPaddedName, char* lpszName);
static void PaddedSectionToName(const char* pPaddedSection, uint8_t count, char** lpszName);
static void NameToPaddedName(const char* lpszName, char* pPaddedName);
static void NameToPaddedSection(const char* lpszName, uint8_t count, char* pPaddedSection, uint8_t padSize);
static BOOL ReservedFileName(const char* lpszFileName);
static uint8_t ValidateDateTime(DATE_TIME* pDateTime);
static uint8_t AllocateTask(PFT_TASK* ppTask);
static void DeallocateTask(PFT_TASK pTask);
static uint8_t StartTask(PFT_TASK pTask);
static uint8_t StopTask(PFT_TASK pTask);
static BOOL ReadTaskBoundsCheck(PFT_TASK pTask);
static uint32_t SizeOfObject(PFT_TASK pTask);
static uint8_t CheckFile(uint16_t fileEntryIndex, PFT_FILE_ENTRY fileEntry, BOOL deleteIfFailed);
static uint8_t CopyFile(const char* lpszSource, const char* lpszDest, FT_FILETYPE fileType, uint16_t* pNewFileIndex);
static uint8_t FindTempFileName(char* lpszTempFileName);
static uint8_t ConvertSDError(uint8_t sdError);
static void RunTasks();
static uint8_t PlaceOperationLock(uint16_t fileIndex);
static uint8_t ReleaseOperationLock(uint16_t fileIndex);
static BOOL IsOperationLocked(uint16_t fileIndex);
static uint32_t gethex(char* output, uint8_t digs);
static void gethexbuffer(char* output, uint8_t* pBuffer, uint8_t bufSize);
static void getstr(char* str, char* input, uint8_t maxLen);
static void puthex(char* output, uint32_t num, uint8_t digs);
static void puthexbuffer(char* output, uint8_t* pBuffer, uint8_t bufSize);
static uint8_t putstr(char* output, char* str);
static uint8_t EnsureDirFileOpen();
static uint8_t CloseDirFile();
static uint8_t FT_GetFileEntryFromHandle(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry, int8_t handle, const char* lpszFileName);
static uint8_t EnsureDirFileEntry();
static uint8_t FT_SetFileEntry2(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry);
static uint8_t GetListTask_Directory(PFT_TASK pTask);
static uint8_t GetListTask_ReportAtDest(PFT_TASK pTask);





////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Initialization and Main Task Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------
//
// Initializes the module
//
// -----------------------------------------------

uint8_t InitializeFileTransferManager()
{
	FT_TASK *pTask = allTasks;
	uint8_t remaining = FT_MAX_FILE_OPS-1;
//	SCHEDULE_ADD_STRUCT sas;

	FILEINFO fi;				// info about the directory file
	char dirname[13];			// name of directory file

	totalIndexedFiles = 0;			// assume no files in directory

	ZERO_OBJ(allTasks);
	ZERO_OBJ(operationLocks);

	while(remaining--)
	{
		pTask->pNext = &pTask[1];
		++pTask;
	}

	allocatedTasks = NULL;			// no allocated tasks
	unallocatedTasks = allTasks;	// all tasks are unallocated

//	// schedule a callback when interrupts are enabled
//	ZERO_OBJ(sas);
//	sas.add = SCHED_ADD;;
//	sas.entry = MgrFileTransferEntry;
//	sas.messageID = MSG_FILE_TRANSFER_INIT2;
//	sas.start = getSystemTickCount();
//	sas.priority = LOW;
//
//	SchedulerAdd(&sas);




	strcpy_P(dirname, dirFile);
	ZERO_OBJ(fi);

	// count the number of records stored
	if (SD_getFileInfo(dirname, &fi) == SD_OK)
		totalIndexedFiles = fi.fsize/FILE_ENTRY_LINE_LENGTH;
	else
		totalIndexedFiles = 0;
	
	// attempt to open the directory file
	if (EnsureDirFileEntry() == SUCCESS)
	{
		uint16_t fileIndex;			// index of file in table
		FT_FILE_ENTRY fileEntry;	// file entry from directory file

		// expunge any temporary files since the context has been lost
		for (fileIndex = 0; fileIndex < totalIndexedFiles; ++fileIndex)
			
			if (FT_GetFileEntry(fileIndex, &fileEntry) == SUCCESS)
				
				switch(fileEntry.fileType)
				{
					case FT_FILETYPE_RECEIVING:
					case FT_FILETYPE_SENDING:
					case FT_FILETYPE_TEMP_DIRECTORY:

						DeleteFileByIndex(fileIndex, TRUE, TRUE);
						break;

					default:
						break;
				}

		// close the directory file for now
		CloseDirFile();
	}

	// schedule a recurring timer
//	lastTaskMessage = MSG_FILE_TRANSFER_SCHEDULE_1;		//This becomes the main operation task JGK
//
//	SCHEDULE_ADD_STRUCT sas;
//
//	ZERO_OBJ(sas);	
//	sas.add = SCHED_ADD;;
//	sas.entry = MgrFileTransferEntry;
//	sas.messageID = lastTaskMessage;
//	sas.start = getSystemTickCount()+1;
//	sas.period = 1;
//	sas.priority = LOW;
//
//	SchedulerAdd(&sas);
//

	return SUCCESS;
}

// ----------------------------------------------
//
// Entry point for the XBee Level manager -- will not be used in uCOS
//
// ----------------------------------------------

//uint8_t MgrFileTransferEntry(uint32_t msgID, PVOID pParam1, uint16_t size1, PVOID pParam2, uint16_t size2, PVOID* pReturn, uint16_t* pSizeData)
//{
//	uint8_t retval = FAIL;
//
//	switch(msgID)
//	{
//		case MSG_INITIALIZE:
//		{
//			retval = InitializeFileTransferManager();
//			break;
//		}
//		case MSG_FILE_TRANSFER_INIT2:
//		{
//			retval = InitializeFileTransferManager2();
//			break;
//		}
//		case MSG_FILE_TRANSFER_SCHEDULE_1:
//		case MSG_FILE_TRANSFER_SCHEDULE_2:
//		{
//			RunTasks(msgID);
//			break;
//		}
//		case MSG_FILE_TRANSFER_DEBUG:
//		{
//			if (size1 == sizeof(uint8_t))
//				return FT_DebugFunction(*((uint8_t*) pParam1));
//
//			break;
//		}
//	}
//
//	return retval;
//}



// ----------------------------------------------
//
// Executes background tasks --> main task in uCOS????
//
// ----------------------------------------------

static void RunTasks()
{
	// find the task to run first by verifying nextTaskToSchedule is on the allocate chain
	PFT_TASK toRun = allocatedTasks;

	while(toRun && (toRun != nextTaskToSchedule))
		toRun = toRun->pNext;

	// if it's not found, then restart at the head of the allocated chain
	if (!toRun)
		toRun = allocatedTasks;

	// if found now, save the next task to schedule and run the current one
	if (toRun)
	{
		nextTaskToSchedule = toRun->pNext;
		(*toRun->callbackFunc)(toRun, FT_TASKACTION_RUN_ONCE);
	}

	// close the directory file in any case
	CloseDirFile();
}






////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//File Write Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


// ----------------------------------------------
//
// Handles InitFileWrite requests from comm
// modules
//
// ----------------------------------------------

uint8_t FT_InitFileWrite(uint32_t storageController, PFT_FILE_ENTRY pRequestInfo, uint8_t overwrite, uint8_t* pFirstBlock,
			uint16_t blockLength)
{
	uint8_t retval = FAIL;					// disposition of the call
	char fileName[13];						// name of the file to write to
	BOOL deleteFileOnCleanup = FALSE;		// true indicates there is a file to be deleted in case of failure, false if not
	FT_FILE_ENTRY activeEntry;				// file entry (i.e. info about the file)
	uint16_t fileEntryIndex;				// index of the file
	BOOL exists = FALSE;					// true if already exists, false if not

	// not supporting PIB controllers yet
	if ((storageController != 0) || (pRequestInfo->attributes & FTATTR_CHECKED))
		retval = NAKDISP_DATA;

	// otherwise, check the date and time stamps and if valid ...
	else
	if ( ((retval = ValidateDateTime(&pRequestInfo->creationDate)) == SUCCESS)
	   && ((retval = ValidateDateTime(&pRequestInfo->modifiedDate)) == SUCCESS) )
    {
		// save the requested name and check for reserved name
		PaddedNameToName(pRequestInfo->reportedFileName, fileName);

		if (ReservedFileName(fileName))
			retval = NAKDISP_DATA;

		// look for a match to the file ID if it's valid; otherwise, use the name
		else if (IS_VALID_FILE_ID(pRequestInfo->fileID))
			 retval = GetFileIndexByID(pRequestInfo->fileID, &fileEntryIndex);
		else
			retval = GetFileIndexByName(fileName, &fileEntryIndex);

		// if successful so far ...
		if (retval == SUCCESS)
		{
			// determine if the file eixsts
			exists = IS_VALID_FILE_INDEX(fileEntryIndex);

			// if it exists, get the entry; otherwise, mock up an entry
			if (exists)
			{
				if ((retval = FT_GetFileEntry(fileEntryIndex, &activeEntry)) == SUCCESS)
				{
					if (overwrite)
					{
						if ((activeEntry.fileType & FT_FILETYPE_PROGRAM_IN_USE) || IsOperationLocked(fileEntryIndex))
							retval = NAKDISP_FILE_IN_USE;

						else if (activeEntry.attributes & FTATTR_READ_ONLY)
							retval = NAKDISP_READ_ONLY;
					}

					else
					{
						retval = PermuteFilename(fileName);
						exists = FALSE;
					}
				}
			}

			// if still successful and does not exist, mock up an entry for it
			if ((retval == SUCCESS) && !exists)
			{
				ZERO_OBJ(activeEntry);
				COPY_OBJ(activeEntry.reportedFileName, pRequestInfo->reportedFileName);
				strcpy(activeEntry.storedFileName, fileName);
				activeEntry.fileID = pRequestInfo->fileID;
				activeEntry.fileSize = pRequestInfo->fileSize;
				activeEntry.attributes = pRequestInfo->attributes;
				COPY_OBJ(activeEntry.creationDate, pRequestInfo->creationDate);
				COPY_OBJ(activeEntry.modifiedDate, pRequestInfo->modifiedDate);
				activeEntry.providedCRC = pRequestInfo->providedCRC;
				COPY_OBJ(activeEntry.providedMD5, pRequestInfo->providedMD5);
				activeEntry.fileType = FT_FILETYPE_DATA;
				
				if ((retval = PermuteFilename(activeEntry.storedFileName)) == SUCCESS)
					NameToPaddedName(activeEntry.storedFileName, activeEntry.reportedFileName);
			}
		}
		
		// if successful so far, then create a file or truncate if it exists, then write the first block if if there is any
		// data accompanying the initialize
		if (retval == SUCCESS)
		{
			if ((retval = CreateOrTruncate(&fileEntryIndex, &activeEntry)) == SUCCESS)
			{
				deleteFileOnCleanup = TRUE;

				if (blockLength != 0)
					retval = FT_WriteFileBlock(0, activeEntry.fileID, 0, pFirstBlock, blockLength);
			}
		}
	}

	// if failed and there is a file to be cleaned up, then
	// delete it
	if ((retval != SUCCESS) && deleteFileOnCleanup)
		DeleteFileByIndex(fileEntryIndex, FALSE, TRUE);

	CloseDirFile();						// ensure the directory file is closed

	return retval;
}


// ----------------------------------------------
//
// Handles WriteFileBlock requests from comm
// modules
//
// ----------------------------------------------

uint8_t FT_WriteFileBlock(uint32_t storageController, uint32_t fileID, uint32_t offset, uint8_t* pBlock, uint16_t blockLength)
{
	uint8_t retval = FAIL;					// disposition code returned
	uint16_t fileEntryIndex;				// index of the file
	FT_FILE_ENTRY fileEntry;				// entry for the file

	// not handling PIB controllers yet, must be a valid file ID
	if ((storageController != 0) || !IS_VALID_FILE_ID(fileID))
		retval = NAKDISP_DATA;

	// look up the file by ID and write to it
	else if ((retval = GetFileIndexByID(fileID, &fileEntryIndex)) == SUCCESS)
	{
		if (!IS_VALID_FILE_INDEX(fileEntryIndex))
			retval = NAKDISP_DATA;
	}

	// get the entry for the file and nak if offset and size aren't right
	if ((retval == SUCCESS)
	 && ((retval = FT_GetFileEntry(fileEntryIndex, &fileEntry)) == SUCCESS))
	{
		if ((offset >= fileEntry.fileSize) || (blockLength > fileEntry.fileSize) || ((fileEntry.fileSize - offset) < blockLength))
			retval = NAKDISP_DATA;

		else if ((fileEntry.attributes & (FTATTR_READ_ONLY | FTATTR_CHECKED)) == (FTATTR_READ_ONLY | FTATTR_CHECKED))
			retval = NAKDISP_READ_ONLY;

		else if (fileEntry.fileType & FT_FILETYPE_PROGRAM_IN_USE)
			retval = NAKDISP_FILE_IN_USE;

		else
		{
			if (fileEntry.attributes & FTATTR_CHECKED)
			{
				tm* pTime;
				time_t time = gettime();
				pTime = gmtime(&time);
				fileEntry.modifiedDate.year = pTime->tm_year;		//timestamp.year;
				fileEntry.modifiedDate.month = pTime->tm_mon;		//timestamp.month;
				fileEntry.modifiedDate.day = pTime->tm_mday;		//timestamp.day;
				fileEntry.modifiedDate.hour = pTime->tm_hour;		//timestamp.hour;
				fileEntry.modifiedDate.minute = pTime->tm_min;		//timestamp.minute;
				fileEntry.modifiedDate.second = pTime->tm_sec;		//timestamp.second;

				fileEntry.attributes &= ~FTATTR_CHECKED;

				retval = FT_SetFileEntry(fileEntryIndex, &fileEntry);
			}

			if (retval == SUCCESS)
			{
				uint16_t bytesWritten = 0;

				retval = ConvertSDError(SD_fwriteat_atomic(fileEntry.storedFileName, (char*) pBlock, offset, blockLength, &bytesWritten));

				if ((retval == SUCCESS) && (bytesWritten < blockLength))
					retval = NAKDISP_STORAGE_FULL;
			}
		}
	}

	CloseDirFile();						// ensure the directory file is closed
	return retval;
}






////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//File Read Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------
//
// Handles FileRead requests from comm modules
//
// ----------------------------------------------

uint8_t FT_StartFileRead(uint32_t storageController, char* pFileName11, uint32_t originalTransactionID, uint32_t offset, uint32_t count,
				uint16_t maxResponseSize, uint32_t transactionID, uint8_t completionType, uint16_t fromAddress)
{
	uint8_t retval = FAIL;					// disposition code for call
	char fileName[13];						// actual file name to delete
	uint16_t fileEntryIndex;				// file index for the file
	FT_SPECIAL special = FT_SPECIAL_NOT;	// type of file being read
	PFT_TASK pTask = NULL;					// task allocated to report results
	FT_FILE_ENTRY fileEntry;				// file entry for the file
	
//JGK	// not handling PIB case yet
//JGK	if ((storageController != 0) || (maxResponseSize && ((ACK_READ_DATA_OVERHEAD + FT_MIN_RESPONSE_DATA) > maxResponseSize)))
//JGK		retval = NAKDISP_DATA;
//JGK
//JGK	// otherwise ...
//JGK	else
	{
		// create the full name
		PaddedNameToName(pFileName11, fileName);

		// translate the filename into a "special" type if necessary
		if (!strcmp_P(fileName, eepromName))
			special = FT_SPECIAL_EEPROM;
		else if (!strcmp_P(fileName, programMemoryName))
			special = FT_SPECIAL_PROGMEM;
		else if (!strcmp_P(fileName, ramName))
			special = FT_SPECIAL_RAM;

		// for regular files, do not report reserved cases; otherwise, look up the file
		// by name
		if (special == FT_SPECIAL_NOT)
		{
			if (ReservedFileName(fileName))
				retval = NAKDISP_DATA;
			
			else
			{
				if (((retval = GetFileIndexByName(fileName, &fileEntryIndex)) == SUCCESS)
				 && !IS_VALID_FILE_INDEX(fileEntryIndex))
					
					retval = NAKDISP_DATA;
			}
		}

		// if successful so far, allow an existing command that is waiting for transaction
		// close to re-request sections of the file
		BOOL resumeTask = FALSE;

		if (retval == SUCCESS)
		{
			if (special == FT_SPECIAL_NOT)
				retval = FT_GetFileEntry(fileEntryIndex, &fileEntry);

			if ((retval == SUCCESS) && (originalTransactionID != 0xFFFFFFFF))
			{
				pTask = allocatedTasks;

				while(pTask)
				{
					if (transactionID == pTask->transactionID)
					{
						if ((pTask->taskType != FT_TASK_TYPE_READ)
						 || ((special == FT_SPECIAL_NOT) && (fileEntryIndex != pTask->fileEntryIndex)))
						
						{
							pTask = NULL;
							retval = NAKDISP_DATA;
						}

						else if (!pTask->closePending)
						{
							pTask = NULL;
							retval = NAKDISP_TOO_MANY_COMMANDS;
						}

						else
							resumeTask = TRUE;

						break;
					}

					pTask = pTask->pNext;
				}
			}
		}

		// if successful so far, then allocate a task (if not reusing); bounds check the request size
		if ((retval == SUCCESS) && !pTask)
			retval = AllocateTask(&pTask);

		if (retval == SUCCESS)
		{
			pTask->special = special;
			pTask->fileEntryIndex = fileEntryIndex;
			pTask->taskType = FT_TASK_TYPE_READ;
			pTask->taskState = FT_TASK_STATE_TX_HOST;
			pTask->storageController = storageController;
			pTask->offset = offset;
			pTask->endOffset = offset+count;
			pTask->respSize = !maxResponseSize ? sizeof(COMM_MGR_TX_BUFFER) : MIN(sizeof(COMM_MGR_TX_BUFFER), maxResponseSize);
			pTask->completionType = completionType;
			pTask->callbackFunc = FT_ReadTask;
			strcpy(pTask->storedFileName, fileEntry.storedFileName);
			pTask->fileID = fileEntry.fileID;
			pTask->transactionID = (originalTransactionID == 0xFFFFFFFF) ? transactionID : originalTransactionID;
			pTask->readFileAckTransID = transactionID;
			pTask->closePending = FALSE;

			if (count == 0)
			{
				uint32_t size = SizeOfObject(pTask);

				if (offset > size)
					retval = NAKDISP_DATA;
				else
					pTask->endOffset = size;
			}

			else if (!ReadTaskBoundsCheck(pTask))
				retval = NAKDISP_DATA;

			if ((retval == SUCCESS) && !resumeTask)
				retval = StartTask(pTask);
		}
	}

	// deallocate the task if allocated but not successful
	if ((retval != SUCCESS) && pTask)
	{
		StopTask(pTask);
		DeallocateTask(pTask);
	}

	CloseDirFile();						// ensure the directory file is closed
	return retval;
}


// ----------------------------------------------
//
// Performs actions for the asynchronous read
// file task
//
// ----------------------------------------------

uint8_t FT_ReadTask(PFT_TASK pTask, FT_TASKACTION taskAction)
{
	uint8_t retval = FAIL;

	switch(taskAction)
	{
		case FT_TASKACTION_START:
			{
				retval = PlaceOperationLock(pTask->fileEntryIndex);
				break;
			}
		case FT_TASKACTION_STOP:
			{
				retval = ReleaseOperationLock(pTask->fileEntryIndex);
				break;
			}
		case FT_TASKACTION_RUN_ONCE:
			{
//JGK				uint16_t numSlotsAvl, numSlots;

				// if the file is in closing state, verify if the task should be deallocated
				if (pTask->closePending)
				{
//JGK					if ((getSystemTickCount()-pTask->enteredClosingState) >= FT_MAX_CLOSE_PENDING_TIME)
					{
						// alarm set affects the cache which will be reported immediately; clearing it means
						// that after telemtry report the alarm will be cleared in the cache
//JGK						uint32_t alarmID = ALARM_READ_TRANS_TIMEOUT;
//JGK						MgrFloatAlarmEntry(MSG_SET_ALARM, &alarmID, sizeof(alarmID), NULL, 0, NULL, NULL);
//JGK						MgrFloatAlarmEntry(MSG_CLR_ALARM, &alarmID, sizeof(alarmID), NULL, 0, NULL, NULL);

						StopTask(pTask);
						DeallocateTask(pTask);
					}
				}

				// wait to run until the transmit task is idle
				else if (1)		//JGK should be --> (((retval = MgrFloatGetTXQueueStatus(pTask->completionType, &numSlotsAvl, &numSlots)) != SUCCESS) || (numSlots == numSlotsAvl))
				{
					
					COMM_MGR_TX_BUFFER tx;											// transmit buffer
					uint16_t maxRead = pTask->respSize - ACK_READ_DATA_OVERHEAD;	// maximum amount to read into buffer
					uint32_t countLeft = pTask->endOffset - pTask->offset;			// count left to read for request
					uint16_t countToRead = MIN(countLeft, maxRead);					// count to actually read this time
					uint16_t *pCRC;													// points to the CRC variable

					// if there's more to send for this file ...
					if (countToRead > 0)
					{
						// read the next chunk of data for normal files
						if (pTask->special == FT_SPECIAL_NOT)
							retval = ConvertSDError(SD_freadat_atomic(pTask->storedFileName, (char*) tx.ackReadFile.data,
											pTask->offset, countToRead, &tx.ackReadFile.numDataBytes));

						// all other types return zeros for data ## TODO: implement this section
						else
						{
							retval = SUCCESS;
							tx.ackReadFile.numDataBytes = pTask->respSize;
							memset(tx.ackReadFile.data, '\0', countToRead);
						}
					}
					else
					tx.ackReadFile.numDataBytes = 0;

					// if successful on the read ...
					if ((retval == SUCCESS) && (countLeft >= 0))
					{
						// fill out the header
						tx.header.type = ACK_WITH_READ_FILE;
						tx.header.packetID = pTask->readFileAckTransID;
						//tx.header.size = sizeof(tx.ackReadFile) - sizeof(tx.ackReadFile.data) + tx.ackReadFile.numDataBytes;
						tx.ackReadFile.storageControllerAddress = pTask->storageController;
						tx.ackReadFile.seekOffset = pTask->offset;
						//DEBUG_PRINT(DBG_CRITICAL, "Read offset %ld\r\n", pTask->offset);


						// in the case where nothing returned from read, I don't want to keep spinning, so I'm just ending here;
						// otherwise, send the eof flag according to the position in the file
						if (tx.ackReadFile.numDataBytes == 0)
						{
							tx.ackReadFile.eofFlag = 0xFF;
							pTask->offset = pTask->endOffset;
						}
						else
							tx.ackReadFile.eofFlag = ((pTask->offset + tx.ackReadFile.numDataBytes) >= pTask->endOffset) ? 0xFF : 0;

						// set the offset just above the end offset if EOF to indicate no more records should be reported;
						// otherwise, just advance to the start of the next record
						if (tx.ackReadFile.eofFlag)
						{
							pTask->offset = pTask->endOffset;
							pTask->closePending = TRUE;
//JGK							pTask->enteredClosingState = getSystemTickCount();
						}
						else
							pTask->offset += tx.ackReadFile.numDataBytes;

						// adjust the location of the checksum and write the checksum
						pCRC = (uint16_t*) &tx.ackReadFile.data[tx.ackReadFile.numDataBytes];
						tx.header.size = ((uint8_t*) pCRC)-((uint8_t*) &tx.ackReadFile);
						*pCRC = CalculateCRC16((uint8_t*) &tx, ((uint8_t*) pCRC)-((uint8_t*) &tx), OTHER_CRC_START);

						// send the message
//JGK						MgrFloatCommunicationSend(&tx, ((uint8_t*) &pCRC[1])-((uint8_t*) &tx), pTask->completionType);
					}
				}

				break;
			}
	}

	return retval;
}


// ----------------------------------------------
//
// Handles ReadFileTransactionClosed messages
// from comm modules
//
// ----------------------------------------------

uint8_t FT_ReadFileTransactionClosed(uint32_t storageController, uint32_t transactionID)
{
	uint8_t retval = FAIL;
	PFT_TASK pTask = allocatedTasks;
	BOOL found = FALSE;

	while(!found && pTask)
		if ((pTask->taskType == FT_TASK_TYPE_READ) && (pTask->transactionID == transactionID))
			found = TRUE;
		else
			pTask = pTask->pNext;

	if (found)
	{
		StopTask(pTask);
		DeallocateTask(pTask);
		retval = SUCCESS;
	}

	CloseDirFile();						// ensure the directory file is closed
	return retval;
}



// ----------------------------------------------
//
// Performs a bounds check on the given task
//
// ----------------------------------------------

static BOOL ReadTaskBoundsCheck(PFT_TASK pTask)
{
	if (pTask->offset >= pTask->endOffset)
		return FALSE;

	return pTask->offset < SizeOfObject(pTask);
}

// ----------------------------------------------
//
// Returns the total size of the item referenced
// by the given task
//
// ----------------------------------------------

static uint32_t SizeOfObject(PFT_TASK pTask)
{
	uint32_t size = 0;

	switch(pTask->special)
	{
		case FT_SPECIAL_NOT:
			{
				FT_FILE_ENTRY fileEntry;
				uint8_t retval = FT_GetFileEntry(pTask->fileEntryIndex, &fileEntry);

				size = (retval == SUCCESS) ? fileEntry.fileSize : 0;
				break;
			}

		case FT_SPECIAL_PROGMEM:		size = 0x40000; break;
		case FT_SPECIAL_EEPROM:			size= 0x10000;	break;
		case FT_SPECIAL_RAM:			size = 0xFE00;	break;
	}

	return size;
}




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Directory Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


// ----------------------------------------------
//
// Handles GetFileList (normal and detailed)
// requests from comm modules
//
// ----------------------------------------------

uint8_t FT_StartGetFileList(uint32_t storageController, char* pFileName11, uint32_t fileID, BOOL checkFiles, BOOL deleteIfFailed,
				BOOL detailedList, uint16_t maxResponseSize, uint8_t completionType, uint32_t transactionID)
{
	uint8_t retval = FAIL;			// the disposition to return
	uint16_t fileEntryIndex;		// file entry index of the specified file
	char fileName[13];				// the name of the specified file if using the name
	FT_FILE_ENTRY fileEntry;		// a file entry for the file being checked
	PFT_TASK pTask = NULL;			// a task that was allocated to report the results
	char tempFileName[13];			// name of temporary file allocated to hold the directory copy
	uint16_t tempFileIndex = INVALID_FILE_INDEX; // index of temporary file

	// temp file will have a copy of the directory to operate on
	ZERO_OBJ(tempFileName);

	// not supporting PIB storage controllers yet
	if ((storageController != 0) || (maxResponseSize && (maxResponseSize < ACK_GET_FILE_MINSIZE(detailedList))))
		retval = NAKDISP_DATA;

	// if it's a valid file ID, then it must exist
	else if (IS_VALID_FILE_ID(fileID))
	{
		if (((retval = GetFileIndexByID(fileID, &fileEntryIndex)) == SUCCESS)
		 && !IS_VALID_FILE_INDEX(fileEntryIndex))

			retval = NAKDISP_DATA;
	}
	
	// if it's not a valid file ID, then it must not be a reserved name; if it doesn't
	// match anything it will be ignored
	else
	{
		PaddedNameToName(pFileName11, fileName);

		if (fileName[0])
		{
			if (ReservedFileName(fileName))
				retval = NAKDISP_DATA;
			else if (((retval = GetFileIndexByName(fileName, &fileEntryIndex)) == SUCCESS)
				  && !IS_VALID_FILE_INDEX(fileEntryIndex))
				retval = NAKDISP_DATA;
		}
		else
		{
			fileEntryIndex = INVALID_FILE_INDEX;
			retval = SUCCESS;
		}
	}

	// if checking files, check the specific file if a file entry index was found; otherwise,
	// check all files; save the indices that need to be transferred as a result of a successful
	// check
	if ((retval == SUCCESS)
	 && checkFiles)
	{
		BOOL schedule = FALSE;

		if (IS_VALID_FILE_INDEX(fileEntryIndex))
		{
			if ((retval = FT_GetFileEntry(fileEntryIndex, &fileEntry)) == SUCCESS)
				retval = CheckFile(fileEntryIndex, &fileEntry, deleteIfFailed);
		}		
		else
		{
			uint16_t feIdx;
			uint8_t status;

			for (feIdx = 0; feIdx < totalIndexedFiles; ++feIdx)
			{
				status = FT_GetFileEntry(feIdx, &fileEntry);

				if (fileEntry.inUse && (fileEntry.fileType == FT_FILETYPE_DATA))
				{
					schedule = FALSE;

					status = CheckFile(feIdx, &fileEntry, deleteIfFailed);

					if ((retval == SUCCESS) && (status != SUCCESS))
						retval = status;
				}
			}
		}
	}

	// if succeeded, then copy the directory to a temporary name and allocate a task for it, and start
	// the task
	if ((retval == SUCCESS)
	 && ((retval = FindTempFileName(tempFileName)) == SUCCESS)
	 && (CloseDirFile(),
	 	 strcpy_P(fileName, dirFile),
	     (retval = CopyFile(fileName, tempFileName, FT_FILETYPE_TEMP_DIRECTORY, &tempFileIndex)) == SUCCESS)
     && ((retval = AllocateTask(&pTask)) == SUCCESS))
	{
		pTask->special = FT_SPECIAL_NOT;
		strcpy(pTask->storedFileName, tempFileName);
		pTask->fileEntryIndex = tempFileIndex;
		pTask->fileEntryIndex2 = fileEntryIndex;
		pTask->taskType = detailedList ? FT_TASK_TYPE_GET_DETAILED_DIRECTORY : FT_TASK_TYPE_GET_DIRECTORY;
		pTask->taskState = FT_TASK_STATE_TX_HOST;
		pTask->storageController = storageController;
		pTask->offset = 0;
		pTask->endOffset = 0;
		pTask->respSize = !maxResponseSize ? ACK_GET_FILE_DEF_SIZE(detailedList) : MIN(ACK_GET_FILE_DEF_SIZE(detailedList), maxResponseSize);
		pTask->completionType = completionType;
		pTask->callbackFunc = FT_GetListTask;
		pTask->transactionID = transactionID;

		retval = StartTask(pTask);
	}

	// if not successful and a task was allocated, stop it and deallocate it
	if ((retval != SUCCESS) && pTask)
	{
		StopTask(pTask);
		DeallocateTask(pTask);
	}

	// if not successful and a temporary file was allocated, delete it
	if ((retval != SUCCESS) && tempFileName[0])
		DeleteFileByIndex(tempFileIndex, TRUE, TRUE);

	CloseDirFile();						// ensure the directory file is closed
	return retval;
}



// ----------------------------------------------
//
// Checks a file's content vs. the provided
// CRC and MD5 signature
//
// ----------------------------------------------

static uint8_t CheckFile(uint16_t fileEntryIndex, PFT_FILE_ENTRY pFileEntry, BOOL deleteIfFailed)
{
	int8_t handle;						// handle to the file to be read
	uint8_t status;						// status from the SD card API's
	uint8_t retval = SUCCESS;			// return value for this function
	uint8_t retval2;					// second return value temporary holding register

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "CheckFile %s\r\n", pFileEntry->storedFileName);
#endif

	// if the file open is successful ...
	if ((status = SD_fopen(pFileEntry->storedFileName, SD_OPEN_EXISTING | SD_READ, &handle)) == SD_OK)
	{
		BOOL eof = FALSE;					// set when end of file reached
		MD5_CTX md5ctx;						// the context used for incrementally computing MD5 digest
		uint16_t crc = OTHER_CRC_START;		// iniitalize the CRC
		uint16_t bytesRead;					// number of bytes read during each pass
		uint8_t buffer[FT_MAX_BUFFER];		// buffer into which file data is read
		BOOL remove = FALSE;				// set if the file should be removed

		MD5Init(&md5ctx);					// initialize the context variable for MD5 calculation

		// read blocks of the file and accumulate the CRC and MD5
		while(!eof && ((status = SD_fread(handle, (char*) buffer, sizeof(buffer), &bytesRead)) == SD_OK))
		{
			eof = (bytesRead != sizeof(buffer));

			if (bytesRead)
			{
				uint16_t idx;
				
				for (idx = 0; idx < bytesRead; ++idx)
					crc = _crc16_update(crc, buffer[idx]);
			
				MD5Update(&md5ctx, (unsigned char*) buffer, (unsigned int) bytesRead);
				wdt_reset();
			}
		}

		// finalizes the MD5 calculation
		MD5Final(&md5ctx);

		// if the disk actions succeeded ...
		if (status == SD_OK)
		{
			// determine whether the file entry has changed
			BOOL changed = (pFileEntry->computedCRC != crc)
				        || memcmp(&pFileEntry->computedMD5, md5ctx.digest, sizeof(pFileEntry->computedMD5));
			
			// store the computed values in the file entry
			pFileEntry->computedCRC = crc;
			COPY_OBJ(pFileEntry->computedMD5, md5ctx.digest);

			// update the return code
			if (pFileEntry->computedCRC != pFileEntry->providedCRC)
				retval = NAKDISP_CRC_FAILED;
			else if (memcmp(&pFileEntry->computedMD5, &pFileEntry->providedMD5, sizeof(pFileEntry->computedMD5)))
				retval = NAKDISP_MD5_FAILED;
			else
				retval = SUCCESS;

			// set the flag for file removal
			remove = (retval != SUCCESS);

			// update the checked flag
			if ((retval == SUCCESS) != !!(pFileEntry->attributes & FTATTR_CHECKED))
			{
				changed = TRUE;

				if (retval == SUCCESS)
					pFileEntry->attributes |= FTATTR_CHECKED;
				else
					pFileEntry->attributes &= ~FTATTR_CHECKED;
			}

			// if anything in the header changed, write the file entry again
			if (changed)
			{
				retval2 = FT_SetFileEntry(fileEntryIndex, pFileEntry);

				if (retval == SUCCESS)
					retval = retval2;
			}
		}

		// decode the disk error
		if ((retval == SUCCESS) && (status != SD_OK))
			retval = ConvertSDError(status);

		// close the file
		retval2 = ConvertSDError(SD_fclose(handle));

		if (retval == SUCCESS)
			retval = retval2;
	
		// if the file is to be removed, delete it
		if (remove && deleteIfFailed)
		{
			retval2 = DeleteFileByIndex(fileEntryIndex, FALSE, TRUE);

			if (retval == SUCCESS)
				retval = retval2;
		}
	}

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "CheckFile: %u\r\n", retval);
#endif

	return retval;
}


// ----------------------------------------------
//
// Code to handle a temporary file name
//
// ----------------------------------------------

static uint8_t FindTempFileName(char* lpszTempFileName)
{
	uint16_t numCache[100];		// numbers in use for this name
	uint8_t numCacheCount = 0;	// count of numbers in the cache
	uint16_t fileIndex;			// file index used for searching
	FT_FILE_ENTRY fileEntry;	// file entry used for search
	uint8_t tempNum;			// number to use when constructing the filename
	BOOL found = FALSE;			// true an open index found
	char numBuf[4];				// formats the number for output
	uint8_t retval = SUCCESS;	// return value from this function

	// construct a list of used cache numbers
	for (fileIndex = 0; (retval == SUCCESS) && (numCacheCount < 100) && (fileIndex < totalIndexedFiles); ++fileIndex)
	{
		if (((retval = FT_GetFileEntry(fileIndex, &fileEntry)) == SUCCESS)
		 && fileEntry.inUse
		 && (strlen(fileEntry.storedFileName) == sizeof(tempHeader)+sizeof(tempSuffix))
		 && !strncmp(fileEntry.storedFileName, tempHeader, sizeof(tempHeader)-1)
		 && !strcmp(&fileEntry.storedFileName[sizeof(tempHeader)+1], tempSuffix))
		{
			char* endPtr = NULL;
			unsigned long num = strtoul(&fileEntry.storedFileName[sizeof(tempHeader)-1], &endPtr, 10);

			if (!strcmp(endPtr, tempSuffix))
				numCache[numCacheCount++] = (uint16_t) num;
		}
	}
	
	// if successful so far ...
	if (retval == SUCCESS)
	{
		// now determine the next available number
		for (tempNum = 0; !found && (tempNum < 100); ++tempNum)
		{
			uint8_t numIdx;

			for (numIdx = 0; (numIdx < numCacheCount) && (numCache[numIdx] != tempNum); ++numIdx)
				;

			found = (numIdx == numCacheCount);
		}

		// if a number was found, format the file name
		if (found)
		{
			strcpy_P(lpszTempFileName, tempHeader);
			ultoa(100+tempNum, numBuf, 10);
			strcat(lpszTempFileName, &numBuf[1]);
			strcat_P(lpszTempFileName, tempSuffix);
		}
		else
			retval = NAKDISP_STORAGE_FULL;
	}

	return retval;
}



// ----------------------------------------------
//
// Copies a file, creating/overwriting new file
// entries
//
// ----------------------------------------------

static uint8_t CopyFile(const char* lpszSource, const char* lpszDest, FT_FILETYPE fileType, uint16_t* pNewFileIndex)
{
	uint8_t retval = FAIL;								// function return code
	uint8_t retval2;									// second holding register for return code
	uint16_t sourceFileIndex = INVALID_FILE_INDEX;		// index of the source file
	FT_FILE_ENTRY sourceFileEntry;						// entry for the source file
	FT_FILE_ENTRY destFileEntry;						// entry for the dest file
	int8_t sourceHandle;								// handle of the source file
	int8_t destHandle;									// handle of the dest file
	BOOL cleanupFile = FALSE;							// true if the file needs to be deleted on failure

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "CopyFile %s %s\r\n", lpszSource, lpszDest);
#endif

	*pNewFileIndex = INVALID_FILE_INDEX;				// assume invalid index

	// get the index of the source file
	if (((retval = GetFileIndexByName(lpszSource, &sourceFileIndex)) == SUCCESS)
	 && !IS_VALID_FILE_INDEX(sourceFileIndex))
	
		retval = NAKDISP_DATA;

	// if success, then get the file entry for the source and the index of the dest,
	// and if successful ...
	if ((retval == SUCCESS)
	 && ((retval = FT_GetFileEntry(sourceFileIndex, &sourceFileEntry)) == SUCCESS)
	 && ((retval = GetFileIndexByName(lpszDest, pNewFileIndex)) == SUCCESS))
	{
		// copy the source attributes
		COPY_OBJ(destFileEntry, sourceFileEntry);

		// make modifications to some fields
		destFileEntry.inUse = TRUE;
		strcpy(destFileEntry.storedFileName, lpszDest);
		NameToPaddedName(destFileEntry.storedFileName, destFileEntry.reportedFileName);
		destFileEntry.attributes &= ~FTATTR_CHECKED;
		destFileEntry.fileType = fileType;
		destFileEntry.copiedTime = (time_t) 0;
		destFileEntry.targetHost = 0;

		// create the destination file, and if successful ...
		if ((retval = CreateOrTruncate(pNewFileIndex, &destFileEntry)) == SUCCESS)
		{
			// flag as needing to clean up
			cleanupFile = TRUE;

			// open the source file, and if successful ...
			if ((retval = ConvertSDError(SD_fopen(sourceFileEntry.storedFileName, SD_OPEN_EXISTING | SD_READ, &sourceHandle))) == SUCCESS)
			{
				/// open the destination file, and if successful ...
				if ((retval = ConvertSDError(SD_fopen(destFileEntry.storedFileName, SD_OPEN_EXISTING | SD_WRITE, &destHandle))) == SUCCESS)
				{
					uint8_t buffer[FT_MAX_BUFFER];	// buffer to hold data read
					uint32_t offset = 0;			// current offset in file
					uint16_t countRead;				// number of bytes read
					uint16_t countWritten;			// number of bytes written

					// while no error indicated and within the size of the file ...
					while((retval == SUCCESS) && (offset < sourceFileEntry.fileSize))
					{
						// read some data, and if successful ...
						if ((retval = ConvertSDError(SD_fread(sourceHandle, (char*) buffer, sizeof(buffer), &countRead))) == SUCCESS)
						{
							// flag an error if the count read is 0
							if (countRead == 0)
								retval = NAKDISP_STORAGE_IO_ERROR;
					
							// otherwise, write the buffer back to the destination file, and if successful ...
							else if ((retval = ConvertSDError(SD_fwrite(destHandle, (char*) buffer, countRead, &countWritten))) == SUCCESS)
							{
								// consider the disk full if not all bytes written
								if (countWritten != countRead)
									retval = NAKDISP_STORAGE_FULL;

								// otherwise, bump to the next offset
								else
									offset += countRead;
							}
						}
					}

					// close the destination file
					retval2 = ConvertSDError(SD_fclose(destHandle));

					if (retval == SUCCESS)
						retval = retval2;
				}

				// close the source file
				retval2 = ConvertSDError(SD_fclose(sourceHandle));

				if (retval == SUCCESS)
					retval = retval2;
			}

			// delete the file if there was any error
			if ((retval != SUCCESS) && cleanupFile)
			{
				destFileEntry.inUse = FALSE;
				FT_SetFileEntry(*pNewFileIndex, &destFileEntry);
				SD_delete(destFileEntry.storedFileName);
			}
		}
	}

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "CopyFile: %u\r\n", retval);
#endif
	return retval;
}



// ----------------------------------------------
//
// Performs actions for the asynchronous get file
// list task
//
// ----------------------------------------------

uint8_t FT_GetListTask(PFT_TASK pTask, FT_TASKACTION taskAction)
{
	uint8_t retval = FAIL;

	switch(taskAction)
	{
		case FT_TASKACTION_START:
			{
				retval = PlaceOperationLock(pTask->fileEntryIndex);
				break;
			}
		case FT_TASKACTION_STOP:
			{
				uint8_t retval2;

				retval = ReleaseOperationLock(pTask->fileEntryIndex);
				retval2 = DeleteFileByIndex(pTask->fileEntryIndex, TRUE, TRUE);

				if (retval == SUCCESS)
					retval = retval2;

				break;
			}
		case FT_TASKACTION_RUN_ONCE:
			{
				if (pTask->taskType == FT_TASK_TYPE_TRANSFER_FILE)
					retval = GetListTask_ReportAtDest(pTask);
				else
					retval = GetListTask_Directory(pTask);

				break;
			}
	}

	return retval;
}

// ----------------------------------------------
//
// Reports file at destination for completed
// files
//
// ----------------------------------------------

static uint8_t GetListTask_ReportAtDest(PFT_TASK pTask)
{
	uint8_t retval = FAIL;				// return value from this function
	COMM_MGR_TX_BUFFER tx;				// buffer for transmitting data
	FT_FILE_ENTRY fileEntry;			// file entry from the temp file
//JGK	uint16_t numSlots;					// number of slots total
//JGK	uint16_t numSlotsAvl;				// numer of slots available

	// wait to run until the transmit task is idle
//JGK	if (((retval = MgrFloatGetTXQueueStatus(pTask->completionType, &numSlotsAvl, &numSlots)) != SUCCESS) || (numSlots == numSlotsAvl))
	{
		uint16_t fileIndex;				// the index that will be reported

		// transmit a "file at destination message" for one storage controller 0 item
		// that has been checked, but has not reported
		for (fileIndex = 0; fileIndex < totalIndexedFiles; ++fileIndex)
		{
			if (((retval = FT_GetFileEntry(fileIndex, &fileEntry)) == SUCCESS)
			 && fileEntry.inUse
			 && (fileEntry.attributes & FTATTR_CHECKED)
			 && !fileEntry.reportedAtDest
			 && !fileEntry.storageController)
			{
				uint16_t* pCRC = &tx.ackFileAtDest.crc;
				
				tx.header.type = ACK_WITH_FILE_AT_DEST;
				tx.header.packetID = pTask->transactionID;
				tx.header.size = OFFSET_OF(ACK_FILE_AT_DEST, crc);

				tx.ackFileAtDest.storageControllerAddress = pTask->storageController;
				tx.ackFileAtDest.fileID = fileEntry.fileID;

				*pCRC = CalculateCRC16((uint8_t*) &tx, ((uint8_t*) pCRC)-((uint8_t*) &tx), OTHER_CRC_START);
//JGK				MgrFloatCommunicationSend(&tx, ((uint8_t*) &pCRC[1])-((uint8_t*) &tx), pTask->completionType);

				fileEntry.reportedAtDest = TRUE;
				FT_SetFileEntry(fileIndex, &fileEntry);

				break;
			}
		}
		
		// deallocate the task if nothing found
		if (fileIndex >= totalIndexedFiles)
			DeallocateTask(pTask);
	}

	return retval;
}


// ----------------------------------------------
//
// Performs reporting of directory entries
//
// ----------------------------------------------

uint8_t GetListTask_Directory(PFT_TASK pTask)
{
	uint8_t retval = FAIL;				// return value from this function
	COMM_MGR_TX_BUFFER tx;				// buffer for transmitting data
	FT_FILE_ENTRY fileEntry;			// file entry from the temp file
	uint8_t status;						// status from SD call
	uint16_t* pCRC;						// pointer to CRC
	int8_t handle;						// handle of temp file
	BOOL handleOpen = FALSE;			// true if the handle is successfully opened
	uint16_t recordsThisFile = 0;		// records available in this file
//JGK	uint16_t numSlots;					// number of slots total
//JGK	uint16_t numSlotsAvl;				// numer of slots available

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_MESSAGE(DBG_ALL, "FT_GetListTask\r\n");
#endif

	// wait to run until the transmit task is idle
//JGK	if (((retval = MgrFloatGetTXQueueStatus(pTask->completionType, &numSlotsAvl, &numSlots)) != SUCCESS) || (numSlots == numSlotsAvl))
	{
		// compute the maximum number of records that can be returned given the response size
		uint16_t maxRecords = (pTask->taskType == FT_TASK_TYPE_GET_DIRECTORY) ? ACK_GET_FILE_MAX_RECORDS(pTask->respSize) : ACK_GET_FILE_D_MAX_RECORDS(pTask->respSize);

		// zero out the transmit buffer
		ZERO_OBJ(tx);

		// open the file that holds the directory info
		status = SD_fopen(pTask->storedFileName, SD_READ | SD_OPEN_EXISTING, &handle);
		retval = ConvertSDError(status);

		// if successful, read the number of directory records in the file
		if (retval == SUCCESS)
		{
			handleOpen = TRUE;
		
			if (IS_VALID_FILE_INDEX(pTask->fileEntryIndex2))
				recordsThisFile = 1;
			else
			{
				FILEINFO fi;

				status = SD_getFileInfo(pTask->storedFileName, &fi);
				retval = ConvertSDError(status);
			 
				if (retval == SUCCESS)
					recordsThisFile = fi.fsize/FILE_ENTRY_LINE_LENGTH;
			}
		}

		// initialize the header of the message
		if (pTask->taskType == FT_TASK_TYPE_GET_DIRECTORY)
		{
			tx.header.type = ACK_WITH_FILE_LIST;
		}
		else
		{
			tx.header.type = ACK_WITH_LIST_DETAILED;
		}

		tx.header.packetID = pTask->transactionID;
		tx.ackFileListCommon.storageControllerAddress = pTask->storageController;
		tx.ackFileListCommon.offset = pTask->offset;
		tx.ackFileListCommon.lastReport = 0;
		tx.ackFileListCommon.numFiles = 0;

		pCRC = (uint16_t*) &(&tx.ackFileListCommon)[1];

		// while not too many records returned (I'm allowing one more so that the file is advanced to the next valid
		// return and I can see if there will be more records available) and more records in the file and successful ...
		while((tx.ackFileListCommon.numFiles <= maxRecords) && (pTask->endOffset < recordsThisFile) && (retval == SUCCESS))
		{
			// get the file entry from the open file
			if (IS_VALID_FILE_INDEX(pTask->fileEntryIndex2))
			{
				retval = FT_GetFileEntryFromHandle(pTask->fileEntryIndex2, &fileEntry, handle, pTask->storedFileName);
				++pTask->endOffset;
			}
			else
				retval = FT_GetFileEntryFromHandle(pTask->endOffset++, &fileEntry, handle, pTask->storedFileName);

			if (retval == SUCCESS)
			{
				// if the entry i sin use and valid to return (one stored in this file system) ...
				if (IS_VALID_FILE_INDEX(pTask->fileEntryIndex2) || (fileEntry.inUse && (fileEntry.fileType == FT_FILETYPE_DATA)))
				{
					// if not too many records to store, store the record; otherwise, move back and
					// break (this will be the first record returned on the next read)
					if (tx.ackFileListCommon.numFiles < maxRecords)
					{
						if (pTask->taskType == FT_TASK_TYPE_GET_DIRECTORY)
						{
							PACK_FILE_LIST_RECORD pRecord =
									&tx.ackGetFileList.records[tx.ackFileListCommon.numFiles++];
							COPY_OBJ(pRecord->fileName, fileEntry.reportedFileName);
							pRecord->fileID = fileEntry.fileID;
							pRecord->fileSize = fileEntry.fileSize;
							pRecord->fileAttributes = fileEntry.attributes;
							pCRC = (uint16_t*) &pRecord[1];
						}
						else
						{
							PACK_FILE_LIST_DETAILED_RECORD pRecord =
									&tx.ackGetDetailedFileList.records[tx.ackFileListCommon.numFiles++];
							COPY_OBJ(pRecord->fileName, fileEntry.reportedFileName);
							pRecord->fileID = fileEntry.fileID;
							pRecord->fileSize = fileEntry.fileSize;
							COPY_OBJ(pRecord->creationDate, fileEntry.creationDate);
							COPY_OBJ(pRecord->modifiedDate, fileEntry.modifiedDate);
							pRecord->fileAttributes = fileEntry.attributes;
							pRecord->providedCRC = fileEntry.providedCRC;
							COPY_OBJ(pRecord->providedMD5, fileEntry.providedMD5);
							pRecord->calculatedCRC = fileEntry.computedCRC;
							COPY_OBJ(pRecord->calculatedMD5, fileEntry.computedMD5);
							pCRC = (uint16_t*) &pRecord[1];
						}

						++pTask->offset;			// bumps the number of records read total
					}
					else
					{
						--pTask->endOffset;
						break;
					}
				}
			}
		}
		
		// set the "last report" flag if appropriate; close the file and deallocate the task if ready
		if ((retval != SUCCESS) || (pTask->endOffset >= recordsThisFile))
		{
			tx.ackFileListCommon.lastReport = 0xFF;
		}

		// close the file if the handle is open
		if (handleOpen)
			SD_fclose(handle);

		// update the size of the message
		tx.header.size = ((uint8_t*) pCRC) - ((uint8_t*) &tx.ackFileListCommon);

		// write the CRC
		*pCRC = CalculateCRC16((uint8_t*) &tx, ((uint8_t*) pCRC)-((uint8_t*) &tx), OTHER_CRC_START);

		// send the message
//JGK		MgrFloatCommunicationSend(&tx, sizeof(tx.header)+tx.header.size, pTask->completionType);
		
		// end the task
		if (tx.ackFileListCommon.lastReport)
		{
			ReleaseOperationLock(pTask->fileEntryIndex);
			DeleteFileByIndex(pTask->fileEntryIndex, TRUE, TRUE);

			pTask->offset = 0;
			pTask->taskType = FT_TASK_TYPE_TRANSFER_FILE;
		}
	}

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "FT_GetListTask: %u\r\n", retval);
#endif

	return retval;
}











////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Delete File Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


// ----------------------------------------------
//
// Handles DeleteFile requests from comm modules
//
// ----------------------------------------------

uint8_t FT_DeleteFile(uint32_t storageController, char* pFileName11, uint32_t fileID, uint8_t overrideReadOnly)
{
	uint8_t retval = FAIL;				// disposition code for the call
	char fileName[13];					// actual name of the file
	uint16_t fileEntryIndex;			// file index of for the file

	// not supporting PIB storage yet
	if (storageController != 0)
		retval = NAKDISP_DATA;

	// if a valid file ID was specified, then look up the file by ID
	else if (IS_VALID_FILE_ID(fileID))
		 retval = GetFileIndexByID(fileID, &fileEntryIndex);
	
	// otherwise, look up by name: refuse to delete it if it's a reserved name
	else
	{
		PaddedNameToName(pFileName11, fileName);

		if (ReservedFileName(fileName))
			retval = NAKDISP_DATA;
		else
			retval = GetFileIndexByName(fileName, &fileEntryIndex);
	}

	// if successful so far, then try deleting by index, override read-only if specified; will not]
	// delete "special" files
	if (retval == SUCCESS)
		retval = DeleteFileByIndex(fileEntryIndex, FALSE, overrideReadOnly);

	CloseDirFile();						// ensure the directory file is closed
	return retval;
}




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Task Management Support Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------
//
// Allocates a task
//
// ----------------------------------------------

static uint8_t AllocateTask(PFT_TASK* ppTask)
{
	uint8_t retval;			// return status code from this function
	PFT_TASK pTask;			// points to the task that is allocated

	// get the first unallocated task, and if it's not null ...
	if ((pTask = unallocatedTasks) != NULL)
	{
		// relink unallocated tasks pointer to the next task (may be null, so no more tasks)
		unallocatedTasks = unallocatedTasks->pNext;

		// the new task is inserted at the front of the allocated tasks list
		pTask->pNext = allocatedTasks;
		allocatedTasks = pTask;

		// successful
		retval = SUCCESS;
	}

	// otherwise, no tasks available, so too many commands running at once
	else
		retval = NAKDISP_TOO_MANY_COMMANDS;

	*ppTask = pTask;		// return the pointer to the caller (will be null on failure)

	return retval;
}

// ----------------------------------------------
//
// Deallocates a task
//
// ----------------------------------------------

static void DeallocateTask(PFT_TASK pTask)
{
	PFT_TASK pSearch = allocatedTasks;		// searcher for the paramterized task
	PFT_TASK pLast = NULL;					// last task encountered during search

	// search along the allocated task chain looking for pTask
	while(pSearch && (pSearch != pTask))
	{
		pLast = pSearch;
		pSearch = pSearch->pNext;
	}

	// if the task was found, unlink it from the allocated task chain and
	// insert it at the head of the unallocated task chain
	if (pSearch && (pSearch == pTask))
	{
		if (pLast)
			pLast->pNext = pSearch->pNext;
		else
			allocatedTasks = pSearch->pNext;

		pTask->pNext = unallocatedTasks;
		unallocatedTasks = pTask;
	}
}

// ----------------------------------------------
//
// Allocates resources for the task
//
// ----------------------------------------------

static uint8_t StartTask(PFT_TASK pTask)
{
	if ((pTask == NULL) || (pTask->callbackFunc == NULL))
		return NAKDISP_DATA;
	else
	{
		uint8_t retval = (*pTask->callbackFunc)(pTask, FT_TASKACTION_START);
		return retval;
	}
}

// ----------------------------------------------
//
// Deallocates resources for the task
//
// ----------------------------------------------

static uint8_t StopTask(PFT_TASK pTask)
{
	if ((pTask == NULL) || (pTask->callbackFunc == NULL))
		return NAKDISP_DATA;
	else
	{
		uint8_t retval = (*pTask->callbackFunc)(pTask, FT_TASKACTION_STOP);
		return retval;
	}
}



// ----------------------------------------------
//
// Places an operation lock on a file
//
// ----------------------------------------------

static uint8_t PlaceOperationLock(uint16_t fileIndex)
{
	PFT_OPERATION_LOCK pOpLock = operationLocks;
	PFT_OPERATION_LOCK pUnused = NULL;
	uint8_t remaining = FT_MAX_FILE_OPS;

	while(remaining--)
		if (pOpLock->inUse && (pOpLock->fileIndex == fileIndex))
		{
			++(pOpLock->lockCount);
			return SUCCESS;
		}
		else
		{
			if (!pOpLock->inUse && !pUnused)
				pUnused = pOpLock;

			++pOpLock;
		}

	if (!pUnused)
		return NAKDISP_TOO_MANY_COMMANDS;

	else
	{
		pUnused->inUse = TRUE;
		pUnused->fileIndex = fileIndex;
		pUnused->lockCount = 1;
		return SUCCESS;
	}
}

// ----------------------------------------------
//
// Releases an operation lock on a file
//
// ----------------------------------------------

static uint8_t ReleaseOperationLock(uint16_t fileIndex)
{
	PFT_OPERATION_LOCK pOpLock = operationLocks;
	uint8_t remaining = FT_MAX_FILE_OPS;

	while(remaining)
		if (pOpLock->inUse && (pOpLock->fileIndex == fileIndex))
			break;
		else
		{
			--remaining;
			++pOpLock;
		}
	
	if (remaining)
	{
		if (!pOpLock->lockCount || !--(pOpLock->lockCount))
			pOpLock->inUse = FALSE;

		return SUCCESS;
	}
	else
		return NAKDISP_DATA;
}








////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//File Transfer Support Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------
//
// Opens the directory and ensure that there is an
// entry for it)
//
// -----------------------------------------------

static uint8_t EnsureDirFileEntry()
{
	uint16_t fileIndex;				// index of directory file
	char dirname[13];				// copy of directory file name
	FT_FILE_ENTRY fileEntry;		// entry of directory file
	uint8_t retval;					// value to return

	strcpy_P(dirname, dirFile);
	ZERO_OBJ(fileEntry);

	// look up the file index, and if it's not valid, write an entry
	if (((retval  = EnsureDirFileOpen()) == SUCCESS)
	 && ((retval = GetFileIndexByName(dirname, &fileIndex)) == SUCCESS)
	 && !IS_VALID_FILE_INDEX(fileIndex))
	{
		FILEINFO fi;					// file info for the directory file

		if (((retval = GetOpenIndex(&fileIndex)) == SUCCESS)
		 && ((retval = FT_SetFileEntry(fileIndex, &fileEntry)) == SUCCESS)
		 && ((retval = ConvertSDError(SD_getFileInfo(dirname, &fi))) == SUCCESS))
		{
			fileEntry.inUse = TRUE;
			strcpy(fileEntry.storedFileName, dirname);
			NameToPaddedName(dirname, fileEntry.reportedFileName);
			fileEntry.fileID = INVALID_FILE_ID;
			fileEntry.fileSize = fi.fsize;
			fileEntry.fileType = FT_FILETYPE_DIRECTORY;

			retval = FT_SetFileEntry(fileIndex, &fileEntry);
		}
	}

	return retval;
}

// ----------------------------------------------
//
// Ensures that the directory file handle is open
// (opens it if it is not open)
//
// ----------------------------------------------

static uint8_t EnsureDirFileOpen()
{
	uint8_t retval = FAIL;

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_MESSAGE(DBG_ALL, "EnsureFileOpen\r\n");
#endif

	if (dirFileHandleOpen)
		retval = SUCCESS;

	else
	{
		char dirFile2[sizeof(dirFile)];
		uint8_t status;

		strcpy_P(dirFile2, dirFile);

		status = SD_fopen(dirFile2, SD_READ | SD_WRITE | SD_OPEN_ALWAYS, &dirFileHandle);
		retval = ConvertSDError(status);

		dirFileHandleOpen = (retval == SUCCESS);
	}

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "EnsureFileOpen: %u\r\n", retval);
#endif
	return retval;
}



// ----------------------------------------------
//
// Writes a file entry to the directory file
//
// ----------------------------------------------

uint8_t FT_SetFileEntry(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry)
{
	// save the number of indexed files, and if there was a change ...
	uint16_t numRecords = totalIndexedFiles;
	uint8_t retval = FT_SetFileEntry2(fileIndex, pFileEntry);

	if (totalIndexedFiles != numRecords)
	{
		// update the directory file
		char dirfname[13];
		uint16_t dirFileIndex;
		uint8_t status;
		FILEINFO fileInfo;
		FT_FILE_ENTRY dirFileEntry;

		CloseDirFile();

		strcpy_P(dirfname, dirFile);

		if (((status = SD_getFileInfo(dirfname, &fileInfo)) == SD_OK)
		 && ((retval = ConvertSDError(status)) == SUCCESS)
		 && ((retval = GetFileIndexByName(dirfname, &dirFileIndex)) == SUCCESS)
		 && IS_VALID_FILE_INDEX(dirFileIndex)
		 && ((retval = FT_GetFileEntry(dirFileIndex, &dirFileEntry)) == SUCCESS))
		{
			dirFileEntry.fileSize = fileInfo.fsize;
			retval = FT_SetFileEntry2(dirFileIndex, &dirFileEntry);
		}
	}

	return retval;
}

// ----------------------------------------------
//
// Writes a file entry to the directory file
//
// ----------------------------------------------

static uint8_t FT_SetFileEntry2(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry)
{
	uint8_t retval = EnsureDirFileOpen();

	if (retval == SUCCESS)
	{
		char buffer[FILE_ENTRY_LINE_LENGTH+1];		// buffer for composing file write
		uint8_t idx = 0;							// current offset in buffer
		uint16_t crcInLine = 0;						// crc to write to line
		uint8_t remaining = 0;						// bytes remaining on line after count
		uint8_t idx_save;							// offset in line at which data begins
		uint8_t status;								// status code from SD call
		uint16_t bytesWritten;						// number of bytes written to line
		uint8_t retval2;							// temporary return code

		// fill the buffer with spaces initially
		memset(buffer, ' ', sizeof(buffer)-1);
		buffer[sizeof(buffer)-1] = '\0';

		// write the crc and remaining count as place holders
		PUT_BUFFER_HEX(buffer, idx, crcInLine);
		PUT_BUFFER_HEX(buffer, idx, remaining);

		idx_save = idx;								// save the current position to calculate remaining

		PUT_BUFFER_CHAR(buffer, idx, 'A');	// revision code
		PUT_BUFFER_CHAR(buffer, idx, pFileEntry->inUse ? 'Y' : 'N');
		PUT_BUFFER_STR_FIXED(buffer, idx, pFileEntry->reportedFileName,
						sizeof(pFileEntry->reportedFileName));
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->fileID);
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->fileSize);
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->attributes);
		PUT_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->creationDate);
		PUT_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->modifiedDate);
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->providedCRC);
		PUT_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->providedMD5);
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->computedCRC);
		PUT_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->computedMD5);
		PUT_BUFFER_STR(buffer, idx, pFileEntry->storedFileName,
						sizeof(pFileEntry->storedFileName)-1);
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->copiedTime);
		PUT_BUFFER_CHAR(buffer, idx, pFileEntry->targetHost ? 'Y' : 'N');
		PUT_BUFFER_HEX(buffer, idx, pFileEntry->fileType);
		PUT_BUFFER_CHAR(buffer, idx, pFileEntry->reportedAtDest ? 'Y' : 'N');
		
		buffer[sizeof(buffer)-3] = '\r';
		buffer[sizeof(buffer)-2] = '\n';

		// calculate bytes used after crc and remaining count
		remaining = idx-idx_save;

		// calculate CRC
		crcInLine = CalculateCRC16((uint8_t*) &buffer[idx_save], remaining, OTHER_CRC_START);
		
		// rewrite the crc and the remaining count
		idx = 0;
		PUT_BUFFER_HEX(buffer, idx, crcInLine);
		PUT_BUFFER_HEX(buffer, idx, remaining);

		// write the string to the file
		status = SD_fwriteat(dirFileHandle, buffer, FILE_ENTRY_LINE_LENGTH*fileIndex, FILE_ENTRY_LINE_LENGTH, &bytesWritten);

		if ((status == SD_OK) && (bytesWritten < FILE_ENTRY_LINE_LENGTH))
			retval = NAKDISP_STORAGE_FULL;
		else
			retval = ConvertSDError(status);

		// close the file
		retval2 = CloseDirFile();

		if (retval == SUCCESS)
			retval = retval2;

		// update the total files
		if ((retval == SUCCESS) && (totalIndexedFiles <= fileIndex))
			totalIndexedFiles = fileIndex+1;
	}

	return retval;
}






// ----------------------------------------------
//
// Reads the directory entry for the main
// directory file
//
// ----------------------------------------------

uint8_t FT_GetFileEntry(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry)
{
	uint8_t retval = EnsureDirFileOpen();

	if (retval == SUCCESS)
	{
		char dirFile2[13];

		strcpy_P(dirFile2, dirFile);
		retval = FT_GetFileEntryFromHandle(fileIndex, pFileEntry, dirFileHandle, dirFile2);
	}

	return retval;
}

// ----------------------------------------------
//
// Reads a file entry from a file referenced by
// a handle
//
// ----------------------------------------------

static uint8_t FT_GetFileEntryFromHandle(uint16_t fileIndex, PFT_FILE_ENTRY pFileEntry, int8_t handle, const char* lpszFileName)
{
	char buffer[FILE_ENTRY_LINE_LENGTH+1];		// buffer for data read
	FILEINFO fi;								// info about the file
	uint8_t status;								// status from SD call
	uint8_t retval = FAIL;						// value to return from this function
	uint32_t seekOffset = FILE_ENTRY_LINE_LENGTH*fileIndex; // compute offset in file

	// clear out the record content
	ZERO_OBJ(*pFileEntry);

	// read information about the file
	if ((status = SD_getFileInfo(lpszFileName, &fi)) == SD_OK)
	{
		// if the file isn't big enough to read the line, then the index is bad
		if (fi.fsize < (seekOffset+FILE_ENTRY_LINE_LENGTH))
			retval = NAKDISP_BAD_FILE_CONTROL_BLOCK;
		
		else
		{
			uint16_t bytesRead = 0;				// count of bytes read

			// read the line content
			if ((status = SD_freadat(handle, buffer, seekOffset, FILE_ENTRY_LINE_LENGTH, &bytesRead)) == SD_OK)
			{
				if (bytesRead == FILE_ENTRY_LINE_LENGTH)
				{
					uint8_t idx = 0;					// current offset in data
					uint16_t crcInLine = 0;				// crc encoded on line
					uint16_t calculatedCRC;				// crc calculated from line data
					uint8_t remaining;					// remaining character count

					buffer[FILE_ENTRY_LINE_LENGTH] = '\0';
					
					GET_BUFFER_HEX(buffer, idx, crcInLine, uint16_t);
					GET_BUFFER_HEX(buffer, idx, remaining, uint8_t);

					calculatedCRC = CalculateCRC16((uint8_t*) &buffer[idx], remaining, OTHER_CRC_START);

					if (calculatedCRC == crcInLine)
					{
						(void) GET_BUFFER_CHAR(buffer, idx); // ignoring the content of the revision char
						
						pFileEntry->inUse = GET_BUFFER_CHAR(buffer, idx) == 'Y';
						GET_BUFFER_STR_FIXED(buffer, idx, pFileEntry->reportedFileName,
										sizeof(pFileEntry->reportedFileName));
						GET_BUFFER_HEX(buffer, idx, pFileEntry->fileID, uint32_t);
						GET_BUFFER_HEX(buffer, idx, pFileEntry->fileSize, uint32_t);
						GET_BUFFER_HEX(buffer, idx, pFileEntry->attributes, uint8_t);
						GET_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->creationDate);
						GET_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->modifiedDate);
						GET_BUFFER_HEX(buffer, idx, pFileEntry->providedCRC, uint16_t);
						GET_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->providedMD5);
						GET_BUFFER_HEX(buffer, idx, pFileEntry->computedCRC, uint16_t);
						GET_BUFFER_HEX_BYTES(buffer, idx, pFileEntry->computedMD5);
						GET_BUFFER_STR(buffer, idx, pFileEntry->storedFileName,
										sizeof(pFileEntry->storedFileName)-1);
						GET_BUFFER_HEX(buffer, idx, pFileEntry->copiedTime, time_t);
						pFileEntry->targetHost = (GET_BUFFER_CHAR(buffer, idx) == 'Y');
						GET_BUFFER_HEX(buffer, idx, pFileEntry->fileType, FT_FILETYPE);
						pFileEntry->reportedAtDest = (GET_BUFFER_CHAR(buffer, idx) == 'Y');

						retval = SUCCESS;
					}
					else
						retval = NAKDISP_BAD_FILE_CONTROL_BLOCK;
				}
				else
					retval = NAKDISP_BAD_FILE_CONTROL_BLOCK;
			}
			else
				retval = ConvertSDError(status);
		}
	}
	else
		retval = ConvertSDError(status);

	return retval;
}



// ----------------------------------------------
//
// Returns the index for the given literal file
// name
//
// ----------------------------------------------

static uint8_t GetFileIndexByName(const char* lpszFileName, uint16_t* pFileIndex)
{
	uint8_t retval = SUCCESS;
	uint16_t idx;
	FT_FILE_ENTRY fileEntry;

	*pFileIndex = INVALID_FILE_INDEX;

	for (idx = 0; (*pFileIndex == INVALID_FILE_INDEX) && (retval == SUCCESS) && (idx < totalIndexedFiles); ++idx)

		if (((retval = FT_GetFileEntry(idx, &fileEntry)) == SUCCESS)
		 && fileEntry.inUse
		 && !strncmp(fileEntry.storedFileName, lpszFileName, sizeof(fileEntry.storedFileName)))

			*pFileIndex = idx;

	return retval;
}


// ----------------------------------------------
//
// Returns the index for the given file ID
//
// ----------------------------------------------

static uint8_t GetFileIndexByID(uint32_t fileID, uint16_t* pFileIndex)
{
	uint8_t retval = SUCCESS;
	uint16_t idx;
	FT_FILE_ENTRY fileEntry;

	*pFileIndex = INVALID_FILE_INDEX;

	for (idx = 0; (*pFileIndex == INVALID_FILE_INDEX) && (retval == SUCCESS) && (idx < totalIndexedFiles); ++idx)

		if (((retval = FT_GetFileEntry(idx, &fileEntry)) == SUCCESS)
		 && fileEntry.inUse
		 && (fileEntry.fileID == fileID))

			*pFileIndex = idx;

	return retval;
}


// ----------------------------------------------
//
// Adds or reuses an entry and returns the index
//
// ----------------------------------------------

static uint8_t GetOpenIndex(uint16_t* fileEntryIndex)
{
	uint8_t retval = SUCCESS;		// return value for the function
	uint16_t idx = 0;				// current index
	FT_FILE_ENTRY fileEntry;		// file entry to initialize with and to read

	// assume invalid initially
	*fileEntryIndex = INVALID_FILE_INDEX;

	// loop through the items and find the first one not in use
	for (idx = 0; !IS_VALID_FILE_INDEX(*fileEntryIndex) && (retval == SUCCESS) && (idx < totalIndexedFiles); ++idx)

		if (((retval = FT_GetFileEntry(idx, &fileEntry)) == SUCCESS)
		 && !fileEntry.inUse)

			*fileEntryIndex = idx;

	// if there was no error, but no open entry found, then initialize a new one and return
	// the new index
	if ((retval == SUCCESS) && !IS_VALID_FILE_INDEX(*fileEntryIndex))
	{
		uint16_t newFileIndex = totalIndexedFiles;

		ZERO_OBJ(fileEntry);

		if ((retval = FT_SetFileEntry(newFileIndex, &fileEntry)) == SUCCESS)
			*fileEntryIndex = newFileIndex;;
	}

	return retval;
}


// ----------------------------------------------
//
// Deletes a file given its file index
//
// ----------------------------------------------

static uint8_t DeleteFileByIndex(uint16_t fileIndex, BOOL anyType, BOOL overrideReadOnly)
{
	uint8_t retval = FAIL;				// return code
	FT_FILE_ENTRY fileEntry;			// entry for file

	// fail immediately if this is not a valid file index
	if (!IS_VALID_FILE_INDEX(fileIndex))
		retval = NAKDISP_DATA;

	// otherwise, look up the file entry for the index and if successful ...
	else if ((retval = FT_GetFileEntry(fileIndex, &fileEntry)) == SUCCESS)
	{
		// if the file is in use, reject as in use
		if ((fileEntry.fileType & FT_FILETYPE_PROGRAM_IN_USE) || IsOperationLocked(fileIndex))
			retval = NAKDISP_FILE_IN_USE;
		
		// if not deleting any time, then fail for deleting non-data files
		else if (!anyType
		      && ((fileEntry.fileType & ~FT_FILETYPE_PROGRAM_IN_USE) != FT_FILETYPE_DATA))

			retval = NAKDISP_DATA;
		
		// if not overriding read-only attribute and it's read only, reject
		else if ((fileEntry.attributes & FTATTR_READ_ONLY) && !overrideReadOnly)
			retval = NAKDISP_READ_ONLY;

		// otherwise, mark the entry as not in use and delete the file
		else
		{
			fileEntry.inUse = FALSE;
			retval = FT_SetFileEntry(fileIndex, &fileEntry);

			if (retval == SUCCESS)
				SD_delete(fileEntry.storedFileName);  //// TODO: check result?
		}
	}
	
	return retval;
}


// ----------------------------------------------
//
// Closes the directory file handle if it is open
//
// ----------------------------------------------

static uint8_t CloseDirFile()
{
	uint8_t retval = FAIL;

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_MESSAGE(DBG_ALL, "CloseDirFile\r\n");
#endif

	if (!dirFileHandleOpen)
		retval = SUCCESS;

	else
	{
		uint8_t status = SD_fclose(dirFileHandle);
		
		retval = ConvertSDError(status);
		dirFileHandleOpen = FALSE;
	}

#ifdef INCLUDE_DEBUG_STATEMENTS
	DEBUG_PRINT(DBG_ALL, "CloseDirFile: %u\r\n", retval);
#endif
	return retval;
}







////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Various Miscellaneous Support Utility Routines
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


// ----------------------------------------------
//
// Tests for reserved file name: not allowed to
// reference from client side
//
// ----------------------------------------------

static BOOL ReservedFileName(const char* lpszFileName)
{
	return !strncmp_P(lpszFileName, tempHeader, sizeof(tempHeader)-1)
		|| !strcmp_P(lpszFileName, dirFile)
		|| !strcmp_P(lpszFileName, eepromName)
		|| !strcmp_P(lpszFileName, programMemoryName)
		|| !strcmp_P(lpszFileName, ramName)
		|| ((lpszFileName[0] == '$') && (lpszFileName[1] == '$'));
}


// ----------------------------------------------
//
// Permutes the given file name if the name is
// in use; the last 3 characters of the file
// name part are modified to allow for up to
// 1000 variations (only 100 implemented to allow
// permutation numbers to be cached)
//
// ----------------------------------------------

static uint8_t PermuteFilename(char* lpszFileName)
{
	char fileNamePart[9];		// the portion of the file that is not the extension
	char fileExtPart[5];		// the extension, including the dot
	char* pDot;					// pointer to the dot in the original file name
	uint16_t fileEntryIndex;	// the index of the entry found by name
	uint8_t baseLen;			// the base length of the name
	uint8_t numStart;			// the start of the number section
	uint16_t numTry;			// number tried
	uint16_t numCache[100];		// numbers in use for this name
	uint8_t numCacheCount = 0;	// count of numbers in the cache
	FT_FILE_ENTRY fileEntry;	// entry from the disk
	uint8_t numCacheIdx;		// index into cache

	// test for existence of the original file name and leave it alone
	// if it does not exist
	uint8_t retval = GetFileIndexByName(lpszFileName, &fileEntryIndex);

	if ((retval != SUCCESS) || !IS_VALID_FILE_INDEX(fileEntryIndex))
		return retval;

	// break up the file into name and extension
	if (!(pDot = strchr(lpszFileName, '.')))
	{
		strncpy(fileNamePart, lpszFileName, 8);
		fileNamePart[8] = '\0';
		fileExtPart[0] = '\0';
	}
	else
	{
		uint8_t baseNameLen = MIN(8, pDot-lpszFileName);

		strncpy(fileNamePart, lpszFileName, baseNameLen);
		fileNamePart[baseNameLen] = '\0';
		strncpy(fileExtPart, pDot, 4);
		fileExtPart[4] = '\0';
	}

	baseLen = strlen(fileNamePart);
	numStart = (baseLen >= 5) ? 5 : baseLen;

	// fill up the cache with numbers in use
	for (fileEntryIndex = 0; (retval == SUCCESS) && (numCacheCount < 100) && (fileEntryIndex < totalIndexedFiles); ++fileEntryIndex)
	{
		if (((retval = FT_GetFileEntry(fileEntryIndex, &fileEntry)) == SUCCESS)
		 && fileEntry.inUse
		 && !strncmp(fileEntry.storedFileName, fileNamePart, numStart)
		 && !strcmp(&fileEntry.storedFileName[numStart+3], fileExtPart))

		{
			char* endPtr = NULL;
			unsigned long num = strtoul(&fileEntry.storedFileName[numStart], &endPtr, 10);

			if (*endPtr == fileExtPart[0])
				numCache[numCacheCount++] = (uint16_t) num;
		}
	}

	// if there was no failure yet ...
	if (retval == SUCCESS)
	{

		// determine the lowest unused number
		for (numTry = 0; numTry < 100; ++numTry)
		{
			for (numCacheIdx = 0; numCacheIdx < numCacheCount; ++numCacheIdx)
				if (numTry == numCacheIdx)
					break;

			if (numCacheIdx == numCacheCount)
				break;
		}

		// set up the filename if successful; otherwise, bad name (can't find a permutation)
		if (numTry < 100)
		{
			char num[5];

			ultoa((unsigned long) (1000+numTry), num, 10);
			strcpy(&lpszFileName[numStart], &num[1]);
			strcat(lpszFileName, fileExtPart);

			retval = SUCCESS;
		}
		else
			retval = NAKDISP_DATA;

	}

	return retval;
}


// -----------------------------------------------
//
// Convert padded file name to 8.3 name
//
// -----------------------------------------------

static void PaddedNameToName(const char* pPaddedName, char* lpszName)
{
	PaddedSectionToName(pPaddedName, 8, &lpszName);

	if (pPaddedName[8] != ' ')
	{
		*lpszName++ = '.';
		PaddedSectionToName(&pPaddedName[8], 3, &lpszName);
	}

	*lpszName = '\0';
}

// -----------------------------------------------
//
// Convert a section of the padded file name to
// an un-padded section
//
// -----------------------------------------------

static void PaddedSectionToName(const char* pPaddedSection, uint8_t count, char** lpszName)
{
	const char* pEnd = &pPaddedSection[count-1];

	while((pEnd != pPaddedSection) && (*pEnd == ' '))
		--pEnd;

	if (pEnd != pPaddedSection)
	{
		uint8_t len = pEnd-pPaddedSection+1;
		uint8_t idx;

		for (idx = 0; idx < len; ++idx)
		{
			char c = pPaddedSection[idx];

			if (c == ' ')
				c = '_';

			*(*lpszName)++ = c;
		}
	}
}

// -----------------------------------------------
//
// Convert 8.3 name to padded file name
//
// -----------------------------------------------

static void NameToPaddedName(const char* lpszName, char* pPaddedName)
{
	char* dot = strchr(lpszName, '.');

	if (dot == NULL)
		memset(pPaddedName, ' ', 11);
	else
	{
		NameToPaddedSection(lpszName, dot-lpszName, pPaddedName, 8);
		NameToPaddedSection(dot+1, strlen(dot+1), pPaddedName+8, 3);
	}
}

// ----------------------------------------------
//
// Copies and uppercases a filename section
//
// ----------------------------------------------

static void NameToPaddedSection(const char* lpszName, uint8_t count, char* pPaddedSection, uint8_t padSize)
{
	int idx;

	if (count > padSize)
		count = padSize;

	if (count != 0)
		for (idx = 0; idx < count; ++idx)
			pPaddedSection[idx] = toupper(lpszName[idx]);

	if (count < padSize)
		memset(pPaddedSection+count, ' ', padSize-count);
}


// ----------------------------------------------
//
// Creates or truncates the given file name using
// the given parameters and update the file entry
// or add a file entry
//
// ----------------------------------------------

static uint8_t CreateOrTruncate(uint16_t* pFileIndex, PFT_FILE_ENTRY pNewFileEntry)
{
	uint8_t retval = SUCCESS;		// return code
	uint8_t status;					// status from SD calls
	FT_FILE_ENTRY tempEntry;		// temporary entry
	int8_t handle;					// handle for SD calls

	// find the index if it's not known initially
	if (!IS_VALID_FILE_INDEX(*pFileIndex))
		retval = GetFileIndexByName(pNewFileEntry->storedFileName, pFileIndex);

	// create a new entry if there isn't one yet
	if ((retval == SUCCESS) && !IS_VALID_FILE_INDEX(*pFileIndex))
		retval = GetOpenIndex(pFileIndex);

	// get the entry and if successful ...
	if ((retval == SUCCESS)
	 && ((retval = FT_GetFileEntry(*pFileIndex, &tempEntry)) == SUCCESS))
	{
		// fail if entry in use and entry is locked
		if (tempEntry.inUse && IsOperationLocked(*pFileIndex))
			retval = NAKDISP_FILE_IN_USE;

		// otherwise ...
		else
		{
			// this entry marked as in use
			pNewFileEntry->inUse = TRUE;

			// attempt to create the file and decode result
			status = SD_CreateFixedSizeFile(pNewFileEntry->storedFileName, pNewFileEntry->fileSize,
							&handle, SD_CREATE_AND_OPEN);

			// close the file
			if (status == SD_OK)
				status = SD_fclose(handle);

			// if successful, then write the entry
			if ((retval = ConvertSDError(status)) == SUCCESS)
				retval = FT_SetFileEntry(*pFileIndex, pNewFileEntry);
		}
	}

	return retval;
}




// ----------------------------------------------
//
// Tests for operation lock
//
// ----------------------------------------------

static BOOL IsOperationLocked(uint16_t fileIndex)
{
	PFT_OPERATION_LOCK pOpLock = operationLocks;
	uint8_t remaining = FT_MAX_FILE_OPS;

	while(remaining--)
		if (pOpLock->inUse && (pOpLock->fileIndex == fileIndex))
			return TRUE;
		else
			++pOpLock;

	return FALSE;
}

// ----------------------------------------------
//
// Converts SD error code to disposition code
//
// ----------------------------------------------

static uint8_t ConvertSDError(uint8_t sdError)
{
	uint8_t retval;

	switch(sdError)
	{
		case SD_OK:

			retval = SUCCESS;
			break;

		case SD_NOT_READY:
		case SD_NOT_ENABLED:

			retval = NAKDISP_MISSING_STORAGE;
			break;

		case SD_INVALID_OBJECT:
		case SD_INVALID_DRIVE:
		case SD_NO_FILESYSTEM:

			retval = NAKDISP_BAD_FILE_CONTROL_BLOCK;
			break;

		case SD_DISK_ERR:
		case SD_WRITE_PROTECTED:
		case SD_TIMEOUT:

			retval = NAKDISP_STORAGE_IO_ERROR;
			break;

		case SD_NO_FILE:
		case SD_NO_PATH:
		case SD_INVALID_NAME:
		case SD_DENIED:
		case SD_EXIST:
		case SD_INT_ERR:
		case SD_INVALID_HANDLE:
		case SD_MKFS_ABORTED:
		case SD_SEEK_PAST_EOF:
		case SD_TOO_MANY_FILES:
		default:

			retval = FAIL;
			break;
	}

	return retval;
}





static uint32_t gethex(char* input, uint8_t digs)
{
	char* endptr = NULL;
	char temp[9];

	if (digs > 8)
		digs = 8;

	strncpy(temp, input, digs);
	temp[digs] = '\0';

	return strtoul(temp, &endptr, 16);
}

static void gethexbuffer(char* input, uint8_t* pBuffer, uint8_t bufSize)
{
	while(bufSize--)
	{
		*pBuffer++ = (uint8_t) gethex(input, 2);
		input += 2;
	}
}

static void getstr(char* str, char* input, uint8_t maxLen)
{
	char* sp;

	strncpy(str, input, maxLen);
	str[maxLen] = '\0';

	if ((sp = strchr(str, ' ')) != NULL)
		*sp = '\0';
}

static void puthex(char* output, uint32_t num, uint8_t digs)
{
	char temp[16];

	if (digs > 8)
		digs = 8;
	else if (digs == 0)
		return;
	
	memset(temp, '0', 7);
	ultoa(num, temp+7, 16);

	if (digs > 8) digs = 8;

	strncpy(output, &temp[7+strlen(temp+7)-digs], digs);
}

static void puthexbuffer(char* output, uint8_t* pBuffer, uint8_t bufSize)
{
	while(bufSize--)
	{
		puthex(output, (uint32_t) *pBuffer++, 2);
		output += 2;
	}
}

static uint8_t putstr(char* output, char* str)
{
	uint8_t len = strlen(str);

	strncpy(output, str, len);
	return len;
}


// ----------------------------------------------
//
// Verifies a date/time setting
//
// ----------------------------------------------

static uint8_t ValidateDateTime(DATE_TIME* pDateTime)
{
	if ((pDateTime->hour > 59)
     || (pDateTime->minute > 59)
	 || (pDateTime->second > 59)
	 || (pDateTime->month < 1)
	 || (pDateTime->month > 12)
	 || (pDateTime->day < 1))

		return NAKDISP_DATA;

	BOOL isLeap;
	uint8_t maxDays;

	switch(pDateTime->month)
	{
		case 2:		// february

			isLeap = ((pDateTime->year & 3) == 0) && (((pDateTime->year % 100) != 0) || (pDateTime->year == 0));
			maxDays = isLeap ? 29 : 28;
			break;

		case 4:		// april
		case 6:		// june
		case 9:		// september
		case 11:	// november

			maxDays = 30;
			break;

		default:

			maxDays = 31;
			break;
	}

	if (pDateTime->day > maxDays)
		return NAKDISP_DATA;

	return SUCCESS;
}






// ----------------------------------------------
//
// Handles SelectProgram requests from comm
// modules
//
// ----------------------------------------------

uint8_t FT_SelectProgram(uint16_t boardID, uint32_t programID, BOOL isDefault)
{
	return NAKDISP_DATA;
}





/*

uint8_t FT_DebugFunction(uint8_t func)
{
	uint8_t retval = FAIL;

	if (func == 0)
	{
		FT_FILE_ENTRY fe;
		ZERO_OBJ(fe);

		memcpy_P(fe.reportedFileName, PSTR("TESTFILETST"), 11);
		fe.fileID = 0xFFFFFFFF;
		fe.fileSize = 550;
		memcpy_P(fe.storedFileName, PSTR("TESTFILE.TST"), 13);
		fe.modifiedDate.year = 9;
		fe.modifiedDate.month = 11;
		fe.modifiedDate.day = 1;
		fe.modifiedDate.hour = 12;
		fe.modifiedDate.minute = 0;
		fe.modifiedDate.second = 0;
		COPY_OBJ(fe.creationDate, fe.modifiedDate);

		uint16_t fileEntryIndex;
		retval = GetFileIndexByName(fe.storedFileName, &fileEntryIndex);

		if (retval == SUCCESS)
		{
			PFT_TASK pTask = allocatedTasks;

			while(pTask && (fileEntryIndex != pTask->fileEntryIndex))
				pTask = pTask->pNext;

			if (pTask)
			{
				StopTask(pTask);
				DeallocateTask(pTask);
			}
		}

		retval = FT_InitFileWrite(0, &fe, TRUE, NULL, 0);

		if (retval != SUCCESS)
			return retval;
			
		char line[51];		
		uint8_t lineNum;

		for (lineNum = 1; lineNum <= 11; ++lineNum)
		{
			memset(line, ' ', sizeof(line)-1);

			sprintf_P(line, PSTR("Line %d"), lineNum);
			line[strlen(line)] = ' ';
			line[48] = '\r';
			line[49] = '\n';
			line[50] = '\0';

			uint16_t bytesWritten = 0;
			retval = ConvertSDError(SD_fwriteat_atomic(fe.storedFileName, line, 50*(lineNum-1), 50, &bytesWritten));

			if (retval != SUCCESS)
				return retval;

		}

		ACK_NEW_FILE_AVAILABLE nfa;
		ZERO_OBJ(nfa);
		memcpy(nfa.fileName, fe.reportedFileName, sizeof(nfa.fileName));
		nfa.fileSize = fe.fileSize;
		nfa.fileAttributes = fe.attributes;
		COPY_OBJ(nfa.creationDate, fe.creationDate);
		COPY_OBJ(nfa.modifiedDate, fe.modifiedDate);

		retval = MgrFloatReportNewFileAvailable(&nfa);
	}

	return retval;

}
*/

#endif
