/*
	$Id: Database.cpp,v 1.1 2003/04/20 23:32:51 dasenbro Exp $

	File:	Database.cpp

	Contains: Misc Database routines.

	Version:	Apple Mail Server - Mac OS X :  $Revision: 1.1 $

	Written by: David O'Rourke 

	Copyright:	© 1996-2001 by Apple Computer, Inc., all rights reserved.

 	NOT_FOR_OPEN_SOURCE <to be reevaluated at a later time>

	Change History:

		$Log: Database.cpp,v $
		Revision 1.1  2003/04/20 23:32:51  dasenbro
		Initial check-in.
		
		Revision 1.29  2002/05/09 16:58:55  dasenbro
		Changed all str... calls to CUtils::Str... to be NULL safe.
		
		Revision 1.28  2002/04/21 16:51:49  dasenbro
		Added 10.2 DB header tag.
		
		Revision 1.27  2002/04/18 18:09:09  dasenbro
		Changed bool to Bool for word alignment.
		
		Revision 1.26  2002/04/16 05:50:44  dasenbro
		CFile() requires Bool values for file premissions.
		
		Revision 1.25  2002/03/21 16:41:17  dasenbro
		Updated file version information.
		
		Revision 1.24  2002/03/11 23:43:39  dasenbro
		Removed host pref object.  Host prefs are now stored in the directory.
		
		Revision 1.23  2002/02/20 21:15:44  dasenbro
		Added new DB version consts and CDBUtils class.
		
		Revision 1.22  2002/01/14 17:46:54  dasenbro
		Initial S4 updates.
		
		Revision 1.21  2001/09/10 23:44:56  dasenbro
		Change the permissions of the mail database to r/w for owner and group only.
		
		Revision 1.20  2001/07/25 18:57:44  dasenbro
		Set min free disk space to a const.
		
		Revision 1.19  2001/06/21 20:50:54  dasenbro
		Updated file header info.
		
		Revision 1.18  2001/06/21 17:01:16  dasenbro
		Added Change History.
		

	Projector History:

	  <ASM2>	  6/7/99	DOR		We now use an instance of a cache object rather than a global
									cache object.
	 <ASM31>	 2/11/99	DOR		Do better error checking during startup.
	 <ASM30>	 1/24/99	MED		Don't call StopAlert if we cannot open the DB, we will try to
									repair it first.
	 <ASM29>	 1/13/99	DOR		Add a new database object type.
	 <ASM28>	11/24/98	DOR		Register the CMessageString type.
		<27>	 9/21/98	DOR		Change all uses of "Assert_" to "MailAssert"..
		<26>	 8/31/98	MED		Added IsDBFull() to check if the DB is at its 2 gig limit.
		<25>	 8/24/98	MED		Added CanCreateDB() to check for enough disk space to create the
									DB.
		<24>	 8/21/98	DOR		Add support for CPOPAccount.
		<23>	 8/10/98	MED		Changed ACL object to CExpansion object.
		<22>	  8/3/98	MED		6.1 - Added an ACL object to the db.
		<21>	  6/3/98	MED		Added a ::memset to GetVolumeSize().
		<20>	  6/3/98	DOR		Use final DB name for product.
		<19>	 5/27/98	DOR		Use the correct constant.
		<18>	 5/19/98	MED		Added #include "CSmallMsgPart.h".
		<17>	 5/15/98	MED		Initialized stack vars.
		<16>	 4/28/98	MED		We now get the DB cache percentage from a resource.
		<15>	 3/25/98	MED		Added a flags argument to InitializeMainDB to return DB open
									state.
		<14>	03/24/98	DOR		Only wait 15 seconds during shutdown for the DB Object list to
									become empty.
		<13>	03/23/98	DOR		Make the DB shutdown wait until the ObjectCache is empty.
		<12>	 3/14/98	DOR		Change ShutDownList to FlushAlllists
		<11>	  2/4/98	DOR		Register the new small message part.
		<10>	  2/3/98	MED		Cleaned OSUtil.cp into CUtils.cp, COSUtils.cp and
									CSmartYield.cp.
		 <9>	12/15/97	DOR		Change AddType to use the constant for object size, rather than
									sizeof...
		 <8>	12/11/97	MED		Get the "Seed" database name.
		 <7>	 12/4/97	DOR		Using the correct size for DB objects avoids embarassing-ass
									crashes.
		 <6>	 12/3/97	DOR		Added CMXRecords.
		 <5>	 12/3/97	MED		Changed CInHost... to CHost...
		 <4>	11/24/97	MED		Addes "Server Prefs" object to the database.
		 <3>	11/19/97	MED		Integrated 5.0.3 changes.
		 <2>	 10/9/97	DOR		Add code to register the incoming host routing DB Object.

	To Do:
 */

// Sys
#include <sys/stat.h>

// Application
#include "AppResources.h"

// Database
#include "Database.h"

// Utilities
#include "COSUtils.h"

// Persistent Objects
#include "CAccount.h"
#include "CMailSpool.h"
#include "CEnvelopeInfo.h"
#include "CEnvelope.h"
#include "CStatMgr.h"
#include "CMailDatabase.h"
#include "CRootObject.h"
#include "CExpansion.h"
#include "CTextObject.h"
#include "DSMailUser.h"
#include "DSMailGroup.h"
#include "MailTypes.h"
#include "CGlobals.h"
#include "CUtils.h"


const uInt32	kStandardMinFreeDiskSpace = 2 * 1024;

// ---------------------------------------------------------------------------
//	* Globals
// ---------------------------------------------------------------------------

CMailDatabase		*gDB = NULL;
MSFSSpec			gDBFolderSpec;

Bool	CDBUtils::fDoDBUpgrade	= false;
CString	CDBUtils::fOldDBPath( PATH_MAX );
CString	CDBUtils::fNewDBPath( PATH_MAX );

// ---------------------------------------------------------------------------
//	* ValidateMailDB ()
//
// ---------------------------------------------------------------------------

sInt32 ValidateMailDB ( const char *inDBPath )
{
	sInt32					siResult	= kFnfErr;	
	CFile					*dbFile		= nil;
	const char				*pPath		= nil;
	long					byteCount	= sizeof( SDatabaseHeaderData );
	struct stat				statbuf;
	SDatabaseHeaderData		dbHeader;

	try
	{
		pPath = inDBPath;

		// check if the directory already exists
		siResult = ::stat( pPath, &statbuf );
		if ( siResult == 0 )
		{
			dbFile = new CFile( pPath, false, false );
			if ( dbFile != nil )
			{
				dbFile->seekg( 0 );
				dbFile->Read( &dbHeader, byteCount );

				siResult = kDBNotSafe;

				if ( (dbHeader.fDBState == kDBClosed) ||
					((dbHeader.fDBState == kDBOpen) && (dbHeader.fDBReOpenState == kDBSafe)) )
				{
					siResult = kDBSafe;
				}
		
				delete( dbFile );
				dbFile = nil;
			}
		}
	}

	catch ( int err )
	{
		siResult = err;

		if ( dbFile != nil )
		{
			delete( dbFile );
			dbFile = nil;
		}
	}

	return( siResult );

} // ValidateMailDB


// ---------------------------------------------------------------------------
//	* CheckDBVersion ()
//
// ---------------------------------------------------------------------------

sInt32 CheckDBVersion ( const char *inDBPath, eDBVers *outDBVer )
{
	sInt32					siResult	= kFnfErr;	
	CFile					*dbFile		= nil;
	const char				*pPath		= nil;
	long					byteCount	= sizeof( SDatabaseHeaderData );
	struct stat				statbuf;
	SDatabaseHeaderData		dbHeader;

	try
	{
		pPath = inDBPath;

		// check if the file already exists
		siResult = ::stat( pPath, &statbuf );
		if ( siResult == 0 )
		{
			dbFile = new CFile( pPath, false, false );
			if ( dbFile != nil )
			{
				dbFile->seekg( 0 );
				dbFile->Read( &dbHeader, byteCount );
		
				if ( ::strncmp( dbHeader.fDatabaseTag, kASIP6xDBTag, CUtils::Strlen( kASIP6xDBTag ) ) == 0 )
				{
					*outDBVer = kDBVer6_X;
				}
				else if ( ::strncmp( dbHeader.fDatabaseTag, kOSX10_0DBTag, CUtils::Strlen( kOSX10_0DBTag ) ) == 0 )
				{
					*outDBVer = kDBVer10_0;
				}
				else if ( ::strncmp( dbHeader.fDatabaseTag, kCurrentDBTag, CUtils::Strlen( kOSX10_1DBTag ) ) == 0 )
				{
					*outDBVer = kDBVer10_1;
				}
				else if ( ::strncmp( dbHeader.fDatabaseTag, kCurrentDBTag, CUtils::Strlen( kCurrentDBTag ) ) == 0 )
				{
					*outDBVer = kDBCurrent;
				}
				else
				{
					*outDBVer = kDBVerUnknown;
				}

				siResult = kNoErr;

				delete( dbFile );
				dbFile = nil;
			}
		}
	}

	catch ( int err )
	{
		siResult = err;

		if ( dbFile != nil )
		{
			delete( dbFile );
			dbFile = nil;
		}
	}

	return( siResult );

} // CheckDBVersion


// ---------------------------------------------------------------------------
//	* InitializeMainDB ()
//
// ---------------------------------------------------------------------------

Bool InitializeMainDB ( const char *inDBPath, uInt32 &outDBFlags )
{
	OSErr					aDBErr				= kNoErr;
	uInt32					anErrorCount		= 0;
	uInt32					pageCount			= 2048;		// xxx what size do we want the DBCache??
	CDBMailObjectCache	   *pDBMailObjectCache	= nil;
	CString					csDBName( 128 );

	pDBMailObjectCache = new CDBMailObjectCache;
	gDB = new CMailDatabase( kStandardMinFreeDiskSpace, pDBMailObjectCache, pageCount );

	if ( gDB != nil )
	{
		csDBName.Set( inDBPath );

		const char *theDBFileSpec = csDBName.GetData();

		// Tell the database to use the file spec
		aDBErr = gDB->OpenDatabaseFile( (CFileSpecPtr)theDBFileSpec, kServerDataFileType, kServerAppSignature, false );
		if ( aDBErr != kNoErr )
		{
			if ( aDBErr == kFnfErr )
			{
				aDBErr = CanCreateDB();
				if ( aDBErr == kNoErr )
				{
					aDBErr = gDB->OpenDatabaseFile( (CFileSpecPtr)theDBFileSpec, kServerDataFileType, kServerAppSignature, true );
				}
			}

			::chmod( theDBFileSpec, 0100600 );	// Change permissions to -rw-------
			if ( aDBErr == kNoErr )
			{			
				outDBFlags |= kNewDBCreated;
			}
			else
			{
				if ( aDBErr == CMailDatabase::kDBWrongVersion )
				{
					outDBFlags |= kWrongDBVersion;
				}
				else if ( aDBErr == CMailDatabase::kDBNotClosedProperly )
				{
					// Database may be bad
					outDBFlags |= kPoorlyClosedDB;
					aDBErr = kNoErr;
				}
				else if ( aDBErr == CMailDatabase::kDBCanNotOpen )
				{
					outDBFlags |= kCantOpenDB;
				}
				else if ( aDBErr == CMailDatabase::kDBDiskFull )
				{
				}
			}
		}
		else
		{
			::chmod( theDBFileSpec, 0100600 );	// Change permissions to -rw-------
			outDBFlags |= kDBIsOK;
		}

		if ( aDBErr == kNoErr )
		{
			aDBErr = gDB->AddObjType( kMailRootSignature,		kCRootObjectDataSize,		false,	CRootObject::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

			aDBErr = gDB->AddObjType( kAccountSignature,			kAccountDataSize,			false,	CAccount::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

			aDBErr = gDB->AddObjType( kMailSpoolSignature,		kMailSpoolDataSize,			false,	CMailSpool::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

			aDBErr = gDB->AddObjType( kEnvelopeInfoSignature,	kEnvelopeInfoDataSize,		false,	CEnvelopeInfo::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

			aDBErr = gDB->AddObjType( kEnvelopeSignature,		kEnvelopeDataSize,			false,	CEnvelope::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

//			aDBErr = gDB->AddObjType( kRecipientSignature,		kRecipientDataSize,			false,	CRecipient::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( kMessagePartSignature,		kMessagePartFixedDataSize,	false,	CMessagePart::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( KSmallMsgPartSignature,	kSmallMsgPartFixedDataSize,	false,	CSmallMsgPart::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( kHostEntrySignature,		kHostEntryDataSize,			false,	CHostEntry::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

			aDBErr = gDB->AddObjType( kStatMgrSignature,			kStatMgrDataSize,		false,	CStatMgr::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

//			aDBErr = gDB->AddObjType( kMsgTOCSignature,				kMsgTOCDataSize,		false,	CMessageTOC::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( kHostPrefSignature,		kHostEntryPrefDataSize,		false,	CHostEntryPref::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( kDNSCacheSignature,		kCMXRecordSize,				false,	CMXRecords::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

			aDBErr = gDB->AddObjType( kExpansionSignature,		kExpansionDataSize,			false,	CExpansion::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}

//			aDBErr = gDB->AddObjType( kPOP3AcctInfoSignature,	kCPOP3AcctInfoDataSize,		false,	CPOP3AcctInfo::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

//			aDBErr = gDB->AddObjType( kMessageStringSignature,	kMessageStringDataSize,		false,	CMessageString::GetSetFields );
//			if (aDBErr != kNoErr)
//			{
//				anErrorCount++;
//			}

			aDBErr = gDB->AddObjType( kTextObjectSignature,		kTextObjectDataSize,		false,	CTextObject::GetSetFields );
			if (aDBErr != kNoErr)
			{
				anErrorCount++;
			}
		}
		// it is very important that we do this _AFTER_ all the database types are registered because
		// the cache interrogates the database to find out what the types are in order to set up
		// a hash table and one cache list per database type.
		pDBMailObjectCache->SetBaseDBPtr(gDB);

		// create version information here!!!
		
		return( (aDBErr == kNoErr ) && (anErrorCount == 0) );
	}

	return( false );

} // InitializeMainDB

// ---------------------------------------------------------------------------
//	* CloseMainDB ()
//
//		- Close, & compress the DB. Delete the memory object.
// ---------------------------------------------------------------------------

void CloseMainDB ( void )
{
	const uTime_t	kExitTime = (uTime_t)::time( nil ) + 15;	// 15 seconds..
	if ( gDB != NULL )
	{
		CDBMailObjectCache	*aDBMailObjectCache = (CDBMailObjectCache *)gDB->GetBaseObjectCachePtr();

		aDBMailObjectCache->FlushAllList( true );
		while ( (aDBMailObjectCache->CalcObjectCount() != 0) && (kExitTime > (uTime_t)::time( nil )) )
		{
			aDBMailObjectCache->FlushAllList( true );
		}
		gDB->CloseDatabaseFile();

		delete( gDB );
		gDB = NULL;

		delete( aDBMailObjectCache );
		aDBMailObjectCache = NULL;
	}
} // CloseMainDB


// ---------------------------------------------------------------------------
//	* GetDiskFreeSpace ()
//
// ---------------------------------------------------------------------------

void GetDiskFreeSpace ( long long &volFree )
{
	volFree = COSUtils::GetFileSystemFreeSpace( "/Library/AppleMailServer" );
} // GetDiskFreeSpace


// ---------------------------------------------------------------------------
//	* CanCreateDB ()
//
// ---------------------------------------------------------------------------

OSErr CanCreateDB ( void )
{
	OSErr		result		= kNoErr;
	long long	freeSpace	= 0;
	long		minFree		= 0;

	GetDiskFreeSpace( freeSpace );

//	minFree = CMailService::GetMinFreeDiskSpaceKB() * 1024;
	minFree = kFreeDiskSpaceKB * 1024;

	freeSpace -= minFree;

	if ( freeSpace < (512 * 1024) )
	{
		result = CMailDatabase::kDBDiskFull;
	}

	return( result );

} // CanCreateDB



// ---------------------------------------------------------------------------
//	* IsDBFull
//
// ---------------------------------------------------------------------------

Boolean IsDBFull ( void )
{
	Boolean		result		= false;
// 	FSSpec		dbFileSpec;
// 	FSSpec		dbFolderSpec;
// 	long long	dbSize		= 0;

// 	if ( GetDatabaseFileSpec( &dbFileSpec, &dbFolderSpec, kNormalDB ) == true )
// 	{
// 		CInfoPBRec		pbC;
// 		::memset( &pbC, 0, sizeof( CInfoPBRec ) );
// 
// 		pbC.hFileInfo.ioNamePtr		= dbFileSpec.name;
// 		pbC.hFileInfo.ioVRefNum		= dbFileSpec.vRefNum;
// 		pbC.hFileInfo.ioDirID		= dbFileSpec.parID;
// 		pbC.hFileInfo.ioFDirIndex	= 0;
// 
// 		::PBGetCatInfoSync( &pbC );
// 
// 		dbSize = pbC.hFileInfo.ioFlLgLen;
// 
// 		if ( dbSize > (kMaxDBFileSize - (512 * 1024)) )
// 		{
// 			result = true;
// 		}
// 	}

	return( result );

} // IsDBFull


//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
//---- Database static class -----------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//	* GetDoDBUpgrade ()
//
//--------------------------------------------------------------------------------------------------

Bool CDBUtils::GetDoDBUpgrade ( void )
{
	return( CDBUtils::fDoDBUpgrade );
} // GetDoDBUpgrade


//--------------------------------------------------------------------------------------------------
//	* SetDoDBUpgrade ()
//
//--------------------------------------------------------------------------------------------------

void CDBUtils::SetDoDBUpgrade ( Bool inFlag )
{
	CDBUtils::fDoDBUpgrade = inFlag;
} // SetDoDBUpgrade

//--------------------------------------------------------------------------------------------------
//	* GetOldDBPath ()
//
//--------------------------------------------------------------------------------------------------

void CDBUtils::GetOldDBPath ( CString &outString )
{
	outString.Clear();
	outString.Set( fOldDBPath.GetData() );
} // GetOldDBPath


//--------------------------------------------------------------------------------------------------
//	* SetOldDBPath ()
//
//--------------------------------------------------------------------------------------------------

void CDBUtils::SetOldDBPath ( const char *inString )
{
	if ( inString != nil )
	{
		fOldDBPath.Set( inString );
	}
} // SetOldDBPath


//--------------------------------------------------------------------------------------------------
//	* GetNewDBPath ()
//
//--------------------------------------------------------------------------------------------------

void CDBUtils::GetNewDBPath ( CString &outString )
{
	outString.Clear();
	outString.Set( fNewDBPath.GetData() );
} // GetNewDBPath


//--------------------------------------------------------------------------------------------------
//	* SetNewDBPath ()
//
//--------------------------------------------------------------------------------------------------

void CDBUtils::SetNewDBPath ( const char *inString )
{
	if ( inString != nil )
	{
		fNewDBPath.Set( inString );
	}
} // SetNewDBPath


//--------------------------------------------------------------------------------------------------
//	* GetOldDBPathLength ()
//
//--------------------------------------------------------------------------------------------------

sInt32 CDBUtils::GetOldDBPathLength ( void )
{
	return( fNewDBPath.GetLength() );
} // GetOldDBPathLength


//--------------------------------------------------------------------------------------------------
//	* GetNewDBPathLength ()
//
//--------------------------------------------------------------------------------------------------

sInt32 CDBUtils::GetNewDBPathLength ( void )
{
	return( fNewDBPath.GetLength() );
} // GetNewDBPathLength
