// XTPCalendarMAPIDataProvider.cpp: implementation of the CXTPCalendarMAPIDataProvider class.
//
// This file is a part of the XTREME CALENDAR MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "Common/XTPSmartPtrInternalT.h"
#include "Common/XTPVC80Helpers.h"

#include "XTPCalendarDefines.h"
#include "XTPCalendarUtils.h"

#include "XTPCalendarEvent.h"
#include "XTPCalendarEvents.h"
#include "XTPCalendarRecurrencePattern.h"

#include "XTPCalendarCustomProperties.h"

#include "XTPCalendarData.h"

#include "XTPCalendarMemoryDataProvider.h"

#include "XTPCalendarMAPIWrapper.h"
#include "XTPCalendarMAPIDataProvider.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//#define XTP_TRACE_READ_EVENTS TRACE
#ifndef XTP_TRACE_READ_EVENTS
	#define XTP_TRACE_READ_EVENTS //1 ? (void)0 : TRACE
#endif

//#define XTP_TRACE_READ_IDS TRACE
#ifndef XTP_TRACE_READ_IDS
	#define XTP_TRACE_READ_IDS //1 ? (void)0 : TRACE
#endif

//#define XTP_TRACE_MAPI_NF TRACE
#ifndef XTP_TRACE_MAPI_NF
	#define XTP_TRACE_MAPI_NF //1 ? (void)0 : TRACE
#endif

#define MAPI_FREEBUFFER(pProvider, pBuffer) if (pProvider && pBuffer) { pProvider->MAPIFreeBuffer(pBuffer); pBuffer = NULL;}
#define MAPI_RELEASE(pProvider, x) if (pProvider && x) { pProvider->UlRelease(x); x = 0;}

#define cFileTimeUnitsPerSecond 10000000i64
//////////////////////////////////////////////////////////////////////////

IMPLEMENT_DYNAMIC(CXTPCalendarMAPIDataProvider, CXTPCalendarData)

/////////////////////////////////////////////////////////////////////////////
template<class _T>
_T xtpGetDataT(const CByteArray& arData, int nOffset)
{
	if (nOffset < 0)
	{
		return 0;
	}
	if ((int)(nOffset + sizeof(_T)) > (int)arData.GetSize())
	{
		ASSERT(FALSE);
		return 0;
	}

	const _T* pDataT = (const _T*)(arData.GetData() + nOffset);

	return *pDataT;
}

//---------------------------------------------------------------------------
template<class _T>
_T xtpGetNextDataT(const BYTE*& rpData, const BYTE* pMax = NULL)
{
	if (!rpData || pMax && (rpData + sizeof(_T)) > pMax)
	{
		ASSERT(FALSE);
		return 0;
	}

	const _T* pDataT = (const _T*)(rpData);
	rpData += sizeof(_T);

	return *pDataT;
}

//---------------------------------------------------------------------------
CString xtpGetNextData_MapiExcStr8(const BYTE*& rpData, const BYTE* pMax = NULL)
{
	if (!rpData)
	{
		ASSERT(FALSE);
		return _T("");
	}

	// layout: [w:buf-size][w:str-len][ascii-char: count=len]
	int nBuffer = (int)xtpGetNextDataT<WORD>(rpData, pMax);
	UNUSED_ALWAYS(nBuffer);
	int nStrLen = (int)xtpGetNextDataT<WORD>(rpData, pMax);

	if (pMax && (rpData + nStrLen) > pMax)
	{
		ASSERT(FALSE);
		return _T("");
	}

	CString strData = CString((char*)rpData, nStrLen);
	rpData += nStrLen;

	return strData;
}

//===========================================================================
template<class _T>
void xtpAddDataT(CByteArray& rarData, _T tValue)
{
	int nSize = sizeof(_T);
	BYTE* pBytes = (BYTE*)&tValue;
	for (int i = 0; i < nSize; i++)
	{
		rarData.Add(pBytes[i]);
	}
}

//---------------------------------------------------------------------------
template<class _T>
int xtpSetDataT(CByteArray& rarData, int nOffset, _T tValue)
{
	if (nOffset < 0)
	{
		return nOffset;
	}

	int nValueSize = sizeof(_T);
	if (rarData.GetSize() < nOffset + nValueSize)
	{
		rarData.SetSize(nOffset + nValueSize, 4096);
	}

	_T* pValue = &tValue;
	_T* pDest = (_T*)(rarData.GetData() + nOffset);

	*pDest = *pValue;

	return nOffset + nValueSize;
}

//---------------------------------------------------------------------------
void xtpAddData_MapiExcStr8(CByteArray& rarData, LPCTSTR pcszString)
{
	// layout: [w:buf-size][w:str-len][ascii-char: count=len]
	int nStrLenW = (int)_tcslen(pcszString);
	int nStrSize = nStrLenW*2 + 1;
	CHAR* pStr = new CHAR[nStrSize];
	if (!pStr)
	{
		xtpAddDataT<WORD>(rarData, 1);
		xtpAddDataT<WORD>(rarData, 0);
		return;
	}

	ZeroMemory(pStr, nStrSize);

	WCSTOMBS_S(pStr, pcszString, nStrSize-1);

	int nStrLen = (int)strlen(pStr);
	int nBuffer = nStrLen + 1;

	xtpAddDataT<WORD>(rarData, (WORD)nBuffer);
	xtpAddDataT<WORD>(rarData, (WORD)nStrLen);

	int nSize = (int)rarData.GetSize();
	rarData.SetSize(nSize + nStrLen, 4096);
	void* pDest = rarData.GetData() + nSize;

	MEMCPY_S(pDest, pStr, nStrLen);

	delete[] pStr;
}

//===========================================================================
void xtp_GetWinTZInfo(const CByteArray& arMapiTZInfo, TIME_ZONE_INFORMATION* pTZInfo)
{
	if (arMapiTZInfo.GetSize() < sizeof(XTP_MAPI_TIME_ZONE_INFORMATION) || !pTZInfo)
	{
		ASSERT(FALSE);
		return;
	}
	XTP_MAPI_TIME_ZONE_INFORMATION* pMapiTZInfo;
	pMapiTZInfo = (XTP_MAPI_TIME_ZONE_INFORMATION*)arMapiTZInfo.GetData();

	ZeroMemory(pTZInfo, sizeof(TIME_ZONE_INFORMATION));

	pTZInfo->Bias         = pMapiTZInfo->Bias;
	pTZInfo->StandardBias = pMapiTZInfo->StandardBias;
	pTZInfo->DaylightBias = pMapiTZInfo->DaylightBias;
	pTZInfo->StandardDate = pMapiTZInfo->StandardDate;
	pTZInfo->DaylightDate = pMapiTZInfo->DaylightDate;
}

void xtp_GetMapiTZInfo(TIME_ZONE_INFORMATION* pTZInfo, CByteArray& arMapiTZInfo)
{
	XTP_MAPI_TIME_ZONE_INFORMATION* pMapiTZInfo;

	arMapiTZInfo.SetSize(sizeof(XTP_MAPI_TIME_ZONE_INFORMATION));
	pMapiTZInfo = (XTP_MAPI_TIME_ZONE_INFORMATION*)arMapiTZInfo.GetData();

	ZeroMemory(pMapiTZInfo, sizeof(XTP_MAPI_TIME_ZONE_INFORMATION));
	pMapiTZInfo->Bias         = pTZInfo->Bias;
	pMapiTZInfo->StandardBias = pTZInfo->StandardBias;
	pMapiTZInfo->DaylightBias = pTZInfo->DaylightBias;
	pMapiTZInfo->StandardDate = pTZInfo->StandardDate;
	pMapiTZInfo->DaylightDate = pTZInfo->DaylightDate;
}

COleDateTime xtp_UtcToTzTime(const TIME_ZONE_INFORMATION* pTZI, const SYSTEMTIME& stUtcTime)
{
	SYSTEMTIME stTzTime;

	if (!CXTPCalendarUtils::SystemTimeToTzSpecificLocalTime(pTZI, &stUtcTime, &stTzTime))
	{
		return (DATE)0;
	}

	COleDateTime dtTzTime(stTzTime);

	return dtTzTime;
}

SYSTEMTIME xtp_TimeToUtc(const TIME_ZONE_INFORMATION* pTZI, const COleDateTime& dtTzTime)
{
	SYSTEMTIME stUtcTime;
	ZeroMemory(&stUtcTime, sizeof(stUtcTime));

	SYSTEMTIME stTzTime;
	if (!CXTPCalendarUtils::GetAsSystemTime(dtTzTime, stTzTime) )
	{
		return stUtcTime;
	}

	COleDateTime dt = xtp_UtcToTzTime(pTZI, stTzTime);
	dt = dtTzTime + (dtTzTime - dt);

	if (!CXTPCalendarUtils::GetAsSystemTime(dt, stUtcTime) )
	{
		return stUtcTime;
	}
	return stUtcTime;


}

/////////////////////////////////////////////////////////////////////////////
class CXTPCalendarMAPI_Recurrence
{
public:

	// Holds byte offsets of data fields within MAPI binary field
	struct XTP_RCDATA_LAYOUT
	{
		int     nType;          // radioSelector;
		int     nType2;         // type
		int     nRCShiftX;      // magic MAPI number. Probably used to generate occurrences.
		int     nInterval;      // interval;
		int     nRegenerate;    // regenerate   // for tasks only
		int     nWeekDayMask;   // weekDayMask  // week Of Month
		int     nInstance;      // instance
		int     nEndByType;     // endByType
		int     nOccurrences;
		int     nDynData;       // Unaligned data offset
	};

	// 0 - Daily, 1 - Weekly, 2 - MonYearly, 3 - MonYearly_Nth
	static XTP_RCDATA_LAYOUT s_arRCDataLayout[4];

	//=======================================================================
	class CXTPRcException
	{
	public:
		DWORD   dwNewStart;
		DWORD   dwNewEnd;
		DWORD   dwOrigStart;

		BOOL    bDeleted;

		WORD    wFlags;

		CString strSubject;     // xtpMAPIRcED_Subject
		BOOL    bIsMeeting;     // xtpMAPIRcED_IsMeeting
		int     nReminderTime;  // xtpMAPIRcED_ReminderTime
		BOOL    bIsReminder;    // xtpMAPIRcED_IsReminder
		CString strLocation;    // xtpMAPIRcED_Location
		int     nBusyStatus;    // xtpMAPIRcED_BusyStatus - enum XTPCalendarEventBusyStatus
		BOOL    bIsAllDay;      // xtpMAPIRcED_IsAllDay
		int     nLabel;         // xtpMAPIRcED_Label

		CXTPRcException();
		const CXTPRcException& operator=(const CXTPRcException& rSrc);
		void Set(CXTPCalendarEvent* pExc, CXTPCalendarEvent* pMaster);
	};

	class CXTPRcExceptions : public CArray<CXTPRcException, CXTPRcException&>
	{
	public:
		CXTPRcExceptions(){};
		virtual ~CXTPRcExceptions(){};

		int FindByOrigStart(DWORD dwOrigStart);

		void SortByOrigStart();
		void SortByNewStart();
	protected:
		typedef int (_cdecl* T_CompareFunc)(const CXTPRcException* pExc1, const CXTPRcException* pExc2);
		void _Sort(T_CompareFunc);

		static int _cdecl CompareAsc_ByOrigStart(const CXTPRcException* pExc1, const CXTPRcException* pExc2);
		static int _cdecl CompareAsc_ByNewStart(const CXTPRcException* pExc1, const CXTPRcException* pExc2);
	};

	//=======================================================================
	//struct XTP_RCDATA
	class CXTPRcData
	{
	public:
		WORD    wType;
		BYTE    ucType2;
		DWORD   dwRCShiftX;
		DWORD   dwInterval;
		DWORD   dwWeekDayMask;
		BYTE    ucInstance;

		DWORD   dwEndByType;
		DWORD   dwOccurrences;
		DWORD   dwEndDate;

		DWORD   dwStartDate;

		DWORD   dwOccStartTime;
		DWORD   dwOccEndTime;

		CXTPRcExceptions arExceptions;

		CXTPRcData();
		virtual ~CXTPRcData(){};

	};

	static BOOL ReadRCData(CXTPRcData& rRCData, const CByteArray& arRCData);

	static void SetRecurrenceOptions(CXTPCalendarRecurrencePattern* pPattern,
								const CXTPRcData& RCData);
	static void SetRecurrenceExceptions(CXTPCalendarEvent* pMasterEvent,
								CXTPCalendarRecurrencePattern* pPattern,
								const CXTPRcData& RCData);

	//-----------------------------------------------------------------------
	static BOOL FillRCData( CXTPRcData& rRCData,
							CXTPCalendarRecurrencePattern* pPattern,
							CXTPCalendarEvent* pMaster);

	static BOOL RCDataToBin(CXTPRcData& rRCData, CByteArray& rarRCData);

	static COleDateTime ConvertRecurrenceMinutesToDate(DWORD dwMinutes);
	static DWORD ConvertRecurrenceDateToMinutes(COleDateTime date);

protected:
	static void _ReadRCDataExceptions (CXTPRcData& rRCData, const BYTE* pExcData,
								const BYTE* pMaxPtr, const BYTE* pExclusions);

	static DWORD _CalcRCShiftX(CXTPCalendarRecurrencePattern* pPattern);
};

//
//  MAPI recurrence layouts definitions
//
CXTPCalendarMAPI_Recurrence::XTP_RCDATA_LAYOUT
CXTPCalendarMAPI_Recurrence::s_arRCDataLayout[] =
{
	//  daily
	{
		4,  // type
		6,  // type2
		10, // RCShiftX
		14, // interval
		18, // regenerate
		-1, // weekDayMask
		-1, // instance
		22, // endByType
		26, // nOccurrences
		26 + 2 * sizeof(DWORD) // nDynData
	},
	//  weakly
	{
		4,  // type
		6,  // type2
		10, // RCShiftX
		14, // interval
		18, // regenerate
		22, // weekDayMask
		-1, // instance
		26, // endByType
		30, // nOccurrences
		30 + 2 * sizeof(DWORD) // nDynData
	},
	//  monthly
	{
		4,  // type
		6,  // type2
		10, // RCShiftX
		14, // interval
		18, // regenerate
		-1, // weekDayMask
		22, // instance
		26, // endByType
		30, // nOccurrences
		30 + 2 * sizeof(DWORD) // nDynData
	},
	//  monthly Nth ()
	{
		4,  // type
		6,  // type2
		10, // RCShiftX
		14, // interval
		18, // regenerate
		22, // weekDayMask
		26, // instance
		30, // endByType
		34, // nOccurrences
		34 + 2 * sizeof(DWORD) // nDynData
	},
};

//////////////////////////////////////////////////////////////////////////
CXTPCalendarMAPI_Recurrence::CXTPRcException::CXTPRcException()
{
	dwNewStart = 0;
	dwNewEnd = 0;
	dwOrigStart = 0;

	bDeleted = FALSE;

	wFlags = 0;
	//strSubject;
	bIsMeeting = FALSE;
	nReminderTime = 0;
	bIsReminder = FALSE;
	//strLocation;
	nBusyStatus = 0;
	bIsAllDay = FALSE;
	nLabel = 0;
}

void CXTPCalendarMAPI_Recurrence::CXTPRcException::Set(CXTPCalendarEvent* pExc, CXTPCalendarEvent* pMaster)
{
	CXTPRcException rexZero;
	*this = rexZero;

	if (!pExc || !pMaster)
	{
		ASSERT(FALSE);
		return;
	}

	wFlags = 0;

	bDeleted = pExc->IsRExceptionDeleted();

	dwOrigStart = CXTPCalendarMAPI_Recurrence::ConvertRecurrenceDateToMinutes(pExc->GetRException_StartTimeOrig());

	if (bDeleted)
	{
		return;
	}

	dwNewStart = CXTPCalendarMAPI_Recurrence::ConvertRecurrenceDateToMinutes(pExc->GetStartTime());
	dwNewEnd = CXTPCalendarMAPI_Recurrence::ConvertRecurrenceDateToMinutes(pExc->GetEndTime());

	if (pExc->GetSubject() != pMaster->GetSubject())
	{
		wFlags |= xtpMAPIRcED_Subject;
		strSubject = pExc->GetSubject();
	}
	if (pExc->IsMeeting() != pMaster->IsMeeting())
	{
		wFlags |= xtpMAPIRcED_IsMeeting;
		bIsMeeting = pExc->IsMeeting();
	}
	if (pExc->GetReminderMinutesBeforeStart() != pMaster->GetReminderMinutesBeforeStart())
	{
		wFlags |= xtpMAPIRcED_ReminderTime;
		nReminderTime = pExc->GetReminderMinutesBeforeStart();
	}
	if (pExc->IsReminder() != pMaster->IsReminder())
	{
		wFlags |= xtpMAPIRcED_IsReminder;
		bIsReminder = pExc->IsReminder();
	}
	if (pExc->GetLocation() != pMaster->GetLocation())
	{
		wFlags |= xtpMAPIRcED_Location;
		strLocation = pExc->GetLocation();
	}
	if (pExc->GetBusyStatus() != pMaster->GetBusyStatus())
	{
		wFlags |= xtpMAPIRcED_BusyStatus;
		nBusyStatus = pExc->GetBusyStatus();
	}
	if (pExc->IsAllDayEvent() != pMaster->IsAllDayEvent())
	{
		wFlags |= xtpMAPIRcED_IsAllDay;
		bIsAllDay = pExc->IsAllDayEvent();
	}
	if (pExc->GetLabelID() != pMaster->GetLabelID())
	{
		wFlags |= xtpMAPIRcED_Label;
		nLabel = pExc->GetLabelID();
	}
}

const CXTPCalendarMAPI_Recurrence::CXTPRcException&
	CXTPCalendarMAPI_Recurrence::CXTPRcException::operator= (
		const CXTPCalendarMAPI_Recurrence::CXTPRcException& rSrc)
{
	dwNewStart = rSrc.dwNewStart;
	dwNewEnd = rSrc.dwNewEnd;
	dwOrigStart = rSrc.dwOrigStart;

	bDeleted = rSrc.bDeleted;

	wFlags = rSrc.wFlags;

	strSubject = rSrc.strSubject;
	bIsMeeting = rSrc.bIsMeeting;
	nReminderTime = rSrc.nReminderTime;
	bIsReminder = rSrc.bIsReminder;
	strLocation = rSrc.strLocation;
	nBusyStatus = rSrc.nBusyStatus;
	bIsAllDay = rSrc.bIsAllDay;
	nLabel = rSrc.nLabel;

	return *this;
}

int CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::FindByOrigStart(DWORD dwOrigStart)
{
	int nCount = (int)GetSize();
	for (int i = 0; i < nCount; i++)
	{
		const CXTPRcException& rce = ElementAt(i);

		if (rce.dwOrigStart == dwOrigStart)
		{
			return i;
		}
	}
	return -1;
}

void CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::SortByOrigStart()
{
	_Sort(CompareAsc_ByOrigStart);
}

void CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::SortByNewStart()
{
	_Sort(CompareAsc_ByNewStart);
}

void CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::_Sort(T_CompareFunc pCompareFunc)
{
	typedef int (_cdecl *GENERICCOMPAREFUNC)(const void *, const void*);

	qsort(GetData(), (size_t)GetSize(), sizeof(CXTPRcException), (GENERICCOMPAREFUNC)pCompareFunc);
}

int _cdecl CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::CompareAsc_ByOrigStart(const CXTPRcException* pExc1, const CXTPRcException* pExc2)
{
	if (pExc1->dwOrigStart < pExc2->dwOrigStart)
	{
		return -1;
	}
	if (pExc1->dwOrigStart > pExc2->dwOrigStart)
	{
		return 1;
	}
	return 0;
}

int _cdecl CXTPCalendarMAPI_Recurrence::CXTPRcExceptions::CompareAsc_ByNewStart(const CXTPRcException* pExc1, const CXTPRcException* pExc2)
{
	if (pExc1->dwNewStart < pExc2->dwNewStart)
	{
		return -1;
	}
	if (pExc1->dwNewStart > pExc2->dwNewStart)
	{
		return 1;
	}
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
CXTPCalendarMAPI_Recurrence::CXTPRcData::CXTPRcData()
{
	wType = 0;
	ucType2 = 0;
	dwRCShiftX = 0;
	dwInterval = 0;
	dwWeekDayMask = 0;
	ucInstance = 0;

	dwEndByType = 0;
	dwOccurrences = 0;
	dwEndDate = 0;

	dwStartDate = 0;

	dwOccStartTime = 0;
	dwOccEndTime = 0;

	//arExceptions;
}


BOOL CXTPCalendarMAPI_Recurrence::ReadRCData(
						CXTPCalendarMAPI_Recurrence::CXTPRcData& rRCData,
						const CByteArray& arRCData)
{
	XTPCalendarMAPIRcType eRCType;
	XTPCalendarMAPIRcType2 eRCType2;
	eRCType =  (XTPCalendarMAPIRcType)xtpGetDataT<WORD>(arRCData, s_arRCDataLayout[0].nType);
	eRCType2 = (XTPCalendarMAPIRcType2)xtpGetDataT<BYTE>(arRCData, s_arRCDataLayout[0].nType2);

	BOOL bTypeValid = eRCType == xtpMAPIRcType_Daily    || eRCType == xtpMAPIRcType_Weekly ||
		eRCType == xtpMAPIRcType_Monthly    || eRCType == xtpMAPIRcType_Yearly;

	if (eRCType2 < xtpMAPIRcType2_First || eRCType2 > xtpMAPIRcType2_Last || !bTypeValid)
	{
		return FALSE;
	}

	XTP_RCDATA_LAYOUT RCLayout;
	::ZeroMemory(&RCLayout, sizeof(RCLayout));

	// select layout
	RCLayout = s_arRCDataLayout[eRCType2];

	rRCData.wType = xtpGetDataT<WORD>(arRCData, RCLayout.nType);
	rRCData.ucType2 = xtpGetDataT<BYTE>(arRCData, RCLayout.nType2);
	rRCData.dwRCShiftX = xtpGetDataT<DWORD>(arRCData, RCLayout.nRCShiftX);
	rRCData.dwInterval = xtpGetDataT<DWORD>(arRCData, RCLayout.nInterval);
	rRCData.dwWeekDayMask = xtpGetDataT<DWORD>(arRCData, RCLayout.nWeekDayMask);
	rRCData.ucInstance = xtpGetDataT<BYTE>(arRCData, RCLayout.nInstance);
	rRCData.dwEndByType = xtpGetDataT<DWORD>(arRCData, RCLayout.nEndByType);
	rRCData.dwOccurrences = xtpGetDataT<DWORD>(arRCData, RCLayout.nOccurrences);

	//
	const BYTE* p = arRCData.GetData() + RCLayout.nDynData;
	const BYTE* pMax = arRCData.GetData() + arRCData.GetSize();

	const BYTE* pExclusions = p;

	// skip old and new dates
	p += sizeof(DWORD) * (xtpGetNextDataT<DWORD>(p, pMax)); // skip old start dates
	p += sizeof(DWORD) * (xtpGetNextDataT<DWORD>(p, pMax)); // old new start dates

	// pick up start/end dates

	//Pattern Start Date
	rRCData.dwStartDate = xtpGetNextDataT<DWORD>(p, pMax);
	// /*DBG*/ ConvertRecurrenceMinutesToDate(rRCData.dwStartDate);

	//Pattern End Date
	rRCData.dwEndDate = xtpGetNextDataT<DWORD>(p, pMax);
	// /*DBG*/ ConvertRecurrenceMinutesToDate(rRCData.dwEndDate);

	// skip unknown reserved fields
	xtpGetNextDataT<DWORD>(p, pMax);
	xtpGetNextDataT<DWORD>(p, pMax);

	// Occurrence start/end time - in minutes from the day begin
	// hour = time / 60, minute = time % 60
	rRCData.dwOccStartTime = xtpGetNextDataT<DWORD>(p, pMax);
	rRCData.dwOccEndTime = xtpGetNextDataT<DWORD>(p, pMax);

	//p - points to detailed exceptions information.

	_ReadRCDataExceptions(rRCData, p, pMax, pExclusions);

	return TRUE;
}

void CXTPCalendarMAPI_Recurrence::_ReadRCDataExceptions(
						CXTPCalendarMAPI_Recurrence::CXTPRcData& rRCData,
						const BYTE* pExcData, const BYTE* pMaxPtr,
						const BYTE* pExclusions
						)
{
	// read detailed recurrence exceptions information
	int nCount = (int)xtpGetNextDataT<WORD>(pExcData, pMaxPtr);
	int i;
	for (i = 0; i < nCount; i++)
	{
		CXTPRcException RCExc;

		RCExc.dwNewStart = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);
		RCExc.dwNewEnd = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);
		RCExc.dwOrigStart = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);

		RCExc.wFlags = xtpGetNextDataT<WORD>(pExcData, pMaxPtr);

		if (RCExc.wFlags & xtpMAPIRcED_Subject)
		{
			// layout: [w:buf-size][w:str-len][ascii-char: count=len]
			RCExc.strSubject = xtpGetNextData_MapiExcStr8(pExcData, pMaxPtr);
		}
		if (RCExc.wFlags & xtpMAPIRcED_IsMeeting)
		{
			// layout: [dw] - bool(0|1)
			RCExc.bIsMeeting = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr) != 0;
		}
		if (RCExc.wFlags & xtpMAPIRcED_ReminderTime)
		{
			// layout: [dw] minutes before start
			RCExc.nReminderTime = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);
		}
		if (RCExc.wFlags & xtpMAPIRcED_IsReminder)
		{
			// layout: [dw] - bool(0|1)
			RCExc.bIsReminder = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr) != 0;
		}
		if (RCExc.wFlags & xtpMAPIRcED_Location)
		{
			// layout: [w:buf-size][w:str-len][ascii-char: count=len]
			RCExc.strLocation = xtpGetNextData_MapiExcStr8(pExcData, pMaxPtr);
		}
		if (RCExc.wFlags & xtpMAPIRcED_BusyStatus)
		{
			// layout: [dw] - busy status [0..3] = XTPCalendarEventBusyStatus
			RCExc.nBusyStatus = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);
		}
		if (RCExc.wFlags & xtpMAPIRcED_IsAllDay)
		{
			// layout: [dw] - bool(0|1)
			RCExc.bIsAllDay = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr) != 0;
		}
		if (RCExc.wFlags & xtpMAPIRcED_Label)
		{
			// layout: [dw] -
			RCExc.nLabel = xtpGetNextDataT<DWORD>(pExcData, pMaxPtr);
		}

		rRCData.arExceptions.Add(RCExc);
	}

	ASSERT(rRCData.dwOccEndTime >= rRCData.dwOccStartTime);

	// determine and add deleted exceptions
	nCount = (int)xtpGetNextDataT<DWORD>(pExclusions, pMaxPtr);
	for (i = 0; i < nCount; i++)
	{
		CXTPRcException RCExc;
		RCExc.dwOrigStart = xtpGetNextDataT<DWORD>(pExclusions, pMaxPtr);
		RCExc.dwOrigStart += rRCData.dwOccStartTime;
		RCExc.bDeleted = TRUE;

		if (rRCData.arExceptions.FindByOrigStart(RCExc.dwOrigStart) < 0)
		{
			rRCData.arExceptions.Add(RCExc);
		}
	}
}


void CXTPCalendarMAPI_Recurrence::SetRecurrenceOptions(
						CXTPCalendarRecurrencePattern* pPattern,
						const CXTPCalendarMAPI_Recurrence::CXTPRcData& RCData)
{
	ASSERT(pPattern);
	if (!pPattern)
	{
		return;
	}

	XTP_CALENDAR_RECURRENCE_OPTIONS opt;

	// Recurrence type
	switch (RCData.wType)
	{
		case xtpMAPIRcType_Daily:
		{
			opt.m_nRecurrenceType = xtpCalendarRecurrenceDaily;
			opt.m_Daily.bEveryWeekDayOnly = (RCData.ucType2 == xtpMAPIRcType2_Weekly);

			if (opt.m_Daily.bEveryWeekDayOnly)
			{
				opt.m_Daily.nIntervalDays = 1;
			}
			else
			{
				ASSERT(RCData.dwInterval % (24 * 60) == 0);
				opt.m_Daily.nIntervalDays = (int)RCData.dwInterval / (24 * 60);
			}
		}
		break;

		case xtpMAPIRcType_Weekly:
		{
			opt.m_nRecurrenceType = xtpCalendarRecurrenceWeekly;
			ASSERT(RCData.ucType2 == xtpMAPIRcType2_Weekly); // == 1, 0 only for tasks

			opt.m_Weekly.nIntervalWeeks = (int)RCData.dwInterval;
			opt.m_Weekly.nDayOfWeekMask = (int)RCData.dwWeekDayMask;
		}
		break;

		case xtpMAPIRcType_Monthly:
		{
			if (RCData.ucType2 == xtpMAPIRcType2_MonYearly)
			{
				opt.m_nRecurrenceType = xtpCalendarRecurrenceMonthly;
				opt.m_Monthly.nIntervalMonths = (int)RCData.dwInterval;
				opt.m_Monthly.nDayOfMonth = (int)RCData.ucInstance;
			}
			else if (RCData.ucType2 == xtpMAPIRcType2_MonYearly_Nth)
			{
				opt.m_nRecurrenceType = xtpCalendarRecurrenceMonthNth;
				opt.m_MonthNth.nIntervalMonths = (int)RCData.dwInterval;
				opt.m_MonthNth.nWhichDay = (int)RCData.ucInstance; // ???
				opt.m_MonthNth.nWhichDayMask = (int)RCData.dwWeekDayMask; // ???
			}
			else
			{
				ASSERT(FALSE);
			}
		}
		break;

		case xtpMAPIRcType_Yearly:
		{
			ASSERT(RCData.dwInterval % 12 == 0 && RCData.dwInterval);

			//---------------------------------------------------------------
			int nMonthOfYear = 1;
			// days per month   - jan feb mar apr may jun   jul aug sep oct nov dec
			static int arDays[] = {31, 28, 31, 30, 31, 30,   31, 31, 30, 31, 30, 31};
			int nDaysInMinutes = 0;
			for (int i = 0; i < 12; i++)
			{
				if (nDaysInMinutes >= (int)RCData.dwRCShiftX)
				{
					nMonthOfYear = i+1;
					break;
				}
				nDaysInMinutes += arDays[i] * 24 * 60;
			}
			ASSERT(nDaysInMinutes == (int)RCData.dwRCShiftX);

			//---------------------------------------------------------------
			if (RCData.ucType2 == xtpMAPIRcType2_MonYearly)
			{
				opt.m_nRecurrenceType = xtpCalendarRecurrenceYearly;
				opt.m_Yearly.nDayOfMonth = RCData.ucInstance;
				opt.m_Yearly.nMonthOfYear = nMonthOfYear;
			}
			else if (RCData.ucType2 == xtpMAPIRcType2_MonYearly_Nth)
			{
				opt.m_nRecurrenceType = xtpCalendarRecurrenceYearNth;
				opt.m_YearNth.nWhichDay = RCData.ucInstance;
				opt.m_YearNth.nWhichDayMask = RCData.dwWeekDayMask;
				opt.m_YearNth.nMonthOfYear = nMonthOfYear;
			}
			else
			{
				ASSERT(FALSE);
			}
		}
		break;
	default:
		ASSERT(FALSE);
	}
	pPattern->SetRecurrenceOptions(opt);

	pPattern->SetPatternStartDate(ConvertRecurrenceMinutesToDate(RCData.dwStartDate));

	XTP_CALENDAR_PATTERN_END rpEnd;
	switch (RCData.dwEndByType)
	{
	case xtpMAPIRcEnd_Date:
		rpEnd.m_nUseEnd = xtpCalendarPatternEndDate;
		rpEnd.m_dtPatternEndDate = ConvertRecurrenceMinutesToDate(RCData.dwEndDate);
		break;
	case xtpMAPIRcEnd_Number:
		rpEnd.m_nUseEnd = xtpCalendarPatternEndAfterOccurrences;
		rpEnd.m_nEndAfterOccurrences = RCData.dwOccurrences;
		break;
	case xtpMAPIRcEnd_Never:
		rpEnd.m_nUseEnd = xtpCalendarPatternEndNoDate;
		break;
	default: // Use no end date by default
		rpEnd.m_nUseEnd = xtpCalendarPatternEndNoDate;
	}
	pPattern->SetPatternEnd(rpEnd);

	COleDateTime dtOccStartTime;
	dtOccStartTime.SetTime(RCData.dwOccStartTime / 60, RCData.dwOccStartTime % 60, 0);

	pPattern->SetStartTime(dtOccStartTime);

	int nDuration = RCData.dwOccEndTime - RCData.dwOccStartTime;
	ASSERT(nDuration >= 0);
	pPattern->SetDurationMinutes(nDuration);
}

void CXTPCalendarMAPI_Recurrence::SetRecurrenceExceptions(
								CXTPCalendarEvent* pMasterEvent,
								CXTPCalendarRecurrencePattern* pPattern,
								const CXTPRcData& RCData)
{
	if (!pMasterEvent || !pPattern)
	{
		ASSERT(FALSE);
		return;
	}

	int nCount = (int)RCData.arExceptions.GetSize();
	for (int i = 0; i < nCount; i++)
	{
		const CXTPRcException& RCExc = RCData.arExceptions[i];

		COleDateTime dtOrigStart = ConvertRecurrenceMinutesToDate(RCExc.dwOrigStart);

		COleDateTime dtOrigEnd = dtOrigStart + pPattern->GetDuration();

		CXTPCalendarEventPtr ptrExc = pMasterEvent->CloneForOccurrence(dtOrigStart, dtOrigEnd);
		if (!ptrExc)
		{
			return;
		}
		VERIFY( ptrExc->MakeAsRException() );

		if (RCExc.bDeleted)
		{
			ptrExc->SetRExceptionDeleted(TRUE);
			VERIFY( pPattern->SetException(ptrExc) );
			continue;
		}

		//---------------------------------------------------------------
		COleDateTime dtNewStart = dtOrigStart;
		COleDateTime dtNewEnd = dtOrigEnd;

		if (RCExc.dwNewStart)
		{
			dtNewStart = ConvertRecurrenceMinutesToDate(RCExc.dwNewStart);
		}
		if (RCExc.dwNewEnd)
		{
			dtNewEnd = ConvertRecurrenceMinutesToDate(RCExc.dwNewEnd);
		}

		ptrExc->SetStartTime(dtNewStart);
		ptrExc->SetEndTime(dtNewEnd);

		//---------------------------------------------------------------
		if (RCExc.wFlags & xtpMAPIRcED_Subject)
		{
			ptrExc->SetSubject(RCExc.strSubject);
		}
		if (RCExc.wFlags & xtpMAPIRcED_IsMeeting)
		{
			ptrExc->SetMeeting(RCExc.bIsMeeting);
		}
		if (RCExc.wFlags & xtpMAPIRcED_ReminderTime)
		{
			ptrExc->SetReminderMinutesBeforeStart(RCExc.nReminderTime);
		}
		if (RCExc.wFlags & xtpMAPIRcED_IsReminder)
		{
			ptrExc->SetReminder(RCExc.bIsReminder);
		}
		if (RCExc.wFlags & xtpMAPIRcED_Location)
		{
			ptrExc->SetLocation(RCExc.strLocation);
		}
		if (RCExc.wFlags & xtpMAPIRcED_BusyStatus)
		{
			ASSERT(RCExc.nBusyStatus < 4);
			if (RCExc.nBusyStatus >= 0 && RCExc.nBusyStatus < 4)
			{
				ptrExc->SetBusyStatus(RCExc.nBusyStatus);
			}
		}

		ASSERT((RCExc.wFlags & xtpMAPIRcED_Reserved) == 0);

		if (RCExc.wFlags & xtpMAPIRcED_IsAllDay)
		{
			ptrExc->SetAllDayEvent(RCExc.bIsAllDay);
		}

		if (RCExc.wFlags & xtpMAPIRcED_Label)
		{
			ptrExc->SetLabelID(RCExc.nLabel);
		}

		VERIFY( pPattern->SetException(ptrExc) );
	}
}

BOOL CXTPCalendarMAPI_Recurrence::FillRCData(CXTPRcData& rRCData,
									CXTPCalendarRecurrencePattern* pPattern,
									CXTPCalendarEvent* pMaster)
{
	// FileTime zero date
	static const COleDateTime cdtFTZero(1601, 1, 1, 0, 0, 0);

	if (!pPattern || !pMaster)
	{
		ASSERT(FALSE);
		return FALSE;
	}
	XTP_CALENDAR_RECURRENCE_OPTIONS opt = pPattern->GetRecurrenceOptions();

	switch (opt.m_nRecurrenceType)
	{
		case xtpCalendarRecurrenceDaily:
		{
			rRCData.wType = xtpMAPIRcType_Daily;

			if (opt.m_Daily.bEveryWeekDayOnly)
			{
				rRCData.ucType2 = xtpMAPIRcType2_Weekly;
				rRCData.dwInterval = 1;
				rRCData.dwWeekDayMask = xtpCalendarDayMo_Fr;
			}
			else
			{
				rRCData.ucType2 = xtpMAPIRcType2_Daily;
				rRCData.dwInterval = opt.m_Daily.nIntervalDays * 24 * 60;
			}
		}
		break;

		case xtpCalendarRecurrenceWeekly:
		{
			rRCData.wType = xtpMAPIRcType_Weekly;
			rRCData.ucType2 = xtpMAPIRcType2_Weekly; // = 1, 0 only for tasks

			rRCData.dwInterval = opt.m_Weekly.nIntervalWeeks;
			rRCData.dwWeekDayMask = opt.m_Weekly.nDayOfWeekMask;
		}
		break;

		case xtpCalendarRecurrenceMonthly:
		{
			rRCData.wType = xtpMAPIRcType_Monthly;
			rRCData.ucType2 = xtpMAPIRcType2_MonYearly;

			rRCData.dwInterval = (DWORD)opt.m_Monthly.nIntervalMonths;
			rRCData.ucInstance = (BYTE)opt.m_Monthly.nDayOfMonth;
		}
		break;

		case xtpCalendarRecurrenceMonthNth:
		{
			rRCData.wType = xtpMAPIRcType_Monthly;
			rRCData.ucType2 = xtpMAPIRcType2_MonYearly_Nth;

			rRCData.dwInterval = (DWORD)opt.m_MonthNth.nIntervalMonths;
			rRCData.ucInstance = (BYTE)opt.m_MonthNth.nWhichDay;
			rRCData.dwWeekDayMask = (DWORD)opt.m_MonthNth.nWhichDayMask;
		}
		break;

		case xtpCalendarRecurrenceYearly:
		{
			rRCData.wType = xtpMAPIRcType_Yearly;
			rRCData.ucType2 = xtpMAPIRcType2_MonYearly;

			rRCData.ucInstance = (BYTE)opt.m_Yearly.nDayOfMonth;

			rRCData.dwInterval = 12;
		}
		break;

		case xtpCalendarRecurrenceYearNth:
		{
			rRCData.wType = xtpMAPIRcType_Yearly;
			rRCData.ucType2 = xtpMAPIRcType2_MonYearly_Nth;

			rRCData.ucInstance = (BYTE)opt.m_YearNth.nWhichDay;
			rRCData.dwWeekDayMask = (DWORD)opt.m_YearNth.nWhichDayMask;

			rRCData.dwInterval = 12;
		}
		break;
	default:
		ASSERT(FALSE);
	}
	rRCData.dwRCShiftX = _CalcRCShiftX(pPattern);

	rRCData.dwStartDate = ConvertRecurrenceDateToMinutes(pPattern->GetPatternStartDate());

	CXTPCalendarDatesArray arOccDates;
	XTP_CALENDAR_PATTERN_END rpEnd = pPattern->GetPatternEnd();
	switch (rpEnd.m_nUseEnd)
	{
	case xtpCalendarPatternEndDate:
		rRCData.dwEndByType  = xtpMAPIRcEnd_Date;
		rRCData.dwEndDate = ConvertRecurrenceDateToMinutes(rpEnd.m_dtPatternEndDate);
		pPattern->GetOccurrencesDates(arOccDates, pPattern->GetPatternStartDate(), pPattern->GetPatternEndDate());
		rRCData.dwOccurrences = (DWORD)arOccDates.GetSize();
		break;
	case xtpCalendarPatternEndAfterOccurrences:
		rRCData.dwEndByType  = xtpMAPIRcEnd_Number;
		rRCData.dwOccurrences = rpEnd.m_nEndAfterOccurrences;
		rRCData.dwEndDate = ConvertRecurrenceDateToMinutes(pMaster->GetEndTime());
		break;
	case xtpCalendarPatternEndNoDate:
		rRCData.dwEndByType  = xtpMAPIRcEnd_Never;
		break;
	default: // Use No end date by default
		rRCData.dwEndByType  = xtpMAPIRcEnd_Never;
	}

	COleDateTime dtOccStartTime = pPattern->GetStartTime();
	rRCData.dwOccStartTime = dtOccStartTime.GetHour() * 60 + dtOccStartTime.GetMinute();

	int nOccDuration = pPattern->GetDurationMinutes();
	if (pMaster->IsAllDayEvent() && ( (nOccDuration % (24 * 60)) || nOccDuration == 0) )
	{
		// align occurrence duration for full day(s) duration
		nOccDuration = (nOccDuration / (24 * 60) + 1) * 24 * 60;
	}
	rRCData.dwOccEndTime = rRCData.dwOccStartTime + nOccDuration;

	//------------------------------------------
	CXTPCalendarEventsPtr ptrExcAr = pPattern->GetExceptions();
	int nExcCount = ptrExcAr ? ptrExcAr->GetCount() : 0;
	for (int i = 0; i < nExcCount; i++)
	{
		CXTPCalendarEvent* pExc = ptrExcAr->GetAt(i);

		CXTPRcException rceTmp;
		rceTmp.Set(pExc, pMaster);

		rRCData.arExceptions.Add(rceTmp);
	}

	return TRUE;
}

BOOL CXTPCalendarMAPI_Recurrence::RCDataToBin(CXTPRcData& RCData, CByteArray& rarData)
{
	XTP_RCDATA_LAYOUT RCLayout;
	::ZeroMemory(&RCLayout, sizeof(RCLayout));

	ASSERT(RCData.ucType2 <= 3);
	if (RCData.ucType2 > 3)
	{
		return FALSE;
	}
	// select layout
	RCLayout = s_arRCDataLayout[RCData.ucType2];

	rarData.SetSize(RCLayout.nDynData, 4096);

	// Set header
	xtpSetDataT<DWORD>(rarData, 0, 0x30043004);

	// Set generic recurrence fields
	xtpSetDataT<WORD> (rarData, RCLayout.nType,         RCData.wType);
	xtpSetDataT<BYTE> (rarData, RCLayout.nType2,        RCData.ucType2);
	xtpSetDataT<DWORD>(rarData, RCLayout.nRCShiftX,     RCData.dwRCShiftX);
	xtpSetDataT<DWORD>(rarData, RCLayout.nInterval,     RCData.dwInterval);
	xtpSetDataT<DWORD>(rarData, RCLayout.nWeekDayMask,  RCData.dwWeekDayMask);
	xtpSetDataT<BYTE> (rarData, RCLayout.nInstance,     RCData.ucInstance);
	xtpSetDataT<DWORD>(rarData, RCLayout.nEndByType,    RCData.dwEndByType);
	xtpSetDataT<DWORD>(rarData, RCLayout.nOccurrences,   RCData.dwOccurrences);

	// Add information about exceptions
	//
	ASSERT(rarData.GetSize() == RCLayout.nDynData);

	int i = 0;

	//- add ALL exceptions dates -----------------------------------------
	RCData.arExceptions.SortByOrigStart();

	int nExcCount = (int)RCData.arExceptions.GetSize();
	xtpAddDataT<DWORD>(rarData, nExcCount);
	for (i = 0; i < nExcCount; i++)
	{
		const CXTPRcException& rce = RCData.arExceptions[i];
		DWORD dwOrigSartDate = (rce.dwOrigStart / 24 / 60) * 24 * 60;
		xtpAddDataT<DWORD>(rarData, dwOrigSartDate);
	}

	//- add changed exception dates -----------------------------------------
	RCData.arExceptions.SortByNewStart();

	int nChangExcCountOffset = (int)rarData.GetSize();
	xtpAddDataT<DWORD>(rarData, 0);
	DWORD dwChangExcCount = 0;

	for (i = 0; i < nExcCount; i++)
	{
		const CXTPRcException& rce = RCData.arExceptions[i];
		if (!rce.bDeleted)
		{
			ASSERT(rce.dwNewStart != 0 && rce.dwNewEnd != 0);
			DWORD dwNewSartDate = (rce.dwNewStart / 24 / 60) * 24 * 60;
			xtpAddDataT<DWORD>(rarData, dwNewSartDate);
			dwChangExcCount++;
		}
	}
	xtpSetDataT<DWORD>(rarData, nChangExcCountOffset, dwChangExcCount);

	// start/end date
	xtpAddDataT<DWORD>(rarData, RCData.dwStartDate);

	if (RCData.dwEndByType == xtpMAPIRcEnd_Date || RCData.dwEndByType == xtpMAPIRcEnd_Number)
	{
		xtpAddDataT<DWORD>(rarData, RCData.dwEndDate);
	}
	else
	{
		ASSERT(RCData.dwEndByType == xtpMAPIRcEnd_Never);
		xtpAddDataT<DWORD>(rarData, 0x5AE980DF);
	}

	// unknown
	xtpAddDataT<DWORD>(rarData, 0x00003006);
	xtpAddDataT<DWORD>(rarData, 0x00003006);

	// start/end time
	xtpAddDataT<DWORD>(rarData, RCData.dwOccStartTime);
	xtpAddDataT<DWORD>(rarData, RCData.dwOccEndTime);

	// detailed exceptions
	xtpAddDataT<WORD>(rarData, (WORD)dwChangExcCount);

	for (i = 0; i < nExcCount; i++)
	{
		const CXTPRcException& rce = RCData.arExceptions[i];
		if (rce.bDeleted)
		{
			continue;
		}

		ASSERT(rce.dwNewStart && rce.dwNewEnd && rce.dwOrigStart);

		xtpAddDataT<DWORD>(rarData, rce.dwNewStart);
		xtpAddDataT<DWORD>(rarData, rce.dwNewEnd);
		xtpAddDataT<DWORD>(rarData, rce.dwOrigStart);

		xtpAddDataT<WORD>(rarData, rce.wFlags);

		if (rce.wFlags & xtpMAPIRcED_Subject)
		{
			xtpAddData_MapiExcStr8(rarData, rce.strSubject);
		}
		if (rce.wFlags & xtpMAPIRcED_IsMeeting)
		{
			// layout: [dw] - bool(0|1)
			xtpAddDataT<DWORD>(rarData, rce.bIsMeeting);
		}
		if (rce.wFlags & xtpMAPIRcED_ReminderTime)
		{
			// layout: [dw] minutes before start
			xtpAddDataT<DWORD>(rarData, rce.nReminderTime);
		}
		if (rce.wFlags & xtpMAPIRcED_IsReminder)
		{
			// layout: [dw] - bool(0|1)
			xtpAddDataT<DWORD>(rarData, rce.bIsReminder);
		}
		if (rce.wFlags & xtpMAPIRcED_Location)
		{
			xtpAddData_MapiExcStr8(rarData, rce.strLocation);
		}
		if (rce.wFlags & xtpMAPIRcED_BusyStatus)
		{
			// layout: [dw] - busy status [0..3] = XTPCalendarEventBusyStatus
			xtpAddDataT<DWORD>(rarData, rce.nBusyStatus);
		}
		if (rce.wFlags & xtpMAPIRcED_IsAllDay)
		{
			// layout: [dw] - bool(0|1)
			xtpAddDataT<DWORD>(rarData, rce.bIsAllDay);
		}
		if (rce.wFlags & xtpMAPIRcED_Label)
		{
			// layout: [dw] -
			xtpAddDataT<DWORD>(rarData, rce.nLabel);
		}
	}

	return TRUE;
}



DWORD CXTPCalendarMAPI_Recurrence::_CalcRCShiftX(CXTPCalendarRecurrencePattern* pPattern)
{
	// FileTime zero date
	static const COleDateTime cdtFTZero(1601, 1, 1, 0, 0, 0);

	ASSERT(pPattern);
	if (!pPattern)
	{
		return 0;
	}
	DWORD dwRCShiftX = 0;

	COleDateTime dtPatternStart = pPattern->GetPatternStartDate();
	COleDateTimeSpan spX;

	XTP_CALENDAR_RECURRENCE_OPTIONS opt = pPattern->GetRecurrenceOptions();

	// xtpMAPIRcType2_Daily
	if (opt.m_nRecurrenceType == xtpCalendarRecurrenceDaily && !opt.m_Daily.bEveryWeekDayOnly)
	{

		spX = dtPatternStart - cdtFTZero;
		int nIntervalDays = max(1, opt.m_Daily.nIntervalDays);
		int nDay = (int)CXTPCalendarUtils::GetTotalDays(spX);

		dwRCShiftX = nDay % nIntervalDays;
		dwRCShiftX *= 24 * 60; // convert to minutes
	}
	// xtpMAPIRcType2_Weekly
	else if (opt.m_nRecurrenceType == xtpCalendarRecurrenceDaily && opt.m_Daily.bEveryWeekDayOnly ||
		opt.m_nRecurrenceType == xtpCalendarRecurrenceWeekly)
	{
		int nIntervalWeeks = 1;
		if (opt.m_nRecurrenceType == xtpCalendarRecurrenceWeekly)
		{
			nIntervalWeeks = opt.m_Weekly.nIntervalWeeks;
		}

		spX = dtPatternStart - cdtFTZero;
		int nWeek = ((int)(CXTPCalendarUtils::GetTotalDays(spX) + 1)) / 7;

		dwRCShiftX = (nWeek * 7 - 1) % (nIntervalWeeks * 7);
		dwRCShiftX *= 24 * 60; // convert to minutes
	}
	else
	{
		//xtpMAPIRcType2_MonYearly
		//xtpMAPIRcType2_MonYearly_Nth

		int nIntervalMonths = -1;

		if (opt.m_nRecurrenceType == xtpCalendarRecurrenceMonthly)
		{
			nIntervalMonths = opt.m_Monthly.nIntervalMonths;
		}
		else if (opt.m_nRecurrenceType == xtpCalendarRecurrenceMonthNth)
		{
			nIntervalMonths = opt.m_MonthNth.nIntervalMonths;
		}
		else if (opt.m_nRecurrenceType == xtpCalendarRecurrenceYearly ||
			opt.m_nRecurrenceType == xtpCalendarRecurrenceYearNth)
		{
			nIntervalMonths = 12;
		}
		else
		{
			ASSERT(FALSE);
			return 0;
		}
		nIntervalMonths = max(1, nIntervalMonths);

		// days per month   - jan feb mar apr may jun   jul aug sep oct nov dec
		static int arDays[] = {31, 28, 31, 30, 31, 30,   31, 31, 30, 31, 30, 31};
		int nMonth = dtPatternStart.GetMonth() + 12 * (dtPatternStart.GetYear() - 1601);

		for (int i = 1 ; i < ((nMonth - 1) % nIntervalMonths) + 1; i++ )
		{
			dwRCShiftX += arDays[(i - 1) % 12];
		}
		dwRCShiftX *= 24 * 60; // convert to minutes
	}

	return dwRCShiftX;
}

COleDateTime CXTPCalendarMAPI_Recurrence::ConvertRecurrenceMinutesToDate(DWORD dwMinutes)
{
	COleDateTime dt = (DATE)0;

	ULONGLONG   ullDT = (ULONGLONG) dwMinutes * 60i64 * cFileTimeUnitsPerSecond;
	FILETIME    ft = *((FILETIME*)&ullDT); // { (DWORD) ( fileTimeUnitsBeforeStart & 0xFFFFFFFF), (DWORD) ( fileTimeUnitsBeforeStart >> 32)};

	SYSTEMTIME  st;
	BOOL bRes = ::FileTimeToSystemTime(&ft, &st);
	ASSERT(bRes);
	if (bRes)
	{
		dt = st;
	}
//  TRACE(_T("MAPItime to DT %d = %s\n"), dwMinutes, dt.Format());

	return dt;
}

DWORD CXTPCalendarMAPI_Recurrence::ConvertRecurrenceDateToMinutes(COleDateTime dt)
{
	SYSTEMTIME st;
	FILETIME ft = {0, 0};

	if (!CXTPCalendarUtils::GetAsSystemTime(dt, st))
	{
		ASSERT(FALSE);
		return 0;
	}
	if (!SystemTimeToFileTime(&st, &ft))
	{
		ASSERT(FALSE);
		return 0;
	}

	ULONGLONG ullMinutes =  *((ULONGLONG*)&ft); //( ( (ULONGLONG) ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
	ullMinutes = ullMinutes / 60i64;
	ullMinutes = ullMinutes / cFileTimeUnitsPerSecond;
	return (DWORD)ullMinutes;
}

//////////////////////////////////////////////////////////////////////////
// see KB239795

STDMETHODIMP CXTPCalendarMAPIDataProvider::OpenInbox(
	LPMDB lpMDB, LPMAPIFOLDER *lpInboxFolder, LPMAPIFOLDER *lpCalendarFolder, CXTPCalendarMAPIDataProvider* pProvider)
{
	if (!lpMDB || !pProvider)
		return E_POINTER;

	ULONG        cbInbox;
	LPENTRYID    lpbInbox;
	ULONG        ulObjType;
	HRESULT      hRes = S_OK;
	LPMAPIFOLDER    lpTempFolder = NULL;

	ULONG pCount; SPropValue *props;
	SizedSPropTagArray(1, spec) = {1, {XTP_TAG_ID_MAPI_CALENDAR_FOLDER}};

	*lpInboxFolder = NULL;
	*lpCalendarFolder = NULL;

	// The Inbox is usually the default receive folder for the message store
	// You call this function as a shortcut to get it's Entry ID
	hRes = lpMDB->GetReceiveFolder(
		NULL,      // Get default receive folder
		NULL,      // Flags
		&cbInbox,  // Size and ...
		&lpbInbox, // Value of the EntryID to be returned
		NULL);     // You don't care to see the class returned
	if (SUCCEEDED(hRes))
	{
		hRes = lpMDB->OpenEntry(
			cbInbox,                      // Size and...
			lpbInbox,                     // Value of the Inbox's EntryID
			NULL,                         // We want the default interface    (IMAPIFolder)
			MAPI_BEST_ACCESS,             // Flags
			&ulObjType,                   // Object returned type
			(LPUNKNOWN *) &lpTempFolder); // Returned folder
		if (SUCCEEDED(hRes))
		{
			// Assign the out parameter
			*lpInboxFolder = lpTempFolder;

			// Open Calendar Folder
			hRes = (*lpInboxFolder)->GetProps((SPropTagArray*)&spec, 0, &pCount, &props);

			if (hRes == S_OK || hRes == MAPI_W_ERRORS_RETURNED)
			{
				if (props[0].ulPropTag != PT_ERROR)
				{
					hRes = lpMDB->OpenEntry(
						props[0].Value.bin.cb,            // Size and...
						(ENTRYID*)props[0].Value.bin.lpb, // Value of the Calendar's EntryID
						NULL,                         // We want the default interface (IMAPIFolder)
						MAPI_BEST_ACCESS,             // Flags
						&ulObjType,                   // Object returned type
						(LPUNKNOWN *) &lpTempFolder); // Returned folder
					if (SUCCEEDED(hRes))
					{
						// Assign the out parameter
						*lpCalendarFolder = lpTempFolder;
					}
				}
			}

			if (props)
			{
				pProvider->MAPIFreeBuffer(props);
				props = NULL;
			}
		}
	}

	// Cleanup
	pProvider->MAPIFreeBuffer(lpbInbox);
	return hRes;
}

STDMETHODIMP CXTPCalendarMAPIDataProvider::OpenDefaultMessageStore(
   LPMAPISESSION lpMAPISession, LPMDB * lpMDB, CXTPCalendarMAPIDataProvider* pProvider)
{
	if (!lpMAPISession || !pProvider)
		return E_POINTER;

	LPMAPITABLE pStoresTbl = NULL;
	LPSRowSet   pRow = NULL;
	static      SRestriction sres;
	SPropValue  spv;
	HRESULT     hRes;
	LPMDB       lpTempMDB = NULL;

	enum {EID, NAME, NUM_COLS};
	static SizedSPropTagArray(NUM_COLS,sptCols) = {NUM_COLS, PR_ENTRYID, PR_DISPLAY_NAME};

	*lpMDB = NULL;

	//Get the table of all the message stores available
	hRes = lpMAPISession->GetMsgStoresTable(0, &pStoresTbl);
	if (SUCCEEDED(hRes))
	{
		//Set up restriction for the default store
		sres.rt = RES_PROPERTY; //Comparing a property
		sres.res.resProperty.relop = RELOP_EQ; //Testing equality
		sres.res.resProperty.ulPropTag = PR_DEFAULT_STORE; //Tag to compare
		sres.res.resProperty.lpProp = &spv; //Prop tag and value to compare against

		spv.ulPropTag = PR_DEFAULT_STORE; //Tag type
		spv.Value.b   = TRUE; //Tag value

		//Convert the table to an array which can be stepped through
		//Only one message store should have PR_DEFAULT_STORE set to true, so only one will be returned
		hRes = pProvider->HrQueryAllRows(
			pStoresTbl, //Table to query
			(LPSPropTagArray) &sptCols, //Which columns to get
			&sres, //Restriction to use
			NULL, //No sort order
			0, //Max number of rows (0 means no limit)
			&pRow); //Array to return
		if (SUCCEEDED(hRes))
		{
			//Open the first returned (default) message store
			hRes = lpMAPISession->OpenMsgStore(
				NULL,//Window handle for dialogs
				pRow->aRow[0].lpProps[EID].Value.bin.cb,//size and...
				(LPENTRYID)pRow->aRow[0].lpProps[EID].Value.bin.lpb,//value of entry to open
				NULL,//Use default interface (IMsgStore) to open store
				MAPI_BEST_ACCESS,//Flags
				&lpTempMDB);//Pointer to place the store in
			if (SUCCEEDED(hRes))
			{
				//Assign the out parameter
				*lpMDB = lpTempMDB;
			}
		}
	}

	// clean up
	pProvider->FreeProws(pRow);
	MAPI_RELEASE(pProvider, pStoresTbl);
	if (FAILED(hRes))
	{
		LPMAPIERROR lpError;
		HRESULT hr = lpMAPISession->GetLastError(hRes, 0, &lpError);
		if (SUCCEEDED(hr) && lpError)
		{
			TRACE(_T("%s\n%s\n"), lpError->lpszError, lpError->lpszComponent);
			pProvider->MAPIFreeBuffer(lpError);
		}
	}
	return hRes;
}

/////////////////////////////////////////////////////////////////////////////
CXTPCalendarMAPIDataProvider::CXTPCalendarMemDPInternal::CXTPCalendarMemDPInternal(CXTPCalendarData* pDPExternal)
{
	ASSERT(pDPExternal);
	m_pDPExternal = pDPExternal;
}

void CXTPCalendarMAPIDataProvider::CXTPCalendarMemDPInternal::SendNotification(XTP_NOTIFY_CODE EventCode, WPARAM wParam , LPARAM lParam)
{
	class CXTPCalendarDataSnNf : public CXTPCalendarData
	{
	public:
		using CXTPCalendarData::SendNotification;
	};
	//===================================
	if (m_bDisableNotificationsSending)
	{
		return;
	}
	if (m_pDPExternal)
	{
		((CXTPCalendarDataSnNf*)m_pDPExternal)->SendNotification(EventCode, wParam, lParam);
	}
	else
	{
		CXTPCalendarData::SendNotification(EventCode, wParam, lParam);
	}
}
//////////////////////////////////////////////////////////////////////////
CXTPCalendarMAPIDataProvider::CXTPCalendarMAPIDataProvider()
{
	m_MapiHelper.m_pProvider = this;

	m_typeProvider = xtpCalendarDataProviderMAPI;

	m_bMAPIInitialized = FALSE;
	m_lpMAPISession = NULL;
	m_lpMDB = NULL;
	m_lpCalendarFolder = NULL;

	m_ulMAPIConID0 = 0;
	m_ulMAPIConID1 = 0;

	m_lpAdviseSink = NULL;
	m_lpAdviseSink_ThrSafe = NULL;

	//m_pMemDP = new CXTPCalendarMemoryDataProvider();
	m_pMemDP = new CXTPCalendarMemDPInternal(this);

}

CXTPCalendarMAPIDataProvider::~CXTPCalendarMAPIDataProvider()
{
	CMDTARGET_RELEASE(m_pMemDP);
}

CXTPCalendarEventsPtr CXTPCalendarMAPIDataProvider::DoRetrieveDayEvents(COleDateTime dtDay)
{
	return m_pMemDP ? m_pMemDP->RetrieveDayEvents(dtDay) : NULL;
}

void CXTPCalendarMAPIDataProvider::DoRemoveAllEvents()
{
	if (m_pMemDP)
	{
		m_pMemDP->RemoveAllEvents();
	}
}

CXTPCalendarEventPtr CXTPCalendarMAPIDataProvider::DoRead_Event(DWORD dwEventID)
{
	return m_pMemDP ? m_pMemDP->GetEvent(dwEventID) : NULL;
}

CXTPCalendarRecurrencePatternPtr CXTPCalendarMAPIDataProvider::DoRead_RPattern(DWORD dwPatternID)
{
	return m_pMemDP ? m_pMemDP->GetRecurrencePattern(dwPatternID) : NULL;
}

BOOL CXTPCalendarMAPIDataProvider::DoCreate_Event(CXTPCalendarEvent* pEvent, DWORD& rdwNewEventID)
{
	CXTPAutoResetValue<BOOL> autoReset(m_bDisableNotificationsSending, FALSE);
	m_bDisableNotificationsSending = TRUE;

	if (!IsOpen())
	{
		return FALSE;
	}

	if (!m_lpCalendarFolder || !m_pMemDP || !pEvent)
	{
		ASSERT(FALSE);
		return FALSE;
	}
	DWORD dwEventID = pEvent->GetEventID();

	int nRState = pEvent->GetRecurrenceState();

	if (nRState != xtpCalendarRecurrenceNotRecurring &&
		nRState != xtpCalendarRecurrenceMaster)
	{
		ASSERT(nRState == xtpCalendarRecurrenceException);
		rdwNewEventID = GetNextUniqueEventID(dwEventID);
		return TRUE;
	}

	LPMESSAGE pMessage = NULL;
	HRESULT hr = m_lpCalendarFolder->CreateMessage(NULL, 0, &pMessage);
	if (FAILED(hr) || !pMessage)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	if (!UpdateMAPIEvent(pMessage, pEvent))
	{
		MAPI_RELEASE(this, pMessage);
		return FALSE;
	}

	// to ensure that PR_SEARCH_KEY property is valid
	hr = pMessage->SaveChanges(KEEP_OPEN_READONLY);
	if (FAILED(hr))
	{
		ASSERT(FALSE);

		MAPI_RELEASE(this, pMessage);
		return FALSE;
	}

	_SetEventRuntimeProps(pEvent->GetCustomProperties(), pMessage);

	//-----------------------------------------------------------------------
	CByteArray arSearchKey;
	HRESULT hrID = _getPropVal(pMessage, PR_SEARCH_KEY, arSearchKey);

	if (FAILED(hrID))
	{
		ASSERT(FALSE);

		MAPI_RELEASE(this, pMessage);
		return NULL;
	}

	rdwNewEventID = GetEventID((int)arSearchKey.GetSize(), (LPENTRYID)arSearchKey.GetData(), TRUE);
	//-----------------------------------------------------------------------
	pEvent->SetEventID(rdwNewEventID);
	BOOL bRes = m_pMemDP->AddEvent(pEvent);

	MAPI_RELEASE(this, pMessage);

	return bRes;
}

BOOL CXTPCalendarMAPIDataProvider::DoUpdate_Event(CXTPCalendarEvent* pEvent)
{
	CXTPAutoResetValue<BOOL> autoReset(m_bDisableNotificationsSending, FALSE);
	m_bDisableNotificationsSending = TRUE;

	if (!IsOpen())
	{
		return FALSE;
	}

	if (!m_lpCalendarFolder || !m_pMemDP || !pEvent)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	//=================================================
	int nRState = pEvent->GetRecurrenceState();

	if (nRState != xtpCalendarRecurrenceNotRecurring &&
		nRState != xtpCalendarRecurrenceMaster)
	{
		ASSERT(nRState == xtpCalendarRecurrenceException);
		return TRUE;
	}

	//=================================================
	BOOL bRes = m_pMemDP->ChangeEvent(pEvent);
	if (!bRes)
	{
		return FALSE;
	}

	DWORD dwEventID = pEvent->GetEventID();
	CXTPMAPIBinary eKey;

	if (!m_mapID.Lookup(dwEventID, eKey))
	{
		ASSERT(FALSE);
		return FALSE;
	}

	LPMESSAGE pMessage = NULL;
	ULONG ulObjType = NULL;

	// Get event ID by its SearchKey
	CXTPMAPIBinary eid = GetEntryID(eKey);

	// open event from Calendar Folder storage
	HRESULT hr = m_lpCalendarFolder->OpenEntry(
						eid.GetBinarySize(), (LPENTRYID)eid.GetBinaryData(),
						NULL,//default interface
						MAPI_BEST_ACCESS,
						&ulObjType,
						(LPUNKNOWN*)&pMessage);

	ASSERT(ulObjType == 5);
	ASSERT(pMessage);
	if (FAILED(hr) || !pMessage)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	if (!UpdateMAPIEvent(pMessage, pEvent))
	{
		MAPI_RELEASE(this, pMessage);
		return FALSE;
	}

	hr = pMessage->SaveChanges(KEEP_OPEN_READONLY);
	if (FAILED(hr))
	{
		ASSERT(FALSE);

		MAPI_RELEASE(this, pMessage);
		return FALSE;
	}

	MAPI_RELEASE(this, pMessage);

	return TRUE;
}

BOOL CXTPCalendarMAPIDataProvider::DoDelete_Event(CXTPCalendarEvent* pEvent)
{
	CXTPAutoResetValue<BOOL> autoReset(m_bDisableNotificationsSending, FALSE);
	m_bDisableNotificationsSending = TRUE;

	if (!IsOpen())
	{
		return FALSE;
	}

	if (!m_lpCalendarFolder || !m_pMemDP || !pEvent)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	int nRState = pEvent->GetRecurrenceState();

	if (nRState != xtpCalendarRecurrenceNotRecurring &&
		nRState != xtpCalendarRecurrenceMaster)
	{
		ASSERT(nRState == xtpCalendarRecurrenceException);
		return TRUE;
	}

	CXTPMAPIBinary eKey;
	DWORD dwEventID = pEvent->GetEventID();

	if (!m_mapID.Lookup(dwEventID, eKey))
	{
		// ASSERT(FALSE); // possible when copied appointments
		return FALSE;
	}

	// get event ID by its SearchKey
	CXTPMAPIBinary eid = GetEntryID(eKey);

	// delete event
	ENTRYLIST eidList1;
	SBinary   eidBinary;

	eidList1.cValues = 1;
	eidList1.lpbin = &eidBinary;

	eidBinary.cb = eid.GetBinarySize();
	eidBinary.lpb = (BYTE*)eid.GetBinaryData();

	HRESULT hr = m_lpCalendarFolder->DeleteMessages(&eidList1, 0, NULL, 0);

	if (FAILED(hr))
	{
		ASSERT(FALSE);
		return FALSE;
	}

	//-----------------------------------------------------------------------
	return m_pMemDP ? m_pMemDP->DeleteEvent(pEvent) : FALSE;
}

BOOL CXTPCalendarMAPIDataProvider::DoCreate_RPattern(CXTPCalendarRecurrencePattern* pPattern, DWORD& rdwNewPatternID)
{
	rdwNewPatternID = pPattern->GetPatternID();
	return TRUE;
}

BOOL CXTPCalendarMAPIDataProvider::DoUpdate_RPattern(CXTPCalendarRecurrencePattern* pPattern)
{
	UNREFERENCED_PARAMETER(pPattern);
	return TRUE;
}

BOOL CXTPCalendarMAPIDataProvider::DoDelete_RPattern(CXTPCalendarRecurrencePattern* pPattern)
{
	UNREFERENCED_PARAMETER(pPattern);
	return TRUE;
}

CXTPCalendarEventsPtr CXTPCalendarMAPIDataProvider::DoGetUpcomingEvents(COleDateTime dtFrom, COleDateTimeSpan spPeriod)
{
	UNREFERENCED_PARAMETER(dtFrom); UNREFERENCED_PARAMETER(spPeriod);
	return DoGetAllEvents_raw();
}


CXTPCalendarEventsPtr CXTPCalendarMAPIDataProvider::DoGetAllEvents_raw()
{
	return m_pMemDP ? m_pMemDP->GetAllEvents_raw() : NULL;
}

BOOL AFX_CDECL CXTPCalendarMAPIDataProvider::GetParameterValueFromConStr(LPCTSTR lpszConnectionString, LPCTSTR pcszParameterName, CString& rstrValue)
{
	rstrValue.Empty();

	ASSERT(lpszConnectionString && pcszParameterName);

	if (!lpszConnectionString || !pcszParameterName ||
		0 == _tcslen(lpszConnectionString) || 0 == _tcslen(pcszParameterName))
	{
		return FALSE;
	}

	CString strConnStr_lower = lpszConnectionString;
	strConnStr_lower.MakeLower();

	CString strParameterName_lower = pcszParameterName;
	strParameterName_lower.MakeLower();

	int nIndex = strConnStr_lower.Find(strParameterName_lower + _T("="));
	if (nIndex == -1)
		nIndex = strConnStr_lower.Find(strParameterName_lower + _T(" ="));

	if (nIndex == -1)
		return FALSE;

	int nParamNameLen = (int)_tcslen(pcszParameterName);
	CString strParam = lpszConnectionString;
	strParam = strParam.Mid(nIndex + nParamNameLen);

	if (strParam[0] == _T(' '))
		strParam.Delete(0);
	if (strParam[0] == _T('='))
		strParam.Delete(0);

	if (strParam.IsEmpty())
		return TRUE;

	if (strParam[0] == _T('\'') || strParam[0] == _T('"'))
	{
		if (strParam.GetLength() <= 2)
			return TRUE;

		TCHAR chQuot = strParam[0];

		strParam.Delete(0);

		int nQuot2Idx = strParam.Find(chQuot, 0);

		if (nQuot2Idx >= 0)
		{
			strParam.Delete(nQuot2Idx, strParam.GetLength() - nQuot2Idx);
		}
	}
	else
	{
		int nParamEndIdx = strParam.Find(_T(';'), 0);
		if (nParamEndIdx >= 0)
		{
			strParam.Delete(nParamEndIdx, strParam.GetLength() - nParamEndIdx);
		}
	}

	rstrValue = strParam;

	return TRUE;
}

void CXTPCalendarMAPIDataProvider::GetMAPILogonParams(LPTSTR& rpszProfile, LPTSTR& rpszPassword, FLAGS& ruFlags)
{
	rpszProfile = NULL;
	rpszPassword = NULL;
	ruFlags = MAPI_LOGON_UI; //Allow a profile picker box to show if not logged in

	if (GetParameterValueFromConStr(m_strConnectionString, cszMAPIProfileName, m_strProfile_tmp))
		rpszProfile = (LPTSTR)(LPCTSTR)m_strProfile_tmp;

	if (GetParameterValueFromConStr(m_strConnectionString, cszMAPIPassword, m_strPassword_tmp))
		rpszPassword = (LPTSTR)(LPCTSTR)m_strPassword_tmp;

	CString strFlags;

	if (GetParameterValueFromConStr(m_strConnectionString, cszMAPIFlags, strFlags))
	{
		DWORD dwFlags = 0;
		SCANF_S(strFlags, _T("%x"), &dwFlags);

		ruFlags = dwFlags;
	}
}

BOOL CXTPCalendarMAPIDataProvider::Open()
{
	if (m_pMemDP)
	{
		m_pMemDP->Open();
	}

	HRESULT       hRes;
	LPMAPIFOLDER  lpInboxFolder = NULL;
	LPSPropValue  lpCalendarFolderEntryID = NULL;

	hRes = this->MAPIInitialize(NULL);
	if (SUCCEEDED(hRes))
	{

		LPTSTR pcszProfile;
		LPTSTR pcszPassword;
		FLAGS uFlags;

		GetMAPILogonParams(pcszProfile, pcszPassword, uFlags);

		m_bMAPIInitialized = TRUE;

		hRes = this->MAPILogonEx(0, pcszProfile, pcszPassword, uFlags, &m_lpMAPISession);

		if (SUCCEEDED(hRes))
		{
			hRes = OpenDefaultMessageStore(m_lpMAPISession, &m_lpMDB, this);
			if (SUCCEEDED(hRes))
			{
				hRes = OpenInbox(m_lpMDB, &lpInboxFolder, &m_lpCalendarFolder, this);

				if (SUCCEEDED(hRes))
				{
					//Checking to see that we did get the Calendar folder
					hRes = this->HrGetOneProp(m_lpCalendarFolder, PR_ENTRYID,
										&lpCalendarFolderEntryID);

					if (SUCCEEDED(hRes))
					{
						m_eidCalendarFolder.Set(lpCalendarFolderEntryID);
					}
				}
			}
		}
	}

	if (SUCCEEDED(hRes))
	{
		// copy all events to the cache data provider
		if (m_pMemDP)
		{
			CXTPAutoResetValue<BOOL> autoReset(m_bDisableNotificationsSending, FALSE);
			m_bDisableNotificationsSending = TRUE;

			m_pMemDP->AddEvents(ImportAllEvents());
		}

		// advise to MAPI notifications
		hRes = this->HrAllocAdviseSink(MAPICallBack_OnNotify, this, &m_lpAdviseSink);
		ASSERT(SUCCEEDED(hRes) && m_lpAdviseSink);

		hRes = this->HrThisThreadAdviseSink(m_lpAdviseSink, &m_lpAdviseSink_ThrSafe);
		ASSERT(SUCCEEDED(hRes) && m_lpAdviseSink_ThrSafe);

		if (SUCCEEDED(hRes) && m_lpAdviseSink_ThrSafe)
		{
			ULONG uEventsMask = fnevObjectCreated | fnevObjectDeleted | fnevObjectModified |
								fnevObjectMoved; // | fnevObjectCopied;

			ULONG uEIDsize =  lpCalendarFolderEntryID->Value.bin.cb;
			LPENTRYID pEIDdata = (LPENTRYID)lpCalendarFolderEntryID->Value.bin.lpb;

			hRes = m_lpMDB->Advise(0, NULL, uEventsMask, m_lpAdviseSink_ThrSafe,
									&m_ulMAPIConID0);
			ASSERT(SUCCEEDED(hRes));

			// to receive fnevObjectDeleted notification
			hRes = m_lpMDB->Advise(uEIDsize, pEIDdata, uEventsMask,
									m_lpAdviseSink_ThrSafe, &m_ulMAPIConID1);
			ASSERT(SUCCEEDED(hRes));

			// to force Advise call in the RPC.
			ULONG nValues = 0;
			LPSPropValue pPropArray;
			hRes = m_lpMDB->GetProps(NULL, 0, &nValues, &pPropArray);
			ASSERT(SUCCEEDED(hRes));

			MAPI_FREEBUFFER(this, pPropArray);
		}
	}

	MAPI_FREEBUFFER(this, lpCalendarFolderEntryID);
	MAPI_RELEASE(this, lpInboxFolder);

	if (FAILED(hRes))
	{
		// cleanup
		m_bOpened = TRUE; // to let Close run.
		Close();
	}

	if (SUCCEEDED(hRes))
	{
		CXTPCalendarData::Open();
	}

	return SUCCEEDED(hRes);
}

BOOL CXTPCalendarMAPIDataProvider::Create()
{
	return Open();
}

BOOL CXTPCalendarMAPIDataProvider::Save()
{
	CXTPCalendarData::Save();
	return TRUE;
}

void CXTPCalendarMAPIDataProvider::Close()
{
	if (!m_bOpened)
		return;

	if (m_pMemDP)
	{
		m_pMemDP->Close();
	}

	HRESULT hRes = 0;

	// Unadvise -------------------------------------
	if (m_ulMAPIConID0)
	{
		hRes = m_lpMDB->Unadvise(m_ulMAPIConID0);
		ASSERT(SUCCEEDED(hRes));
		m_ulMAPIConID0 = 0;
	}

	if (m_ulMAPIConID1)
	{
		hRes = m_lpMDB->Unadvise(m_ulMAPIConID1);
		ASSERT(SUCCEEDED(hRes));
		m_ulMAPIConID1 = 0;
	}

	// release opened objects -----------------------
	MAPI_RELEASE(this, m_lpAdviseSink_ThrSafe);
	MAPI_RELEASE(this, m_lpAdviseSink);

	MAPI_RELEASE(this, m_lpCalendarFolder);
	MAPI_RELEASE(this, m_lpMDB);

	// LogOff session -------------------------------
	if (m_lpMAPISession)
	{
		hRes = m_lpMAPISession->Logoff(0, 0, 0);
		ASSERT(SUCCEEDED(hRes));
	}
	MAPI_RELEASE(this, m_lpMAPISession);

	// Initialize MAPI dll --------------------------
	if (m_bMAPIInitialized)
	{
		this->MAPIUninitialize();
		m_bMAPIInitialized = FALSE;
	}

	CXTPCalendarData::Close();
}

BOOL CXTPCalendarMAPIDataProvider::IsOpen() const
{
	return m_lpCalendarFolder != NULL;
}

LPSRestriction CXTPCalendarMAPIDataProvider::BuildBinaryRestriction(ULONG cbSize, LPBYTE lpData, ULONG ulPropTag)
{
	static SRestriction sres;
	static SPropValue spv;
	ZeroMemory(&spv, sizeof(SPropValue));
	// Set up restriction for the current day event
	sres.rt = RES_PROPERTY; // Comparing a property
	sres.res.resProperty.relop = RELOP_EQ; // Equal
	sres.res.resProperty.ulPropTag = ulPropTag; // Entry ID
	sres.res.resProperty.lpProp = &spv; // Prop tag and value to compare against

	spv.ulPropTag = ulPropTag; // Tag type
	spv.Value.bin.cb = cbSize;
	spv.Value.bin.lpb = lpData;

	return &sres;
}

void CXTPCalendarMAPIDataProvider::_SetEventRuntimeProps(CXTPCalendarCustomProperties* pEventProps, LPMESSAGE pMessage)
{
	ASSERT(pEventProps && pMessage);
	if (!pEventProps || !pMessage)
		return;

	CByteArray arEntryID, arSearchKey;

	HRESULT hrID = _getPropVal(pMessage, PR_ENTRYID, arEntryID);
	if (SUCCEEDED(hrID))
	{
		COleVariant varEntryID(arEntryID);
		pEventProps->SetProperty(cszMAPIpropVal_EntryID, varEntryID);
	}

	hrID = _getPropVal(pMessage, PR_SEARCH_KEY, arSearchKey);
	if (SUCCEEDED(hrID))
	{
		COleVariant varSearchKey(arSearchKey);
		pEventProps->SetProperty(cszMAPIpropVal_SearchKey, varSearchKey);
	}
}

void CXTPCalendarMAPIDataProvider::_MoveMAPIEventRuntimeProps(CXTPCalendarCustomProperties* pDest,
																CXTPCalendarCustomProperties* pSrc)
{
	ASSERT(pSrc && pDest);
	if (!pSrc || !pDest)
		return;

	COleVariant varValue;

	if (pDest && pSrc->GetProperty(cszMAPIpropVal_EntryID, varValue))
	{
		pDest->SetProperty(cszMAPIpropVal_EntryID, varValue);
	}
	pSrc->RemoveProperty(cszMAPIpropVal_EntryID);

	if (pDest && pSrc->GetProperty(cszMAPIpropVal_SearchKey, varValue))
	{
		pDest->SetProperty(cszMAPIpropVal_SearchKey, varValue);
	}
	pSrc->RemoveProperty(cszMAPIpropVal_SearchKey);
}

CXTPCalendarEventPtr CXTPCalendarMAPIDataProvider::ImportEvent_FromCalendarFolderOnly(
											ULONG cbEntryID, LPENTRYID lpEntryID)
{
	LPSRestriction pRestriction = BuildBinaryRestriction(cbEntryID, (LPBYTE)lpEntryID, PR_ENTRYID);
	CXTPCalendarEventsPtr ptrEvents = ImportAllEvents(pRestriction);

	CXTPCalendarEventPtr ptrEvent;
	if (ptrEvents && ptrEvents->GetCount())
	{
		ASSERT(ptrEvents->GetCount() == 1);
		ptrEvent = ptrEvents->GetAt(0, TRUE);
	}
	return ptrEvent;
}

CXTPCalendarEventPtr CXTPCalendarMAPIDataProvider::ImportEvent(LPSRow lpRow)
{
	if (!lpRow)
		return NULL;

	// ------- ID --------
	if (PR_ENTRYID != lpRow->lpProps[0].ulPropTag)
	{
		ASSERT(FALSE);
		return NULL;
	}

	CXTPCalendarEventPtr ptrEvent = ImportEvent(lpRow->lpProps[0].Value.bin.cb,
									(LPENTRYID)lpRow->lpProps[0].Value.bin.lpb);

	return ptrEvent;
}

CXTPCalendarEventPtr CXTPCalendarMAPIDataProvider::ImportEvent(ULONG cbEntryID,
														LPENTRYID lpEntryID)
{
	if (cbEntryID == 0 || !lpEntryID)
	{
		ASSERT(FALSE);
		return NULL;
	}

	LPMESSAGE pMessage = NULL;
	ULONG ulObjType = NULL;

	// open event from Calendar Folder storage
	m_lpCalendarFolder->OpenEntry(cbEntryID, lpEntryID,
						NULL,//default interface
						MAPI_BEST_ACCESS,
						&ulObjType,
						(LPUNKNOWN*)&pMessage);

	ASSERT(ulObjType == 5);
	ASSERT(pMessage);
	if (!pMessage)
		return NULL;

	CXTPCalendarEventPtr ptrEvent = ImportEvent(pMessage);

	MAPI_RELEASE(this, pMessage);

	return ptrEvent;
}


CXTPCalendarEventPtr CXTPCalendarMAPIDataProvider::ImportEvent(LPMESSAGE pMessage)
{
	ASSERT(pMessage);
	if (!pMessage)
		return NULL;

	//-----------------------------------------------------------------------
	CByteArray arSearchKey;
	HRESULT hrID = _getPropVal(pMessage, PR_SEARCH_KEY, arSearchKey);
	if (FAILED(hrID))
	{
		ASSERT(FALSE);
		return NULL;
	}

	DWORD dwEventID = GetEventID((int)arSearchKey.GetSize(), (LPENTRYID)arSearchKey.GetData(), TRUE);

	//============================================================
	CXTPCalendarEventPtr ptrEvent(CreateNewEvent(dwEventID));

	// ------- Is Recurrence --------
	int nIsRecurrence = _getPropVal_int(pMessage, xtpMAPIpropEvent_IsRecuring);

	// get TimeZone
	TIME_ZONE_INFORMATION tziEvent;
	GetTimeZoneInformation(&tziEvent);

	if (nIsRecurrence)
	{
		ULONG ulTZTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_TimeZone);
		CByteArray arTZIData;
		HRESULT hrTZ = _getPropVal(pMessage, ulTZTag, arTZIData);

		if (SUCCEEDED(hrTZ))
		{
			xtp_GetWinTZInfo(arTZIData, &tziEvent);
		}
	}

	XTP_TRACE_READ_EVENTS(_T("\nEvent:------------------\n"));

	// ------- Subject --------
	CString strTmp = _getPropVal_str(pMessage, PR_SUBJECT);
	ptrEvent->SetSubject(strTmp);
	XTP_TRACE_READ_EVENTS(_T("Subject: %s\n"), strTmp);

	// ------- Body --------
	strTmp = _getPropVal_str(pMessage, PR_BODY);
	ptrEvent->SetBody(strTmp);
	XTP_TRACE_READ_EVENTS(_T("Body: %s\n"), strTmp.Left(255));

	// ------- StartTime --------
	SYSTEMTIME stUTC = _getPropVal_UtcTime(pMessage, xtpMAPIpropEvent_StartTime);
	//TRACE(_T("StartTime orig: %s\n"), dtTmp.Format());
	COleDateTime dtTmp = xtp_UtcToTzTime(&tziEvent, stUTC);
	ptrEvent->SetStartTime(dtTmp);
	XTP_TRACE_READ_EVENTS(_T("StartTime tz  : %s\n"), dtTmp.Format());

	// ------- EndTime --------
	stUTC = _getPropVal_UtcTime(pMessage, xtpMAPIpropEvent_EndTime);
	dtTmp = xtp_UtcToTzTime(&tziEvent, stUTC);
	ptrEvent->SetEndTime(dtTmp);
	//XTP_TRACE_READ_EVENTS(_T("EndTime: %s\n"), ptrEvent->GetEndTime().Format());

	// ------- LOCATION --------
	strTmp = _getPropVal_str(pMessage, xtpMAPIpropEvent_Location);
	ptrEvent->SetLocation(strTmp);
	XTP_TRACE_READ_EVENTS(_T("LOCATION: %s\n"), strTmp.Left(255));

	// ------- AllDayEvent --------
	int nTmp = _getPropVal_int(pMessage, xtpMAPIpropEvent_AllDay);
	ptrEvent->SetAllDayEvent(nTmp != 0);
	XTP_TRACE_READ_EVENTS(_T("All day: %d\n"), nTmp);

	// ------- IsReminder --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropCommon_ReminderSet);
	ptrEvent->SetReminder(nTmp);

	// ------- ReminderMinutesBeforeStart --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropCommon_ReminderMinutesBefore);
	ptrEvent->SetReminderMinutesBeforeStart(nTmp);

	// ------- BusyStatus --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropEvent_BusyStatus);
	ptrEvent->SetBusyStatus(nTmp);

	// ------- Importance --------
	nTmp = _getPropVal_int(pMessage, PR_IMPORTANCE);
	ptrEvent->SetImportance(nTmp);

	// ------- Label --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropEvent_Color);
	ptrEvent->SetLabelID(nTmp);

	// ------- Meeting --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropEvent_MeetingStatus);
	ptrEvent->SetMeeting(nTmp != 0);

	// ------- Private --------
	nTmp = _getPropVal_int(pMessage, xtpMAPIpropCommon_IsPrivate);
	ptrEvent->SetPrivate(nTmp != 0);

	// ------- CreationTime --------
	stUTC = _getPropVal_UtcTime(pMessage, PR_CREATION_TIME);
	dtTmp = xtp_UtcToTzTime(&tziEvent, stUTC);
	ptrEvent->SetCreationTime(dtTmp);

	// ------- LastModificationTime --------
	stUTC = _getPropVal_UtcTime(pMessage, PR_LAST_MODIFICATION_TIME);
	dtTmp = xtp_UtcToTzTime(&tziEvent, stUTC);
	ptrEvent->SetLastModificationTime(dtTmp);

	// ------- Custom Properties -------
	CXTPCalendarCustomProperties* pProps = ptrEvent->GetCustomProperties();
	if (pProps)
	{
		strTmp = _getPropVal_str(pMessage, xtpMAPIpropEvent_CustomProperties);
		pProps->LoadFromXML(strTmp);

		_SetEventRuntimeProps(pProps, pMessage);
	}

	// nIsRecurrence
	if (nIsRecurrence)
	{
		ULONG ulPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceState);
		CByteArray arRecData;
		HRESULT hr = _getPropVal(pMessage, ulPropTag, arRecData);
		ASSERT(SUCCEEDED(hr));

		VERIFY(ptrEvent->MakeEventAsRecurrence());
		ImportRecurrence(ptrEvent, arRecData);
	}
	// -------

	return ptrEvent;
}

int CXTPCalendarMAPIDataProvider::GetEventGlobalPropVal_int(LPCTSTR pcszPropName, int nDefault)
{
	CXTPCalendarCustomProperties* pProps = GetCustomProperties();

	ASSERT(pProps);
	if (!pProps)
		return nDefault;

	COleVariant varValue;
	BOOL bRes = pProps->GetProperty(pcszPropName, varValue);
	if (!bRes)
		return nDefault;

	if (varValue.vt != VT_I4)
	{
		if (FAILED(::VariantChangeType(&varValue, &varValue, 0, VT_I4)))
			return nDefault;
	}
	return V_I4(&varValue);
}

CString CXTPCalendarMAPIDataProvider::GetEventGlobalPropVal_str(LPCTSTR pcszPropName, LPCTSTR pcszDefault)
{
	CXTPCalendarCustomProperties* pProps = GetCustomProperties();

	ASSERT(pProps);
	if (!pProps)
		return pcszDefault;

	COleVariant varValue;
	BOOL bRes = pProps->GetProperty(pcszPropName, varValue);
	if (!bRes)
		return pcszDefault;

	if (varValue.vt != VT_BSTR)
	{
		if (FAILED(::VariantChangeType(&varValue, &varValue, 0, VT_BSTR)))
			return pcszDefault;
	}

	return CString(V_BSTR(&varValue));
}

void CXTPCalendarMAPIDataProvider::_SetMAPIEventGlobalPropsIfNeed(LPMESSAGE pMessage, CXTPCalendarEvent* pEvent)
{
	if (!pMessage || !pEvent)
	{
		ASSERT(FALSE);
		return;
	}

	//-----------------------------------------------------------------------
	int nPropTag;

	//**** Common customizable properties
	int nRState = pEvent->GetRecurrenceState();
	int nApptIcon = (nRState == xtpCalendarRecurrenceMaster) ? cnMAPIpropVal_AppointmentIconRecurr :
									cnMAPIpropVal_AppointmentIcon;

	VERIFY(_setPropTagVal_int(pMessage, XTP_PR_MAPI_EVENT_ICON_INDEX, nApptIcon));

	//---------------------------------------
	CString strMsgClass = GetEventGlobalPropVal_str(cszMAPIpropName_AppointmentMessageClass,
								cszMAPIpropVal_AppointmentMessageClass);
	VERIFY(_setPropTagVal_str(pMessage, PR_MESSAGE_CLASS_A, strMsgClass));

	//---------------------------------------
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_OutlookInternalVersion);

	int nOutlookVer = _getPropVal_int(pMessage, nPropTag);
	if (nOutlookVer == 0)
	{
		nOutlookVer = GetEventGlobalPropVal_int(cszMAPIpropName_OutlookInternalVersion,
							cnMAPIpropVal_OutlookInternalVersionDef);
		VERIFY(_setPropTagVal_int(pMessage, nPropTag, nOutlookVer));
	}

	//---------------------------------------
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_OutlookVersion);
	CString strOutlookVer = _getPropVal_str(pMessage, nPropTag);
	if (strOutlookVer.IsEmpty())
	{
		strOutlookVer = GetEventGlobalPropVal_str(cszMAPIpropName_OutlookVersion,
								cszMAPIpropVal_OutlookVersionDef);
		VERIFY(_setPropTagVal_str(pMessage, nPropTag, strOutlookVer));
	}
}

BOOL CXTPCalendarMAPIDataProvider::UpdateMAPIEvent(LPMESSAGE pMessage, CXTPCalendarEvent* pEvent)
{
	if (!pMessage || !pEvent)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	//-----------------------------------------------------------------------
	// ------- Is Recurrence --------
	int nRState = pEvent->GetRecurrenceState();

	int nIsRecurrence = (nRState == xtpCalendarRecurrenceMaster);

	if (nRState != xtpCalendarRecurrenceNotRecurring &&
		nRState != xtpCalendarRecurrenceMaster)
	{
		ASSERT(FALSE);
		return TRUE;
	}

	// get TimeZone
	TIME_ZONE_INFORMATION tziEvent;
	GetTimeZoneInformation(&tziEvent);

	int nPropTag;
	//-------------------

	//**** Common customizable properties
	_SetMAPIEventGlobalPropsIfNeed(pMessage, pEvent);
	//****

	// ------- Subject --------
	CString strTmp = pEvent->GetSubject();
	VERIFY(_setPropTagVal_str(pMessage, PR_SUBJECT_A, strTmp));

	// ------- Body --------
	strTmp = pEvent->GetBody();
	VERIFY(_setPropTagVal_str(pMessage, PR_BODY_A, strTmp));

	// ======= Verify time values for recurrence events ===========
	// If this is a recurring event, the master event stores the time
	// as the beginning of the recurring event through to the end
	// of the last event. We actually need the time of the first event here

	// ------- StartTime --------
	COleDateTime dtTmp = (nIsRecurrence == 0) ?
		pEvent->GetStartTime() :
		pEvent->GetStartTime() + pEvent->GetRecurrencePattern()->GetStartTime();
	SYSTEMTIME stUTCstart = xtp_TimeToUtc(&tziEvent, dtTmp);
	ASSERT(stUTCstart.wYear);
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_StartTime);
	VERIFY(_setPropTagVal_UtcTime(pMessage, nPropTag, stUTCstart));

	// ------- EndTime --------
	dtTmp = (nIsRecurrence == 0) ?
		pEvent->GetEndTime() :
		pEvent->GetStartTime() + pEvent->GetRecurrencePattern()->GetStartTime() + COleDateTimeSpan(0, 0 , pEvent->GetRecurrencePattern()->GetDurationMinutes(), 0);
	SYSTEMTIME stUTCend = xtp_TimeToUtc(&tziEvent, dtTmp);
	ASSERT(stUTCend.wYear);
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_EndTime);
	VERIFY(_setPropTagVal_UtcTime(pMessage, nPropTag, stUTCend));

	// ------- LOCATION --------
	strTmp = pEvent->GetLocation();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_Location);
	VERIFY(_setPropTagVal_str(pMessage, nPropTag, strTmp));

	// ------- AllDayEvent --------
	int nTmp = pEvent->IsAllDayEvent();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_AllDay);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- IsReminder --------
	nTmp = pEvent->IsReminder();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_ReminderSet);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- ReminderMinutesBeforeStart --------
	nTmp = pEvent->GetReminderMinutesBeforeStart();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_ReminderMinutesBefore);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- ReminderDate --------
	ULONG nPTagRmdDate = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_ReminderDate);

	if (pEvent->IsReminder())
	{
		VERIFY(_setPropTagVal_UtcTime(pMessage, nPTagRmdDate, stUTCstart)); // always equal to start time
	}
	else
	{
		_deletePropTag(pMessage, nPTagRmdDate);
	}

	// ------- BusyStatus --------
	nTmp = pEvent->GetBusyStatus();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_BusyStatus);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- Importance --------
	nTmp = pEvent->GetImportance();
	VERIFY(_setPropTagVal_int(pMessage, PR_IMPORTANCE, nTmp));

	// ------- Label --------
	nTmp = pEvent->GetLabelID();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_Color);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- Meeting --------
	nTmp = pEvent->IsMeeting();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_MeetingStatus);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));

	// ------- Private --------
	nTmp = pEvent->IsPrivate();
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropCommon_IsPrivate);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nTmp));
	VERIFY(_setPropTagVal_int(pMessage, PR_SENSITIVITY, nTmp ? SENSITIVITY_PRIVATE : SENSITIVITY_NONE));

	// ------- CreationTime --------
	dtTmp = pEvent->GetCreationTime();
	SYSTEMTIME stUTC = xtp_TimeToUtc(&tziEvent, dtTmp);
	ASSERT(stUTC.wYear);
	VERIFY(_setPropTagVal_UtcTime(pMessage, PR_CREATION_TIME, stUTC));

	// ------- LastModificationTime --------
	dtTmp = pEvent->GetLastModificationTime();
	stUTC = xtp_TimeToUtc(&tziEvent, dtTmp);
	ASSERT(stUTC.wYear);
	VERIFY(_setPropTagVal_UtcTime(pMessage, PR_LAST_MODIFICATION_TIME, stUTC));

	// -------- Custom Properties -------
	CXTPCalendarCustomProperties* pProps = pEvent->GetCustomProperties();
	if (pProps)
	{
		CXTPCalendarCustomProperties tmpStorage;
		_MoveMAPIEventRuntimeProps(&tmpStorage, pProps);

		pProps->SaveToXML(strTmp);

		nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_CustomProperties);
		VERIFY(_setPropTagVal_str(pMessage, nPropTag, strTmp));

		_MoveMAPIEventRuntimeProps(pProps, &tmpStorage);
	}

	// nIsRecurrence
	nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_IsRecuring);
	VERIFY(_setPropTagVal_int(pMessage, nPropTag, nIsRecurrence));

	if (nIsRecurrence)
	{
		CByteArray arRecData;
		CXTPCalendarMAPI_Recurrence::CXTPRcData RCData;
		CXTPCalendarRecurrencePatternPtr ptrPattern = pEvent->GetRecurrencePattern();

		BOOL bRes = CXTPCalendarMAPI_Recurrence::FillRCData(RCData, ptrPattern, pEvent);
		ASSERT(bRes);
		if (bRes)
		{
			bRes = CXTPCalendarMAPI_Recurrence::RCDataToBin(RCData, arRecData);
			ASSERT(bRes);
			if (bRes)
			{
				nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceState);
				VERIFY(_setPropTagVal_bin(pMessage, nPropTag, arRecData));
			}
		}

		//---------------------------------------------------------------------

		CByteArray arTZIData;
		xtp_GetMapiTZInfo(&tziEvent, arTZIData);

		ULONG ulTZTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_TimeZone);
		VERIFY(_setPropTagVal_bin(pMessage, ulTZTag, arTZIData));

		//- recurrence Start/end ----------------------------------------------
		nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceStart);
		VERIFY(_setPropTagVal_UtcTime(pMessage, nPropTag, stUTCstart));

		nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceEnd);
		VERIFY(_setPropTagVal_UtcTime(pMessage, nPropTag, stUTCend));

		if (ptrPattern->GetUseEndMethod() == xtpCalendarPatternEndNoDate)
		{
			// the end date set above was for 1 Jan 9999, which is fine for Outlook
			// but at least some versions of exchange server don't like it. Outlook
			// sets the end date to the end date/time of the first instance of the
			// pattern in this case.

			// ------- Fixed EndTime --------
			int nDuration = RCData.dwOccEndTime - RCData.dwOccStartTime;

			dtTmp = pEvent->GetStartTime() + CXTPCalendarUtils::Minutes2Span(nDuration);
			stUTCend = xtp_TimeToUtc(&tziEvent, dtTmp);
			ASSERT(stUTCend.wYear);

			nPropTag = m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_EndTime);
			VERIFY(_setPropTagVal_UtcTime(pMessage, nPropTag, stUTCend));
		}
	}
	else
	{
		CUIntArray arPropsTags;
		arPropsTags.Add( (ULONG)m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceState));
		arPropsTags.Add( (ULONG)m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceStart));
		arPropsTags.Add( (ULONG)m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_RecurrenceEnd));
		arPropsTags.Add( (ULONG)m_MapiHelper.GetPropTagByID(pMessage, xtpMAPIpropEvent_TimeZone));

		_deletePropsTags(pMessage, arPropsTags);
	}

	// -------

	return TRUE;
}

UINT CXTPCalendarMAPIDataProvider::_getStreamSize(IStream* pStream)
{
	ASSERT(pStream);
	if (!pStream)
	{
		return 0;
	}

	ULARGE_INTEGER  uliSize = {0, 0};
	LARGE_INTEGER   liZero = {0, 0};

	HRESULT hr = pStream->Seek(liZero, STREAM_SEEK_END, &uliSize);
	if (FAILED(hr))
		return 0;

	hr = pStream->Seek(liZero, STREAM_SEEK_SET, NULL);
	if (FAILED(hr))
	{
		return 0;
	}
	return uliSize.u.LowPart;
}

int CXTPCalendarMAPIDataProvider::_getSimpleMAPITypeSize(int nType)
{
	switch (nType)
	{
	case PT_I2:     return 2;       /* Signed 16-bit value */
	case PT_LONG:   return 4;       /* Signed 32-bit value */
	case PT_R4:     return 4;       /* 4-byte floating point */
	case PT_DOUBLE:     return 8;   /* Floating point double */
	case PT_CURRENCY:   return 8;   /* Signed 64-bit int (decimal w/    4 digits right of decimal pt) */
	case PT_APPTIME:    return sizeof(double);  /* Application time */
	case PT_ERROR:      return 4;   /* 32-bit error value */
	case PT_BOOLEAN:    return 2;   /* 16-bit boolean (non-zero true) */
	case PT_I8:         return 8;   /* 8-byte signed integer */
	case PT_SYSTIME:    return sizeof(FILETIME);    /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
	case PT_CLSID:      return sizeof(GUID);        /* OLE GUID */

	case PT_OBJECT:     /* Embedded object in a property */

	case PT_STRING8:    /* Null terminated 8-bit character string */
	case PT_UNICODE:    /* Null terminated Unicode string */
	case PT_BINARY:     /* Uninterpreted (counted byte array) */
		return 0;
	default:
		ASSERT(FALSE);
	}
	return 0;
}

BOOL CXTPCalendarMAPIDataProvider::_setPropTagVal_int(LPMESSAGE pMessage, ULONG ulPropTag, int nValue)
{
	int nType = PROP_TYPE(ulPropTag);
	int nTypeSize = _getSimpleMAPITypeSize(nType);

	if (nTypeSize <= 0 || nTypeSize > 4)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	SPropValue PropVal;
	ZeroMemory(&PropVal, sizeof(PropVal));

	PropVal.ulPropTag = ulPropTag;

	switch (nType)
	{
	case PT_I2:
		PropVal.Value.i = (short int)nValue;
		break;
	case PT_LONG:           /* Signed 32-bit value */
		PropVal.Value.l = (long)nValue;
		break;
	case PT_ERROR:      /* 32-bit error value */
		PropVal.Value.err = (SCODE)nValue;
		break;
	case PT_BOOLEAN:        /* 16-bit boolean (non-zero true) */
		PropVal.Value.b = (unsigned short int)nValue;
		break;

//  case PT_R4:     return 4;       /* 4-byte floating point */
//  case PT_DOUBLE:     return 8;   /* Floating point double */
//  case PT_CURRENCY:   return 8;   /* Signed 64-bit int (decimal w/    4 digits right of decimal pt) */
//  case PT_APPTIME:    return sizeof(double);  /* Application time */

//  case PT_I8:         return 8;   /* 8-byte signed integer */
//  case PT_SYSTIME:    return sizeof(FILETIME);    /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
//  case PT_CLSID:      return sizeof(GUID);        /* OLE GUID */

//  case PT_OBJECT:     /* Embedded object in a property */

//  case PT_STRING8:    /* Null terminated 8-bit character string */
//  case PT_UNICODE:    /* Null terminated Unicode string */
//  case PT_BINARY:     /* Uninterpreted (counted byte array) */
//      return 0;
	default:
		ASSERT(FALSE);
		return FALSE;
	}

	HRESULT hRes = this->HrSetOneProp(pMessage, &PropVal);
	return SUCCEEDED(hRes);
}

BOOL CXTPCalendarMAPIDataProvider::_setPropTagVal_str(LPMESSAGE pMessage, ULONG ulPropTag, LPCTSTR pcszValue)
{
	int nType = PROP_TYPE(ulPropTag);

	LPSPropValue pPropVal = NULL;

	if (nType == PT_STRING8) /* Null terminated 8-bit character string */
	{
		int nStrBuffSize = (int)_tcslen(pcszValue) * sizeof(TCHAR) + 2;
		int nBufSize = sizeof(SPropValue) + nStrBuffSize + 4;

		SCODE sc = this->MAPIAllocateBuffer(nBufSize, (void**)&pPropVal);
		if (sc != S_OK || !pPropVal)
		{
			return FALSE;
		}
		ZeroMemory(pPropVal, nBufSize);
		pPropVal->ulPropTag = ulPropTag;
		pPropVal->Value.lpszA = (LPSTR)(pPropVal+1);

		WCSTOMBS_S(pPropVal->Value.lpszA, pcszValue, nStrBuffSize);
	}
	else if (nType == PT_UNICODE) /* Null terminated Unicode string */
	{
		ASSERT(FALSE); //not implemented
		return FALSE;
	}
	else
	{
		ASSERT(FALSE);
		return FALSE;
	}

	HRESULT hRes = this->HrSetOneProp(pMessage, pPropVal);

	MAPI_FREEBUFFER(this, pPropVal);

	return SUCCEEDED(hRes);
}

BOOL CXTPCalendarMAPIDataProvider::_setPropTagVal_UtcTime(LPMESSAGE pMessage, ULONG ulPropTag, const SYSTEMTIME& stTime)
{
	int nType = PROP_TYPE(ulPropTag);

	SPropValue PropVal;
	ZeroMemory(&PropVal, sizeof(SPropValue));

	if (nType == PT_SYSTIME) // FILETIME
	{
		PropVal.ulPropTag = ulPropTag;

		FILETIME ft;
		ZeroMemory(&ft, sizeof(ft));

		if (!SystemTimeToFileTime(&stTime, &ft))
		{
			ASSERT(FALSE);
			return FALSE;
		}
		PropVal.Value.ft = ft;
	}
	else
	{
		ASSERT(FALSE);
		return FALSE;
	}

	HRESULT hRes = this->HrSetOneProp(pMessage, &PropVal);
	return SUCCEEDED(hRes);
}

BOOL CXTPCalendarMAPIDataProvider::_setPropTagVal_bin(LPMESSAGE pMessage,
									ULONG ulPropTag, const CByteArray& arData)
{
	int nType = PROP_TYPE(ulPropTag);

	SPropValue PropVal;
	ZeroMemory(&PropVal, sizeof(SPropValue));

	if (nType == PT_BINARY)
	{
		PropVal.ulPropTag = ulPropTag;
		PropVal.Value.bin.cb = (ULONG)arData.GetSize();
		PropVal.Value.bin.lpb = (BYTE*)arData.GetData();
	}
	else
	{
		ASSERT(FALSE);
		return FALSE;
	}

	HRESULT hRes = this->HrSetOneProp(pMessage, &PropVal);

	return SUCCEEDED(hRes);
}

HRESULT CXTPCalendarMAPIDataProvider::_getPropVal(LPMESSAGE pMessage, ULONG ulPropTag, CByteArray& rData)
{
	rData.RemoveAll();

	LPSTREAM pStream = NULL;
	LPSPropValue pProp = NULL;

	HRESULT hRes = this->HrGetOneProp(pMessage, ulPropTag, &pProp);

	if (hRes == MAPI_E_NOT_ENOUGH_MEMORY)
	{
		hRes = pMessage->OpenProperty(ulPropTag, &IID_IStream, STGM_READ,
										NULL, (LPUNKNOWN*)&pStream);

		if (FAILED(hRes) || !pStream)
		{
			hRes = FAILED(hRes) ? hRes : E_FAIL;
		}
		else
		{
			UINT nDataSize = _getStreamSize(pStream);
			if (!nDataSize)
			{
				hRes = E_FAIL;
			}
			else
			{
				rData.SetSize((int)nDataSize);

				ULONG nDataSize2 = 0;
				hRes = pStream->Read(rData.GetData(), nDataSize, &nDataSize2);

				ASSERT(nDataSize == nDataSize2);
				rData.SetSize((int)nDataSize2);
			}
			pStream->Release();
		}

	}
	else if (SUCCEEDED(hRes))
	{
		int nType = PROP_TYPE(pProp->ulPropTag);
		int nTypeSize = _getSimpleMAPITypeSize(nType);

		void* pData_src = NULL;
		if (nTypeSize > 0)
		{
			pData_src = (void*)&pProp->Value;
		}
		else
		{
			if (nType == PT_STRING8) /* Null terminated 8-bit character string */
			{
				pData_src = (void*)pProp->Value.lpszA;
				nTypeSize = (int)strlen(pProp->Value.lpszA) + 1;
			}
			else if (nType == PT_UNICODE) /* Null terminated Unicode string */
			{
				pData_src = (void*)pProp->Value.lpszW;
				nTypeSize = (int)wcslen(pProp->Value.lpszW) * 2 + 2;
			}
			else if (nType == PT_BINARY) /* Uninterpreted (counted byte array) */
			{
				pData_src = (void*)pProp->Value.bin.lpb;
				nTypeSize = pProp->Value.bin.cb;
			}
		}

		rData.SetSize(nTypeSize);
		MEMCPY_S(rData.GetData(), pData_src, nTypeSize);
	}

	MAPI_FREEBUFFER(this, pProp);

	return hRes;
}

CString CXTPCalendarMAPIDataProvider::_getPropVal_str(LPMESSAGE pMessage, ULONG ulPropTag)
{
	CString str;
	CByteArray arData;

	int nType = PROP_TYPE(ulPropTag);
	ASSERT(nType == PT_STRING8 || nType == PT_UNICODE);

	HRESULT hr = _getPropVal(pMessage, ulPropTag, arData);

	if (FAILED(hr) || arData.GetSize() == 0)
	{
		return str;
	}

	if (nType == PT_STRING8) /* Null terminated 8-bit character string */
	{
		str = (LPCSTR)arData.GetData();
	}
	else if (nType == PT_UNICODE) /* Null terminated Unicode string */
	{
		str = (LPCWSTR)arData.GetData();
	}
	return str;
}

int CXTPCalendarMAPIDataProvider::_getPropVal_int(LPMESSAGE pMessage, ULONG ulPropTag)
{
	CByteArray arData;

	int nType = PROP_TYPE(ulPropTag);
	ASSERT( nType == PT_I2 ||       nType == PT_LONG ||
			nType == PT_ERROR ||    nType == PT_BOOLEAN ||
			nType == PT_I8);

	HRESULT hr = _getPropVal(pMessage, ulPropTag, arData);

	if (FAILED(hr) || arData.GetSize() == 0)
	{
		return 0;
	}

	int nSize = (int)arData.GetSize();
	int nValue = 0;

	ASSERT(sizeof(ULONGLONG) == 8);

	if (nSize == 1)
	{
		nValue = (int)arData[0];
	}
	else if (nSize == 2)
	{
		nValue = (int)*((WORD*)arData.GetData());
	}
	else if (nSize == 3)
	{
		nValue = (int)RGB(arData[0], arData[1], arData[2]);
	}
	else if (nSize == 4)
	{
		nValue = (int)*((DWORD*)arData.GetData());
	}
	else if (nSize == 8)
	{
		nValue = (int)*((ULONGLONG*)arData.GetData());
	}
	else
	{
		ASSERT(FALSE);
	}

	return nValue;
}

SYSTEMTIME CXTPCalendarMAPIDataProvider::_getPropVal_UtcTime(LPMESSAGE pMessage, ULONG ulPropTag)
{
	SYSTEMTIME stUTCTime, stUTCTime0;
	ZeroMemory(&stUTCTime, sizeof(stUTCTime));
	ZeroMemory(&stUTCTime0, sizeof(stUTCTime0));

	if (ulPropTag == 0)
	{
		return stUTCTime0;
	}

	int nType = PROP_TYPE(ulPropTag);

	if (nType != PT_SYSTIME)
	{
		ASSERT(FALSE);
		return stUTCTime0;
	}

	CByteArray arData;
	HRESULT hr = _getPropVal(pMessage, ulPropTag, arData);

	if (FAILED(hr) || arData.GetSize() == 0)
	{
		return stUTCTime0;
	}
	ASSERT(arData.GetSize() == sizeof(FILETIME));

	FILETIME* pUTCTime = ((FILETIME*)arData.GetData());

	if (FileTimeToSystemTime(pUTCTime, &stUTCTime))
	{
		return stUTCTime;
	}

	return stUTCTime0;
}

CString CXTPCalendarMAPIDataProvider::_getPropVal_str(LPMESSAGE pMessage,
										const XTP_MAPI_PROP_NAME& propNameEx)
{
	ULONG ulPropTag = m_MapiHelper.GetPropTagByID(pMessage, propNameEx);

	return _getPropVal_str(pMessage, ulPropTag);
}

int CXTPCalendarMAPIDataProvider::_getPropVal_int(LPMESSAGE pMessage,
										const XTP_MAPI_PROP_NAME& propNameEx)
{
	ULONG ulPropTag = m_MapiHelper.GetPropTagByID(pMessage, propNameEx);

	return _getPropVal_int(pMessage, ulPropTag);
}

SYSTEMTIME CXTPCalendarMAPIDataProvider::_getPropVal_UtcTime(LPMESSAGE pMessage,
										const XTP_MAPI_PROP_NAME& propNameEx)
{
	ULONG ulPropTag = m_MapiHelper.GetPropTagByID(pMessage, propNameEx);

	return _getPropVal_UtcTime(pMessage, ulPropTag);
}

HRESULT CXTPCalendarMAPIDataProvider::_deletePropTag(LPMESSAGE pMessage, ULONG ulPropTag)
{
	CUIntArray arPropsTags;
	arPropsTags.Add(ulPropTag);

	return _deletePropsTags(pMessage, arPropsTags);
}

HRESULT CXTPCalendarMAPIDataProvider::_deletePropsTags(LPMESSAGE pMessage, CUIntArray& arPropsTags)
{
	if (!pMessage || arPropsTags.GetSize() == 0)
	{
		ASSERT(FALSE);
		return E_INVALIDARG;
	}

	int nTagsCount = (int)arPropsTags.GetSize();
	SPropTagArray* pPorpTagsArray = NULL;

	int nBufSize = sizeof(SPropTagArray) + nTagsCount * sizeof(ULONG) + 4;
	SCODE sc = this->MAPIAllocateBuffer(nBufSize, (void**)&pPorpTagsArray);
	if (sc != S_OK || !pPorpTagsArray)
	{
		return E_OUTOFMEMORY;
	}

	//---------------------------------------------
	pPorpTagsArray->cValues = nTagsCount;
	for (int i = 0; i < nTagsCount; i++)
	{
		pPorpTagsArray->aulPropTag[i] = (ULONG)arPropsTags[i];
	}

	HRESULT hrDelete = pMessage->DeleteProps(pPorpTagsArray, NULL);

	//---------------------------------------------
	MAPI_FREEBUFFER(this, pPorpTagsArray);

	return hrDelete;
}

void CXTPCalendarMAPIDataProvider::ImportRecurrence(CXTPCalendarEvent* pMasterEvent,
													const CByteArray& arRCData)
{
	ASSERT(pMasterEvent->GetRecurrenceState() == xtpCalendarRecurrenceMaster);

	CXTPCalendarRecurrencePatternPtr ptrPattern = pMasterEvent->GetRecurrencePattern();
	ASSERT(ptrPattern);
	if (!ptrPattern)
	{
		return;
	}

	CXTPCalendarMAPI_Recurrence::CXTPRcData RCData;

	BOOL bRead = CXTPCalendarMAPI_Recurrence::ReadRCData(RCData, arRCData);
	ASSERT(bRead);

	if (!bRead)
	{
		return;
	}
	ASSERT((RCData.wType - xtpMAPIRcType_Daily) >= 0 && (RCData.wType - xtpMAPIRcType_Daily) <= 3);

	CXTPCalendarMAPI_Recurrence::SetRecurrenceOptions(ptrPattern, RCData);

	CXTPCalendarMAPI_Recurrence::SetRecurrenceExceptions(pMasterEvent, ptrPattern, RCData);

	VERIFY( pMasterEvent->UpdateRecurrence(ptrPattern) );
}

CXTPCalendarEventsPtr CXTPCalendarMAPIDataProvider::ImportAllEvents(LPSRestriction lpRestriction)
{
	HRESULT hRes = S_OK;
	LPMAPITABLE lpContentsTable = NULL;
	LPSRowSet pRows = NULL;
	ULONG i;
	CXTPCalendarEventsPtr ptrEvents = new CXTPCalendarEvents();

	static SizedSPropTagArray(1, sptCols) = {1, {PR_ENTRYID} };

	hRes = m_lpCalendarFolder->GetContentsTable(0, &lpContentsTable);

	if (SUCCEEDED(hRes))
	{
		hRes = this->HrQueryAllRows(lpContentsTable, (LPSPropTagArray) &sptCols,
			lpRestriction, //restriction...current day
			NULL,//sort order...we're not using this parameter
			0, &pRows);

		if (SUCCEEDED(hRes))
		{
			for (i = 0; i < pRows->cRows; i++)
			{
				CXTPCalendarEventPtr ptrNextEvent = ImportEvent(&pRows->aRow[i]);
				ASSERT(ptrNextEvent);
				if (ptrNextEvent)
				{
					ptrEvents->Add(ptrNextEvent);
				}
			}
		}
	}

	//-------------------------------------------------------------------------
	if (pRows)
	{
		this->FreeProws(pRows);
	}

	MAPI_RELEASE(this, lpContentsTable);

	return ptrEvents;
}

BOOL CXTPCalendarMAPIDataProvider::ImportNewEvents()
{
	ASSERT(m_pMemDP);
	if (!m_pMemDP)
	{
		return FALSE;
	}

	BOOL bChanged = FALSE;

	HRESULT hRes = S_OK;
	LPMAPITABLE lpContentsTable = NULL;
	LPSRowSet pRows = NULL;
	ULONG i;
	CMap_EventIDs mapExistingEntries;
	CMap_EventIDs mapMissedEntries;

	// Retrieve EntryID's of all items from the MAPI Calendar folder
	static SizedSPropTagArray(2, sptCols) = {2, {PR_ENTRYID, PR_SEARCH_KEY} };

	hRes = m_lpCalendarFolder->GetContentsTable(0, &lpContentsTable);

	if (SUCCEEDED(hRes))
	{
		hRes = this->HrQueryAllRows(lpContentsTable, (LPSPropTagArray) &sptCols,
			NULL, //lpRestriction, //restriction...current day??
			NULL,//sort order...we're not using this parameter
			0, &pRows);

		if (SUCCEEDED(hRes))
		{
			// Iterate all appointment items read from data source
			for (i = 0; i < pRows->cRows; i++)
			{
				// ------- SEARCH_KEY --------
				if (PR_SEARCH_KEY != pRows->aRow[i].lpProps[1].ulPropTag)
				{
					ASSERT(FALSE);
					continue;
				}
				// trying to find this eventID
				DWORD dwEventID = GetEventID(pRows->aRow[i].lpProps[1].Value.bin.cb,
					(LPENTRYID)pRows->aRow[i].lpProps[1].Value.bin.lpb);

				// if not found - import
				if (XTP_CALENDAR_UNKNOWN_EVENT_ID == dwEventID)
				{
					CXTPCalendarEventPtr ptrNextEvent = ImportEvent(&pRows->aRow[i]);
					ASSERT(ptrNextEvent);
					if (ptrNextEvent)
					{
						bChanged = TRUE;
						VERIFY(m_pMemDP->AddEvent(ptrNextEvent));
					}
				}
				else
				{
					// if event found - add its ID into a map
					mapExistingEntries.SetAt(dwEventID, TRUE);
				}
			}

			// Delete all items which exists in internal data storage, but
			// were not just found in the contents table of the external folder.
			m_mapID.FindMissing(mapExistingEntries, mapMissedEntries);
			POSITION pos = mapMissedEntries.GetStartPosition();
			DWORD dwKey;
			BOOL bValue = FALSE;
			while (pos != NULL)
			{
				bChanged = TRUE;
				mapMissedEntries.GetNextAssoc(pos, dwKey, bValue);

				CXTPCalendarEventPtr ptrEventToDel = m_pMemDP->GetEvent(dwKey);
				// ASSERT(ptrEventToDel); // could be NULL if processing a few notifications in a row
				if (ptrEventToDel)
				{
					VERIFY(m_pMemDP->DeleteEvent(ptrEventToDel));
				}
			}
		}
	}

	// Cleanup
	if (pRows)
	{
		this->FreeProws(pRows);
	}
	MAPI_RELEASE(this, lpContentsTable);

	return bChanged;
}

LONG STDAPICALLTYPE CXTPCalendarMAPIDataProvider::MAPICallBack_OnNotify(
	LPVOID          lpvContext,
	ULONG           cNotif,
	LPNOTIFICATION  lpNotif)
{
	BOOL bProcessed = FALSE;
	// get current MAPI data provider
	CXTPCalendarMAPIDataProvider* pThis = (CXTPCalendarMAPIDataProvider*)lpvContext;
	ASSERT(pThis);
	if (!pThis)
		return MAPI_E_INVALID_PARAMETER;

	CXTPCalendarData* pMemDP = pThis->m_pMemDP;

	if (!pMemDP)
	{
		return S_OK;
	}

	//***********************
	SAFE_MANAGE_STATE(pThis->m_pModuleState);
	//***********************

	XTP_TRACE_MAPI_NF(_T("\n MAPICallBack_OnNotify [%s] (notifications count = %d)\n"),
		COleDateTime::GetCurrentTime().Format(), (int)cNotif);

	for (int i = 0; i < (int)cNotif; i++)
	{
		ULONG ulNfSender = lpNotif[i].info.obj.ulObjType;
		ULONG ulNfType = lpNotif[i].ulEventType;

		XTP_TRACE_MAPI_NF(_T("    NfSender = %d (%s)\n"), (int)ulNfSender,
							ulNfSender == MAPI_FOLDER ? _T("MAPI_FOLDER") : (ulNfSender == MAPI_MESSAGE ? _T("MAPI_MESSAGE") : _T("")) );

		// Check is notification related to active calendar folder ----------
		CString str_dbg_CalRel;
		CXTPMAPIBinary eidObj;
		if (ulNfSender == MAPI_FOLDER)
		{
			eidObj.Set(lpNotif[i].info.obj.cbEntryID, (LPBYTE)lpNotif[i].info.obj.lpEntryID);
			str_dbg_CalRel = _T("this");
		}
		else if (ulNfSender == MAPI_MESSAGE)
		{
			eidObj.Set(lpNotif[i].info.obj.cbParentID, (LPBYTE)lpNotif[i].info.obj.lpParentID);
			str_dbg_CalRel = _T("Parent folder");
		}

		if (!pThis->Equal(eidObj, pThis->m_eidCalendarFolder))
		{
			if (ulNfSender == MAPI_MESSAGE)
			{
				eidObj.Set(lpNotif[i].info.obj.cbOldParentID, (LPBYTE)lpNotif[i].info.obj.lpOldParentID);
				if (!pThis->Equal(eidObj, pThis->m_eidCalendarFolder))
				{
					XTP_TRACE_MAPI_NF(_T("    Calendar is: not related! SKIP. \n"));
					continue;
				}
				else
				{
					str_dbg_CalRel = _T("OLD Parent folder");
					pThis->ImportNewEvents();
					bProcessed = TRUE;
				}
			}
			else
			{
				XTP_TRACE_MAPI_NF(_T("    Calendar is: not related! SKIP. \n"));
				continue;
			}
		}

		XTP_TRACE_MAPI_NF(_T("    Calendar is: %s\n"), str_dbg_CalRel);

		CString str_dbg_NF;
		str_dbg_NF += (ulNfType & fnevObjectCreated) ? _T(" | Created") : _T("");
		str_dbg_NF += (ulNfType & fnevObjectDeleted) ? _T(" | Deleted") : _T("");
		str_dbg_NF += (ulNfType & fnevObjectModified) ?_T(" | Modified") : _T("");
		str_dbg_NF += (ulNfType & fnevObjectMoved)   ? _T(" | Moved") : _T("");
		str_dbg_NF += (ulNfType & fnevObjectCopied)  ? _T(" | Copied") : _T("");

		XTP_TRACE_MAPI_NF(_T("    fnevObject =%s \n"), str_dbg_NF);

		if (bProcessed)
		{
			return SUCCESS_SUCCESS;
		}
		//-------------------------------------------------------------------
		CXTPMAPIBinary keyID = pThis->GetSearchKey(lpNotif[i].info.obj.cbEntryID, lpNotif[i].info.obj.lpEntryID);
		DWORD dwEventID = pThis->GetEventID(keyID.GetBinarySize(), (LPENTRYID)keyID.GetBinaryData());
		CXTPCalendarEventPtr ptrEvent = pMemDP->GetEvent(dwEventID);

		XTP_TRACE_MAPI_NF(_T("    event ID = %d (%s)\n"), dwEventID,
							(LPCTSTR)(ptrEvent ? _T("exists") : _T("non-exists")) );

		// look what type of change was performed
		if (ulNfType & fnevObjectDeleted)
		{
			// Delete the event
			// Note that this message will never be received for deleted item for Exchange 5.5 and 2000.
			// More info at MS KB261172
			if (ptrEvent)
			{
				VERIFY( pMemDP->DeleteEvent(ptrEvent) );
				XTP_TRACE_MAPI_NF(_T("    Action = DELETE\n"));

				//#pragma NOTE("Advice to DP notification and resend")
				//pThis->m_pConnect->SendEvent(XTP_NC_CALENDAREVENTWASDELETED, (WPARAM)dwEventID, (LPARAM)(CXTPCalendarEvent*)ptrEvent);
			}
			continue;
		}

		if ((ulNfType & fnevObjectCreated) && ulNfSender == MAPI_MESSAGE)
		{
			if (dwEventID != XTP_CALENDAR_UNKNOWN_EVENT_ID)
			{
				//ASSERT(FALSE);
				XTP_TRACE_MAPI_NF(_T("    Action = skip\n"));
				return SUCCESS_SUCCESS;
			}

			// Add a new event
			CXTPCalendarEventPtr ptrEvent2 = pThis->ImportEvent(
										lpNotif[i].info.obj.cbEntryID,
										lpNotif[i].info.obj.lpEntryID);

			if (ptrEvent && ptrEvent2)
			{
				//Event already exists
				VERIFY( pMemDP->ChangeEvent(ptrEvent2) );
				XTP_TRACE_MAPI_NF(_T("    Action = CHANGE\n"));
			}
			else if (ptrEvent2)
			{
				VERIFY( pMemDP->AddEvent(ptrEvent2) );
				XTP_TRACE_MAPI_NF(_T("    Action = ADD\n"));
			}

			continue;
		}

		if ((ulNfType & fnevObjectMoved) && ulNfSender == MAPI_MESSAGE)
		{
			CXTPCalendarEventPtr ptrEvent2 = pThis->ImportEvent_FromCalendarFolderOnly(
										lpNotif[i].info.obj.cbEntryID,
										lpNotif[i].info.obj.lpEntryID);

			if (ptrEvent && ptrEvent2)
			{
				//Event was moved inside Calendar folder
				VERIFY( pMemDP->ChangeEvent(ptrEvent2) );
				XTP_TRACE_MAPI_NF(_T("    Action = CHANGE\n"));
			}
			else if (ptrEvent)
			{
				//Event was moved Out of Calendar folder (to "Deleted Items" folder)
				VERIFY( pMemDP->DeleteEvent(ptrEvent) );
				XTP_TRACE_MAPI_NF(_T("    Action = DELETE (moved to other folder)\n"));
			}

			continue;
		}

		if ((ulNfType & fnevObjectModified) && ulNfSender == MAPI_MESSAGE)
		{
			// is Our event changed
			if (ptrEvent)
			{
				// Read new event data
				CXTPCalendarEventPtr ptrEvent2 = pThis->ImportEvent(
											lpNotif[i].info.obj.cbEntryID,
											lpNotif[i].info.obj.lpEntryID);
				ASSERT(ptrEvent2);

				// Update event properties in the cache
				if (ptrEvent2)
				{
					ASSERT(ptrEvent->GetEventID() == ptrEvent2->GetEventID());

					DWORD dwPatternID = ptrEvent->GetRecurrencePatternID();

					if (dwPatternID != XTP_CALENDAR_UNKNOWN_RECURRENCE_PATTERN_ID)
					{
						ptrEvent2->SetRecurrencePatternID(dwPatternID);
					}

					pMemDP->ChangeEvent(ptrEvent2);
					XTP_TRACE_MAPI_NF(_T("    Action = CHANGE.2\n"));
				}
			}
			continue;
		}
	}

	return SUCCESS_SUCCESS;
}

DWORD CXTPCalendarMAPIDataProvider::GetNextUniqueEventID(DWORD dwEventID)
{
	if (XTP_CALENDAR_UNKNOWN_EVENT_ID == dwEventID)
		dwEventID = GetNextFreeTempID();

	CXTPMAPIBinary eKeyTmp;
	while (m_mapID.Lookup(dwEventID, eKeyTmp))
	{
		dwEventID = GetNextFreeTempID();
	}
	ASSERT(m_mapID.Lookup(dwEventID, eKeyTmp) == FALSE);
	return dwEventID;
}

CXTPMAPIBinary CXTPCalendarMAPIDataProvider::GetSearchKey(ULONG cbEntryID, LPENTRYID lpEntryID)
{
	CXTPMAPIBinary keySearch;

	if (cbEntryID == 0 || !lpEntryID || !m_lpCalendarFolder)
	{
		ASSERT(FALSE);
		return keySearch;
	}

	LPMESSAGE pMessage = NULL;
	ULONG ulObjType = NULL;

	// open event from Calendar Folder storage
	m_lpCalendarFolder->OpenEntry(cbEntryID, lpEntryID,
		NULL,//default interface
		MAPI_BEST_ACCESS,
		&ulObjType,
		(LPUNKNOWN*)&pMessage);

	//ASSERT(ulObjType == 5);
	//ASSERT(pMessage); // CHECK
	if (!pMessage)
	{
		return keySearch;
	}

	// Get SearchKey
	CByteArray arSearchKey;
	HRESULT hrID = _getPropVal(pMessage, PR_SEARCH_KEY, arSearchKey);
	if (SUCCEEDED(hrID))
	{
		keySearch.Set((int)arSearchKey.GetSize(), arSearchKey.GetData());
	}

	MAPI_RELEASE(this, pMessage);

	return keySearch;
}

CXTPMAPIBinary CXTPCalendarMAPIDataProvider::GetEntryID(CXTPMAPIBinary& eSearchKey)
{
	if (eSearchKey.GetBinarySize() == 0 || !eSearchKey.GetBinaryData() || !m_lpCalendarFolder)
	{
		ASSERT(FALSE);
		return eSearchKey;
	}

	HRESULT hRes = S_OK;
	LPMAPITABLE lpContentsTable = NULL;
	LPSRowSet pRows = NULL;
	CXTPMAPIBinary eid;

	LPSRestriction pRestriction = BuildBinaryRestriction(eSearchKey.GetBinarySize(), eSearchKey.GetBinaryData(), PR_SEARCH_KEY);

	static SizedSPropTagArray(1, sptCols) = {1, {PR_ENTRYID} };

	hRes = m_lpCalendarFolder->GetContentsTable(0, &lpContentsTable);

	if (SUCCEEDED(hRes))
	{
		hRes = this->HrQueryAllRows(lpContentsTable, (LPSPropTagArray) &sptCols,
			pRestriction, //restriction...
			NULL,//sort order...we're not using this parameter
			0, &pRows);

		if (SUCCEEDED(hRes) && pRows->cRows > 0)
		{
			// ASSERT(pRows->cRows == 1); // we can get only one item by its Search Key
			ASSERT(PR_ENTRYID == pRows->aRow[0].lpProps[0].ulPropTag); // we asked only for EntryID
			eid.Set(pRows->aRow[0].lpProps[0].Value.bin.cb,
				pRows->aRow[0].lpProps[0].Value.bin.lpb);
		}
	}

	// cleanup
	if (pRows)
	{
		this->FreeProws(pRows);
	}
	MAPI_RELEASE(this, lpContentsTable);

	return eid;
}

DWORD CXTPCalendarMAPIDataProvider::GetEventID(
	ULONG cbSearchKey, LPENTRYID lpSearchKey, BOOL bAddNew)
{
	CXTPMAPIBinary eKey(cbSearchKey, lpSearchKey);

	DWORD dwNextID = XTP_CALENDAR_UNKNOWN_EVENT_ID;

	// lookup a map.
	if (m_mapID.Lookup(eKey, dwNextID))
		return dwNextID;

	// ID not found - add new one if necessary
	if (!bAddNew)
		return XTP_CALENDAR_UNKNOWN_EVENT_ID;

	dwNextID = GetNextUniqueEventID();
	m_mapID.Add(dwNextID, eKey);

	return dwNextID;
}

BOOL CXTPCalendarMAPIDataProvider::Equal(const CXTPMAPIBinary& eid1, const CXTPMAPIBinary& eid2)
{
	BOOL bRes = FALSE;

	// 1st check using simple byte-to-byte comparison
	bRes = CXTPMAPIBinary::IsBinaryEqual(eid1, eid2);
	if (bRes)
		return bRes;

	// 2nd check using CompareEntryIDs method.
	// This is required because EntryID versions can change (i.e. A single
	// provider can support more than one form of entry id) and it is up to
	// a specific provider to decide if two EntryID's compare. The way of
	// doing this is calling the CompareEntryIDs method on the on the
	// appropriate object.
	if (!m_lpMAPISession)
		return bRes;

	ULONG ulRes;
	if (SUCCEEDED(m_lpMAPISession->CompareEntryIDs(
		eid1.m_cb, (LPENTRYID)eid1.GetBinaryData(),
		eid2.m_cb, (LPENTRYID)eid2.GetBinaryData(),
		0, &ulRes)))
	{
		bRes = (BOOL)ulRes;
	}

	return bRes;
}

BOOL CXTPCalendarMAPIDataProvider::OnTimeZoneChanged()
{
	if (IsOpen())
	{
		// Re-Read all events to the cache data provider
		if (m_pMemDP)
		{
			CXTPAutoResetValue<BOOL> autoReset(m_bDisableNotificationsSending, FALSE);
			m_bDisableNotificationsSending = TRUE;

			m_pMemDP->RemoveAllEvents();

			m_pMemDP->AddEvents(ImportAllEvents());
		}

		return TRUE;
	}

	return FALSE;
};
/////////////////////////////////////////////////////////////////////////////
//
// CXTPMAPIBinary
//

CXTPMAPIBinary::CXTPMAPIBinary()
{
	m_cb = 0;
}

CXTPMAPIBinary::CXTPMAPIBinary(ULONG cbEntryID, LPENTRYID lpEntryID)
{
	Set(cbEntryID, (LPBYTE)lpEntryID);
}

CXTPMAPIBinary::CXTPMAPIBinary(const CXTPMAPIBinary& eid)
{
	m_cb = eid.m_cb;
	m_arBytes.RemoveAll();
	m_arBytes.Append(eid.m_arBytes);
}

CXTPMAPIBinary::~CXTPMAPIBinary()
{
}

CXTPMAPIBinary& CXTPMAPIBinary::operator=(const CXTPMAPIBinary& eid)
{
	m_cb = eid.m_cb;
	m_arBytes.SetSize(m_cb);
	MEMCPY_S(m_arBytes.GetData(), eid.GetBinaryData(), m_cb);

	return *this;
}

ULONG CXTPMAPIBinary::GetBinarySize() const
{
	return m_cb;
}

LPBYTE CXTPMAPIBinary::GetBinaryData() const
{
	return (BYTE*)m_arBytes.GetData();
}

void CXTPMAPIBinary::Set(ULONG cbSize, LPBYTE lpData)
{
	m_cb = cbSize;

	m_arBytes.SetSize(m_cb);
	MEMCPY_S(m_arBytes.GetData(), lpData, m_cb);
}

void CXTPMAPIBinary::Set(LPSPropValue pPropEntryID)
{
	if (!pPropEntryID || pPropEntryID->ulPropTag != PR_ENTRYID)
	{
		ASSERT(FALSE);
		Set(0, NULL);
		return;
	}

	Set(pPropEntryID->Value.bin.cb, pPropEntryID->Value.bin.lpb);
}

BOOL CXTPMAPIBinary::IsBinaryEqual(
	const CXTPMAPIBinary& eid1, const CXTPMAPIBinary& eid2)
{
	if ((eid1.m_cb != eid2.m_cb) || (eid1.m_arBytes.GetSize() != eid2.m_arBytes.GetSize()) )
		return FALSE;

	return 0 == memcmp(
		eid1.m_arBytes.GetData(),
		eid2.m_arBytes.GetData(),
		eid1.m_arBytes.GetSize() );
}

#ifdef _DEBUG
void CXTPMAPIBinary::DebugPrint()
{
	TRACE(_T("Bin size:%d\t"), m_cb);
	for (int i = 0; i < m_arBytes.GetSize(); i++)
	{
		TRACE(_T("%d "), m_arBytes.GetAt(i));
	}
	TRACE(_T("\n"));
}
#endif

/////////////////////////////////////////////////////////////////////////////
//
// CXTP_ID_Collection
//

CXTP_ID_Collection::CXTP_ID_Collection()
{
	m_mapEventID.InitHashTable(XTP_OBJECT_CACHE_HASH_TABLE_SIZE, FALSE);
	m_mapEntryID.InitHashTable(XTP_OBJECT_CACHE_HASH_TABLE_SIZE, FALSE);
}

BOOL CXTP_ID_Collection::Lookup(
	const DWORD dwEventID, CXTPMAPIBinary& obEntryID)
{
	return m_mapEventID.Lookup(dwEventID, obEntryID);
}

BOOL CXTP_ID_Collection::Lookup(
	CXTPMAPIBinary& obEntryID, DWORD& dwEventID)
{
	return m_mapEntryID.Lookup(obEntryID, dwEventID);
}

BOOL CXTP_ID_Collection::Add(
	const DWORD dwEventID, CXTPMAPIBinary& obEntryID)
{
	m_mapEventID.SetAt(dwEventID, obEntryID);
	m_mapEntryID.SetAt(obEntryID, dwEventID);

	return TRUE;
}

void CXTP_ID_Collection::FindMissing(CMap_EventIDs& mapExisting, CMap_EventIDs& mapMissed)
{
	POSITION pos = m_mapEventID.GetStartPosition();
	DWORD dwKey;
	CXTPMAPIBinary eID;
	BOOL bValue = FALSE;
	while (pos != NULL)
	{
		m_mapEventID.GetNextAssoc(pos, dwKey, eID);

		BOOL bFound = mapExisting.Lookup(dwKey, bValue);
		if (!bFound)
		{
			mapMissed.SetAt(dwKey, TRUE);
		}
	}
}
/////////////////////////////////////////////////////////////////////////////
//
// CMAPIPropIDMap
//

CXTPCalendarMAPIDataProvider::CMAPIPropIDMap::CMAPIPropIDMap()
	: m_pProvider(NULL)
{
	m_mapPropID2Tag.InitHashTable(199, FALSE);
	m_mapPropTag2ID.InitHashTable(199, FALSE);
}

void CXTPCalendarMAPIDataProvider::CMAPIPropIDMap::_UpdateMaps(LPMESSAGE pMessage)
{
	ASSERT(pMessage);
	if (!pMessage)
		return;

	ULONG ulPropNames = 0;
	LPMAPINAMEID* ppArPropNames = NULL;
	LPSPropTagArray pPropTags = NULL;

	HRESULT hRes = pMessage->GetPropList(0, &pPropTags);

	if (SUCCEEDED(hRes) && pPropTags)
	{
		hRes = pMessage->GetNamesFromIDs(&pPropTags, NULL, MAPI_NO_STRINGS, &ulPropNames, &ppArPropNames);

		if (SUCCEEDED(hRes) && ppArPropNames && ulPropNames)
		{
			ASSERT(pPropTags->cValues == ulPropNames);
			XTP_TRACE_READ_IDS(_T("Prop ID -> Tag (%d)\n"), ulPropNames);

			for (int i = 0; i < (int)ulPropNames; i++)
			{
				if (!ppArPropNames[i])
				{
					continue;
				}
				if (ppArPropNames[i]->ulKind == MNID_ID)
				{
					ULONG ulID = (ULONG)ppArPropNames[i]->Kind.lID;
					ULONG ulTag = pPropTags->aulPropTag[i];

					m_mapPropID2Tag[ulID] = ulTag;
					m_mapPropTag2ID[ulTag] = ulID;

					//if (ulID >= 0x8000)
					{
						XTP_TRACE_READ_IDS(_T("Prop ID -> Tag (%x -> %x)\n"), ulID, ulTag);
					}
				}
			}
		}
	}

	MAPI_FREEBUFFER(m_pProvider, ppArPropNames);
	MAPI_FREEBUFFER(m_pProvider, pPropTags);
}

ULONG CXTPCalendarMAPIDataProvider::CMAPIPropIDMap::_GetPropTagFromID_ex(LPMESSAGE pMessage,
												const XTP_MAPI_PROP_NAME& propNameEx)
{
	ASSERT(pMessage);
	if (!pMessage || !m_pProvider)
		return 0;

	ULONG ulPropTag = 0;

	LPMAPINAMEID pPropName = NULL;
	LPSPropTagArray pPropTag = NULL;

	HRESULT hRes = m_pProvider->MAPIAllocateBuffer(sizeof(MAPINAMEID), (void**)&pPropName);

	if (SUCCEEDED(hRes) && pPropName)
	{
		ZeroMemory(pPropName, sizeof(MAPINAMEID));
		pPropName->lpguid = (LPGUID)&propNameEx.m_GuidPS;
		pPropName->ulKind = MNID_ID;
		pPropName->Kind.lID = propNameEx.m_ulID;

		hRes = pMessage->GetIDsFromNames(1, &pPropName, MAPI_CREATE, &pPropTag);
		if (SUCCEEDED(hRes) && pPropTag && pPropTag->cValues)
		{
			ulPropTag = pPropTag->aulPropTag[0];

			// validate prop type
			ulPropTag = CHANGE_PROP_TYPE(ulPropTag, propNameEx.m_ulType);
		}
	}
	MAPI_FREEBUFFER(m_pProvider, pPropName);
	MAPI_FREEBUFFER(m_pProvider, pPropTag);

	return ulPropTag;
}

ULONG CXTPCalendarMAPIDataProvider::CMAPIPropIDMap::GetPropTagByID(
					LPMESSAGE pMessage, const XTP_MAPI_PROP_NAME& propNameEx)
{
	ULONG ulTag = 0;

	if (!m_mapPropID2Tag.Lookup(propNameEx.m_ulID, ulTag))
	{
		_UpdateMaps(pMessage);

		if (!m_mapPropID2Tag.Lookup(propNameEx.m_ulID, ulTag))
		{
			ulTag = _GetPropTagFromID_ex(pMessage, propNameEx);
			ASSERT(ulTag); // WARNING!
			if (ulTag)
			{
				m_mapPropID2Tag[propNameEx.m_ulID] = ulTag;
				m_mapPropTag2ID[ulTag] = propNameEx.m_ulID;

				return ulTag;
			}
		}
	}

	return ulTag;
}

ULONG CXTPCalendarMAPIDataProvider::CMAPIPropIDMap::GetPropIDByTag(LPMESSAGE pMessage, ULONG ulPropTag)
{
	ULONG ulID = 0;

	if (!m_mapPropTag2ID.Lookup(ulPropTag, ulID))
	{
		_UpdateMaps(pMessage);
	}
	if (!m_mapPropTag2ID.Lookup(ulPropTag, ulID))
	{
		ulID = 0;
	}
	return ulID;
}