/******************************************************************************
 *
 * Copyright (c) 1998-2000 Palm, Inc. or its subsidiaries.
 * All rights reserved.
 *
 * File: DateTransfer.c
 *
 * Release: Palm OS SDK 4.0 (63220)
 *
 * Description:
 *      DateBook routines to transfer records.
 *
 * History:
 *			Name		Date		Description
 *			----		----		-----------
 *			djk		8/9/97	Initial Revision
 *			rbb		6/10/99	Removed obsoleted code that worked around
 *									single-segment linker limitation
 *			peter		3/29/00	Add support for parsing universal times (as generated by Outlook)
 *
 *****************************************************************************/

#include <PalmOS.h>

#include <TraceMgr.h>
#include <PdiLib.h>
#include <UDAMgr.h>

#include <PalmUtils.h>

#include "Datebook.h"
#include "ToDo.h"

//#include "DateTime.h"
//#include  "DateTransfer.h"


// Use this to determine whether the vCalendar 1.0 format used for beaming events uses
// universal time (GMT) instead of local time. Both formats are supported by the standard,
// but Palm OS 3.5 and older devices have always used local time. Using universal time
// causes problems when beaming to these older Palm devices, since they don't know how to
// convert from GMT to local time. Turn this on for testing the ability of this version
// of Datebook to handle incoming beams from devices which use universal time, such as a
// Windows laptop beaming a vCalendar exported from Outlook (or from Palm desktop).
#define GENERATE_UNIVERSAL_TIME_VCALENDARS	0

#define identifierLengthMax		40
#define tempStringLengthMax		24			// AALARM is longest
#define RRULEStringLengthMax	48			// ABa: Now used to construct the whole RRULE value
#define dateDBType				'DATA'

#define dateSuffix					("." dateExtension)

// Aba: internal version of vCalendar. Must be updated
// the export side of the vCalendar code evoluate

#define kVObjectVersion						"4.0"

/////////////////////////////////////
//  Macros
#define BitAtPosition(pos)                ((UInt32)1 << (pos))
#define GetBitMacro(bitfield, index)      ((bitfield) & BitAtPosition(index))
#define SetBitMacro(bitfield, index)      ((bitfield) |= BitAtPosition(index))
#define RemoveBitMacro(bitfield, index)   ((bitfield) &= ~BitAtPosition(index))



static UInt16 GetChar(const void * exgSocketP);
static void PutString(void * exgSocketP, const Char * const stringP);


/***********************************************************************
 *
 *   Global variables from MainApp can NOT be referenced in our PlugIn!
 *
 ***********************************************************************/

/***********************************************************************
 *
 * FUNCTION:		PrvPdiLibLoad
 *
 * DESCRIPTION:		Load Pdi library
 * PARAMETERS:		a pointer to an integer: the refNum of the libary
 *						return whether the library had to be loaded (and therefore
 *								needs to be unloaded)
 *
 * RETURNED:		An error if library is not found
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			ABa		4/10/00		Created
 *
 ***********************************************************************/
static Err PrvPdiLibLoad(UInt16* refNum, Boolean *loadedP)
{
    Err	error;

    // Load the Pdi library

    // Check if the library was pre-loaded (this is useful if we can
    // be called from another app via an action code and want to use an existing
    // instance of the library in case our caller has already loaded it)
    *loadedP = false;
    error = SysLibFind(kPdiLibName, refNum);
    if (error != 0)
    {
        error = SysLibLoad(sysResTLibrary, sysFileCPdiLib, refNum);
		TraceOutput(TL(appErrorClass, "Loading Pdi library."));
        if (! error)
            *loadedP = true;
    }
    if (error)
    {
        // We're here because the Pdi library failed to load.
        // Inform the user or do something else defensive here.
        ErrNonFatalDisplay(kPdiLibName " not found");
        return error;
    }
    
    error = PdiLibOpen(*refNum);

    return error;
}

/***********************************************************************
 *
 * FUNCTION:		PrvPdiLibUnload
 *
 * DESCRIPTION:		Unload Pdi library
 * PARAMETERS:		The refnum of the pdi library 
 *						Whether the library was loaded (and therefore needs to be unloaded)
 *
 * RETURNED:		NONE
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			ABa		4/10/00		Created
 *
 ***********************************************************************/
static void PrvPdiLibUnload(UInt16 refNum, Boolean loaded)
{
	if (PdiLibClose(refNum) == 0)
	{
		TraceOutput(TL(appErrorClass, "Unloading Pdi library."));

		if (loaded)
			SysLibRemove(refNum);
	}
}

/***********************************************************************
 *
 * FUNCTION:		PrvTransferCleanFileName
 *
 * DESCRIPTION:		Remove dot characters in file name but not the least
 * PARAMETERS:		a pointer to a string
 *
 * RETURNED:		String parameter doesn't contains superfluous dot characters
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			ABa		7/28/00		Created
 *
 ***********************************************************************/
static void PrvTransferCleanFileName(Char* ioFileName)
{
	Char* 	mayBeLastDotP;
	Char*  	lastDotP;
	UInt32	chrFullStopSize = TxtCharSize(chrFullStop);	
    
	// prevent NULL & empty string
	if (ioFileName == NULL || *ioFileName == 0)
		return;

	// remove dot but not the last one
	mayBeLastDotP = StrChr(ioFileName, 	chrFullStop);
	while ((lastDotP = StrChr(mayBeLastDotP + chrFullStopSize, chrFullStop)))
	{
		// remove the dot
		StrCopy(mayBeLastDotP, mayBeLastDotP + chrFullStopSize);
		mayBeLastDotP = lastDotP - chrFullStopSize;
	}
}

/***********************************************************************
 *
 * FUNCTION:    ApptGetAlarmTimeVCalForm
 *
 * DESCRIPTION: This routine determines the date and time of the next alarm
 *              for the appointment passed.
 *
 * PARAMETERS:  apptRec     - pointer to an appointment record
 *
 * RETURNED:    date and time of the alarm, in seconds, or zero if there
 *              is no alarm
 *
 *	NOTE:		the only differences between this function and the
 *             	function ApptGetAlarmTime in the App is that
 *			 	this function does not return 0 if the alarm time has passed
 *				and it returns the first event Date as the alarm date
 *				for reapeating events
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art		6/20/95		Initial Revision
 *			djk		7/28/97		modified for vCal
 *
 ***********************************************************************/
static UInt32 ApptGetAlarmTimeVCalForm (ApptDBRecordPtr apptRec)
{
    UInt32 advance;
    UInt32 alarmTime;
    DateTimeType apptDateTime;


    // An alarm on an untimed event triggers at midnight.
    if (TimeToInt (apptRec->when->startTime) == apptNoTime)
    {
        apptDateTime.minute = 0;
        apptDateTime.hour = 0;
    }
    else
    {
        apptDateTime.minute = apptRec->when->startTime.minutes;
        apptDateTime.hour = apptRec->when->startTime.hours;
    }
    apptDateTime.second = 0;
    apptDateTime.day = apptRec->when->date.day;
    apptDateTime.month = apptRec->when->date.month;
    apptDateTime.year = apptRec->when->date.year + firstYear;



    // Compute the time of the alarm by adjusting the date and time
    // of the appointment by the length of the advance notice.
    advance = apptRec->alarm->advance;
    switch (apptRec->alarm->advanceUnit)
    {
    case aauMinutes:
        advance *= minutesInSeconds;
        break;
    case aauHours:
        advance *= hoursInSeconds;
        break;
    case aauDays:
        advance *= daysInSeconds;
        break;
    }

    alarmTime = TimDateTimeToSeconds (&apptDateTime) - advance;

    return alarmTime;
}


/************************************************************
 *
 * FUNCTION: TranslateAlarm
 *
 * DESCRIPTION: Translate an alarm in seconds to a DateTimeType.
 * Broken out of DateImportVEvent for linking on the device purposes.
 *
 * PARAMETERS:
 *			newDateRecordP - the new record
 *			alarmDTinSec - date and time of the alarm in seconds
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			roger	9/19/97	Created
 *
 *************************************************************/

static void TranslateAlarm(ApptDBRecordType *newDateRecordP, UInt32 alarmDTinSec)
{

    DateTimeType eventDT;
    UInt32 alarmAdvanceSeconds;
    UInt32 alarmAdvance;

    // allocate a new AlarmInfoType
    newDateRecordP->alarm = (AlarmInfoType *) MemPtrNew (sizeof(AlarmInfoType));

    eventDT.year  = newDateRecordP->when->date.year + firstYear;
    eventDT.month  = newDateRecordP->when->date.month;
    eventDT.day  = newDateRecordP->when->date.day;

    if (TimeToInt (newDateRecordP->when->startTime) == noTime)
    {
        // The time for alarms is midnight.
        eventDT.hour  = 0;
        eventDT.minute = 0;
    }
    else
    {
        eventDT.hour  = newDateRecordP->when->startTime.hours;
        eventDT.minute = newDateRecordP->when->startTime.minutes;
    }
    eventDT.second = 0;

    alarmAdvanceSeconds = TimDateTimeToSeconds(&eventDT) - alarmDTinSec;
    
	// aro - Fix Bug #51570
	// Check if the alarm is after the event.
	// In this case set it to the same time since we don't handle post event alarm
	if ((Int32)alarmAdvanceSeconds < 0)
	{
		TraceOutput(TL(appErrorClass, "*** Alarm is after the starting time. Set it to the same time"));
		alarmAdvanceSeconds = 0;
	}
	
	// convert to minutes
    alarmAdvance = alarmAdvanceSeconds / minutesInSeconds;

    if (alarmAdvance < 100 && alarmAdvance != hoursInMinutes)
    {
        newDateRecordP->alarm->advanceUnit = aauMinutes;
        newDateRecordP->alarm->advance = (Int8) alarmAdvance;
    }
    else
    {
        // convert to hours
        alarmAdvance = alarmAdvance / hoursInMinutes;

        if (alarmAdvance < 100 && (alarmAdvance % hoursPerDay))
        {
            newDateRecordP->alarm->advanceUnit = aauHours;
            newDateRecordP->alarm->advance = (Int8) alarmAdvance;
        }
        else
        {
            // convert to days
            alarmAdvance = alarmAdvance / hoursPerDay;

            // set to the lesser of 99 and alarmAdvance
            newDateRecordP->alarm->advanceUnit = aauDays;

            if (alarmAdvance < 99)
                newDateRecordP->alarm->advance = (Int8) alarmAdvance;
            else
                newDateRecordP->alarm->advance = 99;
        }
    }
}


#pragma mark ----------------------------
/************************************************************
 *
 * FUNCTION: GetToken
 *
 * DESCRIPTION: Extracts first available token from given
 *		string. Tokens are assumed to be separated by "white
 *		space", as used by the IsSpace() function.
 *
 * PARAMETERS:
 *		startP		-	str ptr from which to extract
 *		tokenP		-	str ptr where to store found token
 *
 * RETURNS: str ptr of where to start next token, or null if
 *		end of string is reached.
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			frigino	12/3/97	Stolen from rest of code & modified
 *
 *************************************************************/
static char* GetToken(char* startP, char* tokenP)
{
    char		c;

    // Skip leading "blank space"
    while (TxtCharIsSpace(*startP))
        startP += TxtNextCharSize(startP, 0);

    // DOLATER kwk - figure out if we need to worry about
    // anything other than 7-bit ascii in this routine.

    // Get first char
    c = *startP;
    // While char is not terminator, nor is it "blank space"
    while (c != '\0' && !TxtCharIsSpace(c))
    {
        // Copy char to token
        *tokenP++ = c;
        // Advance to next char
        c = *(++startP);
    }
    // Terminate token
    *tokenP = '\0';

    // Skip trailing "blank space"
    if (c != '\0')
        while (TxtCharIsSpace(*startP))
            ++startP;

    // Return next token ptr
    return ((*startP == '\0') ? NULL : startP);
}


/************************************************************
 *
 * FUNCTION: MatchDateTimeToken
 *
 * DESCRIPTION: Extract date and time from the given string,
 *					 converting from GMT to local time if necessary.
 *
 * PARAMETERS:
 *		tokenP	-	string ptr from which to extract
 *		dateP		-	ptr where to store date (optional)
 *		timeP		-	ptr where to store time (optional)
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			frigino	12/3/97	Stolen from rest of code & modified
 *			peter		3/29/00	Add support for universal time format, converting
 *									to local time based on current time zone settings.
 *
 *************************************************************/
static void MatchDateTimeToken(
                               const char*	tokenP,
                               DateType*	dateP,
                               TimeType*	timeP)
{
    char				identifier[identifierLengthMax];
    int				nv;
    DateType			date;
    TimeType			time;
    DateTimeType	dateTime;

    // Use identifier[] as a temp buffer to copy parts of the vCal DateTime
    // so we can convert them to correct form.  This date portion
    // is 4 chars (date) + 2 chars (month) + 2 chars (day) = 8 chars long.
    // Optional Z suffix for universal time yields total of 9 chars long.

    // Get the date whether desired by caller or not. It must precede the time.
    // Read the Year.
    StrNCopy(identifier, tokenP, 4);
    identifier[4] = nullChr;
    nv = StrAToI(identifier);
    // Validate the number and use it.
    if (nv < firstYear || lastYear < nv)
        nv = firstYear;
    date.year = nv - firstYear;
    tokenP += StrLen(identifier) * sizeof(char);

    // Read the Month.
    StrNCopy(identifier, tokenP, 2);
    identifier[2] = nullChr;
    nv = StrAToI(identifier);
    // Validate the number and use it.
    if (nv < 1 || 12 < nv)
        nv = 1;
    date.month = nv;
    tokenP += StrLen(identifier) * sizeof(char);

    // Read the Day.
    StrNCopy(identifier, tokenP, 2);
    identifier[2] = nullChr;
    nv = StrAToI(identifier);
    // Validate the number and use it.
    if (nv < 1 || 31 < nv)
        nv = 1;
    date.day = nv;
    tokenP += StrLen(identifier) * sizeof(char);

    // Get the time whether desired by caller or not.
    // Check to see if there is a time value, if so read it in,
    // if not assume that the event has no time.
    if (StrNCaselessCompare(tokenP, "T", StrLen("T")) != 0)
    {
        TimeToInt(time) = apptNoTime;
    }
    else
    {
        // Move over the time/date separator
        tokenP = tokenP + sizeOf7BitChar('T');

        // Read in the Hours
        StrNCopy(identifier, tokenP, 2);
        identifier[2] = nullChr;
        nv = StrAToI(identifier);
        // Validate the number and use it.
        if (nv < 0 || 24 <= nv)
            nv = 0;
        time.hours = nv;
        tokenP += StrLen(identifier) * sizeof(char);

        // Read in Minutes
        StrNCopy(identifier, tokenP, 2);
        identifier[2] = nullChr;
        nv = StrAToI(identifier);
        // Validate the number and use it.
        if (nv < 0 || 59 < nv)
            nv = 1;
        time.minutes = nv;
        tokenP += StrLen(identifier) * sizeof(char);

        // Skip the Seconds
        tokenP += 2 * sizeof(char);

        // Read the universal time indicator if present
        if (StrNCaselessCompare(tokenP, "Z", StrLen("Z")) == 0)
        {
            // Convert the time as parsed from GMT to local time.
            dateTime.year = date.year + firstYear;
            dateTime.month = date.month;
            dateTime.day = date.day;
            dateTime.hour = time.hours;
            dateTime.minute = time.minutes;
            dateTime.second = 0;
            TimSecondsToDateTime(TimUTCToTimeZone(TimDateTimeToSeconds(&dateTime),
                                                  PrefGetPreference(prefTimeZone),
                                                  PrefGetPreference(prefDaylightSavingAdjustment)),
                                 &dateTime);
            date.year = dateTime.year - firstYear;
            date.month = dateTime.month;
            date.day = dateTime.day;
            time.hours = dateTime.hour;
            time.minutes = dateTime.minute;
        }
    }

    // Give the date and/or time to the caller.
    if (dateP != NULL)
        MemMove(dateP, &date, sizeof(date));
    if (timeP != NULL)
        MemMove(timeP, &time, sizeof(time));
}


/************************************************************
 *
 * FUNCTION: GenerateDateTimeToken
 *
 * DESCRIPTION: Print a date and time into a given string in
 *					 the vCalendar 1.0 format.
 *
 * PARAMETERS:
 *		outputString	-	string ptr to write the output
 *		dateP				-	ptr to date to print (required)
 *		timeP				-	ptr to time to print (optional)
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			peter		3/30/00	Initial Revision.
 *
 *************************************************************/
static void GenerateDateTimeToken(
                                  char*			outputString,
                                  DateType*	dateP,
                                  TimeType*	timeP)
{
#if GENERATE_UNIVERSAL_TIME_VCALENDARS
    DateTimeType dateTime;

    dateTime.year = dateP->year + firstYear;
    dateTime.month = dateP->month;
    dateTime.day = dateP->day;
    if (timeP == NULL)
    {
        dateTime.hour = 0;
        dateTime.minute = 0;
    }
    else
    {
        dateTime.hour = timeP->hours;
        dateTime.minute = timeP->minutes;
    }
    dateTime.second = 0;
    TimSecondsToDateTime(TimTimeZoneToGMT(TimDateTimeToSeconds(&dateTime),
                                          PrefGetPreference(prefTimeZone),
                                          PrefGetPreference(prefDaylightSavingAdjustment)),
                         &dateTime);
    StrPrintF(outputString, "%d%02d%02dT%02d%02d00Z", dateTime.year, dateTime.month,
              dateTime.day, dateTime.hour, dateTime.minute);
#else
    if (timeP == NULL)
    {
        TraceOutput(TL(appErrorClass, "GenerateDateTimeToken: (if) %d%02d%02dT000000", firstYear + dateP->year, dateP->month, dateP->day));
        StrPrintF(outputString, "%d%02d%02dT000000",
                  firstYear + dateP->year, dateP->month, dateP->day);
    }
    else
    {
        TraceOutput(TL(appErrorClass, "GenerateDateTimeToken: (else) %d%02d%02dT%02d%02d00", firstYear + dateP->year, dateP->month, dateP->day, timeP->hours, timeP->minutes));
        StrPrintF(outputString, "%d%02d%02dT%02d%02d00",
                  firstYear + dateP->year, dateP->month, dateP->day,
                  timeP->hours, timeP->minutes);
    }
#endif
}


/************************************************************
 *
 * FUNCTION: GenerateDateTimeTokenForSeconds
 *
 * DESCRIPTION: Print a date and time into a given string in
 *					 the vCalendar 1.0 format.
 *
 * PARAMETERS:
 *		outputString	-	string ptr to write the output
 *		seconds			-	second count to print
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			peter		3/30/00	Initial Revision.
 *
 *************************************************************/
static void GenerateDateTimeTokenForSeconds(
                                            char*		outputString,
                                            UInt32	seconds)
{
    DateTimeType dateTime;

#if GENERATE_UNIVERSAL_TIME_VCALENDARS
    TimSecondsToDateTime(TimTimeZoneToGMT(seconds,
                                          PrefGetPreference(prefTimeZone),
                                          PrefGetPreference(prefDaylightSavingAdjustment)),
                         &dateTime);
    StrPrintF(outputString, "%d%02d%02dT%02d%02d00Z", dateTime.year, dateTime.month,
              dateTime.day, dateTime.hour, dateTime.minute);
#else
    TimSecondsToDateTime(seconds, &dateTime);
    StrPrintF(outputString, "%d%02d%02dT%02d%02d00", dateTime.year, dateTime.month,
              dateTime.day, dateTime.hour, dateTime.minute);
#endif
}


/************************************************************
 *
 * FUNCTION: MatchWeekDayToken
 *
 * DESCRIPTION:
 *		Matches the given string to a week day value.
 *
 *		=== THE TOKEN STRING MUST BE CONVERTED TO LOWERCASE
 *		=== BEFORE IT IS SENT TO THIS FUNCTION
 *
 * PARAMETERS:
 *		tokenP	-	string ptr from which to extract weekday
 *
 * RETURNS: the week day value (sunday -> saturday) or 255
 *				if token didnt match.
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			frigino	12/3/97	Original
 *
 *************************************************************/
static UInt8 MatchWeekDayToken(const char* tokenP)
{
    // Token must already be converted to lower-case string

    // Get 2-char token
    UInt16 weekDay = *((UInt16*)tokenP);
    // Find it
    switch (weekDay)
    {
    case 'su':
        return sunday;
        break;
    case 'mo':
        return monday;
        break;
    case 'tu':
        return tuesday;
        break;
    case 'we':
        return wednesday;
        break;
    case 'th':
        return thursday;
        break;
    case 'fr':
        return friday;
        break;
    case 'sa':
        return saturday;
        break;
    default:
        // Bad weekday token
        ErrNonFatalDisplay("Bad weekday");
        return 255;
        break;
    }
}


/************************************************************
 *
 * FUNCTION: MatchDayOrPositionToken
 *
 * DESCRIPTION:
 *		Extracts a day or position value and its sign from
 *		the given token string
 *
 * PARAMETERS:
 *		tokenP		-	string ptr from which to extract
 *		valueP		-	ptr of where to store day/position value
 *		negativeP	-	true if valueP is negative
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name		Date		Description
 *			----		----		-----------
 *			frigino	12/3/97	Original
 *
 *************************************************************/
static void MatchDayPositionToken(
                                  const char*			tokenP,
                                  UInt32*		valueP,
                                  Boolean*				negativeP)
{
    // Get token length
    UInt16 len = StrLen(tokenP);
    // Determine sign from last char if present
    *negativeP = (tokenP[len - 1] == '-');
    // Convert string value to integer. Any non-numeric chars
    // after the digits will be ignored
    *valueP = StrAToI(tokenP);
    // Return sign
}

/************************************************************
 *
 * FUNCTION: DateImportVEvent
 *
 * DESCRIPTION: Import a VCal record of type vEvent
 *
 * PARAMETERS:
 *			ruleTextP - pointer to the imported rule string
 *
 * RETURNS: a pointer to the resulting RepeatInfoType or NULL
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			djk	8/9/97	Created
 *			art	10/17/97	Added parameter to return unique id
 *			art	2/2/98	Handle yearly events better (but not great).
 *			tlw	2/9/98	Use UInt16 for yearly (monthly repeatFrequency) so its not truncated.
 *			roger	8/4/98	Broke out of DateImportVEvent.
 *			grant	8/31/99	Return NULL if the repeat rule is invalid.
 *			jmp	10/21/99	Eliminated "incomplete notes" warning, and added a DOLATER comment.
 *
 *************************************************************/

static RepeatInfoPtr
DateImportRepeatingRule(Char * ruleTextP)
{
    MemHandle repeatInfoH;
    RepeatInfoPtr repeatInfoP = NULL;
    Char * fieldP;
    Char identifier[identifierLengthMax];

#if EMULATION_LEVEL != EMULATION_NONE
    // Spec allows for both a duration AND and end date for all repeat rule cases.
    // This parsing will only work for Datebook beamed records for now.
    //
    // DOLATER:  Document this more fully!
#endif

    // If some rule was read
    if (ruleTextP != NULL)
    {
        // Allocate repeat info handle
        repeatInfoH = MemHandleNew(sizeof(RepeatInfoType));
        // Debug check
        ErrFatalDisplayIf(repeatInfoH == NULL, "Memory full");
        repeatInfoP = MemHandleLock(repeatInfoH);
        // Save repeat info into datebook record

        // Initialize all fields
        repeatInfoP->repeatType = repeatNone;
        DateToInt(repeatInfoP->repeatEndDate) = defaultRepeatEndDate;
        repeatInfoP->repeatFrequency = 0;
        repeatInfoP->repeatOn = 0;
        repeatInfoP->repeatStartOfWeek = 0;

        // Convert entire field to lower-case
        StrToLower(ruleTextP, ruleTextP);
        // Get the rule type and interval token (required)
        fieldP = GetToken(ruleTextP, identifier);
        // Determine rule type from first char
        switch (identifier[0])
        {
        case 'd':
            {
                // Daily
                repeatInfoP->repeatType = repeatDaily;
                // Convert interval string to integer
                repeatInfoP->repeatFrequency = StrAToI(&identifier[1]);
                // If there's more to read (optional)
                if (fieldP != NULL)
                {
                    // Read duration or end-date
                    fieldP = GetToken(fieldP, identifier);
                    // Is literal a duration or end-date?
                    if (identifier[0] == '#')
                    {
                        // It's a duration. Extract it.
                        UInt32 duration = StrAToI(&identifier[1]);
                        // If duration is not zero
                        if (duration != 0)
                        {
                            // Compute end-date from available data and duration
                        }
                    }
                    else
                    {
                        // It's an end-date. Read & convert to Palm OS date.
                        MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                    }
                }
            }
            break;
        case 'w':
            {
                //							Boolean		foundWeekday = false;
                // Weekly
                repeatInfoP->repeatType = repeatWeekly;
                // Convert interval string to integer
                repeatInfoP->repeatFrequency = StrAToI(&identifier[1]);
                // Read remaining tokens: weekdays, occurrences, duration, end date
                while (fieldP != NULL)
                {
                    // Read a token
                    fieldP = GetToken(fieldP, identifier);
                    // Determine token type
                    if (identifier[0] == '#')
                    {
                        // It's a duration. Extract it.
                        UInt32 duration = StrAToI(&identifier[1]);
                        // If duration is not zero
                        if (duration != 0)
                        {
                            // Compute end-date from available data and duration
                        }
                    }
                    else if (TxtCharIsDigit(identifier[0]))
                    {
                        // It's an end-date. Read & convert to Palm OS date.
                        MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                    }
                    else
                    {
                        UInt8		weekDay;
                        // Try to match weekday token
                        weekDay = MatchWeekDayToken(identifier);
                        if (weekDay != 255)
                        {
                            // Set the bit for this day
                            SetBitMacro(repeatInfoP->repeatOn, weekDay);
                            // We found at least one weekday
                            //										foundWeekday = true;
                        }
                    }
                }
            }
            break;
        case 'm':
            {
                // Monthly
                // Convert interval string to integer
                UInt16 repeatFrequency = StrAToI(&identifier[2]);
                // Determine if monthly by day or by position
                switch (identifier[1])
                {
                case 'p':
                    {
                        // Monthly by position
                        UInt32		position;
                        Boolean				fromEndOfMonth;

                        repeatInfoP->repeatType = repeatMonthlyByDay;
                        repeatInfoP->repeatFrequency = repeatFrequency;
                        // Read remaining tokens: weekdays, occurrences, duration, end date
                        while (fieldP != NULL)
                        {
                            // Read a token
                            fieldP = GetToken(fieldP, identifier);
                            // Determine token type
                            if (identifier[0] == '#')
                            {
                                // It's a duration. Extract it.
                                UInt32 duration = StrAToI(&identifier[1]);
                                // If duration is not zero
                                if (duration != 0)
                                {
                                    // Compute end-date from available data and duration
                                }
                            }
                            else if (TxtCharIsDigit(identifier[0]))
                            {
                                // It's an occurrence or an end-date. Judge by length
                                if (StrLen(identifier) > 2)
                                {
                                    // It should be an end-date
                                    MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                                }
                                else
                                {
                                    // It should be an occurrence
                                    // Extract day/position and sign
                                    MatchDayPositionToken(identifier, &position,
                                                          &fromEndOfMonth);
                                    // Validate position
                                    if (position < 1)
                                        position = 1;
                                    else if (position > 5)
                                        position = 5;
                                }
                            }
                            else
                            {
                                // It should be a weekday
                                UInt8		weekDay;
                                // Try to match weekday token
                                weekDay = MatchWeekDayToken(identifier);
                                if (weekDay != 255)
                                {
                                    // Calc day of week to repeat. Note that an
                                    // occurrence should already have been found
                                    if (fromEndOfMonth)
                                        // assume the position is 1, since datebook doesn't handle
                                        // things like 2nd-to-the-last Monday...
                                        repeatInfoP->repeatOn = domLastSun + weekDay;
                                    else
                                        repeatInfoP->repeatOn = dom1stSun + ((position - 1) * daysInWeek) + weekDay;
                                }
                            }
                        }
                    }
                    break;
                case 'd':
                    {
                        // Monthly By day or Yearly
                        //
                        // Yearly repeating events are passed a monthly-by-date repeating
                        // event with the frequency in months instead of years.  This is due
                        // to the fact that vCal's years rule uses days by number, which creates
                        // problems in leap years.
                        if (repeatFrequency % monthsInYear)
                        {
                            repeatInfoP->repeatType = repeatMonthlyByDate;
                            repeatInfoP->repeatFrequency = repeatFrequency;
                        }
                        else
                        {
                            repeatInfoP->repeatType = repeatYearly;
                            repeatInfoP->repeatFrequency = repeatFrequency / monthsInYear;
                            // Has no meaning for this case
                            repeatInfoP->repeatOn = 0;
                        }
                        // Read remaining tokens: occurrences, duration, end date
                        while (fieldP != NULL)
                        {
                            // Read a token
                            fieldP = GetToken(fieldP, identifier);
                            // Determine token type
                            if (identifier[0] == '#')
                            {
                                // It's a duration. Extract it.
                                UInt32 duration = StrAToI(&identifier[1]);
                                // If duration is not zero
                                if (duration != 0)
                                {
                                    // Compute end-date from available data and duration
                                }
                            }
                            else if (TxtCharIsAlNum(identifier[0]))
                            {
                                // It's an occurrence or an end-date. Judge by length
                                if (StrLen(identifier) > 3)
                                {
                                    // It should be an end-date
                                    MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                                }
                                else
                                {
#if 1
                                    // Datebook doesnt support repeating on a day which isnt the
                                    // same as the start day. Thus, occurrences are not used and
                                    // this value should be zero
                                    repeatInfoP->repeatOn = 0;
#else
                                    // It should be an occurrence
                                    // Check for the "LD" special case
                                    if (StrCompare(identifier, "LD") == 0)
                                    {
                                        // It's the "last day" occurrence. Use maximum
                                        repeatInfoP->repeatOn = 31;
                                    }
                                    else
                                    {
                                        UInt32		position;
                                        Boolean				fromEndOfMonth;

                                        // Extract position and sign
                                        MatchDayPositionToken(identifier, &position,
                                                              &fromEndOfMonth);
                                        // Validate occurrence
                                        if (position < 1)
                                            position = 1;
                                        else if (position > 31)
                                            position = 31;

                                        if (fromEndOfMonth)
                                        {
                                            // This wont be accurate, since not all months
                                            // have 31 days. Datebook doesnt support it anyway
                                            repeatInfoP->repeatOn = 31 - position;
                                        }
                                        else
                                        {
                                            // It's an exact day from the start of the month
                                            repeatInfoP->repeatOn = position;
                                        }
                                    }
#endif
                                }
                            }
                        }
                    }
                    break;
                default:
                    // Bad monthly sub-type
                    ErrNonFatalDisplay("Bad monthly rule");
                    MemHandleFree(repeatInfoH);
                    repeatInfoP = NULL;
                    break;
                }
            }
            break;
        case 'y':
            {
                // Yearly
                repeatInfoP->repeatType = repeatYearly;
                // Has no meaning for this case
                repeatInfoP->repeatFrequency = StrAToI(&identifier[2]);
                // Determine if yearly by day or by month
                switch (identifier[1])
                {
                case 'm':
                    {
                        // By month

                        // Read remaining tokens: months, duration, end date
                        while (fieldP != NULL)
                        {
                            // Read a token
                            fieldP = GetToken(fieldP, identifier);
                            // Determine token type
                            if (identifier[0] == '#')
                            {
                                // It's a duration. Extract it.
                                UInt32 duration = StrAToI(&identifier[1]);
                                // If duration is not zero
                                if (duration != 0)
                                {
                                    // Compute end-date from available data and duration
                                }
                            }
                            else if (TxtCharIsDigit(identifier[0]))
                            {
                                // It's a month occurrence or an end-date. Judge by length
                                if (StrLen(identifier) > 2)
                                {
                                    // It should be an end-date
                                    MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                                }
                                else
                                {
#if 1
                                    // Datebook doesnt support monthly repeats on a date which isnt
                                    // the same as the start day. Thus, occurrences are not used and
                                    // this value should be zero
                                    repeatInfoP->repeatOn = 0;
#else
                                    // It should be a monthly occurrence
                                    UInt32	month;
                                    // Extract month
                                    month = StrAToI(identifier);
                                    // Validate month
                                    if (month < 1)
                                        month = 1;
                                    else if (month > december)
                                        month = december;
                                    // Do something with month here
#endif
                                }
                            }
                        }
                    }
                    break;
                case 'd':
                    {
                        // By day

                        // Read remaining tokens: days, duration, end date
                        while (fieldP != NULL)
                        {
                            // Read a token
                            fieldP = GetToken(fieldP, identifier);
                            // Determine token type
                            if (identifier[0] == '#')
                            {
                                // It's a duration. Extract it.
                                UInt32 duration = StrAToI(&identifier[1]);
                                // If duration is not zero
                                if (duration != 0)
                                {
                                    // Compute end-date from available data and duration
                                }
                            }
                            else if (TxtCharIsDigit(identifier[0]))
                            {
                                // It's a day occurrence or an end-date. Judge by length
                                if (StrLen(identifier) > 3)
                                {
                                    // It should be an end-date
                                    MatchDateTimeToken(identifier, &repeatInfoP->repeatEndDate, NULL);
                                }
                                else
                                {
#if 1
                                    // Datebook doesnt support daily repeats on a days which arent
                                    // the same as the start day. Thus, occurrences are not used and
                                    // this value should be zero
                                    repeatInfoP->repeatOn = 0;
#else
                                    // It should be a daily occurrence
                                    UInt32	day;
                                    // Extract day
                                    day = StrAToI(identifier);
                                    // Validate day
                                    if (day < 1)
                                        day = 1;
                                    else if (day > 31)
                                        day = 31;
                                    // Do something with day here
#endif
                                }
                            }
                        }
                    }
                    break;
                default:
                    // Bad yearly sub-type
                    ErrNonFatalDisplay("Bad yearly rule");
                    MemHandleFree(repeatInfoH);
                    repeatInfoP = NULL;
                    break;
                }
            }
            break;
        default:
            // Unknown rule
            ErrNonFatalDisplay("Bad repeat rule");
            MemHandleFree(repeatInfoH);
            repeatInfoP = NULL;
            break;
        }
        
        // ABa release in ImportVCal
        // Free entire repeat rule string
	//        MemPtrFree(ruleTextP);
    }

    return repeatInfoP;
}


#pragma mark ----------------------------
/************************************************************
 *
 * FUNCTION: DateImportVEvent
 *
 * DESCRIPTION: Import a VCal record of type vEvent
 *
 * PARAMETERS:
 *			dbP - pointer to the database to add the record to
 *			inputStream	- pointer to where to import the record from
 *			inputFunc - function to get input from the stream
 *			obeyUniqueIDs - true to obey any unique ids if possible
 *			beginAlreadyRead - whether the begin statement has been read
 *
 * RETURNS: true if the input was read
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			djk	8/9/97	Created
 *			art	10/17/97	Added parameter to return unique id
 *			art	2/2/98	Handle yearly events better (but not great).
 *			tlw	2/9/98	Use UInt16 for yearly (monthly repeatFrequency) so its not truncated.
 *			peter	3/29/00	Deal with events whose start and end times fall on different days,
 *								as occurs when events expressed in universal time are shifted.
 *
 *************************************************************/


static Boolean 
DateImportVEvent(DmOpenRef dbP, UInt16 pdiRefNum, PdiReaderType* reader, 
	Boolean obeyUniqueIDs, Boolean beginAlreadyRead, UInt32 * uniqueIDP)
{
    UInt16 indexNew;
    UInt16 indexOld;
    UInt32 uid;
    /*volatile*/ ApptDBRecordType newDateRecord;
    Char * volatile newAttachment;
    ApptDateTimeType nWhen;
    UInt32 alarmDTinSec = 0; // ASSUMPTION: Alarm is not set for 1/1/1904 at 00:00:00
    char *tempP;
    volatile Err error = 0;
    Boolean datedEvent = false;	// Aba: true iff a date was found in the event

	TraceOutput(TL(appErrorClass, "DateImportVEvent: BEGIN"));
    // Read in the vEvent entry
    if (!beginAlreadyRead)
    {
        PdiReadProperty(pdiRefNum, reader);
        beginAlreadyRead = reader->property == kPdiPRN_BEGIN_VEVENT;
    }
    if (!beginAlreadyRead)
        return false;
        
    PdiEnterObject(pdiRefNum, reader);

    // Initialize the record to NULL
    newDateRecord.when = &nWhen;
    newDateRecord.alarm = NULL;
    newDateRecord.repeat = NULL;
    newDateRecord.exceptions = NULL;
    newDateRecord.description = NULL;
    newDateRecord.note = NULL;
    newAttachment = NULL;
    tempP = NULL;

    // An error happens usually due to no memory.  It's easier just to
    // catch the error.  If an error happens, we remove the last record.
    // Then we throw a second time so the caller receives it and displays a message.
    ErrTry
    {
        while (PdiReadProperty(pdiRefNum, reader) == 0 && reader->property != kPdiPRN_END_VEVENT)
        {

			TraceOutput(TL(appErrorClass, "DateImportVEvent: property = %s", reader->propertyName));
			switch(reader->property)
			{
			case kPdiPRN_DTSTART:
	            // Handle Start tag
	            // ASSUMPTION: we will use the date from the start time, not the end time
	            // NOTE: This function will break if the DTSTART value is truncated
	            // beyond the day
                PdiReadPropertyField(pdiRefNum, reader, &tempP, kPdiResizableBuffer, kPdiDefaultFields);
                if (tempP != NULL)
                {
                    // Extract start date & time
                    MatchDateTimeToken(tempP, &newDateRecord.when->date,
                                       &newDateRecord.when->startTime);
                    // Assume end time same as start time for now...
                    newDateRecord.when->endTime = newDateRecord.when->startTime;
	                datedEvent = true;
                }
                break;
	
			case kPdiPRN_DTEND:
	            // Read in the end time
	            // ASSUMPTION: It is possible to send an end date that is different from
	            // the start date, this would cause a problem for us, since we
	            // assume that the end time has the same date as the start time. This can
	            // happen even if the event starts and ends on the same day if the sending
	            // device uses universal time format and is in a different time zone than
	            // the receiving device.
                PdiReadPropertyField(pdiRefNum, reader, &tempP, kPdiResizableBuffer, kPdiDefaultFields);
                if (tempP != NULL)
                {
                    DateType endDate;

                    // Extract end time
                    MatchDateTimeToken(tempP, &endDate, &newDateRecord.when->endTime);
                    if (DateToDays(endDate) != DateToDays(newDateRecord.when->date))
                    {
                        ErrNonFatalDisplayIf(DateToDays(endDate) < DateToDays(newDateRecord.when->date),
                                             "Event ends on an earlier date than it starts");

                        // Force event end time to be on same day as start time, reducing
                        // event duration as necessary.
                        if (DateToDays(endDate) > DateToDays(newDateRecord.when->date))
                        {// Set end time to 11:55 PM.
                            newDateRecord.when->endTime.hours = hoursPerDay - 1;
                            newDateRecord.when->endTime.minutes = hoursInMinutes - 5;
                        }
                    }
                }
	            break;
	
	            // Read Repeat info
	            case kPdiPRN_RRULE:
                    PdiReadPropertyField(pdiRefNum, reader, &tempP, kPdiResizableBuffer, kPdiDefaultFields);
	                newDateRecord.repeat = DateImportRepeatingRule(tempP);
	                
					// aro - Fix bug #51567 - handle the case where no days of week is set
					// for weekly event.
					// Assume the starting day of the event is the repeating day
					if ((newDateRecord.repeat)
						&& (newDateRecord.repeat->repeatType == repeatWeekly)
						&& (newDateRecord.repeat->repeatOn == 0))
					{
						// Assume that start date is known and set it as the repeat day
						SetBitMacro(newDateRecord.repeat->repeatOn,
							DayOfWeek(newDateRecord.when->date.month, newDateRecord.when->date.day, newDateRecord.when->date.year + firstYear));
					}
					break;
	
	            // Read Repeat exceptions
				case kPdiPRN_EXDATE:
		            {
		                MemHandle						exceptionListH;
		                ExceptionsListType*		exceptionListP;
		                DateType*					exceptionP;
		                UInt16							exceptionCount = 0;
		                Err							err;
		
		                // Allocate exception list handle to hold at least the first exception
		                exceptionListH = MemHandleNew(sizeof(ExceptionsListType));
		                // Debug check
		                ErrFatalDisplayIf(exceptionListH == NULL, "Memory full");
		
		                // Read each exception
		                while (PdiReadPropertyField(pdiRefNum, reader, &tempP, kPdiResizableBuffer, kPdiSemicolonFields) == 0)
		                {
		                    // Resize handle to hold exception
		                    err = MemHandleResize(exceptionListH, sizeof(ExceptionsListType) +
		                                          sizeof(DateType) * exceptionCount);
		                    ErrFatalDisplayIf(err != 0, "Memory full");
		                    // Lock exception handle
		                    exceptionListP = MemHandleLock(exceptionListH);
		                    // Calc exception ptr
		                    exceptionP = (DateType*)((UInt32)exceptionListP + (UInt32)sizeof(UInt16) +
		                                             (UInt32)(sizeof(DateType) * exceptionCount));
		                    // Store exception into exception handle
		                    MatchDateTimeToken(tempP, exceptionP, NULL);
		                    // Increase exception count
		                    exceptionCount++;
		                    // Unlock exceptions list handle
		                    MemHandleUnlock(exceptionListH);
		                }
		
		                // Lock exception handle
		                exceptionListP = MemHandleLock(exceptionListH);
		                // Store final exception count
		                exceptionListP->numExceptions = exceptionCount;
		                // Save exception list into datebook record
		                newDateRecord.exceptions = exceptionListP;
		            }
	            	break;
	
	            // 	Read in Alarm info
	            case kPdiPRN_AALARM:
	            case kPdiPRN_DALARM:
		            {
		                DateTimeType	alarmDT;
		                DateType			alarmDate;
		                TimeType			alarmTime;
		
	                    PdiReadPropertyField(pdiRefNum, reader, &tempP, kPdiResizableBuffer, kPdiDefaultFields);
		                if (tempP != NULL)
		                {
		                    // Extract alarm date & time
		                    MatchDateTimeToken(tempP, &alarmDate, &alarmTime);
		                    // Copy values to DateTimeType struct
		                    alarmDT.year = alarmDate.year + firstYear;
		                    alarmDT.month = alarmDate.month;
		                    alarmDT.day = alarmDate.day;
		                    alarmDT.hour = alarmTime.hours;
		                    alarmDT.minute = alarmTime.minutes;
		                    alarmDT.second = 0;
		
		                    alarmDTinSec = TimDateTimeToSeconds(&alarmDT);
		                }
		            }
		            break;
	
	            // Read in Summary
	            case kPdiPRN_SUMMARY:
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, tableMaxTextItemSize);
                    PdiReadPropertyField(pdiRefNum, reader, (Char**) &newDateRecord.description, kPdiResizableBuffer, kPdiDefaultFields);
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, kPdiDefaultBufferMaxSize);
                    break;
	
	            // Read in Description
	            case kPdiPRN_DESCRIPTION:
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, tableMaxTextItemSize);
                    PdiReadPropertyField(pdiRefNum, reader, (Char**) &newDateRecord.note, kPdiResizableBuffer, kPdiDefaultFields);
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, kPdiDefaultBufferMaxSize);
                    TraceOutput(TL(appErrorClass, "newDateRecord.note = %p", newDateRecord.note));
                    break;
	
	            // Read in attachments.  At the end we place the attachment into the record.
				case kPdiPRN_ATTACH:
	                // Note: vCal permits attachments of types other than text, specifically
	                // URLs and Content ID's.  At the moment, wee will just treat both of these
	                // as text strings	
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, noteViewMaxLength);
                    PdiReadPropertyField(pdiRefNum, reader, (Char**) &newAttachment, kPdiResizableBuffer, kPdiDefaultFields);
                    PdiDefineResizing(pdiRefNum, reader, kPdiDefaultBufferDeltaSize, kPdiDefaultBufferMaxSize);
                    break;
	
	            // read in the unique identifier
	            case kPdiPRN_UID:
		            {
		                Char* uniqueIDStringP = NULL;
	                    PdiReadPropertyField(pdiRefNum, reader, &uniqueIDStringP, kPdiResizableBuffer, kPdiDefaultFields);
		                if (uniqueIDStringP != NULL)
		                {
		                    uid = StrAToI(uniqueIDStringP);
		                    MemPtrFree(uniqueIDStringP);
		
		                    // Check the uid for reasonableness.
		                    if (uid < (dmRecordIDReservedRange << 12))
		                        uid = 0;
		                }
		            }
		            break;
	        }
			TraceOutput(TL(appErrorClass, "DateImportVEvent: (in the while) tempP = %p", tempP));
			TraceOutput(TL(appErrorClass, "DateImportVEvent: (in the while) tempP = %s", tempP));
			TraceOutput(TL(appErrorClass, "DateImportVEvent: (in the while) newDateRecord.note = %p", newDateRecord.note));
	    }

		TraceOutput(TL(appErrorClass, "DateImportVEvent (end while): property = %s", reader->propertyName));

		TraceOutput(TL(appErrorClass, "DateImportVEvent: tempP = %p", tempP));

		if (tempP)	    
	        MemPtrFree(tempP);
	     
	    // If we go out the loop and last property is not an END:VEVENT => bad data   
	    if (reader->property != kPdiPRN_END_VEVENT)
			ErrThrow(exgErrBadData);	    
		
		// If no date: no syntax error but data can't be accepted
		if (datedEvent == false)
			ErrThrow(errNone);
		
		TraceOutput(TL(appErrorClass, "(.)"));
        // if an alarm was read in, translate it appropriately
        // ASSUMPTION: alarm is before start time
        if (alarmDTinSec != 0)
        {
	        TraceOutput(TL(appErrorClass, "*** newDateRecord.note = %s", newDateRecord.note));
            TranslateAlarm((ApptDBRecordType*)&newDateRecord, alarmDTinSec);
	        TraceOutput(TL(appErrorClass, "*** newDateRecord.note = %s", newDateRecord.note));
        }

		TraceOutput(TL(appErrorClass, "(.)"));
        // PalmIII stored descriptions as DESCRIPTION and notes as ATTACH.
        // vCal spec stores considers them to be SUMMARY and DESCRIPTION.
        // For now we write records in the old terminology but here we
        // handle both.
        if (newDateRecord.description == NULL)
        {
            newDateRecord.description = newDateRecord.note;
            newDateRecord.note = newAttachment;
            newAttachment = NULL;
        }

		TraceOutput(TL(appErrorClass, "(.)"));
        // Some vCal implementations send duplicate SUMMARY and DESCRIPTION fields.
        if (newDateRecord.description != NULL && newDateRecord.note != NULL &&
            StrCompare(newDateRecord.description, newDateRecord.note) == 0)
        {
            // Delete the duplicate note.
            MemPtrFree(newDateRecord.note);
            newDateRecord.note = NULL;
        }


        // Write the actual record
        TraceOutput(TL(appErrorClass, "newDateRecord.note = %p", newDateRecord.note));
        TraceOutput(TL(appErrorClass, "newDateRecord.description = %p", newDateRecord.description));
		TraceOutput(TL(appErrorClass, "DateImportVEvent (before ApptNewRecord): description = %s", newDateRecord.description));
		TraceOutput(TL(appErrorClass, "(.)"));
        if (ApptNewRecord(dbP, (ApptDBRecordType*)&newDateRecord, & indexNew))
            ErrThrow(exgMemError);

		TraceOutput(TL(appErrorClass, "(.)"));
        // If uid was set then a unique id was passed to be used.
        if (uid != 0 && obeyUniqueIDs)
        {
            // We can't simply remove any old record using the unique id and
            // then add the new record because removing the old record could
            // move the new one.  So, we find any old record, change the new
            // record, and then remove the old one.
            indexOld = indexNew;

            // Find any record with this uid.  indexOld changes only if
            // such a record is found.
            DmFindRecordByID (dbP, uid, &indexOld);

            // Change this record to this uid.  The dirty bit is set from
            // newly making this record.
            DmSetRecordInfo(dbP, indexNew, NULL, &uid);

            // Now remove any old record.
            if (indexOld != indexNew)
            {
                DmRemoveRecord(dbP, indexOld);
            }
        }

        // Return the unique id of the record inserted.
        DmRecordInfo(dbP, indexNew, NULL, uniqueIDP, NULL);
    }

    ErrCatch(inErr)
    {
        error = inErr;
    } ErrEndCatch


    // Free any temporary buffers used to store the incoming data.
    if (newAttachment)
        MemPtrFree(newAttachment);
    if (newDateRecord.note)
        MemPtrFree(newDateRecord.note);
    if (newDateRecord.description)
        MemPtrFree(newDateRecord.description);
    if (newDateRecord.alarm)
        MemPtrFree(newDateRecord.alarm);
    if (newDateRecord.repeat)
        MemPtrFree(newDateRecord.repeat);
    if (newDateRecord.exceptions)
        MemPtrFree(newDateRecord.exceptions);

    if (error)
        ErrThrow(error);

    return true; // ABa: never tested by DateImportVCal
}


/************************************************************
 *
 * FUNCTION: DateExportVCal
 *
 * DESCRIPTION: Export a VCALENDAR record.
 *
 * PARAMETERS:
 *			dbP - pointer to the database to export the records from
 *			index - the record number to export
 *			recordP - whether the begin statement has been read
 *			outputStream - pointer to where to export the record to
 *			outputFunc - function to send output to the stream
 *			writeUniqueIDs - true to write the record's unique id
 *
 * RETURNS: nothing
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			djk	8/9/97	Created
 *			CS		05/16/00	Use DayOfMonthType instead of DayOfWeekType.
 *
 *************************************************************/
                        
extern void DateExportVCal(DmOpenRef dbP, Int16 index, ApptDBRecordPtr recordP,
                            UInt16 pdiRefNum, PdiWriterType* writer, Boolean writeUniqueIDs)

{
    UInt32			uid;
    Char 			tempString[RRULEStringLengthMax];


    PdiWriteBeginObject(pdiRefNum, writer, kPdiPRN_BEGIN_VEVENT);

    // Handle When
    // ASSUMPTION: To represent non-timed events, we will use a
    // DTSTART propert with a date value (instead of a date/time value)
    // and _no_ DTEND proprty.  This is permitted by ISO 8601.

    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_DTSTART);
    if (TimeToInt(recordP->when->startTime) != apptNoTime)
    {
        GenerateDateTimeToken(tempString, &recordP->when->date, &recordP->when->startTime);
		PdiWritePropertyValue(pdiRefNum, writer, tempString, kPdiWriteData);
        // End Time/Date
        PdiWriteProperty(pdiRefNum, writer, kPdiPRN_DTEND);
        GenerateDateTimeToken(tempString, &recordP->when->date, &recordP->when->endTime);
    }
    else
        // Handle a non-timed event -- see note above for convention
    {
        StrPrintF(tempString, "%d%02d%02d", firstYear + recordP->when->date.year,
                  recordP->when->date.month, recordP->when->date.day);
    }
	PdiWritePropertyValue(pdiRefNum, writer, tempString, kPdiWriteData);

    // Handle Alarm
    if (recordP->alarm != NULL &&
        recordP->alarm->advance != apptNoAlarm)
	{
	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_AALARM);

        // Because we cannot access DateBook globals due to our simple second code segment scheme, we
        // can't access AlarmSoundRepeatInterval or AlarmSoundRepeatCount.  When we switch to a
        // smarter multisegment scheme or a large segment scheme, resume using the globals.
        //		TimSecondsToDateTime(ApptGetAlarmTimeVCalForm(recordP), &alrmDT);
        //		StrPrintF(tempString, "%d%02d%02dT%02d%02d00;PT%uM;%u" ,  alrmDT.year, alrmDT.month, alrmDT.day,
        //					alrmDT.hour, alrmDT.minute, AlarmSoundRepeatInterval / minutesInSeconds, AlarmSoundRepeatCount);

        GenerateDateTimeTokenForSeconds(tempString, ApptGetAlarmTimeVCalForm(recordP));
		PdiWritePropertyValue(pdiRefNum, writer, tempString, kPdiWriteData);
    }

    // Handle Repeating Events
    if (recordP->repeat && (recordP->repeat->repeatType != repeatNone))
    {
    	Char* writePtr = tempString;
    	Char* prepareString;
    	
	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_RRULE);

        // Set the rule type
        switch(recordP->repeat->repeatType)
        {
        case repeatDaily:
        	prepareString = (Char*) "D";
            break;

        case repeatWeekly:
        	prepareString = (Char*) "W";
            break;

            // One small oddity is that our repeatMonthlyByDay is equivelent
            // to vCal's by-postion and our repeatMonthlyByDate is equivelent
            // to vCal's by-Day rule
        case repeatMonthlyByDay:
        	prepareString = (Char*) "MP";
            break;

        case repeatMonthlyByDate:
        case repeatYearly:
            // vCal's years rule uses days by number, which creates problems in leap years
            // so instead we will use the month by-Day rule (equiv to our month by date rule)
            // and multiply by monthsInYear
        	prepareString = (Char*) "MD";
            break;
        }

        // Set the freqency
        if (recordP->repeat->repeatType == repeatYearly)
            writePtr += StrPrintF(writePtr, "%s%d ", prepareString, monthsInYear * (UInt16) recordP->repeat->repeatFrequency);
        else
            writePtr += StrPrintF(writePtr, "%s%d ", prepareString, (UInt16) recordP->repeat->repeatFrequency);

        // if the repeat type is repeatWeekly, emit which days the event is on
        if (recordP->repeat->repeatType == repeatWeekly)
        {
            int dayIndex;

            for (dayIndex = 0; dayIndex < daysInWeek; dayIndex++)
            {
                if (GetBitMacro(recordP->repeat->repeatOn, dayIndex))
                {
                    switch(dayIndex)
                    {
                    case sunday:
                    	prepareString = (Char*) "SU";
                        break;
                    case monday:
                    	prepareString = (Char*) "MO";
                        break;
                    case tuesday:
                    	prepareString = (Char*) "TU";
                        break;
                    case wednesday:
                    	prepareString = (Char*) "WE";
                        break;
                    case thursday:
                    	prepareString = (Char*) "TH";
                        break;
                    case friday:
                    	prepareString = (Char*) "FR";
                        break;
                    case saturday:
                    	prepareString = (Char*) "SA";
                        break;
                    }
                    writePtr += StrPrintF(writePtr, "%s ", prepareString);
                }
            }
        }

        // If the repeat type is a pilot monthly repeat by day (as opposed to by date),
        // emit the repetition rule
        if (recordP->repeat->repeatType == repeatMonthlyByDay)
        {

            // Deal with the case that are not the domLast___ cases
            if (((DayOfMonthType) recordP->repeat->repeatOn) < domLastSun)
            {
                // Figure out which week were in and emit it
                writePtr += StrPrintF(writePtr, "%d+ ", (recordP->repeat->repeatOn / daysInWeek) + 1);
            }
            else
            {
                // domLast___ are all in week -1
                writePtr += StrPrintF(writePtr, "1- ");
            }

            //	Figure out what the day of the week is and emit it
            if ((recordP->repeat->repeatOn == dom1stSun) || (recordP->repeat->repeatOn == dom2ndSun) ||
                (recordP->repeat->repeatOn == dom3rdSun) || (recordP->repeat->repeatOn == dom4thSun) ||
                (recordP->repeat->repeatOn == domLastSun))
                prepareString = (Char*) "SU ";

            if ((recordP->repeat->repeatOn == dom1stMon) || (recordP->repeat->repeatOn == dom2ndMon) ||
                (recordP->repeat->repeatOn == dom3rdMon) || (recordP->repeat->repeatOn == dom4thMon) ||
                (recordP->repeat->repeatOn == domLastMon))
                prepareString = (Char*) "MO ";

            if ((recordP->repeat->repeatOn == dom1stTue) || (recordP->repeat->repeatOn == dom2ndTue) ||
                (recordP->repeat->repeatOn == dom3rdTue) || (recordP->repeat->repeatOn == dom4thTue) ||
                (recordP->repeat->repeatOn == domLastTue))
                prepareString = (Char*) "TU ";

            if ((recordP->repeat->repeatOn == dom1stWen) || (recordP->repeat->repeatOn == dom2ndWen) ||
                (recordP->repeat->repeatOn == dom3rdWen) || (recordP->repeat->repeatOn == dom4thWen) ||
                (recordP->repeat->repeatOn == domLastWen))
                prepareString = (Char*) "WE ";

            if ((recordP->repeat->repeatOn == dom1stThu) || (recordP->repeat->repeatOn == dom2ndThu) ||
                (recordP->repeat->repeatOn == dom3rdThu) || (recordP->repeat->repeatOn == dom4thThu) ||
                (recordP->repeat->repeatOn == domLastThu))
                prepareString = (Char*) "TH ";

            if ((recordP->repeat->repeatOn == dom1stFri) || (recordP->repeat->repeatOn == dom2ndFri) ||
                (recordP->repeat->repeatOn == dom3rdFri) || (recordP->repeat->repeatOn == dom4thFri) ||
                (recordP->repeat->repeatOn == domLastFri))
                prepareString = (Char*) "FR ";

            if ((recordP->repeat->repeatOn == dom1stSat) || (recordP->repeat->repeatOn == dom2ndSat) ||
                (recordP->repeat->repeatOn == dom3rdSat) || (recordP->repeat->repeatOn == dom4thSat) ||
                (recordP->repeat->repeatOn == domLastSat))
                prepareString = (Char*) "SA ";

            writePtr += StrPrintF(writePtr, prepareString);
        }

        // If the record is repeatMonthlyByDate, put out the # of the day
        if (recordP->repeat->repeatType == repeatMonthlyByDate)
        {
            writePtr += StrPrintF(writePtr, "%d ", recordP->when->date.day);
        }

        // Emit the end date
        if (TimeToInt(recordP->repeat->repeatEndDate) == apptNoEndDate)
        {
    //    	prepareString = (Char*) "#0";
	        writePtr += StrPrintF(writePtr, "#0");
        }
        else
        {
            // NOTE: The vCalendar 1.0 specification says that the repeat end date is a date/time
            // between the start time of the last occurrence and the start time of the next occurrence
            // (inclusing of start, exclusive of end). Previous versions of this code, as found in Palm
            // OS 3.0 to 3.5, emitted the start of the day specified as the repeat end date. That did
            // not conform to the specification. Since the old versions of this code ignored the time
            // specified, we can correct this while maintaining compatibility. We now emit the date and
            // time of the start of the last occurrence.
            Char dateTimeString[tempStringLengthMax];	
            GenerateDateTimeToken(dateTimeString, &recordP->repeat->repeatEndDate, &recordP->when->startTime);
	        writePtr += StrPrintF(writePtr, dateTimeString);
		}
        PdiWritePropertyValue(pdiRefNum, writer, tempString, kPdiWriteData);
    }


    // Handle exceptions to repeating
    if (recordP->exceptions != NULL)
    {
        DateType *exArray = &recordP->exceptions->exception;
        Char** fields;
        UInt16 i;

	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_EXDATE);
	    fields = MemPtrNew(sizeof(Char*) * recordP->exceptions->numExceptions);

        for (i = 0; i < recordP->exceptions->numExceptions; i++)
        {
            // ASSUMPTION:  EXDATE has a date/time property, although the ISO 8601 standard allows
            // us to truncate this to a date (which is what we keep), we will make the reasonable
            // assumption that the time of an exception is the same as the time of the event it is
            // an exception for.  This does not affect communication with another pilot, but will
            // require less robustness for other devices we communicate with
            fields[i] = MemPtrNew(tempStringLengthMax);
            GenerateDateTimeToken(fields[i], &exArray[i], &recordP->when->startTime);
        }
        PdiWritePropertyFields(pdiRefNum, writer, fields, recordP->exceptions->numExceptions, kPdiWriteData);
        for (i = 0; i < recordP->exceptions->numExceptions; i++)
        {
        	MemPtrFree(fields[i]);
        }
        MemPtrFree(fields);
    }


    // Handle description
    if (recordP->description != NULL)
    {
	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_DESCRIPTION);
        PdiWritePropertyValue(pdiRefNum, writer, recordP->description, kPdiWriteText);
    }

    // Handle note
    if (recordP->note != NULL)
    {
	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_ATTACH);
        PdiWritePropertyValue(pdiRefNum, writer, recordP->note, kPdiWriteText);
    }

    // Emit an unique id
    if (writeUniqueIDs)
    {
	    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_UID);
        DmRecordInfo(dbP, index, NULL, &uid, NULL);
        StrIToA(tempString, uid);
        PdiWritePropertyValue(pdiRefNum, writer, tempString, kPdiWriteData);
    }

    PdiWriteEndObject(pdiRefNum, writer, kPdiPRN_END_VEVENT);
}


/***********************************************************************
 *
 * FUNCTION:		DateSetGoToParams
 *
 * DESCRIPTION:	Store the information necessary to navigate to the
 *                record inserted into the launch code's parameter block.
 *
 * PARAMETERS:		 dbP        - pointer to the database to add the record to
 *						 exgSocketP - parameter block passed with the launch code
 *						 uniqueID   - unique id of the record inserted
 *
 * RETURNED:		nothing
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	10/17/97	Created
 *
 ***********************************************************************/
static void DateSetGoToParams (DmOpenRef dbP, ExgSocketPtr exgSocketP, UInt32 uniqueID)
{
    UInt16		recordNum;
    UInt16		cardNo;
    LocalID 	dbID;


    if (! uniqueID) return;

    DmOpenDatabaseInfo (dbP, &dbID, NULL, NULL, &cardNo, NULL);

    // The this the the first record inserted, save the information
    // necessary to navigate to the record.
    if (! exgSocketP->goToParams.uniqueID)
    {
        DmFindRecordByID (dbP, uniqueID, &recordNum);

        exgSocketP->goToCreator = sysFileCDatebook;
        exgSocketP->goToParams.uniqueID = uniqueID;
        exgSocketP->goToParams.dbID = dbID;
        exgSocketP->goToParams.dbCardNo = cardNo;
        exgSocketP->goToParams.recordNum = recordNum;
    }

    // If we already have a record then make sure the record index
    // is still correct.  Don't update the index if the record is not
    // in your the app's database.
    else if (dbID == exgSocketP->goToParams.dbID &&
             cardNo == exgSocketP->goToParams.dbCardNo)
    {
        DmFindRecordByID (dbP, exgSocketP->goToParams.uniqueID, &recordNum);

        exgSocketP->goToParams.recordNum = recordNum;
    }
}


/************************************************************
 *
 * FUNCTION: DateImportVCal
 *
 * DESCRIPTION: Import a VCal record of type vEvent and vToDo
 *
 * The Datebook handles vCalendar records.  Any vToDo records
 * are sent to the ToDo app for importing.
 *
 * PARAMETERS:
 *			dbP - pointer to the database to add the record to
 *			inputStream	- pointer to where to import the record from
 *			inputFunc - function to get input from the stream
 *			obeyUniqueIDs - true to obey any unique ids if possible
 *			beginAlreadyRead - whether the begin statement has been read
 *			vToDoFunc - function to import vToDo records
 *						on the device this is a function to call ToDo to read
 *						for the shell command this is an empty function
 *
 * RETURNS: true if the input was read
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			djk	8/9/97		Created
 *			roger	9/12/97		Modified to work on the device
 *			art	10/17/97		Return unique id of first record inserted
 *			ABa	06/21/00		Integrate Pdi library
 *
 *************************************************************/

extern Boolean
DateImportVCal(DmOpenRef dbP, UInt16 pdiRefNum, PdiReaderType* reader, 
               Boolean obeyUniqueIDs, Boolean beginAlreadyRead, ImportVToDoF vToDoFunc)
{
    UInt32 uniqueID = 0;
    Err error = 0;

    TraceInit();

    if (!beginAlreadyRead)
    {
        PdiReadProperty(pdiRefNum, reader);
        beginAlreadyRead = reader->property == kPdiPRN_BEGIN_VCALENDAR;
    }
    if (!beginAlreadyRead)
        return false;

    // Read in the vcard entry
    
    PdiEnterObject(pdiRefNum, reader);
    
    if (beginAlreadyRead)
    {
        while (PdiReadProperty(pdiRefNum, reader) == 0 && reader->property != kPdiPRN_END_VCALENDAR)
        {
            // Hand it off to the correct sub-routine
            // Note: here VCalEventRead is a dummy routine that just runs until it finds an end
            if (reader->property == kPdiPRN_BEGIN_VTODO)
            {
                error = vToDoFunc(dbP, pdiRefNum, reader, obeyUniqueIDs, true);
                if (error)
                    ErrThrow(error);
            }
            else if (reader->property == kPdiPRN_BEGIN_VEVENT)
            {
                DateImportVEvent(dbP, pdiRefNum, reader, obeyUniqueIDs, true, &uniqueID);
                DateSetGoToParams (dbP, reader->appData, uniqueID);
           	}
        }
    }

    TraceClose();
    return ((reader->events & kPdiEOFEventMask) == 0);
}

/***********************************************************************
 *
 * FUNCTION:    SetDescriptionAndFilename
 *
 * DESCRIPTION: Derive and allocate a decription and filename from some text.
 *
 * PARAMETERS:  textP - the text string to derive the names from
 *					 descriptionPP - pointer to set to the allocated description
 *					 descriptionHP - handle to set to the allocated description
 *					 filenamePP - pointer to set to the allocated filename
 *					 filenameHP - handle to set to the allocated description
 *					 prefix - the scheme with ":" suffix and optional "?" prefix
 *
 * RETURNED:    a description and filename are allocated and the pointers are set
 *
 * REVISION HISTORY:
 *         Name   Date      Description
 *         ----   ----      -----------
 *         roger   11/4/97   Initial Revision
 *
 ***********************************************************************/
static void SetDescriptionAndFilename(Char * textP, Char **descriptionPP,
                                      MemHandle *descriptionHP, Char **filenamePP, MemHandle *filenameHP,
                                      const Char * const prefix)
{
    Char * descriptionP;
    Int16 descriptionSize;
    Int16 descriptionWidth;
    Boolean descriptionFit;
    Char * spaceP;
    Char * filenameP;
    MemHandle resourceH;
    Char * resourceP;
    UInt8 filenameLength;
    UInt8 schemeLength;


    descriptionSize = StrLen(textP);
    WinGetDisplayExtent(&descriptionWidth, NULL);
    FntCharsInWidth (textP, &descriptionWidth, &descriptionSize, &descriptionFit);

    if (descriptionSize > 0)
    {
        *descriptionHP = MemHandleNew(descriptionSize+sizeOf7BitChar('\0'));
        descriptionP = MemHandleLock(*descriptionHP);
        MemMove(descriptionP, textP, descriptionSize);
        descriptionP[descriptionSize] = nullChr;
    }
    else
    {
        *descriptionHP = DmGetResource(strRsc, beamDescriptionStrID);
        descriptionP = MemHandleLock(*descriptionHP);
    }


    if (descriptionSize > 0)
    {
        // Now form a file name.  Use only the first word or two.
        spaceP = StrChr(descriptionP, spaceChr);
        if (spaceP)
            // Check for a second space
            spaceP = StrChr(spaceP + sizeOf7BitChar(spaceChr), spaceChr);

        // If at least two spaces were found then use only that much of the description.
        // If less than two spaces were found then use all of the description.
        if (spaceP)
            filenameLength = spaceP - descriptionP;
        else
            filenameLength = StrLen(descriptionP);


        // Allocate space and form the filename
        schemeLength = StrLen(prefix);
        *filenameHP = MemHandleNew(schemeLength + filenameLength + StrLen(dateSuffix) + sizeOf7BitChar('\0'));
        filenameP = MemHandleLock(*filenameHP);
        if (filenameP)
        {
            StrCopy(filenameP, prefix);
            MemMove(&filenameP[schemeLength], descriptionP, filenameLength);
            MemMove(&filenameP[schemeLength + filenameLength], dateSuffix,
                    StrLen(dateSuffix) + sizeOf7BitChar('\0'));
        }
    }
    else
    {
        resourceH = DmGetResource(strRsc, beamFilenameStrID);
        resourceP = MemHandleLock(resourceH);
        
        // Allocate space and form the filename
        filenameLength = StrLen(resourceP);
        schemeLength = StrLen(prefix);
        *filenameHP = MemHandleNew(schemeLength + filenameLength + 4 + sizeOf7BitChar('\0'));
        filenameP = MemHandleLock(*filenameHP);
        if (filenameP)
        {
            StrCopy(filenameP, prefix);
            StrCat(filenameP, resourceP);
            StrCat(filenameP, dateSuffix);
        }
        
        MemHandleUnlock(resourceH);
        DmReleaseResource(resourceH);
    }


    *descriptionPP = descriptionP;
    *filenamePP = filenameP;
}


/***********************************************************************
 *
 * FUNCTION:    DateSendRecordTryCatch
 *
 * DESCRIPTION: Send a record.
 *
 * PARAMETERS:	 dbP - pointer to the database to add the record to
 * 				 recordNum - the record number to send
 * 				 recordP - pointer to the record to send
 * 				 exgSocketP - the exchange socket used to send
 *
 * RETURNED:    0 if there's no error
 *
 * REVISION HISTORY:
 *         Name   Date      Description
 *         ----   ----      -----------
 *         roger  12/11/97  Initial Revision
 *		   ABa:   06/21/00  Integrate Pdi library
 *
 ***********************************************************************/

static Err DateSendRecordTryCatch (DmOpenRef dbP, Int16 recordNum,
    ApptDBRecordPtr recordP, UDAWriterType* media)
{
    volatile Err error = 0;
    UInt16 pdiRefNum;

    PdiWriterType* writer;
    Boolean loaded;

    if ((error = PrvPdiLibLoad(&pdiRefNum, &loaded)))
        return error;

    writer = PdiWriterNew(pdiRefNum, media, kPdiPalmCompatibility);
    if (writer)
    {

        // An error can happen anywhere during the send process.  It's easier just to
        // catch the error.  If an error happens, we must pass it into ExgDisconnect.
        // It will then cancel the send and display appropriate ui.
        ErrTry
        {
		    PdiWriteBeginObject(pdiRefNum, writer, kPdiPRN_BEGIN_VCALENDAR);
		    PdiWriteProperty(pdiRefNum, writer, kPdiPRN_VERSION);
		    PdiWritePropertyValue(pdiRefNum, writer, (Char*)"1.0", kPdiWriteData);
		    
			PdiWritePropertyStr(pdiRefNum, writer, "X-PALM", kPdiNoFields, 1);
			PdiWritePropertyValue(pdiRefNum, writer, (Char*) kVObjectVersion, kPdiWriteData);
		    
            DateExportVCal(dbP, recordNum, recordP, pdiRefNum, writer, true);
		    PdiWriteEndObject(pdiRefNum, writer, kPdiPRN_END_VCALENDAR);
		    if ((error = writer->error) == errNone)
	            UDAWriterFlush(media);
        }

        ErrCatch(inErr)
        {
            error = inErr;
        } ErrEndCatch

        PdiWriterDelete(pdiRefNum, &writer);
    }
    else
    {
    	error = exgMemError;
    }
	PrvPdiLibUnload(pdiRefNum, loaded);
    return error;
}

/***********************************************************************
 *
 * FUNCTION:    DateSendRecord
 *
 * DESCRIPTION: Send a record.
 *
 * PARAMETERS:	 dbP - pointer to the database to add the record to
 * 				 recordNum - the record to send
 *					 prefix - the scheme with ":" suffix and optional "?" prefix
 *
 * RETURNED:    true if the record is found and sent
 *
 * REVISION HISTORY:
 *         Name   Date      Description
 *         ----   ----      -----------
 *         roger   5/9/97   Initial Revision
 *         dje     4/24/00  Don't specify target creator ID
 *
 ***********************************************************************/
extern void DateSendRecord (DmOpenRef dbP, UInt16 recordNum, const Char * const prefix)
{
    ApptDBRecordType record;
    MemHandle recordH;
    MemHandle descriptionH = NULL;
    Err error;
    ExgSocketType exgSocket;
    MemHandle nameH = NULL;
    Boolean empty;
    UDAWriterType* media;

	TraceInit();
    // Form a description of what's being sent.  This will be displayed
    // by the system send dialog on the sending and receiving devices.
    error = ApptGetRecord (dbP, recordNum, &record, &recordH);

    // If the description field is empty and the note field is empty, then
    // consider the record empty and don't beam it.  (Logic from ClearEditState)
    empty = true;
    if (record.description && *record.description)
        empty = false;
    else if (record.note && *record.note)
        empty = false;

    if (!empty)
    {
        // important to init structure to zeros...
        MemSet(&exgSocket, sizeof(exgSocket), 0);

		TraceOutput(TL(appErrorClass, "---"));
        // Set the exg description to the record's description.
        SetDescriptionAndFilename(record.description, &exgSocket.description,
                                  &descriptionH, &exgSocket.name, &nameH, prefix);
		TraceOutput(TL(appErrorClass, "---"));

		//ABa: remove superfluous '.' characters
		PrvTransferCleanFileName(exgSocket.name);


		TraceOutput(TL(appErrorClass, "DateSendRecord: description = %s, name = %s", exgSocket.description, exgSocket.name));

        exgSocket.length = MemHandleSize(recordH) + 100;		// rough guess
        //exgSocket.target = sysFileCDatebook;		// commented out 4/24/00 dje
        exgSocket.type = (Char *)dateMIMEType;

        error = ExgPut(&exgSocket);   // put data to destination

		// ABa: Changes to use new streaming mechanism 
        media = UDAExchangeWriterNew(&exgSocket,  512);
        
        if (!error)
        {
        	if (media)
	            error = DateSendRecordTryCatch(dbP, recordNum, &record, media);
	        else
	        	error = exgMemError;

            // Release the record before the database is sorted in loopback mode.
            if (recordH)
            {
                MemHandleUnlock(recordH);
                recordH = NULL;
            }

            ExgDisconnect(&exgSocket, error);
        }
        
        if (media)
	        UDADelete(media);
    }
    else
        FrmAlert(NoDataToBeamAlert);


    // Clean winUp
    //
    // The description may be an allocated memeory block or it may be a
    // locked resource.
    if (descriptionH)
    {
        MemHandleUnlock (descriptionH);
        if (MemHandleDataStorage (descriptionH))
            DmReleaseResource(descriptionH);
        else
            MemHandleFree(descriptionH);
    }

    // The name may be an allocated memeory block or it may be a
    // locked resource.
    if (nameH)
    {
        MemHandleUnlock (nameH);
        if (MemHandleDataStorage (nameH))
            DmReleaseResource(nameH);
        else
            MemHandleFree(nameH);
    }

    if (recordH)
        MemHandleUnlock(recordH);


	TraceClose();

    return;
}


/************************************************************
 *
 * FUNCTION: DateImportVToDo
 *
 * DESCRIPTION: Import a VCal record of type vToDo by calling
 * the ToDo List app.
 *
 * PARAMETERS:
 *			dbP - pointer to the database to add the record to
 *			inputStream	- pointer to where to import the record from
 *			inputFunc - function to get input from the stream
 *			obeyUniqueIDs - true to obey any unique ids if possible
 *			beginAlreadyRead - whether the begin statement has been read
 *
 * RETURNS: true if the input was read
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			roger	9/12/97		Created
 *			ABa		7/4/00		PDI integration (new launch code)
 *								(removing bug from Mandalay)
 *
 *************************************************************/

static Boolean DateImportVToDo(DmOpenRef dbP, UInt16 pdiRefNum, PdiReaderType* reader, 
               Boolean obeyUniqueIDs, Boolean UNUSED_PARAM(beginAlreadyRead))

//static Boolean DateImportVToDo(DmOpenRef dbP, void * inputStream, GetCharF inputFunc,
//                               Boolean obeyUniqueIDs, Boolean )
{
#if EMULATION_LEVEL == EMULATION_NONE
#pragma unused (dbP, pdiRefNum, obeyUniqueIDs)
#else
#pragma unused (dbP, pdiRefNum, reader, obeyUniqueIDs)
#endif

#if EMULATION_LEVEL == EMULATION_NONE
	TraceOutput(TL(appErrorClass, "DateImportVToDo: ToDoApp sublaunch"));
    AppCallWithCommand(sysFileCToDo, todoLaunchCmdImportVObject, reader);
#endif

    return false;
}


/***********************************************************************
 *
 * FUNCTION:		DateReceiveData
 *
 * DESCRIPTION:		Receives data into the output field using the Exg API
 *
 * PARAMETERS:		exgSocketP, socket from the app code
 *						 sysAppLaunchCmdExgReceiveData
 *
 * RETURNED:		error code or zero for no error.
 *
 * REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			???	???		Created
 *			art	10/17/97	Added "go to" record logic
 *          ABa     6/20/00  Integrate Pdi library
 *
 ***********************************************************************/
extern Err DateReceiveData(DmOpenRef dbP, ExgSocketPtr exgSocketP)
{
    volatile Err err;
    UInt16 pdiRefNum = sysInvalidRefNum;
    PdiReaderType* reader;
    UDAReaderType* stream;
    Boolean loaded;

    // accept will open a progress dialog and wait for your receive commands
    if ((err = ExgAccept(exgSocketP)) != 0)
    	return err;
    

    TraceInit();

    if ((err = PrvPdiLibLoad(&pdiRefNum, &loaded)))
    {
		pdiRefNum = sysInvalidRefNum;
    	goto errorDisconnect;
    }

    if ((stream = UDAExchangeReaderNew(exgSocketP)) == NULL)
	{
		err = exgMemError;
    	goto errorDisconnect;
	}
	
    if ((reader = PdiReaderNew(pdiRefNum, stream, kPdiOpenParser)) == NULL)
	{
		err = exgMemError;
    	goto errorDisconnect;
	}

    reader->appData = exgSocketP;

    // Catch errors receiving records.  The import routine will clean up the
    // incomplete record.  This routine passes the error to ExgDisconnect
    // which displays appropriate ui.

    ErrTry
    {
        // Keep importing records until it can't
        while(DateImportVCal(dbP, pdiRefNum, reader, false, false, DateImportVToDo)){};
    }
    ErrCatch(inErr)
    {
        err = inErr;
    } ErrEndCatch

	// Aba: A record has been added in the Database iff the GoTo
	// uniqueID parameter != 0.
	// In the case no record is added, return an error
	if (err == errNone && exgSocketP->goToParams.uniqueID == 0)
		err = exgErrBadData;
	
errorDisconnect:
	if (reader)
		PdiReaderDelete(pdiRefNum, &reader);

	if (stream)
		UDADelete(stream);
	
	if (pdiRefNum != sysInvalidRefNum)
		PrvPdiLibUnload(pdiRefNum, loaded);
		
	ExgDisconnect(exgSocketP, err); // closes transfer dialog
	err = errNone;	// error was reported, so don't return it

    TraceClose();
    return err;
}


/***********************************************************************
 *
 * FUNCTION:		DateTransferPreview
 *
 * DESCRIPTION:	Create a short string preview of the data coming in.
 *
 * PARAMETERS:		infoP - the preview info from the command parameter block
 *						        of the sysAppLaunchCmdExgPreview launch
 *
 * RETURNED:		nothing
 *
 * REVISION HISTORY:
 *         Name   Date      Description
 *         ----   ----      -----------
 *         ABa    10/20/00   Created
 *
 * COMMENTS:
 *        DateTransferPreview is called in several different cases:
 *   		 Palm & non-Palm to Palm normal beam of a vevent.
 *           non Palm to Palm beam of vtodo
 *           Palm & non-Palm to Palm SMS send of vevent
 *           Palm & non-Palm to Palm SMS send of vtodo
 *
 ***********************************************************************/

void DateTransferPreview(ExgPreviewInfoType *infoP)
{
	Err err;
	UInt16 pdiRefNum;
	PdiReaderType* reader;
	UDAReaderType* stream;
	Boolean loaded;
	UInt16  todoObjects = 0;
	UInt16  objects = 0;
	Char*	descriptionP = NULL;
	const Char*	toCopyP = NULL;

	if (infoP->op == exgPreviewQuery)
	{
		infoP->types = exgPreviewShortString;
		return;
	}
	if (infoP->op != exgPreviewShortString)
	{
		infoP->error = exgErrNotSupported;
		return;
	}

	// if we have a description we don't have to parse the vObject
	if (infoP->socketP->description && *infoP->socketP->description)
	{
		StrNCopy(infoP->string, infoP->socketP->description, infoP->size - 1);
		infoP->string[infoP->size - 1] = 0;
		infoP->error = errNone;
		return;
	}

	err = ExgAccept(infoP->socketP);
	if (!err)
	{
		err = PrvPdiLibLoad(&pdiRefNum, &loaded);
	}
	if (!err)
	{
		stream = UDAExchangeReaderNew(infoP->socketP);
		reader = PdiReaderNew(pdiRefNum, stream, kPdiOpenParser);
		reader->appData = infoP->socketP;
		if (reader)
		{
			PdiReadProperty(pdiRefNum, reader);
			if  (reader->property != kPdiPRN_BEGIN_VCALENDAR)
				goto ParseError;
			PdiEnterObject(pdiRefNum, reader);
			PdiDefineResizing(pdiRefNum, reader, 16, tableMaxTextItemSize);

			while (PdiReadProperty(pdiRefNum, reader) == 0 && objects <= 1)
			{
				switch (reader->property)
				{
				case kPdiPRN_BEGIN_VTODO:
					todoObjects++;
					
				case kPdiPRN_BEGIN_VEVENT:
					objects++;
					PdiEnterObject(pdiRefNum, reader);
					break;

				// VEVENT & VTODO description strings
				case kPdiPRN_DESCRIPTION:
				case kPdiPRN_SUMMARY:
					PdiReadPropertyField(pdiRefNum, reader, &descriptionP, kPdiResizableBuffer, kPdiDefaultFields);
					break;
				}
			}
			
			if (objects == 0)
			{
			ParseError:
				err = exgErrBadData;
			}	
			else 
			{
				DmResID		resID = 0;
				MemHandle 	resH;
				void 		*resP;
				
				if (objects == 1)
				{
					// a single object
					if (descriptionP == NULL || *descriptionP == 0)
					{
						resID = exgDesciptionUnknownStrID;
					}
				}
				else
				{
					// multiple objects
					if (todoObjects == objects)
					{
						// todo items
						resID = exgDescriptionMultipleToDoStrID;
					}
					else
					{
						// events
						resID = exgDescriptionMultipleStrID;
					}
				}
				
				if (resID)
				{
					resH = DmGetResource(strRsc, resID);
					resP = MemHandleLock(resH);
					StrNCopy(infoP->string, resP, infoP->size);
					MemHandleUnlock(resH);
					DmReleaseResource(resH);
				}
				else
				{
					StrNCopy(infoP->string, descriptionP, infoP->size);
				}
				infoP->string[infoP->size - 1] = chrNull;
			}
			
			if (descriptionP)
			{
				MemPtrFree(descriptionP);
			}
	
			ExgDisconnect(infoP->socketP, err);
		}
		PdiReaderDelete(pdiRefNum, &reader);
		UDADelete(stream);
		PrvPdiLibUnload(pdiRefNum, loaded);
	}
	infoP->error = err;
}
