/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
	File:		QTSSRawFileModule.cpp

	Contains:	Implementation of Raw File module

	

*/

#include "QTSSRawFileModule.h"
#include "OSHeaders.h"
#include "StrPtrLen.h"
#include "OSArrayObjectDeleter.h"
#include "QTSSModuleUtils.h"
#include "OSMemory.h"
#include "ev.h"

#define RAWFILE_FILE_ASYNC 1
#define RAW_FILE_DEBUGGING 0


// ATTRIBUTES IDs

static QTSS_AttributeID	sStateAttr 					= qtssIllegalAttrID;
static QTSS_AttributeID	sFileAttr 					= qtssIllegalAttrID;
static QTSS_AttributeID	sFileBufferAttr 			= qtssIllegalAttrID;
static QTSS_AttributeID	sReadOffsetAttr 			= qtssIllegalAttrID;
static QTSS_AttributeID	sWriteOffsetAttr 			= qtssIllegalAttrID;

// STATIC DATA

static StrPtrLen	sRawSuffix(".raw");

static const UInt32 kReadingBufferState = 0;
static const UInt32 kWritingBufferState = 1;

// FUNCTIONS

static QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
static QTSS_Error 	Register(QTSS_Register_Params* inParams);
static QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams);



QTSS_Error QTSSRawFileModule_Main(void* inPrivateArgs)
{
	return _stublibrary_main(inPrivateArgs, QTSSRawFileModuleDispatch);
}


QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
	switch (inRole)
	{
		case QTSS_Register_Role:
			return Register(&inParams->regParams);
		case QTSS_RTSPPreProcessor_Role:
			return Preprocess(&inParams->rtspPreProcessorParams);
	}
	return QTSS_NoErr;
}

QTSS_Error Register(QTSS_Register_Params* inParams)
{
	(void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);

	static char*		sStateName 			= 	"QTSSRawFileModuleState";
	static char*		sFileName		 	= 	"QTSSRawFileModuleFile";
	static char*		sFileBufferName		= 	"QTSSRawFileModuleFileBuffer";
	static char*		sReadOffsetName		= 	"QTSSRawFileModuleReadOffset";
	static char*		sWriteOffsetName	= 	"QTSSRawFileModuleWriteOffset";

	(void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sStateName, NULL, qtssAttrDataTypeUInt32);
	(void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sStateName, &sStateAttr);
	
	(void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileName, NULL, qtssAttrDataTypeUInt32);
	(void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileName, &sFileAttr);

	(void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileBufferName, NULL, qtssAttrDataTypeCharArray);
	(void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileBufferName, &sFileBufferAttr);

	(void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sReadOffsetName, NULL, qtssAttrDataTypeUInt32);
	(void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sReadOffsetName, &sReadOffsetAttr);

	(void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sWriteOffsetName, NULL, qtssAttrDataTypeUInt32);
	(void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sWriteOffsetName, &sWriteOffsetAttr);

	// Tell the server our name!
	static char* sModuleName = "QTSSRawFileModule";
	::strcpy(inParams->outModuleName, sModuleName);

	return QTSS_NoErr;
}


QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams)
{
	static UInt32 sFileBufSize = 32768;
	static UInt32 sInitialState = kReadingBufferState;
	static UInt32 sZero = 0;
	
	UInt32 theLen = 0;
	UInt32* theStateP = NULL;
	QTSS_Error theErr = QTSS_NoErr;

	QTSS_Object theFile = NULL;
	
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sStateAttr, 0, (void**)&theStateP, &theLen);
	if ((theStateP == NULL) || (theLen != sizeof(UInt32)))
	{
		// Initial state. We haven't started sending the file yet, so
		// check to see if this is our request, and if it is, set everything up.
		
		// Only operate if this is a DESCRIBE
		QTSS_RTSPMethod* theMethod = NULL;
		UInt32 theLen = 0;
		if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
				(void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod)))
		{
			Assert(0);
			return QTSS_RequestFailed;
		}

		if (*theMethod != qtssDescribeMethod)
			return QTSS_RequestFailed;
			
		char* theFilePath = NULL;
		
		// Check to see if this is a raw file request
		(void)QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&theFilePath, &theLen);

		// Copy the full path, and append a ".raw"
		OSCharArrayDeleter rawPath(NEW char[theLen + sRawSuffix.Len + 4]);
		::memcpy(rawPath.GetObject(), theFilePath, theLen);
		::strcpy(rawPath.GetObject() + theLen, sRawSuffix.Ptr);
		
#if RAWFILE_FILE_ASYNC
		theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#else
		theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#endif
		
		// If the file doesn't exist, and if this is a path with a '.raw' at the end,
		// check to see if the path without the extra .raw exists
		if (theErr != QTSS_NoErr)
		{
			theFile = NULL;
			rawPath.GetObject()[theLen] = '\0';
			
			if (theLen > sRawSuffix.Len)
			{
				StrPtrLen comparer((theFilePath + theLen) - sRawSuffix.Len, sRawSuffix.Len);
				if (comparer.Equal(sRawSuffix))
				{
#if RAWFILE_FILE_ASYNC
					theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#else
					theErr = QTSS_OpenFileObject(rawPath.GetObject(), kOpenFileNoFlags, &theFile);
#endif
				}
			}
		}

		// If the file doesn't exist, we should probably return a 404 not found.
		if (theErr != QTSS_NoErr)
			return QTSS_RequestFailed;

		// Before sending any response, set keep alive to off for this connection
		// Regardless of what the client sends, the server always closes the connection after sending the file
		static Bool16 sFalse = false;
		(void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse));

		// We have a real file. Setup all the dictionary values we need
		(void)QTSS_SetValue(inParams->inRTSPSession, sFileAttr, 0, &theFile, sizeof(theFile));

		// Create a buffer to store data.
		char* theFileBuffer = NEW char[sFileBufSize];
		(void)QTSS_SetValue(inParams->inRTSPSession, sFileBufferAttr, 0, &theFileBuffer, sizeof(theFileBuffer));

		// Store our initial state
		(void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &sInitialState, sizeof(sInitialState));
		theStateP = &sInitialState; // so we can proceed normally

		(void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &sZero, sizeof(sZero));
		(void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &sZero, sizeof(sZero));
	}
	
	// Get our attributes
	char** theFileBufferP = NULL;
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen);
	Assert(theFileBufferP != NULL);
	Assert(theLen == sizeof(char*));
	
	QTSS_Object* theFileP = NULL;
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen);
	Assert(theFileP != NULL);
	Assert(theLen == sizeof(QTSS_Object));
	
	UInt32* theReadOffsetP = NULL;
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sReadOffsetAttr, 0, (void**)&theReadOffsetP, &theLen);
	Assert(theReadOffsetP != NULL);
	Assert(theLen == sizeof(UInt32));

	UInt32* theWriteOffsetP = NULL;
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sWriteOffsetAttr, 0, (void**)&theWriteOffsetP, &theLen);
	Assert(theWriteOffsetP != NULL);
	Assert(theLen == sizeof(UInt32));
	
	UInt32 theReadOffset = *theReadOffsetP;
	UInt32 theWriteOffset = *theWriteOffsetP;
	UInt32 theState = *theStateP;

	Bool16 isBlocked = false;
	
	// Get the length of the file onto the stack
	theLen = sizeof(UInt64);
	UInt64 theFileLength = 0;
	theErr = QTSS_GetValue(*theFileP, qtssFlObjLength, 0, (void*)&theFileLength, &theLen);
	Assert(theErr == QTSS_NoErr);
	Assert(theLen == sizeof(UInt64));

	// Get the offset in the file onto the stack
	theLen = sizeof(UInt64);
	UInt64 theOffset = 0;
	theErr = QTSS_GetValue(*theFileP, qtssFlObjPosition, 0, (void*)&theOffset, &theLen);
	Assert(theErr == QTSS_NoErr);
	Assert(theLen == sizeof(UInt64));

	while (!isBlocked)
	{
		// If we have less than the full buffer size left to go in the file,
		// down adjust our buffer size to be the amount of data remaining in the file
		UInt32 theBufferSize = sFileBufSize;
		if ((theFileLength - theOffset) < sFileBufSize)
			theBufferSize = theFileLength - theOffset;

		switch (theState)
		{
			case kReadingBufferState:
			{
				// Read as much data as possible out of the file
				UInt32 theRecvLen = 0;
				(void)QTSS_Read(*theFileP,
								(*theFileBufferP) + theReadOffset,
									theBufferSize - theReadOffset,
									&theRecvLen);
				theReadOffset += theRecvLen;
				theOffset += theRecvLen;
#if RAW_FILE_DEBUGGING
				printf("Got %lu bytes back from file read. Now at: %"_64BITARG_"u\n", theRecvLen, theOffset);
#endif
				if (theReadOffset < theBufferSize)
				{
#if RAW_FILE_DEBUGGING
					printf("Flow controlled on file. Waiting for read event\n");
#endif
					isBlocked = true;
					break;
				}
				
				theReadOffset = 0;
				Assert(theWriteOffset == 0);
				theState = kWritingBufferState;
			}
			case kWritingBufferState:
			{
				UInt32 theWrittenLen = 0;
				
				// for debugging purposes, construct an IOVec out of this data
				iovec theVec[5];
				UInt32 units = (theBufferSize - theWriteOffset) /4;
				UInt32 offset = theWriteOffset;
				theVec[1].iov_base = (*theFileBufferP) + offset;
				theVec[1].iov_len = units;
				offset += units;
				theVec[2].iov_base = (*theFileBufferP) + offset;
				theVec[2].iov_len = units;
				offset += units;
				theVec[3].iov_base = (*theFileBufferP) + offset;
				theVec[3].iov_len = units;
				offset += units;
				theVec[4].iov_base = (*theFileBufferP) + offset;
				theVec[4].iov_len = theBufferSize - offset;
				
				(void)QTSS_WriteV(	inParams->inRTSPSession,
									theVec, 5,
									theBufferSize - theWriteOffset,
									&theWrittenLen);
				theWriteOffset += theWrittenLen;
#if RAW_FILE_DEBUGGING
				printf("Got %lu bytes back from socket write.\n", theWrittenLen);
#endif
				if (theWriteOffset < theBufferSize)
				{
#if RAW_FILE_DEBUGGING
					printf("Flow controlled on socket. Waiting for write event.\n");
#endif
					isBlocked = true;
					break;
				}
				
				// Check to see if we're done. If we are, delete stuff and return
				if (theOffset == theFileLength)
				{
#if RAW_FILE_DEBUGGING
					printf("File transfer complete\n");
#endif
					return QTSS_NoErr;
				}
				
				theWriteOffset = 0;
				Assert(theReadOffset == 0);
				theState = kReadingBufferState;
			}
		}
	}
	
	Assert(isBlocked);
	
	// We've reached a blocking condition for some reason.
	// Save our state, request an event, and return.
	(void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &theReadOffset, sizeof(theReadOffset));
	(void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &theWriteOffset, sizeof(theWriteOffset));
	(void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &theState, sizeof(theState));

	// If we're reading, wait for the file to become readable
	if (theState == kReadingBufferState)
		(void)QTSS_RequestEvent(*theFileP, QTSS_ReadableEvent);
	// If we're writing, wait for the socket to become writable
	else
		(void)QTSS_RequestEvent(inParams->inRTSPSession, QTSS_WriteableEvent);
	return QTSS_NoErr;
}

QTSS_Error CloseRTSPSession(QTSS_RTSPSession_Params* inParams)
{
	// In this role, the allocated resources are deleted before closing the RTSP session
	UInt32 theLen = 0;
	
	// Get our file buffer pointer and delete it
	char** theFileBufferP = NULL;					// File buffer pointer
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen);
	if (theFileBufferP != NULL) 
		delete [] *theFileBufferP;
	
	QTSS_Object* theFileP = NULL;
	// Get our file pointer and delete it
	(void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen);
	Assert(theFileP != NULL);
	Assert(theLen == sizeof(QTSS_Object));
	(void)QTSS_CloseFileObject(*theFileP);

	return QTSS_NoErr;
}
