1522 lines
46 KiB
C
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;
|
|
}
|