palm-os-sdk/handera-105/examples/ExampleI/Src/MemoTransfer.c
Tavisco 1c2f65cd40 Renamed handera-sdk-105 to handera-105
Its obviously a SDK, no need to repeat it :P
2023-11-16 22:06:58 -03:00

1522 lines
46 KiB
C

/******************************************************************************
*
* Copyright (c) 1997-2000 Palm, Inc. or its subsidiaries.
* All rights reserved.
*
* File: MemoTransfer.c
*
* Release: Palm OS SDK 4.0 (63220)
*
* Description:
* Memo Book routines to transfer records.
*
* History:
* 9/17/97 roger - Created
*
*****************************************************************************/
#include <StringMgr.h>
#include <TextMgr.h>
#include <UIResources.h>
#include <Category.h>
#include <Form.h>
#include <ErrorMgr.h>
#include <TraceMgr.h>
#include <PalmUtils.h>
#include "MemoDB.h"
#include "MemoMain.h"
#include "MemoRsc.h"
/***********************************************************************
*
* Internal Constants
*
***********************************************************************/
#define identifierLengthMax 40
#define mimeVersionString "MIME-Version: 1.0\015\012"
#define mimeMultipartString "Content-type: multipart/mixed;"
#define mimeBoundaryString "boundary="
#define memoSuffix ("." memoExtension)
#define simpleBoundary "simple boundary"
#define delimiter "--" simpleBoundary
#define crlf "\015\012"
#define importBufferMaxLength 80
#define stringZLen -1 // pass to WriteFunc to calculate strlen
// Stream interface to exgsockets to optimize performance
#define maxStreamBuf 512 // MUST BE LARGER than importBufferMaxLength
typedef struct StreamType {
ExgSocketPtr socket;
UInt16 pos;
UInt16 len;
UInt16 bufSize;
Char buf[maxStreamBuf];
} StreamType;
/***********************************************************************
*
* Internal Functions
*
***********************************************************************/
static void PrvMemoImportMimeCleanup(DmOpenRef dbP, UInt32 firstRecordID, void* inputStream, UInt16 numRecordsReceived, UInt16* numRecordsReceivedP);
static void PrvStreamInit(StreamType *streamP, ExgSocketPtr exgSocketP);
static void PrvStreamFlush(StreamType *streamP);
static UInt32 PrvStreamWrite(StreamType *streamP, const Char * stringP, Int32 length, Err *errP);
static UInt32 PrvStreamRead(StreamType * streamP, Char *bufP, UInt32 length, Err *errP);
static ExgSocketPtr PrvStreamSocket(StreamType *streamP);
static UInt32 PrvReadFunction(const void * stream, Char * bufferP, UInt32 length);
static UInt32 PrvWriteFunction(void * stream, const Char * const bufferP, Int32 length);
static void PrvTransferCleanFileName(Char* ioFileName);
static void PrvSetDescriptionAndFilename(Char * textP, Char **descriptionPP, MemHandle *descriptionHP, Char **filenamePP, MemHandle *filenameHP, const Char * const prefix);
static Err PrvMemoSendRecordTryCatch (DmOpenRef dbP, Int16 recordNum, MemoDBRecordPtr recordP, ExgSocketPtr exgSocketP);
static Err PrvMemoSendCategoryTryCatch (DmOpenRef dbP, UInt16 categoryNum, ExgSocketPtr exgSocketP, UInt16 index);
static void PrvMemoSetGoToParams (DmOpenRef dbP, ExgSocketPtr exgSocketP, UInt32 uniqueID);
static Err PrvReadThroughCRLF(ReadFunctionF inputFunc, void * inputStreamP, Char * bufferP, UInt16 * bufferLengthP);
static void PrvMemoImportFinishRecord(DmOpenRef dbP, UInt16 indexNew, MemHandle *newRecordHPtr, UInt16 *newRecordSizePtr, void * inputStream);
/************************************************************
*
* FUNCTION: MemoImportMime
*
* DESCRIPTION: Import a Mime record.
*
* 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
* numRecordsRecievedP - number of records received
*
* RETURNS: true if the input was read
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* rsf 4/24/97 Initial Revision
* bob 01/26/98 re-wrote MIME parser part to get delimiters right
* grant 6/25/99 Return count of records received in numRecordsReceivedP.
* kwk 06/25/99 Moved return out of ErrTry block.
* FPa 11/22/00 Fixed ErrTry/Catch/Throw problems
*
*************************************************************/
extern Boolean
MemoImportMime(DmOpenRef dbR, void * inputStream, ReadFunctionF inputFunc,
Boolean UNUSED_PARAM(obeyUniqueIDs), Boolean UNUSED_PARAM(beginAlreadyRead), UInt16 *numRecordsReceivedP, Char* descriptionP, UInt16 descriptionSize)
{
char *c;
char boundaryString[69+2] = "";
MemHandle newRecordH = NULL;
Char * newRecordP;
MemHandle newHandle; // Used to follow resized records which move
UInt16 indexNew = dmMaxRecordIndex;
DmOpenRef dbP = dbR;
Err err = 0;
Char buffer[importBufferMaxLength + 1];
UInt16 bufferLength = 0;
UInt16 charsRead;
UInt16 charsToWrite;
UInt16 newRecordSize = 0;
Char * nextCrChr;
int addCr;
Char * boundaryP;
Char * boundaryEndP;
UInt16 numRecordsReceived = 0;
UInt32 firstRecordID = 0;
// Keep the buffer always null terminated so we can use string functions on it.
buffer[importBufferMaxLength] = nullChr;
// Read chars into the buffer
charsRead = inputFunc( inputStream, buffer, importBufferMaxLength - bufferLength);
bufferLength += charsRead;
buffer[bufferLength] = nullChr;
if (charsRead == 0)
{
*numRecordsReceivedP = 0;
return false;
}
// 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
{
// MIME start, find MIME ID and version
if (StrNCompare(buffer, mimeVersionString, StrLen(mimeVersionString)) == 0)
{
// Remove the MIME header
MemMove(buffer, &buffer[StrLen(mimeVersionString)], bufferLength - StrLen(mimeVersionString));
bufferLength -= StrLen(mimeVersionString);
// Read chars into the buffer
charsRead = inputFunc( inputStream, &buffer[bufferLength], importBufferMaxLength - bufferLength);
bufferLength += charsRead;
buffer[bufferLength] = nullChr;
// scan header for a multi-part identifier
// skip anything else until we get an entirely blank line
do {
if (StrNCompare(buffer, mimeMultipartString, StrLen(mimeMultipartString)) == 0)
{
// found a multi-part header, parse out the boundary string
// PREVIEW Aba: Here we know that the memo is multipart => several memos
if (descriptionP)
{
MemHandle headerStringH;
Char* headerStringP;
headerStringH = DmGetResource(strRsc, FindMemoHeaderStr);
headerStringP = MemHandleLock(headerStringH);
StrCopy(descriptionP, headerStringP);
MemHandleUnlock(headerStringH);
DmReleaseResource(headerStringH);
return true;
}
boundaryP = StrStr(buffer, mimeBoundaryString);
boundaryP += StrLen(mimeBoundaryString);
// Remove the boundary stuff so we can read in more into the buffer
MemMove(buffer, boundaryP, &buffer[bufferLength] - boundaryP);
bufferLength = (&buffer[bufferLength] - boundaryP);
// Read chars into the buffer
charsRead = inputFunc( inputStream, &buffer[bufferLength], importBufferMaxLength - bufferLength);
bufferLength += charsRead;
buffer[bufferLength] = nullChr;
boundaryP = buffer;
if (*boundaryP == '"')
{
boundaryP++;
boundaryEndP = StrChr(boundaryP, '"');
}
else
{
boundaryEndP = StrChr(boundaryP, crChr);
}
if (boundaryEndP == NULL)
{
ErrThrow(exgErrBadData);
}
boundaryString[0] = '-';
boundaryString[1] = '-';
MemMove(&boundaryString[2], boundaryP, boundaryEndP - boundaryP);
boundaryString[boundaryEndP - boundaryP + 2] = nullChr;
c = StrChr(boundaryEndP, crChr);
if (c == NULL)
{
ErrThrow(exgErrBadData);
}
c += sizeOf7BitChar(crChr) + sizeOf7BitChar(linefeedChr);
// Remove the boundary stuff so we can read in more into the buffer
MemMove(buffer, c, &buffer[bufferLength] - c);
bufferLength = (&buffer[bufferLength] - c);
}
else
{
// just an ordinary header line, skip it
err = PrvReadThroughCRLF(inputFunc, inputStream, buffer, &bufferLength);
if (err)
ErrThrow(err);
}
// Read chars into the buffer
charsRead = inputFunc( inputStream, &buffer[bufferLength], importBufferMaxLength - bufferLength);
bufferLength += charsRead;
buffer[bufferLength] = nullChr;
// stop at blank line by itself or EOF
} while (buffer[0] != crChr && buffer[0] != nullChr);
// We've now parsed the MIME header. Preamble, segments, and postamble below.
} // end of MIME parser
do {
// find the boundary and remove it, along with any header info in the body part
if (*boundaryString != nullChr)
{
// Keep reading until we find a boundary
while (buffer[0] != nullChr && StrNCompare(buffer, boundaryString, StrLen(boundaryString)) != 0)
{
err = PrvReadThroughCRLF(inputFunc, inputStream, buffer, &bufferLength);
if (err)
ErrThrow(err);
}
// Remove the boundary by removing all text until the end of the line.
err = PrvReadThroughCRLF(inputFunc, inputStream, buffer, &bufferLength);
if (err)
ErrThrow(err);
while (buffer[0] != nullChr && buffer[0] != crChr)
{
err = PrvReadThroughCRLF(inputFunc, inputStream, buffer, &bufferLength);
if (err)
ErrThrow(err);
}
err = PrvReadThroughCRLF(inputFunc, inputStream, buffer, &bufferLength);
if (err)
ErrThrow(err);
}
// could be that everything was header, and we're out of data.
// weird error, but MemHandle it.
if (bufferLength == 0)
{
ErrThrow(exgErrBadData);
}
addCr = 0;
while (bufferLength > 0 &&
(*boundaryString == nullChr || StrNCompare(buffer, boundaryString, StrLen(boundaryString)) != 0))
{
// find CR or end of buffer
nextCrChr = StrChr(buffer, crChr);
if (nextCrChr != NULL)
charsToWrite = nextCrChr - buffer;
else
charsToWrite = bufferLength;
// PREVIEW Aba: Here we have the first line and we can exit
if (descriptionP)
{
if (charsToWrite >= descriptionSize)
charsToWrite = descriptionSize - 1;
StrNCopy(descriptionP, buffer, charsToWrite);
descriptionP[charsToWrite] = '\0';
return true;
}
// if we're going to overflow record, close it out (leave room for terminating null)
if (newRecordSize + charsToWrite + addCr > memoMaxLength)
{
// since we try to stop parsing at each CR, and most records from other sources (MIME)
// should have a CR at least every 76 characters, we probably don't have to worry about
// word wrap. Still, beaming a lot of just plain text could break records on random
// boundaries...
PrvMemoImportFinishRecord(dbP, indexNew, &newRecordH, &newRecordSize, inputStream);
addCr = 0;
numRecordsReceived++;
}
// Make a record if we need one
if (newRecordH == NULL)
{
indexNew = dmMaxRecordIndex;
newRecordH = DmNewRecord(dbP, (UInt16 *)&indexNew, bufferLength);
if (newRecordH == 0)
ErrThrow(exgMemError);
newRecordSize = 0;
}
// Write the buffer out to the record
newHandle = DmResizeRecord(dbP, indexNew, newRecordSize + charsToWrite + addCr);
if (newHandle)
newRecordH = newHandle;
else
ErrThrow(exgMemError);
newRecordP = MemHandleLock(newRecordH);
if (addCr != 0)
DmWrite(newRecordP, newRecordSize++, "\n", 1);
DmWrite(newRecordP, newRecordSize, buffer, charsToWrite);
newRecordSize += charsToWrite;
MemHandleUnlock(newRecordH);
// Remove the chars written so we can read more into the buffer
if (nextCrChr != NULL)
{
if (charsToWrite < importBufferMaxLength-1)
{
MemMove(buffer, nextCrChr+2, bufferLength-(charsToWrite+2)); // delete LF
bufferLength -= charsToWrite+2;
}
else
// CR/LF was split by end of buffer, so DON'T delete the CR, catch it next time 'round
{
MemMove(buffer, nextCrChr, bufferLength-(charsToWrite)); // don't delete CR or LF
bufferLength -= charsToWrite;
nextCrChr = NULL;
}
}
else
buffer[bufferLength = 0] = nullChr;
// Now read more
charsRead = inputFunc( inputStream, &buffer[bufferLength], importBufferMaxLength - bufferLength);
bufferLength += charsRead;
buffer[bufferLength] = nullChr;
if (nextCrChr != NULL)
addCr = 1;
else
addCr = 0;
} // end of segment parser
// Set the category for the record
if (PrvStreamSocket(inputStream)->appData) {
UInt16 attr;
Err err;
UInt16 categoryID = PrvStreamSocket(inputStream)->appData;
// Get the attributes
err = DmRecordInfo(dbP, indexNew, &attr, NULL, NULL);
// Set them to include the category, and mark the record dirty
if ((attr & dmRecAttrCategoryMask) != categoryID) {
attr &= ~dmRecAttrCategoryMask;
attr |= categoryID | dmRecAttrDirty;
err = DmSetRecordInfo(dbP, indexNew, &attr, NULL);
}
}
PrvMemoImportFinishRecord(dbP, indexNew, &newRecordH, &newRecordSize, inputStream);
numRecordsReceived++;
// save the uniqueID of the first record we loaded
// we will goto this record when we are done (after sorting)
if (!firstRecordID)
{
// Store the information necessary to navigate to the record inserted.
DmRecordInfo(dbP, indexNew, NULL, &firstRecordID, NULL);
}
// Now that the record is imported check if we need to import any more
// Stop if there isn't any more input
if (bufferLength == 0)
break;
// Stop if the boundary is followed by "--"
if ((*boundaryString != nullChr)
&& bufferLength >= StrLen(boundaryString) + 2
&& StrNCompare(&buffer[StrLen(boundaryString)], "--", 2) == 0)
break;
} while (true); // end of segment parser
} // end of Try
ErrCatch(inErr)
{
// PREVIEW Aba
if (descriptionP)
return false;
// Remove the incomplete record
if (inErr)
DmRemoveRecord(dbP, indexNew);
// if we got at least one record, sort and set goto parameters...
if (firstRecordID)
{
MemoSort(dbP);
PrvMemoSetGoToParams (dbP, PrvStreamSocket(inputStream), firstRecordID);
}
// return number of records received
*numRecordsReceivedP = numRecordsReceived;
if ( inErr != exgMemError )
PrvMemoImportMimeCleanup(dbP, firstRecordID, inputStream, numRecordsReceived, numRecordsReceivedP);
ErrThrow(inErr);
} ErrEndCatch
PrvMemoImportMimeCleanup(dbP, firstRecordID, inputStream, numRecordsReceived, numRecordsReceivedP);
return false;
}
/***********************************************************************
*
* FUNCTION: MemoSendRecord
*
* DESCRIPTION: Beam or 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/21/00 Add Send support
* dje 4/24/00 Don't specify target creator ID
*
***********************************************************************/
extern void MemoSendRecord (DmOpenRef dbP, Int16 recordNum, const Char * const prefix)
{
MemoDBRecordPtr recordP;
MemHandle recordH;
MemHandle descriptionH;
Err error;
ExgSocketType exgSocket;
MemHandle nameH;
// important to init structure to zeros...
MemSet(&exgSocket, sizeof(exgSocket), 0);
// Form a description of what's being sent. This will be displayed
// by the system send dialog on the sending and receiving devices.
recordH = DmQueryRecord (dbP, recordNum);
recordP = (MemoDBRecordType *) MemHandleLock(recordH);
// Set the description to be the beginning of the memo
descriptionH = NULL;
exgSocket.description = NULL;
// Set the exg description to the record's description.
PrvSetDescriptionAndFilename(&recordP->note, &exgSocket.description,
&descriptionH, &exgSocket.name, &nameH, prefix);
// ABa: Clean superfluous '.' characters
PrvTransferCleanFileName(exgSocket.name);
exgSocket.length = MemHandleSize(recordH); // rough guess
//exgSocket.target = sysFileCMemo; // commented out 4/24/00 dje
exgSocket.type = (Char *)memoMIMEType;
error = ExgPut(&exgSocket); // put data to destination
if (!error)
{
error = PrvMemoSendRecordTryCatch(dbP, recordNum, recordP, &exgSocket);
ExgDisconnect(&exgSocket, error);
}
// Clean up
if (descriptionH)
{
MemHandleUnlock (descriptionH);
if (MemHandleDataStorage (descriptionH))
DmReleaseResource(descriptionH); // DOLATER dje - this shouldn't be possible any more
else
MemHandleFree(descriptionH);
}
if (nameH)
{
MemHandleUnlock (nameH);
if (MemHandleDataStorage (nameH))
DmReleaseResource(nameH); // DOLATER dje - this shouldn't be possible any more
else
MemHandleFree(nameH);
}
MemHandleUnlock(recordH);
return;
}
/***********************************************************************
*
* FUNCTION: MemoSendCategory
*
* DESCRIPTION: Beam or send all visible records in a category.
*
* PARAMETERS: dbP - pointer to the database to add the record to
* categoryNum - the category of records to send
* prefix - the scheme with ":" suffix and optional "?" prefix
* noDataAlertID - alert to put up if there is nothing to send
*
* RETURNED: true if any records are found and sent
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* roger 5/9/97 Initial Revision
* dje 4/21/00 Add Send support
* dje 4/24/00 Don't specify target creator ID
*
***********************************************************************/
extern void MemoSendCategory (DmOpenRef dbP, UInt16 categoryNum, const Char * const prefix, UInt16 noDataAlertID)
{
Err error;
Char description[dmCategoryLength];
UInt16 index;
Boolean foundAtLeastOneRecord;
ExgSocketType exgSocket;
UInt16 mode;
LocalID dbID;
UInt16 cardNo;
Boolean databaseReopened;
// If the database was opened to show secret records, reopen it to not see
// secret records. The idea is that secret records are not sent when a
// category is sent. They must be explicitly sent one by one.
DmOpenDatabaseInfo(dbP, &dbID, NULL, &mode, &cardNo, NULL);
if (mode & dmModeShowSecret)
{
dbP = DmOpenDatabase(cardNo, dbID, dmModeReadOnly);
databaseReopened = true;
}
else
databaseReopened = false;
// important to init structure to zeros...
MemSet(&exgSocket, sizeof(exgSocket), 0);
// Make sure there is at least one record in the category.
index = 0;
foundAtLeastOneRecord = false;
while (true)
{
if (DmSeekRecordInCategory(dbP, &index, 0, dmSeekForward, categoryNum) != 0)
break;
foundAtLeastOneRecord = DmQueryRecord(dbP, index) != 0;
if (foundAtLeastOneRecord)
break;
index++;
}
// We should send the category because there's at least one record to send.
if (foundAtLeastOneRecord)
{
// Form a description of what's being sent. This will be displayed
// by the system send dialog on the sending and receiving devices.
CategoryGetName (dbP, categoryNum, description);
exgSocket.description = description;
// Now form a file name
exgSocket.name = MemPtrNew(StrLen(prefix) + StrLen(description) + StrLen(memoSuffix) + sizeOf7BitChar('\0'));
if (exgSocket.name)
{
StrCopy(exgSocket.name, prefix);
StrCat(exgSocket.name, description);
StrCat(exgSocket.name, memoSuffix);
}
// ABa: Clean superfluous '.' characters
PrvTransferCleanFileName(exgSocket.name);
exgSocket.length = 0; // rough guess
//exgSocket.target = sysFileCMemo; // commented out 4/24/00 dje
exgSocket.type = (Char *)memoMIMEType;
error = ExgPut(&exgSocket); // put data to destination
if (!error)
{
error = PrvMemoSendCategoryTryCatch (dbP, categoryNum, &exgSocket, index);
ExgDisconnect(&exgSocket, error);
}
// Clean up
if (exgSocket.name)
MemPtrFree(exgSocket.name);
}
else
FrmAlert(noDataAlertID);
if (databaseReopened)
DmCloseDatabase(dbP);
return;
}
/***********************************************************************
*
* FUNCTION: MemoReceiveData
*
* DESCRIPTION: Receives data into the output field using the Exg API
*
* PARAMETERS: dbP - database to put received memos in
* exgSocketP - socket from the app code sysAppLaunchCmdExgReceiveData
* numRecordsReceivedP - number of records received is returned here
*
* RETURNED: error code or zero for no error.
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* grant 6/25/99 Keep count of received records and return in numRecordsReceivedP
*
***********************************************************************/
extern Err MemoReceiveData(DmOpenRef dbP, ExgSocketPtr exgSocketP, UInt16 *numRecordsReceivedP)
{
volatile Err err;
UInt16 numRecordsReceived;
StreamType stream;
// initialize new record count
ErrNonFatalDisplayIf(numRecordsReceivedP == NULL, "NULL numRecordsReceivedP");
*numRecordsReceivedP = 0;
PrvStreamInit( &stream, exgSocketP);
// accept will open a progress dialog and wait for your receive commands
err = ExgAccept(exgSocketP);
if (!err)
{
// Catch errors receiving records. The import routine will clean up the
// incomplete record. This routine displays an error message.
ErrTry
{
// Keep importing records until it can't
while (MemoImportMime(dbP, &stream, PrvReadFunction, false, false, &numRecordsReceived, NULL, 0))
{
*numRecordsReceivedP += numRecordsReceived;
};
// catch the records from the final MemoImportMime
*numRecordsReceivedP += numRecordsReceived;
}
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;
ExgDisconnect(exgSocketP, err); // closes transfer dialog
err = errNone; // error was reported, so don't return it
}
return err;
}
/************************************************************
*
* FUNCTION: MemoExportMime
*
* DESCRIPTION: Export a record as a Imc Mime 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
* ---- ---- -----------
* rsf 8/6/97 Initial Revision
*
*************************************************************/
void MemoExportMime(DmOpenRef UNUSED_PARAM(dbP), Int16 UNUSED_PARAM(index), MemoDBRecordType * recordP,
void * outputStream, WriteFunctionF outputFunc, Boolean UNUSED_PARAM(writeUniqueIDs),
Boolean outputMimeInfo)
{
Char * c;
Char * eolP;
UInt32 len;
// Write out all of the memo. All linefeeds must be replaced with CRLF combos.
c = &recordP->note;
if (outputMimeInfo)
{
if (!ImcStringIsAscii(c))
outputFunc(outputStream, "Content-Type: Text/plain; charset=ISO-8859-1" crlf, stringZLen);
outputFunc(outputStream, crlf, stringZLen);
}
while (*c != '\0')
{
eolP = StrChr(c, linefeedChr);
if (eolP)
{
len = outputFunc( outputStream, c, eolP - c);
outputFunc(outputStream, crlf, stringZLen);
c = eolP + sizeOf7BitChar(linefeedChr);
}
else if (*c != '\0')
{
eolP = StrChr(c, '\0');
len = outputFunc( outputStream, c, eolP - c);
c = eolP;
}
}
outputFunc(outputStream, crlf, stringZLen); // always end with an extra crlf
}
/***********************************************************************
*
* FUNCTION: MemoTransferPreview
*
* 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 11/10/00 Created
*
***********************************************************************/
void MemoTransferPreview(ExgPreviewInfoType *infoP)
{
volatile Err err;
UInt16 numRecordsReceived;
StreamType stream;
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;
}
PrvStreamInit(&stream, infoP->socketP);
err = ExgAccept(infoP->socketP);
if (!err)
{
ErrTry
{
MemoImportMime((DmOpenRef) NULL, &stream, PrvReadFunction, false, false, &numRecordsReceived, infoP->string, infoP->size);
}
ErrCatch(inErr)
{
err = inErr;
} ErrEndCatch
ExgDisconnect(infoP->socketP, err); // closes transfer dialog
}
infoP->error = err;
}
#pragma mark -
/***********************************************************************
*
* FUNCTION: PrvStreamInit
*
* DESCRIPTION: Function to put Initialize a stream socket.
*
* PARAMETERS: streamP - the output stream
* exgSocketP - pointer to an intitialized exgSocket
*
* RETURNED: nothing
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* gavin 10/5/99 Initial revision
*
***********************************************************************/
static void PrvStreamInit(StreamType *streamP, ExgSocketPtr exgSocketP)
{
streamP->socket = exgSocketP;
streamP->bufSize = maxStreamBuf;
streamP->pos = 0;
streamP->len = 0;
}
/***********************************************************************
*
* FUNCTION: PrvStreamFlush
*
* DESCRIPTION: Function to put a string to the exg transport.
*
* PARAMETERS: streamP - the output stream
*
* RETURNED: nothing
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* gavin 10/5/99 Initial revision
*
***********************************************************************/
static void PrvStreamFlush(StreamType *streamP)
{
Err err = 0;
while (streamP->len && !err)
streamP->len -= ExgSend(streamP->socket,streamP->buf,streamP->len,&err);
}
/***********************************************************************
*
* FUNCTION: PrvStreamWrite
*
* DESCRIPTION: Function to put a string to the exg transport.
*
* PARAMETERS: streamP - the output stream
* stringP - the string to put
*
* RETURNED: nothing
* If the all the string isn't sent an error is thrown using ErrThrow.
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* gavin 10/5/99 Initial revision
*
***********************************************************************/
static UInt32 PrvStreamWrite(StreamType *streamP, const Char * stringP, Int32 length, Err *errP)
{
UInt32 count = 0;
*errP = 0;
while (count < length && !*errP)
{
if (streamP->len < streamP->bufSize)
{
streamP->buf[streamP->len++] = *stringP++;
count++;
}
else
streamP->len -= ExgSend(streamP->socket, streamP->buf, streamP->len, errP);
}
return count;
}
/***********************************************************************
*
* FUNCTION: PrvStreamRead
*
* DESCRIPTION: Function to get a character from the input stream.
*
* PARAMETERS: streamP - the output stream
*
* RETURNED: a character of EOF if no more data
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* gavin 10/5/99 Initial revision
*
***********************************************************************/
static UInt32 PrvStreamRead(StreamType * streamP, Char *bufP, UInt32 length, Err *errP)
{
UInt32 count = 0;
*errP = 0;
while (count < length)
{
if (streamP->pos < streamP->len)
bufP[count++] = streamP->buf[streamP->pos++];
else
{ streamP->pos = 0;
streamP->len = ExgReceive(streamP->socket, streamP->buf, streamP->bufSize, errP);
if (!streamP->len || *errP)
break;
}
}
return count;
}
/***********************************************************************
*
* FUNCTION: PrvStreamSocket
*
* DESCRIPTION: returns the socket from a stream.
*
* PARAMETERS: streamP - the output stream
*
* RETURNED: The socket associated with the stream
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* gavin 10/5/99 Initial revision
*
***********************************************************************/
static ExgSocketPtr PrvStreamSocket(StreamType *streamP)
{
return streamP->socket;
}
/***********************************************************************
*
* FUNCTION: GetChar
*
* DESCRIPTION: Function to get a character from the exg transport.
*
* PARAMETERS: exgSocketP - the exg connection
*
* RETURNED: nothing
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* roger 8/15/97 Initial Revision
*
***********************************************************************/
static UInt32 PrvReadFunction(const void * stream, Char * bufferP, UInt32 length)
{
Err err;
UInt32 bytesRead;
bytesRead = PrvStreamRead((StreamType *)stream, bufferP, length, &err);
if (err)
ErrThrow(err);
return bytesRead;
}
/***********************************************************************
*
* FUNCTION: PutString
*
* DESCRIPTION: Function to put a string to the exg transport.
*
* PARAMETERS: exgSocketP - the exg connection
* stringP - the string to put
*
* RETURNED: nothing
* If the all the string isn't sent an error is thrown using ErrThrow.
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* roger 8/15/97 Initial Revision
*
***********************************************************************/
static UInt32 PrvWriteFunction(void * stream, const Char * const bufferP, Int32 length)
{
UInt32 len;
Err err;
// passing -1 length will assume a null terminated string
if (length == -1) length = StrLen(bufferP);
len = PrvStreamWrite( stream, bufferP, length, &err);
// If the bytes were not sent throw an error.
if ((len == 0 && length > 0) || err)
ErrThrow(err);
return len;
}
/***********************************************************************
*
* 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: PrvSetDescriptionAndFilename
*
* DESCRIPTION: Derive and allocate a decription and filename from some text.
* The filename will be a URL which includes the specified scheme.
*
* PARAMETERS: textP - the text string to derive the names from
* descriptionPP - pointer to set to the allocated description
* descriptionHP - MemHandle to set to the allocated description
* filenamePP - pointer to set to the allocated filename
* filenameHP - MemHandle 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 PrvSetDescriptionAndFilename(Char * textP, Char **descriptionPP,
MemHandle *descriptionHP, Char **filenamePP, MemHandle *filenameHP, const Char * const prefix)
{
Char * descriptionP;
Int16 descriptionSize;
Coord descriptionWidth;
Boolean descriptionFit;
Char * spaceP;
Char * filenameP;
MemHandle resourceH;
Char * resourceP;
UInt8 filenameLength;
UInt8 schemeLength;
Coord unused;
descriptionSize = StrLen(textP);
WinGetDisplayExtent(&descriptionWidth, &unused);
FntCharsInWidth (textP, &descriptionWidth, &descriptionSize, &descriptionFit);
if (descriptionSize > 0)
{
*descriptionHP = MemHandleNew(descriptionSize+sizeOf7BitChar('\0'));
if (*descriptionHP)
{
descriptionP = MemHandleLock(*descriptionHP);
MemMove(descriptionP, textP, descriptionSize);
descriptionP[descriptionSize] = nullChr;
}
}
else
{
*descriptionHP = DmGetResource(strRsc, BeamDescriptionStr);
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(memoSuffix) + sizeOf7BitChar('\0'));
filenameP = MemHandleLock(*filenameHP);
if (filenameP)
{
StrCopy(filenameP, prefix);
MemMove(&filenameP[schemeLength], descriptionP, filenameLength);
MemMove(&filenameP[schemeLength + filenameLength], memoSuffix,
StrLen(memoSuffix) + sizeOf7BitChar('\0'));
}
}
else
{
resourceH = DmGetResource(strRsc, BeamFilenameStr);
resourceP = MemHandleLock(resourceH);
// Allocate space and form the filename
filenameLength = StrLen(resourceP);
schemeLength = StrLen(prefix);
*filenameHP = MemHandleNew(schemeLength + filenameLength + sizeOf7BitChar('\0'));
filenameP = MemHandleLock(*filenameHP);
if (filenameP)
{
StrCopy(filenameP, prefix);
StrCat(filenameP, resourceP);
}
MemHandleUnlock(resourceH);
DmReleaseResource(resourceH);
}
*descriptionPP = descriptionP;
*filenamePP = filenameP;
}
/***********************************************************************
*
* FUNCTION: PrvMemoSendRecordTryCatch
*
* 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
*
***********************************************************************/
static Err PrvMemoSendRecordTryCatch (DmOpenRef dbP, Int16 recordNum,
MemoDBRecordPtr recordP, ExgSocketPtr exgSocketP)
{
volatile Err error = 0;
StreamType stream;
PrvStreamInit(&stream, exgSocketP);
// 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
{
MemoExportMime(dbP, recordNum, recordP, &stream, PrvWriteFunction, true, false);
}
ErrCatch(inErr)
{
error = inErr;
} ErrEndCatch
PrvStreamFlush( &stream);
return error;
}
/***********************************************************************
*
* FUNCTION: PrvMemoSendCategoryTryCatch
*
* DESCRIPTION: Send all visible records in a category.
*
* PARAMETERS: dbP - pointer to the database to add the record to
* categoryNum - the category of records to send
* exgSocketP - the exchange socket used to send
* index - the record number of the first record in the category to send
*
* RETURNED: 0 if there's no error
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* roger 12/11/97 Initial Revision
*
***********************************************************************/
static Err PrvMemoSendCategoryTryCatch (DmOpenRef dbP, UInt16 categoryNum,
ExgSocketPtr exgSocketP, UInt16 index)
{
volatile Err error = 0;
volatile MemHandle outRecordH = 0;
MemoDBRecordType *outRecordP;
StreamType stream;
// 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
{
PrvStreamInit(&stream, exgSocketP);
// Write out the beginning of a multipart mime message
PrvWriteFunction(&stream,
mimeVersionString
"Content-type: multipart/mixed; boundary=\"" simpleBoundary "\"" crlf crlf, stringZLen);
// Loop through all records in the category.
while (DmSeekRecordInCategory(dbP, &index, 0, dmSeekForward, categoryNum) == 0)
{
// Emit the record. If the record is private do not emit it.
outRecordH = DmQueryRecord (dbP, index);
if (outRecordH != 0)
{
outRecordP = (MemoDBRecordType *) MemHandleLock(outRecordH);
// Emit a mime boundary
PrvWriteFunction(&stream, delimiter crlf, stringZLen);
MemoExportMime(dbP, index, outRecordP, &stream, PrvWriteFunction, true, true);
MemHandleUnlock(outRecordH);
}
index++;
}
outRecordH = 0;
dbP = 0;
// All done. Write out an epilogue.
PrvWriteFunction(&stream, delimiter "--" crlf crlf, stringZLen);
}
ErrCatch(inErr)
{
error = inErr;
if (outRecordH)
MemHandleUnlock(outRecordH);
} ErrEndCatch
PrvStreamFlush(&stream);
return error;
}
/***********************************************************************
*
* FUNCTION: PrvMemoSetGoToParams
*
* 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 PrvMemoSetGoToParams (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 = sysFileCMemo;
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: PrvReadThroughCRLF
*
* DESCRIPTION: Consume data up to and including the next CRLF.
*
* PARAMETERS:
* inputStreamP - pointer to where to import from
* bufferP - where the input stream is stored
* bufferLengthP - the length of bufferP used
*
* RETURNED: error code or zero for no error.
*
* ASSUMPTIONS:
* Buffer is full when called
* Buffer is big enough to hold a full line (including LF)
* ...so CR/LF will never split
* END CONDITION:
* Buffer is full when routine exits.
*
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* bob 1/26/98 initial revision
*
*************************************************************/
static Err PrvReadThroughCRLF(ReadFunctionF inputFunc, void * inputStreamP, Char * bufferP, UInt16 * bufferLengthP)
{
char *c;
UInt16 charsRead;
Err err = 0;
Boolean gotOne = false;
while (*bufferLengthP > 0 && !gotOne)
{
c = StrChr(bufferP, crChr);
if (c == NULL)
c = &bufferP[*bufferLengthP]; // end of the buffer
else if (c < bufferP + *bufferLengthP - 1) // guard against buffer splitting cr/lf
{
c += sizeOf7BitChar(crChr) + sizeOf7BitChar(linefeedChr);
gotOne = true;
}
// Consume everything up to the CR/NULL
MemMove(bufferP, c, &bufferP[*bufferLengthP] - c);
*bufferLengthP = &bufferP[*bufferLengthP] - c;
// Read in more chars
charsRead = inputFunc(inputStreamP, bufferP + *bufferLengthP, importBufferMaxLength - *bufferLengthP);
*bufferLengthP += charsRead;
bufferP[*bufferLengthP] = nullChr;
}
return err;
}
/************************************************************
*
* FUNCTION: PrvMemoImportFinishRecord
*
* DESCRIPTION: Make sure record is null terminated, and close it.
*
* PARAMETERS:
* dbP - pointer to the database to add the record to
* indexNew - index of new record
* newRecordH - MemHandle to new record
* newRecordSize - bytes currently in new record
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* bob 1998-02-05 Moved out of MemoImportMime
*
*************************************************************/
static void PrvMemoImportFinishRecord(DmOpenRef dbP, UInt16 indexNew, MemHandle *newRecordHPtr, UInt16 *newRecordSizePtr, void * inputStream)
{
Char * newRecordP;
MemHandle newHandle; // Used to follow resized records which move
ErrNonFatalDisplayIf(*newRecordHPtr == NULL, "Null record MemHandle.");
// Make sure the record is nullChr terminated
newRecordP = MemHandleLock(*newRecordHPtr);
if (newRecordP[*newRecordSizePtr - sizeof(char)] != nullChr)
{
MemHandleUnlock(*newRecordHPtr);
newHandle = DmResizeRecord(dbP, indexNew, *newRecordSizePtr + sizeOf7BitChar(nullChr));
if (newHandle)
*newRecordHPtr = newHandle;
else
ErrThrow(exgMemError);
newRecordP = MemHandleLock(*newRecordHPtr);
DmWrite(newRecordP, *newRecordSizePtr, "", sizeOf7BitChar(nullChr));
}
// let the record go
MemHandleUnlock(*newRecordHPtr);
DmReleaseRecord(dbP, indexNew, true);
*newRecordHPtr = NULL;
*newRecordSizePtr = 0;
}
/************************************************************
*
* FUNCTION: PrvMemoImportMimeCleanup
*
* DESCRIPTION: Cleanup function for MemoImportMime
*
* PARAMETERS:
* dbP - pointer to the database to add the record to
* firstRecordID - uniqueID of the first record we loaded
* inputStream - pointer to where to import the record from
* numRecordsReceived - number of records received
* numRecordsReceivedP - pointer to the number of records received
*
* RETURNS: None
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* FPa 11/22/00 Initial Revision
*
*************************************************************/
void PrvMemoImportMimeCleanup(DmOpenRef dbP, UInt32 firstRecordID, void* inputStream, UInt16 numRecordsReceived, UInt16* numRecordsReceivedP)
{
// if we got at least one record, sort and set goto parameters...
if (firstRecordID)
{
MemoSort(dbP);
PrvMemoSetGoToParams (dbP, PrvStreamSocket(inputStream), firstRecordID);
}
// return number of records received
*numRecordsReceivedP = numRecordsReceived;
}