sys7.1-doc-wip/Toolbox/DataPubsMgr/dpPubIO.inc.p
2019-07-27 22:37:48 +08:00

2478 lines
78 KiB
OpenEdge ABL

{
File: dpPubIO.inc.p
Written by: Nick Kledzik
Copyright: © 1989-1990, 1992 by Apple Computer, Inc., all rights reserved.
This file is used in these builds: BigBang
Change History (most recent first):
<40> 1/20/92 ngk Fix bug in dpPubCloseFile. It was not switching to system mode
before closing the file. This is now required in CubeE.
<39> 4/5/91 ngk just added a comment about FindPublisher
<38> 4/3/91 ngk MM,#c1-MTM-007: Check for nil controlblock in OpenNewEdition.
<37> 3/22/91 ngk VL, #b6-ngk-005: check MemErr after SetHandleSize.
Add missing Success calls.
<36> 2/27/91 ngk MM,#83903: Move sending of read events to pubSync
from dp_EditionMgrBackGroundTask in dpEvents.inc.p
<35> 1/25/91 ngk VL, #?????: Fix DeleteEditionContainer to not reference a
control block after releasing it.
<34> 1/14/91 ngk <MM> Fix use count problem in dp_DeleteEditionContainerFile
<33> 12/14/90 ngk <KSM>Fix setting of type & creator til after copying of other
Finder info.
<32> 12/14/90 ngk <MM>Use new PBxxxSync and FSpXxx calls. Changed dpFindPublisher
to tell app to open edition if edition does not contain an alias.
Fix for "throw me away" bug, by retrying delete. Fixed some
failure handlers that were not cleaning up properly. Shrunk
window for throw me aways to be made by change the subscriber lock
strategy to try to read before retring lock - this distingushes
between the publisher lock and another subscriber lock. Change
Sync to only try FCBInfo if GetCatInfo failed - this prevents syncing
to temp file on FileShare during exchange files. Clear Inited bit
if overwriting non-edition file. Don't require edition to have been
open during SwapAndDelete.
<31> 12/5/90 dba <dnf> Get rid of conflicts of bit definitions in here with new
global definitions in Files.p.
<30> 10/30/90 ngk Don't track editions into trash. In PubClose, now free maps.
Remove sync param from PBDTGetPath.
<29> 9/21/90 ngk DC NIL ioNamePtr when calling PBDTGetPath
<28> 9/15/90 ngk Fixed memory leak in dpResolveAliasToPublisher if edition
does not contain an alias.
<27> 9/13/90 ngk Changed file type for edition files to be all lowercase.
<26> 8/28/90 dnf Changed references to ResolveFileID into references to
ResolveFileIDRef.
<25> 8/26/90 ngk Another fix to GetStandardFormats. It was decrementing the
usage count one too many if it encountered an error.
<24> 8/16/90 ngk In sync, if file is missing, then mark so in control block.
In GetStandardFormats, on error remember to close edition.
Now handle case of bad verb to standard opener by return paramErr.
Fix createEditionFile to not set script if it is smSystemScript(-1).
<23> 8/4/90 ngk fix createEditionFile to have type edtu.
<22> 7/31/90 ngk fix throw me away files. Fix register usage with failure handling.
<21> 7/14/90 ngk Fix sync of MFS floppies. Changed more bAND's to bTST's.
Put hack in to work around GetVolParms bug.
Fix dpStandardSubscriberClose when sectionH=NIL to dec use count.
Fix dpStandardSubscriberClose to not close file if it was open for writing.
Fix dpGetStandardFormats to release thePubCB on error.
<20> 7/2/90 ngk need to release pubcb in delete edition
<19> 7/2/90 ngk bAND to bTST. Rework dpResolveAliasToPublisher and
dpFindPublisher.
Changed HDelete to FSpDelete. Did a bunch of code saving munges.
DeleteEditionFile stops all I/O with subscribers and only deletes
the file if there are no register publishers.
<18> 6/20/90 ngk Changed CanonicalFileSpec to FSSpec.
<17> 6/1/90 ngk Add 'snd ' to GetStandardFormats and established a priority
for choosing the preview format.
<16> 5/31/90 ngk Added new edition file types and made dpStandardCanSubscribe a
routine.
<15> 5/3/90 ngk Added ResolveAliasToPublisher routine.
<14> 4/7/90 ngk Use dynamic arrays for allocation and format lists. Changed
dpPubSetInfo to dpPubSync. Use RangeLocking to avoid "Throw me
away" files. Use new FailOSErr stuff.
<13> 3/20/90 ngk Added script parameter to dp_CreateEditionContainerFile
<12> 3/16/90 dnf Change DTOpen to DTGetPath
<12> 3/16/90 dnf Change DTOpen to DTGetPath
<11> 3/10/90 ngk Use bHasFileIDs attribute of VolParms. Removed dpGetVolumeInfo
and call GetVolParms directly. Moved locking and unlocking of
package to dispatcher.
dpGetVolumeInfo and call GetVolParms directly.
Moved locking and unlocking of package to dispatcher.
<10> 2/26/90 ngk When previewing TEXT, only read in first 500 bytes.
<9> 2/25/90 ngk Fix replace-existing non-edition file bug.
<8> 2/16/90 ngk Publisher^^.mdDate is used to set the edition File's mdDate.
Fixed bug where edition's create date kepts changing.
<7> 2/4/90 ngk Fix bug header.formatLength of edition files. Allow
OpenNewEdition to have NIL sectionDocument. Get mdDate for
newEditions from section record. Now use PBDT calls to exchange
edition comments.
<6> 1/26/90 ngk Fixed calls to Alias manager to use new non-pointer style
<5> 1/22/90 ngk Fix standard formatIO routine to use symbolic name. Changed
publisher to OpenDenyOtherWriters.
<4> 1/8/90 ngk Renamed GetAppRefNum
<2+> 1/6/90 ngk Changed name of GetAppRefNum,removed eoCanPublish
<2> 1/6/90 ngk Convert to BBS, removed wakeUpTime. Added bottleneck routines.
renamed some routines.
<2.3> 11/13/89 ngk Added Locking and restoring of Pack11 in all public routines
<2.2> 11/04/89 ngk Removed call to PBHCreateID in NewPublicationFile
Changed AppRefNum to be a handle to app's globals.
<2.1> 10/25/89 ngk Moved some inline code out.
<2.0> 10/13/89 ngk nothing
<1.9> 10/02/89 ngk added wakeUpTime
<1.8> 09/18/89 ngk Added dp_DeleteEditionContainerFile
<1.7> 09/08/89 ngk Removed call to CreateID in NewPublication
<1.6> 09/07/89 ngk Changed DisposeAlias to DisposHandle
<1.5> 08/29/89 ngk Changed dpGetVolumeInfo to return VolumeServices. Changed pub open
strategy to always have pub files open for read/write if a
publisher is registered and Subscribers leave the file open for
read-only. dpBackGroundTask will close read-only pub files if
no Subscribers are doing I/O. Seperated the loading of allocMap
and formats into dpPubLoadMap and dpPubReleaseMap. Stop using fileIDs.
<1.4> 08/08/89 ngk Split dp_CloseEdition into closing after reading and after writing
Used new error codes. qDebug -> qCatchFailures & qRangeCheck
Started using new FileID calls.
<1.3> 06/11/89 ngk Changed file format to include a separate foramt list and allocation table
changed dpGetPubInfo to dpSetPubInfo
made interface to dpWriteFormat & dpReadFormat consistent
<1.2> 05/31/89 ngk Changed SofaLink usage to Aliases
<1.1> 05/29/89 ngk Removed VirtualOpen and VirtualClose. Changed AccessMode to my own
enumerated type. Added WriteFormat and ReadFormat to allow I/O
with a Section I/O Control Block, as needed for the "pointer" from
the Publication file back to the Publisher document.
<1.0> 05/19/89 ngk Submitted for first time
To Do:
}
{$IFC UNDEFINED qExtraDebug }
{$SETC qExtraDebug = FALSE}
{$ENDC}
{========================== low level ========================}
CONST
kThrowMeAwayName = 'throw me away';
kTicksBetweenDeleteRetries = 20;
kMaxDeleteRetries = 10;
kTicksUntilOpenRetry = 10;
TYPE
VolumeParms = RECORD
vMVersion: INTEGER;
vMAttrib: LONGINT;
vMLocalHand: LONGINT;
vMServerAdr: LONGINT;
{vMVolumeGrade: LONGINT;}
{vMAltPrivModel:INTEGER;}
END;
{========================== internal routines ========================}
{ this will be eventually moved into File system }
FUNCTION FSpOpenDeny(spec: FSSpec; denyModes: INTEGER; VAR refNum: INTEGER): OSErr;
VAR
PBH: HParamBlockRec;
BEGIN
WITH PBH DO
BEGIN
PBH.ioVRefNum := spec.vRefNum;
PBH.ioDirID := spec.parID;
PBH.ioNamePtr := @spec.name;
PBH.ioVersNum := 0;
PBH.ioMisc := NIL;
PBH.ioDenyModes := denyModes;
FSpOpenDeny := PBHOpenDenySync(@PBH);
refNum := ioRefNum;
END; {with}
END; { FSpOpenDeny }
{------------- dpNotifySubscribers -------------}
FUNCTION dpNotifySubscribers(thePubCB: PubCBHandle): OSErr;
PROCEDURE NotifyIfOutOfDateSubscriber(aSection: SectionHandle; inApp: AppRefNum);
BEGIN
WITH aSection^^ DO
BEGIN
IF controlBlock = Handle(thePubCB)
{&} THEN IF bTST(kind, kCanReadEditionsBit)
{&} THEN IF mode = sumAutomatic
{&} THEN IF mdDate <> thePubCB^^.info.mdDate THEN
BEGIN
IgnoreOSErr(dp_PostSectionEvent(aSection, inApp, sectionReadMsgID));
END; {if}
END; {with}
END; { NotifyIfOutOfDateSubscriber }
BEGIN
dpForEachSectionDo(NotifyIfOutOfDateSubscriber);
dpNotifySubscribers := noErr;
END; { dpNotifySubscribers }
{ makes sure a controlblock's fields are up to date with the file system world }
{------------- dpPubSync -------------}
FUNCTION dpPubSync(thePubCB: PubCBHandle): OSErr;
TYPE
VCBPtr = ^VCB;
VAR
PBH: HParamBlockRec;
PBF: FCBPBRec;
VP: VolumeParms;
theInfo: EditionInfoRecord;
infoErr: OSErr;
tempStr: Str63;
tempThrow: Str63;
p: VCBPtr;
trashVRefNum:INTEGER;
trashDirID: LONGINT;
BEGIN
WITH thePubCB^^ DO
BEGIN
BlockMove(@{thePubCB^^.}info, @theInfo, SizeOf(theInfo)); { theInfo := thePubCB^^.info; }
{ PBHGetVolParms only called once }
IF {thePubCB^^.}volumeInfo = 0 THEN
BEGIN
{ get volume attributes }
WITH PBH DO
BEGIN
ioVRefNum := theInfo.container.theFile.vRefNum;
ioNamePtr := NIL;
ioBuffer := @VP;
ioReqCount := SizeOf(VP);
END; {with}
{ If it fails, then volume has no attributes }
IF PBHGetVolParmsSync(@PBH) = noErr
THEN {thePubCB^^.}volumeInfo := VP.vMAttrib;
{### super hack to get around bug that MFS volumes return bHasFileIDs ###}
p := VCBPtr(GetVCBQHdr^.qHead);
WHILE (p<>NIL) & (p^.vcbVRefNum <> theInfo.container.theFile.vRefNum)
DO p := VCBPtr(p^.qLink);
IF (p <> NIL) & (p^.vcbSigWord = $D2D7 )
THEN bClr(thePubCB^^.volumeInfo, bHasFileIDs);
{### end of super hack ###}
END; {if}
{ get edition, create date, and creator }
PBH.ioNamePtr := @theInfo.container.theFile.name;
PBH.ioVRefNum := theInfo.container.theFile.vRefNum;
PBH.ioFDirIndex := 0;
PBH.ioFVersNum := 0;
PBH.ioDirID := theInfo.container.theFile.parID;
infoErr := PBGetCatInfoSync(@PBH);
IF infoErr = fnfErr THEN
BEGIN
{ if file is known to be missing no need to try any harder }
IF {thePubCB^^.}fileMissing THEN
BEGIN
dpPubSync := noErr;
EXIT(dpPubSync);
END; {if}
{ if file is open use FCB to track file }
IF {thePubCB^^.}fileRefNum <> kClosedFileRefNum THEN
BEGIN
{ file is open, can track down access path to get info }
PBF.ioNamePtr := @tempStr;
PBF.ioRefNum := {thePubCB^^.}fileRefNum;
PBF.ioFCBIndx := 0;
IF PBGetFCBInfoSync(@PBF) = noErr
{&} THEN IF PBF.ioFCBParID <> 0 THEN { only do this for HFS volumes }
BEGIN
{ don't sync to files that start with 'throw me away' }
tempThrow := kThrowMeAwayName;
IF NOT dpSameBytes(@tempStr[1], @tempThrow[1], LENGTH(tempThrow)) THEN
BEGIN
BlockMove(@tempStr, @theInfo.container.theFile.name, SizeOf(Str63));
theInfo.container.theFile.parID := PBF.ioFCBParID;
PBH.ioNamePtr := @theInfo.container.theFile.name; { <36, #83903> }
PBH.ioVRefNum := theInfo.container.theFile.vRefNum; { <36, #83903> }
PBH.ioFDirIndex := 0; { <36, #83903> }
PBH.ioDirID := theInfo.container.theFile.parID; { <36, #83903> }
infoErr := PBGetCatInfoSync(@PBH); { <36, #83903> }
END; {if}
END; {if}
END; {if}
END; {if}
IF infoErr <> noErr THEN
BEGIN
{ file was renamed or moved, try looking up by cNodeID }
IF bTST({thePubCB^^.}volumeInfo, bHasFileIDs) THEN
BEGIN
PBH.ioVRefNum := theInfo.container.theFile.vRefNum;
PBH.ioFileID := {thePubCB^^.}pubCNodeID;
PBH.ioNamePtr := @theInfo.container.theFile.name;
IF PBResolveFileIDRefSync(@PBH) = noErr THEN
BEGIN
{ make sure that it was not moved into the trash }
IF FindFolder(theInfo.container.theFile.vRefNum,{vRefNum}
kTrashFolderType, {folderType}
FALSE, {createFolder}
trashVRefNum, {foundVRefNum}
trashDirID) = noErr THEN {foundDirID}
BEGIN
IF PBH.ioDirID <> trashDirID THEN
BEGIN
{ if found, but not in trash, then sync to new location }
theInfo.container.theFile.parID := PBH.ioSrcDirID;
PBH.ioNamePtr := @theInfo.container.theFile.name;
PBH.ioVRefNum := theInfo.container.theFile.vRefNum;
PBH.ioFDirIndex := 0;
PBH.ioDirID := theInfo.container.theFile.parID;
infoErr := PBGetCatInfoSync(@PBH);
END
ELSE infoErr := fnfErr;
END; {if}
END; {if}
END; {if}
END; {if}
{ files can not be found so mark it so }
IF infoErr = fnfErr
THEN {thePubCB^^.}fileMissing := TRUE;
IF infoErr = noErr THEN
BEGIN
{ update control block }
BlockMove(@theInfo.container.theFile, @info.container.theFile, SizeOf(FSSpec)); { info.container.theFile := theInfo.container.theFile; }
info.crDate := PBH.ioFlCrDat;
info.fdCreator := PBH.ioFlFndrInfo.fdCreator;
info.fdType := PBH.ioFlFndrInfo.fdType;
pubCNodeID := PBH.ioDirID;
{ file definitely exists now. This reverses 'fileMissing' if file should reappear }
{thePubCB^^.}fileMissing := FALSE;
{ Whenever the control block's modDate needs to be updated, read events need to be sent }
IF info.mdDate <> PBH.ioFlMdDat THEN { <36, #83903> }
BEGIN { <36, #83903> }
info.mdDate := PBH.ioFlMdDat; { <36, #83903> }
IgnoreOSErr(dpNotifySubscribers(thePubCB)); { <36, #83903> }
END; { <36, #83903> }
END; {if}
END; {with}
dpPubSync := infoErr;
END; { dpPubSync }
{------------- dpPubOpenFile -------------}
FUNCTION dpPubOpenFile(thePubCB: PubCBHandle; kind: SectionType): OSErr;
VAR
theRefNum: INTEGER;
pubFile: FSSpec;
transPerm: INTEGER;
hfsPerm: INTEGER;
orgOpenMode: INTEGER;
PBH: HParamBlockRec;
inSystemMode: BOOLEAN;
anErr: OSErr;
ignoreLong: LONGINT;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@inSystemMode);
DoNotPutInRegister(@orgOpenMode);
inSystemMode := FALSE;
theRefNum := kClosedFileRefNum;
orgOpenMode := thePubCB^^.openMode;
pubFile := thePubCB^^.info.container.theFile;
{ set up failure handling }
IF isFailure(fi, dpPubOpenFile) THEN
BEGIN
IF inSystemMode THEN IgnoreOSErr(EndSystemMode);
WITH thePubCB^^ DO
BEGIN
fileRefNum := kClosedFileRefNum;
openMode := dmNotOpen;
END; {with}
IF orgOpenMode <> dmNotOpen THEN
BEGIN
{$IFC qExtraDebug }
DebugStr('dpPubOpenFile: Failing and re-opening file');
{$ENDC }
{ failed opening for more permssions, so reopen with less }
CASE orgOpenMode OF
dmRequestReadPerm:
IgnoreOSErr(dpPubOpenFile(thePubCB, stSubscriber));
dmRequestReadPerm+dmRequestWritePerm+dmDenyOtherWriters:
IgnoreOSErr(dpPubOpenFile(thePubCB, stSubscriber));
END; {case}
END; {if}
EXIT(dpPubOpenFile);
END; {if}
{ do translation to deny mode permissions }
transPerm := 0;
IF bTST(kind, kCanReadEditionsBit)
THEN transPerm := bOR(transPerm, dmRequestReadPerm);
IF bTST(kind, kCanWriteEditionsBit)
THEN transPerm := bOR(transPerm, dmRequestWritePerm+dmRequestReadPerm+dmDenyOtherWriters);
{ see if already open with enough permissions }
{ i.e. a subscriber tries to open, but publisher already has it open }
IF bAND(orgOpenMode, transPerm) = transPerm THEN
BEGIN
{ already open with enough perms }
dpPubOpenFile := noErr;
Success(fi);
EXIT(dpPubOpenFile);
END;
{ to handle case of subscriber already has it open and then a publisher }
{ tries to open it, close the file and reopen with more permissions }
IF orgOpenMode <> dmNotOpen THEN
BEGIN
FailOSErr(dpPubCloseFile(thePubCB, NOT kFlush));
END;
IF bTST(thePubCB^^.volumeInfo, bHasOpenDeny) THEN
BEGIN
FailOSErr(BeginSystemMode); inSystemMode := TRUE;
{ open file of given spec }
anErr := FSpOpenDeny(pubFile, transPerm, theRefNum);
IF anErr = fnfErr {&} THEN IF (NOT bTST(thePubCB^^.volumeInfo, bHasFileIDs)) THEN
BEGIN
{ there is a small time window, during swap and delete when the file is gone. }
{ for this case, wait a moment and retry. }
Delay({numTicks}kTicksUntilOpenRetry, {VAR finalTicks}ignoreLong);
anErr := FSpOpenDeny(pubFile, transPerm, theRefNum);
END;
{$IFC qExtraDebug }
IF anErr <> noErr
THEN DebugStr('dpPubOpenFile: OpenDeny failed');
{$ENDC }
FailOSErr(anErr);
FailOSErr(EndSystemMode); inSystemMode := FALSE;
END ELSE
BEGIN
{ do translation to HFS permissions }
hfsPerm := 0;
IF bTST(kind, kCanWriteEditionsBit)
THEN hfsPerm := fsRdWrPerm
ELSE IF bTST(kind, kCanReadEditionsBit)
THEN hfsPerm := fsRdPerm
ELSE FailOSErr(permErr);
FailOSErr(BeginSystemMode); inSystemMode := TRUE;
{ open file using spec in thePubCB }
WITH PBH DO
BEGIN
ioNamePtr := @pubFile.name;
ioVRefNum := pubFile.vRefNum;
ioDirID := pubFile.parID;
ioVersNum := 0;
ioMisc := NIL;
ioPermssn := hfsPerm;
FailOSErr(PBHOpenSync(@PBH));
theRefNum := ioRefNum;
END; {with}
FailOSErr(EndSystemMode); inSystemMode := FALSE;
END; {if}
{ set more info fields }
WITH thePubCB^^ DO
BEGIN
fileRefNum := theRefNum;
openMode := transPerm;
END; {with}
Success(fi);
END; { dpPubOpenFile }
{------------- dpPubFlushFile -------------}
{ only used after writing a new edition }
FUNCTION dpPubFlushFile(thePubCB: PubCBHandle): OSErr;
VAR
theMap: AllocationMapHandle;
theFormats: FormatListHandle;
theRefNum: INTEGER;
header: EditionFileMinHeader;
PB: ParamBlockRec;
i: INTEGER;
BEGIN
{ get pub file refnum & map handle }
WITH thePubCB^^ DO
BEGIN
theRefNum := fileRefNum;
theMap := allocMap;
theFormats := formats;
END; {with}
{ make new header }
WITH header DO
BEGIN
version := kMapVersion;
formatsOffset := bAND(thePubCB^^.fileMark+$F,$FFFFFFF0); { round up to nearest paragraph }
formatsLength := theFormats^^.lastUsedSlot * SizeOf(Format) + SizeOf(INTEGER);
mapOffset := bAND(formatsOffset+formatsLength+$F,$FFFFFFF0); { round up to nearest paragraph }
mapLength := theMap^^.lastUsedSlot * SizeOf(AllocationRecord) + SizeOf(INTEGER);
END; {with}
{ write formats out }
WITH PB DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @theFormats^^.lastUsedSlot;
ioReqCount := header.formatsLength;
ioPosMode := fsFromStart;
ioPosOffset := header.formatsOffset;
FailOSErr(PBWriteSync(@PB));
IF ioActCount <> header.formatsLength THEN FailOSErr(dskFulErr); {when would this ever happen?}
END; {with}
{ write map out }
WITH PB DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @theMap^^.lastUsedSlot;
ioReqCount := header.mapLength;
ioPosMode := fsFromStart;
ioPosOffset := header.mapOffset;
FailOSErr(PBWriteSync(@PB));
IF ioActCount <> header.mapLength THEN FailOSErr(dskFulErr); {when would this ever happen?}
END; {with}
{ write header out }
WITH PB DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @header;
ioReqCount := SizeOf(header);
ioPosMode := fsFromStart;
ioPosOffset := 0;
FailOSErr(PBWriteSync(@PB));
IF ioActCount <> SizeOf(header) THEN FailOSErr(dskFulErr); {when would this ever happen?}
END; {with}
{ call _flushFile }
WITH PB DO
BEGIN
ioRefNum := theRefNum;
FailOSErr(PBFlushFileSync(@PB));
END; {with}
{### should we call flush volume? }
dpPubFlushFile := noErr;
END; { dpPubFlushFile }
{------------- dpPubCloseFile -------------}
FUNCTION dpPubCloseFile(thePubCB: PubCBHandle; flush: BOOLEAN): OSErr;
VAR
PB: ParamBlockRec;
theRefNum: INTEGER;
BEGIN
theRefNum := thePubCB^^.fileRefNum;
{$IFC qRangeCheck}
IF flush {&} THEN IF NOT bTST(thePubCB^^.openMode, dmRequestWritePermBit)
THEN DebugStr('dpPubCloseFile: trying to flush a read-only pub.');
IF theRefNum = kClosedFileRefNum
THEN DebugStr('dpPubCloseFile: trying to close a closed pub file.');
{$ENDC}
{ write out map to disk if needed }
IF flush THEN FailOSErr(dpPubFlushFile(thePubCB));
{ free the map, cause it is invalid once the file is closed }
IF thePubCB^^.allocMap <> NIL
THEN IgnoreOSErr(dpPubReleaseMap(thePubCB));
{ mark file closed in the PCB }
WITH thePubCB^^ DO
BEGIN
fileRefNum := kClosedFileRefNum;
openMode := dmNotOpen;
rangeLockStart := 0; { once the file is closed, range locks are invalid }
rangeLockLen := 0;
fileMark := kBadPosition;
END; {with}
{ close file }
WITH PB DO
BEGIN
{ need to be in system mode to close this file }
IgnoreOSErr(BeginSystemMode);
ioRefNum := theRefNum;
dpPubCloseFile := PBCloseSync(@PB);
IgnoreOSErr(EndSystemMode);
END; {with}
END; { dpPubCloseFile }
{------------- dpPubLoadMap -------------}
FUNCTION dpPubLoadMap(thePubCB: PubCBHandle; kind: SectionType): OSErr;
VAR
theRefNum: INTEGER;
header: EditionFileMinHeader;
PBH: HParamBlockRec;
theMap: AllocationMapHandle;
theFormats: FormatListHandle;
justOpened: BOOLEAN;
slots: INTEGER;
remainder: INTEGER;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@theMap);
DoNotPutInRegister(@theFormats);
DoNotPutInRegister(@justOpened);
theMap := NIL;
theFormats := NIL;
justOpened := FALSE;
{ set up failure handling }
IF isFailure(fi, dpPubLoadMap) THEN
BEGIN
IF justOpened THEN IgnoreOSErr(dpPubCloseFile(thePubCB, NOT kFlush));
IF theMap <> NIL THEN DisposHandle(Handle(theMap));
IF theFormats <> NIL THEN DisposHandle(Handle(theFormats));
EXIT(dpPubLoadMap);
END; {if}
{ make sure the file is open }
IF thePubCB^^.openMode = dmNotOpen THEN
BEGIN
FailOSErr(dpPubOpenFile(thePubCB, kind));
justOpened := TRUE;
END;
(*
{$IFC qRangeCheck}
IF thePubCB^^.openMode <> kind
THEN DebugStr('dpPubLoadMap: already open for wrong access');
{$ENDC}
*)
CASE kind OF
stPublisher:
BEGIN
{ create a map for allocation }
FailOSErr(dpNewDynamicArray(0, {headerSize}
SizeOf(AllocationRecord), {slotSize}
kInitialAllocCount, {initialSlots}
NewHandleSys, {MyNewHandle}
theMap)); {VAR arrayH}
{ create a format list for an edition with no formats }
FailOSErr(dpNewDynamicArray(0, {headerSize}
SizeOf(Format), {slotSize}
kInitialFormats,{initialSlots}
NewHandleSys, {MyNewHandle}
theFormats)); {VAR arrayH}
END;
stSubscriber:
BEGIN
{ if starting to read then get map and format info from file }
theRefNum := thePubCB^^.fileRefNum;
{ read header from file }
WITH PBH DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @header;
ioReqCount := 20; { only required part of header }
ioPosMode := fsFromStart;
ioPosOffset := 0;
FailOSErr(PBReadSync(@PBH));
IF ioActCount <> 20 THEN FailOSErr(badEditionFileErr);
END; {with}
{ check header for validity }
IF header.version <> kMapVersion THEN FailOSErr(badEditionFileErr);
{ allocate space for formats, read it from file }
UnsignedDivide(header.formatsLength-SizeOf(INTEGER), {numer}
SizeOf(Format), {denom}
slots, {quotient}
remainder); {remainder}
{$IFC qRangeCheck }
IF remainder <> 0 THEN DebugStr('dpPubLoadMap: bad formats size');
{$ENDC }
FailOSErr(dpNewDynamicArray(0, {headerSize}
SizeOf(Format), {slotSize}
slots, {initialSlots}
NewHandleSys, {MyNewHandle}
theFormats)); {VAR arrayH}
theFormats^^.lastUsedSlot := slots;
HLock(Handle(theFormats));
WITH PBH DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @theFormats^^.lastUsedSlot;
ioReqCount := header.formatsLength;
ioPosMode := fsFromStart;
ioPosOffset := header.formatsOffset;
FailOSErr(PBReadSync(@PBH));
IF ioActCount <> header.formatsLength THEN FailOSErr(badEditionFileErr);
END; {with}
HUnLock(Handle(theFormats));
{ allocate space for map, read it from file }
UnsignedDivide(header.mapLength-SizeOf(INTEGER), {numer}
SizeOf(AllocationRecord), {denom}
slots, {quotient}
remainder); {remainder}
{$IFC qRangeCheck }
IF remainder <> 0 THEN DebugStr('dpPubLoadMap: bad formats size');
{$ENDC }
FailOSErr(dpNewDynamicArray(0, {headerSize}
SizeOf(AllocationRecord), {slotSize}
slots, {initialSlots}
NewHandleSys, {MyNewHandle}
theMap)); {VAR arrayH}
theMap^^.lastUsedSlot := slots;
HLock(Handle(theMap));
WITH PBH DO
BEGIN
ioRefNum := theRefNum;
ioBuffer := @theMap^^.lastUsedSlot;
ioReqCount := header.mapLength;
ioPosMode := fsFromStart;
ioPosOffset := header.mapOffset;
FailOSErr(PBReadSync(@PBH));
IF ioActCount <> header.mapLength THEN FailOSErr(badEditionFileErr);
END; {with}
HUnLock(Handle(theMap));
END;
END; {case}
{ set more info fields }
WITH thePubCB^^ DO
BEGIN
allocMap := theMap;
formats := theFormats;
fileMark := SizeOf(EditionFileHeader); { really only needed for writing }
END; {with}
Success(fi);
END; { dpPubLoadMap }
{------------- dpPubReleaseMap -------------}
FUNCTION dpPubReleaseMap(thePubCB: PubCBHandle): OSErr;
BEGIN
WITH thePubCB^^ DO
BEGIN
{ dispose of the allocation map }
DisposHandle(Handle(allocMap));
allocMap := NIL;
{ dispose of the format list }
DisposHandle(Handle(formats));
formats := NIL;
END; {with}
dpPubReleaseMap := noErr;
END; { dpPubReleaseMap }
{------------- dpTweakCatInfo -------------}
FUNCTION dpTweakCatInfo(edition: FSSpec;
PROCEDURE Tweaker(VAR PBC: CInfoPBRec) ): OSErr;
VAR
PBC: CInfoPBRec;
BEGIN
WITH PBC, edition DO
BEGIN
{ set up param block }
ioNamePtr := @name;
ioVRefNum := vRefNum;
ioFDirIndex := 0;
ioFVersNum := 0;
ioDirID := parID;
FailOSErr(PBGetCatInfoSync(@PBC));
{ tweak the param block }
Tweaker(PBC);
{ write out the tweaked param block }
ioNamePtr := @name;
ioVRefNum := vRefNum;
ioFDirIndex := 0;
ioDirID := parID;
FailOSErr(PBSetCatInfoSync(@PBC));
END; {with}
dpTweakCatInfo := noErr;
END; { dpTweakCatInfo }
{------------- dpRandomFileName -------------}
PROCEDURE dpRandomFileName(VAR seedName: Str63);
VAR
timeNumber: Str255;
seedLen: INTEGER;
timeLen: INTEGER;
BEGIN
NumToString(TickCount, timeNumber);
seedLen := LENGTH(seedName);
timeLen := LENGTH(timeNumber);
IF seedLen + timeLen <= 31 THEN
BEGIN
BlockMove( Ptr(ORD(@timeNumber)+1),
Ptr(ORD(@seedName)+seedLen+1),
timeLen );
seedName[0] := CHAR(SeedLen + timeLen);
END ELSE
BEGIN
BlockMove( Ptr(ORD(@timeNumber)+1),
Ptr(ORD(@seedName)+31-timeLen),
TimeLen );
seedName[0] := CHAR(31);
END; {if}
END; { dpRandomFileName }
{------------- dpSwapAndDelete -------------}
FUNCTION dpSwapAndDelete(orgPubCB, newPubCB: PubCBHandle): OSErr;
VAR
PBH: HParamBlockRec;
PBC: CInfoPBRec;
orgEdition: FSSpec;
newEdition: FSSpec;
throwAway: FSSpec;
info1: FInfo;
info2: FXInfo;
originalCrDate: TimeStamp;
finderComments: Str255;
deleteErr: OSErr;
exchangeErr: OSErr;
setCommentErr: OSErr;
getCommentErr: OSErr;
openDTErr: OSErr;
DTRefNum: INTEGER;
i: INTEGER;
ignoreLong: LONGINT;
fileToDelete: FSSpecPtr;
wasNotEdition: BOOLEAN;
fi: FailInfo;
PROCEDURE dpSetFinderInfo(VAR PBC: CInfoPBRec);
CONST
hasBeenInited = 8;
VAR
tempLong: LONGINT;
BEGIN
WITH newPubCB^^.info DO
BEGIN
{ only need to set other info if we can't use exchangeFiles }
IF NOT bTST(orgPubCB^^.volumeInfo, bHasFileIDs) THEN
BEGIN
PBC.ioFlFndrInfo := info1;
PBC.ioFlXFndrInfo := info2;
PBC.ioFlCrDat := originalCrDate;
END;
{ set new type and creator }
PBC.ioFlFndrInfo.fdCreator := fdCreator;
PBC.ioFlFndrInfo.fdType := fdType;
{ if we are overwriting a non-edition file, we need to let the }
{ Finder know to re-instantiate the object for this file. }
IF wasNotEdition THEN
BEGIN
tempLong := PBC.ioFlFndrInfo.fdFlags;
bCLR(tempLong, hasBeenInited);
PBC.ioFlFndrInfo.fdFlags := tempLong;
END;
END; {with}
END; { dpSetFinderInfo }
BEGIN
{ set up failure handling }
IF isFailure(fi, dpSwapAndDelete) THEN
BEGIN
{ well, all hell broke loose. Lets at least close the }
{ new file as if it did swap. }
IgnoreOSErr(dpPubCloseFile(newPubCB, NOT kFlush));
EXIT(dpSwapAndDelete);
END; {if}
{ switch contents of old and new pub file }
{$IFC qRangeCheck }
IF original.vRefNum <> newEdition.vRefNum
THEN DebugStr('dpSwapAndDelete: files on different volumes!');
{$ENDC}
{ make sure the original was not moved during write of new edition }
IgnoreOSErr(dpPubSync(orgPubCB));
{ make local copies of files specs do reduce code size }
BlockMove(@orgPubCB^^.info.container.theFile, @orgEdition, SizeOf(FSSpec));
BlockMove(@newPubCB^^.info.container.theFile, @newEdition, SizeOf(FSSpec));
{ remember if we are overwriting a non-edition file }
wasNotEdition := NOT IsEditionFile(orgPubCB^^.info.fdType);
{$IFC qRangeCheck }
{ if original pubCB file is open, close it }
IF orgPubCB^^.openMode = dmNotOpen THEN
BEGIN
DebugStr('dpSwapAndDelete: org edition file not open.');
IF orgPubCB^^.usageInfo^^.totalIOCount <> 0
THEN DebugStr('dpSwapAndDelete: could not kill off all subscribers.');
END; {if}
{$ENDC }
IF bTST(orgPubCB^^.volumeInfo, bHasFileIDs) THEN
BEGIN
{ Use ExchangeFiles }
FailOSErr(FSpExchangeFiles(orgEdition, newEdition));
{ set type and creator of to match contents }
FailOSErr(dpTweakCatInfo(orgEdition, dpSetFinderInfo));
{ remember which file to delete }
fileToDelete := @newEdition;
END ELSE
BEGIN
{ exchangeFiles is not implemented, so do it by hand }
{ get Finder info from orginal file }
WITH PBC DO
BEGIN
ioNamePtr := @orgEdition.name;
ioVRefNum := orgEdition.vRefNum;
ioFDirIndex := 0;
ioFVersNum := 0;
ioDirID := orgEdition.parID;
FailOSErr(PBGetCatInfoSync(@PBC));
info1 := ioFlFndrInfo;
info2 := ioFlXFndrInfo;
originalCrDate := ioFlCrDat;
END; {with}
{ move Finder info to new edition file, except for type and creator }
FailOSErr(dpTweakCatInfo(newEdition, dpSetFinderInfo));
{ get original's Finder comments if able }
finderComments := '';
IF bTST(orgPubCB^^.volumeInfo, bHasDesktopMgr) THEN
BEGIN
WITH PBH DO
BEGIN
ioVRefNum := orgEdition.vRefnum;
ioNamePtr := NIL; { <29> }
openDTErr := PBDTGetPath(@PBH);
{$IFC qCatchFailures}
IF (openDTErr <> noErr)
THEN DebugStr('dpSwapAndDelete: PBDTOpen failed.');
{$ENDC}
DTRefNum := ioRefNum;
END;
{ continue if could open desk top database }
IF openDTErr = noErr THEN
BEGIN
WITH PBH DO
BEGIN
ioNamePtr := @orgEdition.name;
ioRefNum := DTRefNum;
ioDirID := orgEdition.parID;
ioBuffer := @finderComments[1];
ioReqCount := 255;
END; {with}
getCommentErr := PBDTGetCommentSync(@PBH);
IF getCommentErr = noErr
THEN finderComments[0] := CHR(PBH.ioActCount);
{$IFC qCatchFailures}
IF (getCommentErr <> noErr)
{&} THEN IF (getCommentErr <> afpItemNotFound)
{&} THEN IF (getCommentErr <> fnfErr){## bug in DTmanger}
THEN DebugStr('dpSwapAndDelete: GetComment failed.');
{$ENDC}
END; {if}
END; {if}
{ if original had finder comments, set the new one to have the same }
IF LENGTH(finderComments) <> 0 THEN
BEGIN
WITH PBH DO
BEGIN
ioNamePtr := @newEdition.name;
ioRefNum := DTRefNum;
ioDirID := newEdition.parID;
ioBuffer := @finderComments[1];
ioReqCount := LENGTH(finderComments);
setCommentErr:= PBDTSetCommentSync(@PBH);
END;
{$IFC qCatchFailures}
IF setCommentErr <> noErr THEN DebugStr('dpSwapAndDelete: PBDTSetComment failed.');
{$ENDC}
END; {if}
{ rename orginal Edition to temp name }
throwAway.vRefNum := orgEdition.vRefNum;
throwAway.parID := orgEdition.parID;
throwAway.name := kThrowMeAwayName;
dpRandomFileName(throwAway.name);
FailOSErr(FSpRename(orgEdition, throwAway.name));
{ rename new Edition to original name }
FailOSErr(FSpRename(newEdition, orgEdition.name));
{ remember which file to delete }
fileToDelete := @throwAway;
END; {if}
{ close original (if it was open) now that we are done }
{ it could be close now becuase 1) we are overwriting a non-edition file }
{ or 2) there was multiple publishers on this machine and the publisher }
{ unregistered while this publishers was between OpenNew/CloseEdition. }
IF orgPubCB^^.fileRefNum <> kClosedFileRefNum
THEN IgnoreOSErr(dpPubCloseFile(orgPubCB, NOT kFlush));
{ delete original file }
deleteErr := FSpDelete(fileToDelete^);
{$IFC qExtraDebug }
IF deleteErr <> noErr
THEN DebugStr('dpSwapAndDelete: final delete failed');
{$ENDC }
{ There is a small window between the close and delete, during which a subscriber could }
{ add a range lock. When this happens, a subscriber has the "throw me away" file open, }
{ so the delete fails. The fix for this is to wait for the subscriber to close, then delete. }
{ We don't have to worry about another subscriber re-opening, because they don't }
{ open files called "throw me away". }
FOR i := 1 TO kMaxDeleteRetries DO
BEGIN
IF deleteErr = noErr THEN LEAVE; {for}
Delay({numTicks}kTicksBetweenDeleteRetries, {VAR finalTicks}ignoreLong);
deleteErr := FSpDelete(fileToDelete^);
END; {for}
FailOSErr(deleteErr);
Success(fi); { <37> }
END; { dpSwapAndDelete }
{------------- dpTryToGetFormat -------------}
FUNCTION dpTryToGetFormat(whichFormat: FormatType; dataHandle: Handle;
ioRefNum: LONGINT; ioProc: FormatIOProcPtr): OSErr;
CONST
kMaxTextPreviewSize = 500; { max byte count of a text preview }
VAR
IOParams: FormatIOParamBlock;
fi: FailInfo;
hasFormatErr: OSErr;
BEGIN
{ failure handler needed }
IF IsFailure(fi, dpTryToGetFormat)
THEN EXIT(dpTryToGetFormat);
{ see if format is available }
IOParams.ioRefNum := ioRefNum;
IOParams.format := whichFormat;
FailOSErr(dp_CallFormatIOProc(ioHasFormat, IOParams, ioProc));
{ limit text size }
IF LONGINT(whichFormat) = LONGINT('TEXT') THEN
BEGIN
IF IOParams.buffLen > kMaxTextPreviewSize
THEN IOParams.buffLen := kMaxTextPreviewSize;
END; {if}
{ resize handle to hold it }
SetHandleSize(dataHandle, IOParams.buffLen);
FailOSErr(MemError); { <37> }
HLock(dataHandle);
{ read in format into handle }
IOParams.ioRefNum := ioRefNum;
IOParams.format := whichFormat;
{IOParams.formatIndex already set}
IOParams.offset := 0;
IOParams.buffPtr := dataHandle^;
{IOParams.buffLen already set }
FailOSErr(dp_CallFormatIOProc(ioReadFormat, IOParams, ioProc));
HUnLock(dataHandle);
Success(fi);
END; { dpTryToGetFormat }
{------------- dpPubRemoveLock -------------}
FUNCTION dpPubRemoveLock(thePubCB: PubCBHandle): OSErr;
VAR
PBH: HParamBlockRec;
anErr: OSErr;
BEGIN
dpPubRemoveLock := noErr;
WITH thePubCB^^ DO
BEGIN
{ is there a lock? }
IF rangeLockLen > 0 THEN
BEGIN
{ remove lock we put on file header }
WITH PBH DO
BEGIN
ioRefNum := fileRefNum;
ioReqCount := rangeLockLen;
ioPosMode := fsFromStart;
ioPosOffset := rangeLockStart;
END; {with}
anErr := PBUnLockRangeSync(@PBH);
dpPubRemoveLock := anErr;
{$IFC qExtraDebug }
IF anErr <> noErr
THEN DebugStr('dpPubRemoveLock: unlock returned unexpected err');
{$ENDC }
END; {if}
END; {with}
WITH thePubCB^^ DO
BEGIN
rangeLockStart := 0;
rangeLockLen := 0;
END; {with}
END; { dpPubRemoveLock }
{------------- dpPubAddLock -------------}
FUNCTION dpPubAddLock(thePubCB: PubCBHandle; lockStart, lockLen: INTEGER): OSErr;
VAR
PBH: HParamBlockRec;
lockErr: OSErr;
BEGIN
lockErr := noErr;
{$IFC qRangeCheck}
IF lockLen = 0
THEN DebugStr('dpPubAddLock: no lock to add');
IF thePubCB^^.rangeLockLen <> 0
THEN DebugStr('dpPubAddLock: a lock already exists;g');
{$ENDC}
WITH thePubCB^^ DO
BEGIN
{ is there a lock? }
IF rangeLockLen = 0 THEN
BEGIN
{ try to lock the requested range }
WITH PBH DO
BEGIN
ioRefNum := fileRefNum;
ioReqCount := lockLen;
ioPosMode := fsFromStart;
ioPosOffset := lockStart;
END; {with}
lockErr := PBLockRangeSync(@PBH);
IF lockErr = noErr THEN
BEGIN
{ if succesful, record it in control block }
rangeLockStart := lockStart;
rangeLockLen := lockLen;
END; {if}
END; {if}
END; {with}
dpPubAddLock := lockErr;
END; { dpPubAddLock }
{------------- dpFindPublisher -------------}
FUNCTION dpFindPublisher(thePubCB: PubCBHandle; canAskUser: BOOLEAN;
VAR publisherSectionH: SectionHandle;
VAR publisherApplication: AppRefNum;
VAR publisherDoc: FSSpec;
VAR theSectionID: LONGINT): OSErr;
{
,(publisherDoc, publisherDoc), xor (publisherDoc).
publisherSectionH is not NIL is the first case.
theSectionID is non-zero is the second case.
xor (publisherDoc, publisherDoc). publisherSectionH is not NIL is the first case.
Note: currently no one calls this with canAskUser=FALSE. The parameter
is here in case this is ever used for "preflighting" of finding the publisher.
}
VAR
publisherAlias: AliasHandle;
dummyFormat: FormatType;
tempPtr: pLongint;
aliasCount: INTEGER;
needsUpdate: BOOLEAN;
resolveErr: OSErr;
openErr: OSErr;
PBH: HParamBlockRec;
rfi: FailInfo;
PROCEDURE dpLookForRegisteredPublisher(sectionH: SectionHandle; inApp: AppRefNum);
BEGIN
IF sectionH^^.controlBlock = Handle(thePubCB) THEN
BEGIN
publisherSectionH := sectionH;
publisherApplication := inApp;
END; {if}
END; { dpLookForRegisteredPublisher }
BEGIN
dpFindPublisher := noErr;
publisherSectionH := NIL;
{ see if we already know publisher, if so we are done }
WITH thePubCB^^ DO
BEGIN
IF publisher <> NIL THEN
BEGIN
{ easy case, publisher known }
publisherSectionH := publisher;
publisherApplication := publisherApp;
EXIT(dpFindPublisher);
END; {if}
END; {with}
{ try using alias in edition file to find publisher }
IF NOT IsFailure(rfi, resolveErr) THEN { catch any failures properly }
BEGIN
FailOSErr(dpResolveAliasToPublisher(thePubCB, canAskUser, publisherDoc, theSectionID));
Success(rfi);
END;
{ if we found publisher via alias then we are done }
IF resolveErr = noErr
THEN EXIT(dpFindPublisher);
{ next possible way to find publisher is to check all currently registered sections }
IF thePubCB^^.publisherCount > 0 THEN
BEGIN
{ so scan all register sections }
dpForEachSectionDo(dpLookForRegisteredPublisher);
EXIT(dpFindPublisher);
END;
{ last way to find publisher is if there was no alias and no registered publisher }
IF resolveErr <> noTypeErr THEN FailOSErr(resolveErr); {noTypeErr => no alias in edition file }
{ #### note: the rest of this code needs to be fixed. This was written }
{ back when no alias meant that the edition was a "publisherless-edition" }
{ (that is a stand alone file. Opening a "publisherless-edition" launches an }
{ app which can edit it.) Now, the existance of a 'doev' format has that meaning. }
{ The following code should be replaced with a check for a 'doev' format }
{ (by calling GetStandardFormats and looking at the formats list.) If there }
{ is a 'doev' format, this routine should return the edition file as the }
{ publisherDoc (i.e. keep the BlockMove). }
{ assume app knows what it is doing (by not putting an alias }
{ in edition) and send it an odoc event to open edition }
WITH thePubCB^^ DO
BEGIN
IF bTST({thePubCB^^.}volumeInfo, bHasOpenDeny) THEN
BEGIN
{ This is a shared volume, so the publisher could be in }
{ an untitled document on another machine. If that is the }
{ case, we don't want to open edition here. }
{ We will try to open the edition for writing, if that fails }
{ then there is a publisher open for it somewhere. }
{ If edition file is already open, we can't check so just fail. }
IF {thePubCB^^.}fileRefNum <> kClosedFileRefNum THEN FailOSErr(fBsyErr);
{ try to open }
FailOSErr(FSpOpenDeny(info.container.theFile, dmRequestWritePerm+dmDenyOtherWriters, PBH.ioRefNum));
{ we could open it, so close it }
IgnoreOSErr(PBCloseSync(@PBH));
END; {if}
{ return edition file as publisher, and sectionID = 0 }
BlockMove(@info.container.theFile, @publisherDoc, SizeOf(FSSpec));
theSectionID := 0;
END; {with}
END; { dpFindPublisher }
{------------- dpResolveAliasToPublisher -------------}
FUNCTION dpResolveAliasToPublisher(thePubCB: PubCBHandle; canAskUser: BOOLEAN;
VAR publisherDoc: FSSpec;
VAR theSectionID: LONGINT): OSErr;
VAR
publisherAlias: AliasHandle;
dummyFormat: FormatType;
tempPtr: pLongint;
aliasCount: INTEGER;
needsUpdate: BOOLEAN;
rulesMask: LONGINT;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@publisherAlias);
IF IsFailure(fi, dpResolveAliasToPublisher) THEN
BEGIN
IF publisherAlias <> NIL THEN DisposHandle(Handle(publisherAlias));
EXIT(dpResolveAliasToPublisher);
END;
{ try to resolve alias to publisher doc }
Handle(publisherAlias) := NewHandle(100); { dp_GetStandardFormats will resize as needed }
FailOSErr(MemError);
FailOSErr(dp_GetStandardFormats(thePubCB^^.info.container, dummyFormat, NIL, Handle(publisherAlias), NIL));
{ snag sectionID of publisher from end of alias }
tempPtr := pLongint(pLongint(publisherAlias)^ + publisherAlias^^.aliasSize);
theSectionID := tempPtr^ ;
{ resolve the alias }
aliasCount := 1;
IF canAskUser
THEN rulesMask := kARMsearch+kARMsearchRelFirst+kARMmountVol
ELSE rulesMask := kARMsearch+kARMsearchRelFirst+kARMnoUI;
{###? add a filter for documents only }
FailOSErr(MatchAlias(@thePubCB^^.info.container.theFile, { fromFile }
rulesMask, { rulesMask }
publisherAlias, { alias }
aliasCount, { VAR aliasCount }
@publisherDoc, { buffer }
needsUpdate, { VAR needsUpdate }
NIL, { aliasFilter }
NIL)); { yourDataPtr }
{ free up the temp alias }
DisposHandle(Handle(publisherAlias));
Success(fi); { <37> }
END; { dpResolveAliasToPublisher }
{================================ standard routines ===================================}
{------------- dpStandardSubscriberOpen -------------}
FUNCTION dpStandardSubscriberOpen(VAR info: EditionInfoRecord;
sectionH: SectionHandle;
VAR ioRefNum: LONGINT;
VAR ioProc: FormatIOProcPtr): OSErr;
VAR
thePubCB: PubCBHandle;
thisApp: AppRefNum;
PBH: HParamBlockRec;
PBF: FCBPBRec;
randomRangeLock:LONGINT;
lockErr: OSErr;
i: INTEGER;
justOpened: BOOLEAN;
mapJustLoaded: BOOLEAN;
addedLock: BOOLEAN;
editionName: Str63;
temp: LONGINT;
fi: FailInfo;
BEGIN
{ initialize state }
DoNotPutInRegister(@justOpened);
DoNotPutInRegister(@mapJustLoaded);
DoNotPutInRegister(@addedLock);
DoNotPutInRegister(@thePubCB);
mapJustLoaded := FALSE;
justOpened := FALSE;
addedLock := FALSE;
thePubCB := NIL;
{ need a failure handler to undo, if failed half way through }
IF IsFailure(fi, dpStandardSubscriberOpen) THEN
BEGIN
IF NOT justOpened THEN
BEGIN
IF addedLock THEN IgnoreOSErr(dpPubRemoveLock(thePubCB));
END
ELSE IgnoreOSErr(dpPubCloseFile(thePubCB, NOT kFlush));
IF mapJustLoaded THEN IgnoreOSErr(dpPubReleaseMap(thePubCB));
IF thePubCB <> NIL {&} THEN IF sectionH = NIL
THEN IgnoreOSErr(dpReleasePubCB(thePubCB, thisApp));
EXIT(dpStandardSubscriberOpen);
END;
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
{ get the control block through the section or by filename }
IF sectionH = NIL THEN
BEGIN
FailOSErr(dpRequestPubCB(info.container.theFile, thisApp,
kCheckExisting, NOT kCanAllocateNew, thePubCB));
END ELSE
BEGIN
thePubCB := PubCBHandle(sectionH^^.controlBlock);
END;
{ load edition file maps, if not already so }
IF thePubCB^^.allocMap = NIL THEN
BEGIN
{ make sure the file is open }
IF thePubCB^^.openMode = dmNotOpen THEN
BEGIN
FailOSErr(dpPubOpenFile(thePubCB, stSubscriber));
justOpened := TRUE;
END;
{ on shared volumes we have to range lock a byte in the header }
{ to prevent publisher from writing }
IF bTST(thePubCB^^.volumeInfo, bHasOpenDeny) THEN
BEGIN
{ only need to add a lock if we don't already have one }
IF thePubCB^^.rangeLockLen = 0 THEN
BEGIN
{ first try random lock }
randomRangeLock := kSubscriberRangeLockStart + bAND(Random, kSubscriberRangeLockMask);
{ try to lock a bit, to let the publisher know we are here }
lockErr := dpPubAddLock(thePubCB, randomRangeLock, 1);
IF lockErr = fLckdErr THEN
BEGIN
{ if range lock failed, then chances are it is because }
{ the publisher is writing. To test this we try to read }
{ a bit from the header, which publishers have locked. }
PBH.ioRefNum := thePubCB^^.fileRefNum;
PBH.ioBuffer := @temp;
PBH.ioReqCount := 2;
PBH.ioPosMode := fsFromStart;
PBH.ioPosOffset := 0;
FailOSErr(PBReadSync(@PBH)); { if publisher is writing, this will fail }
END; {if}
{ publisher is not writing, so it is rotton luck that we }
{ happened to choose the same offset as another subscriber }
FOR i := kSubscriberLockRetries DOWNTO 0 DO
BEGIN
{ if unexpected error then FailOSErr out }
IF lockErr <> fLckdErr
THEN FailOSErr(lockErr);
{ try next byte, but wrap around if needed }
randomRangeLock := kSubscriberRangeLockStart + bAND(randomRangeLock+1, kSubscriberRangeLockMask);
lockErr := dpPubAddLock(thePubCB, randomRangeLock, 1);
{ jump out as soon as lock succeeds }
IF lockErr = noErr THEN LEAVE; {for}
END; {for}
{ fail if last try did not succeed }
FailOSErr(lockErr);
addedLock := TRUE;
END; {if}
{ check FCB to see if file has been renamed }
{ this can happen if lock gets in between time publisher closes and deletes }
WITH thePubCB^^ DO
BEGIN
{ file is open, can track down access path to get info }
PBF.ioNamePtr := @editionName; { put current name here }
PBF.ioRefNum := {thePubCB^^.}fileRefNum;
PBF.ioFCBIndx := 0;
FailOSErr(PBGetFCBInfoSync(@PBF));
IF PBF.ioFCBParID <> 0 THEN { only do this for HFS volumes }
BEGIN
{ if the file name has changed, then publisher is busy writing }
IF NOT dpSameBytes(@editionName[0], @{thePubCB^^.}info.container.theFile.name[0], LENGTH(editionName)+1)
THEN FailOSErr(fLckdErr);
END; {if}
END; {with}
END; {if}
{ load map }
FailOSErr(dpPubLoadMap(thePubCB, stSubscriber));
mapJustLoaded := TRUE;
END; {if}
{ bump this apps open count for this PubCB }
FailOSErr(dpPubCBbumpUsageCount(thePubCB^^.usageInfo, thisApp, kCanGrowUsage, 0, 1));
ioRefNum := ORD(thePubCB);
ioProc := FormatIOProcPtr(kStandardFormatIOProcPtr);
Success(fi);
END; { dpStandardSubscriberOpen }
{------------- dpStandardSubscriberClose -------------}
FUNCTION dpStandardSubscriberClose(sectionH: SectionHandle;
ioRefNum: LONGINT;
ioProc: FormatIOProcPtr): OSErr;
VAR
thePubCB: PubCBHandle;
thisApp: AppRefNum;
anErr: OSErr;
BEGIN
thePubCB := PubCBHandle(ioRefNum);
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
{ dec this apps open count for this PubCB }
FailOSErr(dpPubCBbumpUsageCount(thePubCB^^.usageInfo, thisApp, NOT kCanGrowUsage, 0, -1));
{ stop IO, if this is last section that is doing any IO }
IF thePubCB^^.usageInfo^^.totalIOCount = 0 THEN
BEGIN
{ dump allocation map and formats list }
FailOSErr(dpPubReleaseMap(thePubCB));
IF bTST(thePubCB^^.openMode, dmRequestWritePermBit) THEN
BEGIN
{ a publisher also has the file open, so don't close it, }
{ just remove the subscriber's lock. }
FailOSErr(dpPubRemoveLock(thePubCB));
END ELSE
BEGIN
{ no publisher, so close and release lock }
FailOSErr(dpPubCloseFile(thePubCB, NOT kFlush)); { this will also remove the range lock }
END;
END; {if}
{ undo what we did in dpStandardSubscriberOpen }
IF sectionH = NIL
THEN FailOSErr(dpReleasePubCB(thePubCB, thisApp));
dpStandardSubscriberClose := noErr;
END; { dpStandardSubscriberClose }
{------------- dpStandardPublisherOpen -------------}
FUNCTION dpStandardPublisherOpen(sectionH: SectionHandle;
fdCreator: OSType;
document: FSSpecPtr;
VAR ioRefNum: LONGINT;
VAR ioProc: FormatIOProcPtr): OSErr;
VAR
thisApp: AppRefNum;
tempEdtnFile: FSSpec;
thePubCB: PubCBHandle;
newPubCB: PubCBHandle;
aliasH: AliasHandle;
tempPtr: pLongint;
fileCreated: BOOLEAN;
ioCountBumped: BOOLEAN;
PBH: HParamBlockRec;
ignoreLong: LONGINT;
i: INTEGER;
lockErr: OSErr;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@fileCreated);
DoNotPutInRegister(@ioCountBumped);
DoNotPutInRegister(@newPubCB);
fileCreated := FALSE;
ioCountBumped := FALSE;
newPubCB := NIL;
{ set up failure handling }
IF isFailure(fi, dpStandardPublisherOpen) THEN
BEGIN
{ undo any thing we allocated, in reverse order of allocation }
IF ioCountBumped THEN
BEGIN
IgnoreOSErr(dpPubCBbumpUsageCount(newPubCB^^.usageInfo, thisApp, NOT kCanGrowUsage, 0, -1));
END;
IF newPubCB <> NIL THEN
BEGIN
sectionH^^.controlBlock := Handle(thePubCB);
IgnoreOSErr(dpReleasePubCB(newPubCB, thisApp));
END;
IF fileCreated THEN
BEGIN
IgnoreOSErr(FSpDelete(tempEdtnFile));
END; {if}
EXIT(dpStandardPublisherOpen);
END; {if}
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
thePubCB := PubCBHandle(sectionH^^.controlBlock);
{ is this on a shared volume? }
IF bTST(thePubCB^^.volumeInfo, bHasOpenDeny) THEN
BEGIN
{ if subscriber/publisher already has a lock (is reading/writing), tell publisher it is inuse }
IF thePubCB^^.rangeLockLen > 0
THEN FailOSErr(fLckdErr);
{ range lock file header to prevent subscribers from opening for read }
{ and potentially preventing me from deleting old edition file when I am done }
FOR i := kPublisherLockRetries DOWNTO 0 DO
BEGIN
lockErr := dpPubAddLock(thePubCB, kPublisherRangeLockStart, kPublisherRangeLockLength);
{ jump out as soon as lock succeeds }
IF lockErr = noErr THEN LEAVE; {for}
{ if unexpected error then FailOSErr out }
IF lockErr <> fLckdErr THEN FailOSErr(lockErr);
{ wait a bit before retry }
Delay({numTicks}kTicksBetweenPublisherRetries, {VAR finalTicks}ignoreLong);
END; {for}
FailOSErr(lockErr);
END; {if}
{ create a temp-named edition file }
tempEdtnFile := thePubCB^^.info.container.theFile;
dpRandomFileName(tempEdtnFile.name);
FailOSErr(FSpCreate(tempEdtnFile, fdCreator, kUnknownEditionFileType, thePubCB^^.info.container.theFileScript));
fileCreated := TRUE;
{ create a PubCB for it }
FailOSErr(dpRequestPubCB(tempEdtnFile, thisApp, NOT kCheckExisting, kCanAllocateNew, newPubCB));
sectionH^^.controlBlock := Handle(newPubCB);
{ fill in the fields not done right by RequestPubCB }
WITH newPubCB^^ DO
BEGIN
oldPubCB := thePubCB;
publisher := sectionH; { same as thePubCB^^.publisher }
publisherApp := thisApp; { same as thePubCB^^.publisherApp }
END; {with}
{ initialize an empty map and formats list }
FailOSErr(dpPubLoadMap(newPubCB, stPublisher));
{ bump this apps open count for this PubCB }
FailOSErr(dpPubCBbumpUsageCount(newPubCB^^.usageInfo, thisApp, kCanGrowUsage, 0, 1));
ioCountBumped := TRUE;
{ create the "backward" link from the EditionFile to the PublisherDocument }
aliasH := NIL;
IF document <> NIL THEN
BEGIN
FailOSErr(NewAlias(@newPubCB^^.info.container.theFile, document^, aliasH));
{$IFC qCatchFailures}
IF aliasH^^.aliasSize <> GetHandleSize(Handle(aliasH))
THEN DebugStr('dp_OpenNewEdition: incorrect alias size');
{$ENDC}
{ add the sectionID of the publisher to the end of the alias }
SetHandleSize(Handle(aliasH), aliasH^^.aliasSize + SizeOf(LONGINT));
IF MemError = noErr THEN
BEGIN
tempPtr := pLongint(pLongint(aliasH)^ + aliasH^^.aliasSize);
tempPtr^ := sectionH^^.sectionID;
END; {if}
END; {if}
{ save off alias until close }
newPubCB^^.publisherAlias := aliasH;
ioRefNum := ORD4(newPubCB);
ioProc := FormatIOProcPtr(kStandardFormatIOProcPtr);
Success(fi);
END; { dpStandardPublisherOpen }
{------------- dpStandardPublisherClose -------------}
FUNCTION dpStandardPublisherClose(sectionH: SectionHandle;
success: BOOLEAN;
ioRefNum: LONGINT;
ioProc: FormatIOProcPtr): OSErr;
VAR
newPubCB: PubCBHandle;
orgPubCB: PubCBHandle;
thisApp: AppRefNum;
aliasH: AliasHandle;
IOParams: FormatIOParamBlock;
areSubscribers: BOOLEAN;
originalCrDate: TimeStamp;
newMdDate: TimeStamp;
foundIndex: INTEGER;
foundPtr: FormatPtr;
newType: OSType;
knownFormatsCount: INTEGER;
savedPublisherCount:INTEGER;
swapSucceeded: BOOLEAN;
fi: FailInfo;
FUNCTION FindStandardFormat(anEntryIndex: INTEGER; anEntryPtr: FormatPtr): BOOLEAN;
BEGIN
{ handle each case }
WITH anEntryPtr^ DO
BEGIN
IF LONGINT(theType) = LONGINT('PICT') THEN
BEGIN
IF knownFormatsCount = 0
THEN newType := kPICTEditionFileType;
knownFormatsCount := knownFormatsCount + 1;
END ELSE
IF LONGINT(theType) = LONGINT('TEXT') THEN
BEGIN
IF knownFormatsCount = 0
THEN newType := kTEXTEditionFileType;
knownFormatsCount := knownFormatsCount + 1;
END ELSE
IF LONGINT(theType) = LONGINT('snd ') THEN
BEGIN
IF knownFormatsCount = 0
THEN newType := ksndEditionFileType;
knownFormatsCount := knownFormatsCount + 1;
END;
END; {with}
FindStandardFormat := FALSE;
END; { FindStandardFormat }
PROCEDURE KillIOtoOldPubCB(aSection: SectionHandle; inApp: AppRefNum);
BEGIN
WITH aSection^^ DO
BEGIN
IF (SIOCBHandle(refNum) <> NIL) {&} THEN IF (PubCBHandle(controlBlock) = orgPubCB) THEN
BEGIN
SIOCBHandle(refNum)^^.ioProc := FormatIOProcPtr(kBogusFormatIOProcPtr);
IF dpPubCBbumpUsageCount(orgPubCB^^.usageInfo, inApp, NOT kCanGrowUsage, 0, -1) <> noErr THEN
BEGIN
{$IFC qRangeCheck }
DebugStr('dp_CloseEditionThatWasOpenForWriting.KillIOtoOldPubCB: could not close a subscriber.');
{$ENDC}
END;
END; {if}
END; {with}
END; { KillIOtoOldPubCB }
PROCEDURE dpSetMdCrDate(VAR PBC: CInfoPBRec);
VAR
flags: LONGINT;
BEGIN
PBC.ioFlMdDat := newMdDate;
PBC.ioFlCrDat := originalCrDate;
flags := PBC.ioFlFndrInfo.fdFlags;
IF knownFormatsCount > 1
THEN bSet(flags, kHasMultipleFormatsBit)
ELSE bClr(flags, kHasMultipleFormatsBit);
PBC.ioFlFndrInfo.fdFlags := flags;
END; { dpSetMdCrDate }
BEGIN
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
newPubCB := PubCBHandle(ioRefNum);
orgPubCB := newPubCB^^.oldPubCB;
{ dec this apps IO count for this PubCB }
FailOSErr(dpPubCBbumpUsageCount(newPubCB^^.usageInfo, thisApp, NOT kCanGrowUsage, 0, -1));
{$IFC qRangeCheck }
IF newPubCB^^.usageInfo^^.totalIOCount <> 0
THEN DebugStr('dp_CloseEditionThatWasOpenForWriting: opened by someone other than Publisher ?!?!');
{$ENDC }
IF success THEN
BEGIN
{ retrieve the "backward" link from the EditionFile to the PublisherDocument }
aliasH := newPubCB^^.publisherAlias;
{ no alias means the app does not have a disk version of the document yet }
IF aliasH <> NIL THEN
BEGIN
{ create an 'alis' format }
IOParams.ioRefNum := ioRefNum;
IOParams.format := kPublisherDocAliasFormat;
FailOSErr(dp_CallFormatIOProc(ioNewFormat, IOParams, ioProc));
{ write the 'alis' format }
HLock(Handle(aliasH));
{IOParams.refNum already set }
{IOParams.format already set }
{IOParams.formatIndex already set }
IOParams.offset := 0;
IOParams.buffPtr := Ptr(aliasH^);
IOParams.buffLen := GetHandleSize(Handle(aliasH));
FailOSErr(dp_CallFormatIOProc(ioWriteFormat, IOParams, ioProc));
{ dispose of backward link }
newPubCB^^.publisherAlias := NIL;
DisposHandle(Handle(aliasH));
END; {if}
{ flush info to disk }
FailOSErr(dpPubFlushFile(newPubCB));
{ figure out fdType for new edition file }
newType := kUnknownEditionFileType;
knownFormatsCount := 0;
IF dpFindElement(newPubCB^^.formats, 0, FindStandardFormat, foundIndex, foundPtr) THEN;
{ set new finder type based of first known format and set flag if multiple known }
newPubCB^^.info.fdType := newType;
{ try to dump new map and format list }
areSubscribers := (orgPubCB^^.usageInfo^^.totalUseCount > 1);
IF NOT areSubscribers THEN
BEGIN
{ no current subscribers, so can free up new map and formats }
FailOSErr(dpPubReleaseMap(newPubCB));
END;
{ if orgPubCB file has map in memory, free it (maybe blowing up subscribers) }
IF orgPubCB^^.allocMap <> NIL THEN
BEGIN
IF areSubscribers THEN dpForEachSectionDo(KillIOtoOldPubCB);
FailOSErr(dpPubReleaseMap(orgPubCB));
END; {if}
{ save create and modification date }
IF sectionH <> NIL
THEN newMdDate := sectionH^^.mdDate
ELSE newMdDate := newPubCB^^.info.mdDate;
originalCrDate := orgPubCB^^.info.crDate;
{ swap data from new into old pub file, delete new pub file }
{ if swap fails, we keep orgPubCB as is, and delete newPubCB }
swapSucceeded := (dpSwapAndDelete(orgPubCB, newPubCB) = noErr);
{ blockmove contents of newPubCB over orgPubCB, to keep subscribers }
{ but, keep old info.container and publisherCount }
WITH orgPubCB^^ DO
BEGIN
IF swapSucceeded THEN
BEGIN
savedPublisherCount := {orgPubCB^^.}publisherCount;
BlockMove( @newPubCB^^.pubCNodeID, {from}
@{orgPubCB^^.}pubCNodeID, {to}
ORD(@{orgPubCB^^.}info.container) - ORD(@{orgPubCB^^.}pubCNodeID) {len});
{orgPubCB^^.}publisherCount := savedPublisherCount;
{orgPubCB^^.}oldPubCB := NIL;
{ fix up mod dates to make polling more efficient }
lastVolMod := {orgPubCB^^.}info.mdDate;
lastDirMod := {orgPubCB^^.}info.mdDate;
{ set new edition's mdDate and crDate }
{orgPubCB^^.}info.crDate := originalCrDate;
{orgPubCB^^.}info.mdDate := newMdDate;
FailOSErr(dpTweakCatInfo({orgPubCB^^.}info.container.theFile, dpSetMdCrDate));
END;
{ make this section the publisher for orgPubCB }
{orgPubCB^^.}publisher := sectionH;
WITH sectionH^^ DO
BEGIN
PubCBHandle({sectionH^^.}controlBlock) := orgPubCB;
publisherKind := {sectionH^^.}kind;
END; {with}
END; {with}
{ remove newPubCB }
WITH newPubCB^^ DO
BEGIN
{ the refnum in newPubCB has been copied into orgPubCB, but DisposePubCB }
{ does a sanity check that the fileRefNum says the file is closed, so fake it }
{newPubCB^^.}fileRefNum := kClosedFileRefNum;
IgnoreOSErr(dpPubCBbumpUsageCount({newPubCB^^.}usageInfo, thisApp, NOT kCanGrowUsage, -1, 0));
END; {with}
IgnoreOSErr(dpDisposePubCB(newPubCB));
END ELSE
BEGIN
{ unsuccessful write, want to roll back to state before OpenNewEdition }
{ note that since we are closing, we ignore all errors and plow on ahead }
{ remove lock }
IgnoreOSErr(dpPubRemoveLock(newPubCB));
{ point section back to old PubCB }
PubCBHandle(sectionH^^.controlBlock) := orgPubCB;
{ dump map built so far and close temp file }
IgnoreOSErr(dpPubReleaseMap(newPubCB));
IgnoreOSErr(dpPubCloseFile(newPubCB, NOT kFlush));
{ delete temp file, since it won't be used }
IgnoreOSErr(FSpDelete(newPubCB^^.info.container.theFile));
{ remove newPubCB }
IgnoreOSErr(dpDisposePubCB(newPubCB));
END; {if success}
dpStandardPublisherClose := noErr;
END; { dpStandardPublisherClose }
{------------- dpStandardCanSubscribe -------------}
FUNCTION dpStandardCanSubscribe(VAR info: EditionInfoRecord; formatsMask: SignedByte; hasMultipleFormats: BOOLEAN): OSErr;
VAR
hasFormat: SignedByte;
formatsH: Handle;
ignoreType: FormatType;
getErr: OSErr;
ignoreRem: INTEGER;
i: INTEGER;
p: FormatPtr;
BEGIN
{ assume failure }
dpStandardCanSubscribe := noTypeErr;
{ can only subscribe to whole files of type: edtp, edtt, edts, or edtu }
IF info.container.thePart <> kPartsNotUsed
THEN EXIT(dpStandardCanSubscribe);
{ ### grandfather in all 'publ' s}
IF LONGINT(info.fdType) = LONGINT('publ') THEN
BEGIN
dpStandardCanSubscribe := noErr;
EXIT(dpStandardCanSubscribe);
END;
{ map finder type to appropriate mask bit or fail }
IF LONGINT(info.fdType) = LONGINT(kPICTEditionFileType) THEN hasFormat := kPICTformatMask
ELSE IF LONGINT(info.fdType) = LONGINT(kTEXTEditionFileType) THEN hasFormat := kTEXTformatMask
ELSE IF LONGINT(info.fdType) = LONGINT(ksndEditionFileType) THEN hasFormat := ksndformatMask
ELSE IF LONGINT(info.fdType) = LONGINT(kUnknownEditionFileType) THEN hasFormat := 0
ELSE IF LONGINT(info.fdType) = LONGINT('edtP') THEN hasFormat := kPICTformatMask { ### grandfather in edtP }
ELSE IF LONGINT(info.fdType) = LONGINT('edtT') THEN hasFormat := kTEXTformatMask { ### grandfather in edtT }
ELSE EXIT(dpStandardCanSubscribe);
{ return if bit corrosponding to finder type is in mask }
IF bAND(formatsMask, hasFormat) <> 0 THEN
BEGIN
{ the mask says it can handle this edition file }
dpStandardCanSubscribe := noErr;
EXIT(dpStandardCanSubscribe);
END;
{ bit not in mask, so need to check secondary formats }
{ hasMultipleFormats flag is state of kHasMultipleFormatsBit flag }
IF hasMultipleFormats {&} THEN IF bAND(formatsMask,kPICTformatMask+kTEXTformatMask+ksndformatMask) <> 0 THEN
BEGIN
formatsH := NewHandle(0);
getErr := GetStandardFormats(info.container, ignoreType, NIL, NIL, formatsH);
IF getErr = noErr THEN
BEGIN
UnSignedDivide(GetHandleSize(formatsH), SizeOf(Format), i, ignoreRem);
{$IFC qRangeCheck }
IF ignoreRem <> 0 THEN DebugStr('dpStandardCanSubscribe: bad fmts handle length.');
{$ENDC }
p := FormatPtr(formatsH^);
REPEAT
WITH p^ DO
BEGIN
IF LONGINT(theType) = LONGINT('PICT') {&} THEN IF bAND(formatsMask, kPICTformatMask) <> 0
THEN dpStandardCanSubscribe := noErr;
IF LONGINT(theType) = LONGINT('TEXT') {&} THEN IF bAND(formatsMask, kTEXTformatMask) <> 0
THEN dpStandardCanSubscribe := noErr;
IF LONGINT(theType) = LONGINT('snd ') {&} THEN IF bAND(formatsMask, ksndformatMask) <> 0
THEN dpStandardCanSubscribe := noErr;
END; {with}
p := FormatPtr( ORD(p) + SizeOf(Format) );
i := i - 1;
UNTIL i <= 0;
DisposHandle(formatsH);
END
ELSE dpStandardCanSubscribe := getErr;
END; {if}
END; { dpStandardCanSubscribe }
{------------- dpStandardOpener -------------}
FUNCTION dpStandardOpener(selector: EditionOpenerVerb; VAR PB: EditionOpenerParamBlock): OSErr;
BEGIN
WITH PB DO
BEGIN
CASE selector OF
eoOpen:
dpStandardOpener := dpStandardSubscriberOpen(info, sectionH, ioRefNum, ioProc);
eoClose:
dpStandardOpener := dpStandardSubscriberClose(sectionH, ioRefNum, ioProc);
eoOpenNew:
dpStandardOpener := dpStandardPublisherOpen(sectionH, fdCreator, document, ioRefNum, ioProc);
eoCloseNew:
dpStandardOpener := dpStandardPublisherClose(sectionH, success, ioRefNum, ioProc);
eoCanSubscribe:
dpStandardOpener := dpStandardCanSubscribe(info, formatsMask, success);
OTHERWISE
dpStandardOpener := paramErr;
END; {case}
END; {with}
END; { dpStandardOpener }
{================================ public routines ===================================}
{------------- dp_CreateEditionContainerFile -------------}
FUNCTION dp_CreateEditionContainerFile(editionFile: FSSpec;
fdCreator: OSType;
editionFileNameScript: INTEGER): OSErr;
BEGIN
dp_CreateEditionContainerFile := FSpCreate(editionFile, fdCreator,
kUnknownEditionFileType, editionFileNameScript);
END; { dp_CreateEditionContainerFile }
{------------- dp_DeleteEditionContainerFile -------------}
FUNCTION dp_DeleteEditionContainerFile(editionFile: FSSpec): OSErr;
VAR
thePubCB: PubCBHandle;
thisApp: AppRefNum;
deleteIt: Boolean;
fi: FailInfo;
PROCEDURE KillIOtoThePubCB(aSection: SectionHandle; inApp: AppRefNum);
BEGIN
WITH aSection^^ DO
BEGIN
IF (SIOCBHandle(refNum) <> NIL) {&} THEN IF (PubCBHandle(controlBlock) = thePubCB) THEN
BEGIN
SIOCBHandle(refNum)^^.ioProc := FormatIOProcPtr(kBogusFormatIOProcPtr);
IF dpPubCBbumpUsageCount(thePubCB^^.usageInfo, inApp, NOT kCanGrowUsage, 0, -1) <> noErr THEN
BEGIN
{$IFC qRangeCheck }
DebugStr('dp_DeleteEditionContainerFile.KillIOtoThePubCB: could not close a subscriber.');
{$ENDC}
END;
END; {if}
END; {with}
END; { KillIOtoThePubCB }
BEGIN
{ assume we will delete the edition file }
deleteIt := TRUE;
{ top level functions must set up a failure handler }
IF IsFailure(fi, dp_DeleteEditionContainerFile)
THEN EXIT(dp_DeleteEditionContainerFile);
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
{ see if it is in use by any sections }
IF dpRequestPubCB(editionFile, thisApp, kCheckExisting,
NOT kCanAllocateNew, thePubCB) = noErr THEN
BEGIN
{ are there are publishers? }
IF thePubCB^^.publisherCount = 0 THEN
BEGIN
{ is some subscribers are doing I/O then stop them }
IF thePubCB^^.usageInfo^^.totalIOCount > 0 THEN
BEGIN
dpForEachSectionDo(KillIOtoThePubCB);
{ if original pubCB file is open, close it }
IF thePubCB^^.openMode <> dmNotOpen THEN
BEGIN
{$IFC qRangeCheck }
IF thePubCB^^.usageInfo^^.totalIOCount <> 0
THEN DebugStr('dp_DeleteEditionContainerFile: could not kill off all subscribers.');
{$ENDC }
IgnoreOSErr(dpPubCloseFile(thePubCB, NOT kFlush));
END; {if}
END; {if}
{ mark file missing in control block }
thePubCB^^.fileMissing := TRUE;
END
ELSE deleteIt := FALSE;
{ now release the control block we requested }
IgnoreOSErr(dpReleasePubCB(thePubCB, thisApp));
END; {if}
IF deleteIt THEN
BEGIN
{ delete pub file }
FailOSErr(FSpDelete(editionFile));
END;
Success(fi);
END; { dp_DeleteEditionContainerFile }
{------------- dp_OpenEdition -------------}
FUNCTION dp_OpenEdition(sectionH: SectionHandle; VAR sectCB: SIOCBHandle): OSErr;
VAR
thePubCB: PubCBHandle;
thisApp: AppRefNum;
openerParams: EditionOpenerParamBlock;
fi: FailInfo;
BEGIN
sectCB := NIL;
{ set up failure handling }
IF isFailure(fi, dp_OpenEdition) THEN
BEGIN
IF sectCB <> NIL
THEN DisposHandle(Handle(sectCB));
sectCB := NIL;
SIOCBHandle(sectionH^^.refNum) := NIL;
EXIT(dp_OpenEdition);
END; {if}
{$IFC qDebug }
FailOSErr(dpCheckSection(sectionH));
{$ENDC }
{ make sure section can read }
IF NOT bTST(sectionH^^.kind, kCanReadEditionsBit)
THEN FailOSErr(permErr);
{ make sure this section is not already reading }
IF SIOCBHandle(sectionH^^.refNum) = NIL THEN
BEGIN
{ be sure we have a control block }
thePubCB := PubCBHandle(sectionH^^.controlBlock);
IF thePubCB = NIL THEN FailOSErr(fnfErr);
{ make sure control block is up to date }
FailOSErr(dpPubSync(thePubCB));
{ create a Section I/O record for this Section }
FailOSErr(dpCreateSIOCBRecord(sectionH, 0, NIL, sectCB));
SIOCBHandle(sectionH^^.refNum) := sectCB;
{ call EditionOpener }
openerParams.info := thePubCB^^.info;
openerParams.sectionH := sectionH;
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
FailOSErr(dp_CallEditionOpenerProc(eoOpen, openerParams, thisApp^^.emulator));
{ set up fields in I/O control block, now that we have them }
WITH sectCB^^ DO
BEGIN
{sectCB^^.}ioRefNum := openerParams.ioRefNum;
{sectCB^^.}ioProc := openerParams.ioProc;
END; {with}
END ELSE
BEGIN
sectCB := SIOCBHandle(sectionH^^.refNum);
dp_OpenEdition := containerAlreadyOpenWrn;
END;
Success(fi);
END; { dp_OpenEdition }
{------------- dp_OpenNewEdition -------------}
FUNCTION dp_OpenNewEdition(sectionH: SectionHandle; fdCreator: OSType;
sectionDocument: FSSpecPtr;
VAR sectCB: SIOCBHandle): OSErr;
VAR
thisApp: AppRefNum;
thePubCB: PubCBHandle;
openerParams: EditionOpenerParamBlock;
localSectionDocument: FSSpec;
openErr: OSErr;
fi: FailInfo;
BEGIN
sectCB := NIL;
{ set up failure handling }
IF isFailure(fi, dp_OpenNewEdition) THEN
BEGIN
IF sectCB <> NIL
THEN DisposHandle(Handle(sectCB));
sectCB := NIL;
SIOCBHandle(sectionH^^.refNum) := NIL;
EXIT(dp_OpenNewEdition);
END; {if}
{ make local copy if not NIL }
IF sectionDocument <> NIL THEN
BEGIN
BlockMove(Ptr(sectionDocument), @localSectionDocument, SizeOf(FSSpec)); { localSectionDocument := sectionDocument^;}
sectionDocument := @localSectionDocument;
END;
{$IFC qRangeCheck }
FailOSErr(dpCheckSection(SectionH));
{$ENDC }
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
thePubCB := PubCBHandle(sectionH^^.controlBlock);
{ make sure section can write }
IF NOT bTST(sectionH^^.kind, kCanWriteEditionsBit)
THEN FailOSErr(wrPermErr);
{ make sure it is not already open }
IF SIOCBHandle(sectionH^^.refNum) <> NIL THEN
BEGIN
sectCB := SIOCBHandle(sectionH^^.refNum);
FailOSErr(containerAlreadyOpenWrn);
END;
{ make sure control block is not NIL } { <38, #c1-MTM-007> }
IF thePubCB = NIL { <38, #c1-MTM-007> }
THEN FailOSErr(nsvErr); { <38, #c1-MTM-007> }
{ make sure control block is up to date }
FailOSErr(dpPubSync(thePubCB));
{ make sure that the edition file is open }
WITH thePubCB^^ DO
BEGIN
IF IsEditionFile(info.fdType)
{&} THEN IF (bAND(openMode, dmRequestWritePerm) = 0) THEN
BEGIN
{ ahh!! pub file is not open, probably means when the publisher was }
{ instantiated, there was another publisher on another machine with file open. }
{ So see if that publisher is gone and 'sectionH' can become the publisher. }
{ Open the file, to lock it as the publisher }
openErr := dpPubOpenFile(thePubCB, sectionH^^.kind);
CASE openErr OF
permErr, afpDenyConflict, afpAccessDenied:
FailOSErr(openErr);
noErr:
;
OTHERWISE
FailOSErr(openErr);
END; {case}
END; {if}
END; {with}
{ create a Section I/O record for this Section }
FailOSErr(dpCreateSIOCBRecord(sectionH, 0, NIL, sectCB));
SIOCBHandle(sectionH^^.refNum) := sectCB;
{ call Edition Opener }
openerParams.info := thePubCB^^.info;
openerParams.sectionH := sectionH;
openerParams.fdCreator := fdCreator;
openerParams.document := sectionDocument;
FailOSErr(dp_CallEditionOpenerProc(eoOpenNew, openerParams, thisApp^^.emulator));
{ set up fields in I/O control block, now that we have them }
WITH sectCB^^ DO
BEGIN
ioRefNum := openerParams.ioRefNum;
ioProc := openerParams.ioProc;
END; {with}
Success(fi);
END; { dp_OpenNewEdition }
{------------- dp_CloseEdition -------------}
FUNCTION dp_CloseEdition(sectCB: SIOCBHandle; appWasSuccessful: BOOLEAN): OSErr;
VAR
thisApp: AppRefNum;
sectionH: SectionHandle;
thePubCB: PubCBHandle;
params: EditionOpenerParamBlock;
originalEditionMdDate: TimeStamp;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@sectionH);
sectionH := NIL;
{ set up failure handling }
IF isFailure(fi, dp_CloseEdition) THEN
BEGIN
IF (sectionH <> NIL) {&} THEN IF (SIOCBHandle(sectionH^^.refNum) <> NIL) THEN
BEGIN
DisposHandle(Handle(sectCB));
sectionH^^.refNum := NIL;
END;
EXIT(dp_CloseEdition);
END; {if}
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
IF sectCB = NIL THEN FailOSErr(rfNumErr);
{ get preliminary stuff }
sectionH := SectionHandle(sectCB^^.section);
thePubCB := PubCBHandle(sectionH^^.controlBlock);
{ make sure it is already opened by this section }
IF SIOCBHandle(sectionH^^.refNum) <> sectCB THEN FailOSErr(rfNumErr);
{ set up call to Edition Opener }
params.info := thePubCB^^.info;
params.sectionH := sectionH;
params.success := appWasSuccessful;
params.ioRefNum := sectCB^^.ioRefNum;
params.ioProc := sectCB^^.ioProc;
IF thePubCB^^.oldPubCB = NIL THEN
BEGIN
{ closing an OpenEdition }
FailOSErr(dp_CallEditionOpenerProc(eoClose, params, thisApp^^.emulator));
{ if successful, then mark that the subscirber has same stuff as edition }
IF appWasSuccessful
THEN sectionH^^.mdDate := thePubCB^^.info.mdDate;
END ELSE
BEGIN
{ save off the mdDate of the last successful edition }
originalEditionMdDate := thePubCB^^.oldPubCB^^.info.mdDate;
{ closing an OpenNewEdition }
FailOSErr(dp_CallEditionOpenerProc(eoCloseNew, params, thisApp^^.emulator));
{ control block has changed }
thePubCB := PubCBHandle(sectionH^^.controlBlock);
IF appWasSuccessful THEN
BEGIN
{ sync the controlBlock to have the new mod. date }
thePubCB^^.info.mdDate := sectionH^^.mdDate;
{ only send read event to subscribers if data has changed }
IF originalEditionMdDate <> sectionH^^.mdDate THEN
BEGIN
{ send read event to all automatic subscribers }
FailOSErr(dpNotifySubscribers(thePubCB));
END;
{ now mark this section as 'publisher' for priority in goto publisher }
FailOSErr(dpMakeItThePublisher(thePubCB, sectionH, thisApp));
END; {if}
END; {else}
{ dispose of the Section I/O record for this Section }
SIOCBHandle(sectionH^^.refNum) := NIL;
DisposHandle(Handle(sectCB));
FailOSErr(MemError);
Success(fi);
END; { dp_CloseEdition }
{------------- dp_GetStandardFormats -------------}
FUNCTION dp_GetStandardFormats(container: EditionContainerSpec; VAR previewFormat: FormatType;
preview, publisherAlias, formats: Handle): OSErr;
CONST
kNumberOfPreviewFormats = 4;
kNumberOfPriorityArrangements = 3;
TYPE
FormatArray = ARRAY [1..kNumberOfPreviewFormats] OF FormatType;
FormatPriorityList = ARRAY [1..kNumberOfPriorityArrangements] OF FormatArray;
FormatPriorityListAlt = PACKED ARRAY [1..SizeOf(FormatPriorityList)] OF CHAR;
VAR
thisApp: AppRefNum;
thePubCB: PubCBHandle;
openerParams: EditionOpenerParamBlock;
previewErr: OSErr;
aliasErr: OSErr;
formatsErr: OSErr;
i: INTEGER;
aFormat: FormatType;
formatsPriority:FormatPriorityList;
thePriorityList:INTEGER;
theError: OSErr;
opened: BOOLEAN;
fi: FailInfo;
BEGIN
DoNotPutInRegister(@thePubCB);
DoNotPutInRegister(@opened);
thePubCB := NIL;
opened := FALSE;
dp_GetStandardFormats := noErr;
{ top level functions must set up a failure handler }
IF IsFailure(fi, theError) THEN
BEGIN
IF opened THEN
BEGIN
openerParams.success := FALSE;
IgnoreOSErr(dp_CallEditionOpenerProc(eoClose, openerParams, thisApp^^.emulator));
END;
IF thePubCB <> NIL
THEN IgnoreOSErr(dpReleasePubCB(thePubCB, thisApp));
IF theError = eofErr
THEN dp_GetStandardFormats := noTypeErr { map end-of-file errors into noType errors }
ELSE dp_GetStandardFormats := theError;
EXIT(dp_GetStandardFormats);
END;
{ make sure we have a PubCB for it }
FailOSErr(dp_GetCurrentAppRefNum(thisApp));
FailOSErr(dpRequestPubCB(container.theFile, thisApp, kCheckExisting, kCanAllocateNew, thePubCB));
{ call EditionOpener to open file }
openerParams.info := thePubCB^^.info;
openerParams.sectionH := NIL;
FailOSErr(dp_CallEditionOpenerProc(eoOpen, openerParams, thisApp^^.emulator));
opened := TRUE;
{ look for a preview }
previewErr := noErr;
IF preview <> NIL THEN
BEGIN
previewErr := noTypeErr;
{ set up priority of search }
FormatPriorityListAlt(formatsPriority) := 'prvwPICTTEXTsnd prvwTEXTPICTsnd prvwsnd TEXTPICT';
thePriorityList := 1; {default priority }
IF LONGINT(openerParams.info.fdType) = LONGINT(kTEXTEditionFileType)
THEN thePriorityList := 2
ELSE IF LONGINT(openerParams.info.fdType) = LONGINT(ksndEditionFileType)
THEN thePriorityList := 3;
{ search for format }
FOR i := 1 TO kNumberOfPreviewFormats DO
BEGIN
aFormat := formatsPriority[thePriorityList,i];
IF dpTryToGetFormat(aFormat, preview, openerParams.ioRefNum, openerParams.ioProc) = noErr THEN
BEGIN
previewFormat := aFormat;
previewErr := noErr;
LEAVE;
END; {if}
END; {for}
END; {else}
{ look for 'alis' }
aliasErr := noErr;
IF publisherAlias <> NIL THEN
BEGIN
aliasErr := dpTryToGetFormat(kPublisherDocAliasFormat, publisherAlias, openerParams.ioRefNum, openerParams.ioProc);
END;
{ look for 'fmts' }
formatsErr := noErr;
IF formats <> NIL THEN
BEGIN
formatsErr := dpTryToGetFormat(kFormatListFormat, formats, openerParams.ioRefNum, openerParams.ioProc);
END;
{ call EditionOpener to close file }
{openerParams.ioRefNum already set }
{openerParams.ioProc already set }
{openerParams.sectionH already set }
openerParams.success := FALSE;
FailOSErr(dp_CallEditionOpenerProc(eoClose, openerParams, thisApp^^.emulator));
opened := FALSE;
{ release the PubCB }
FailOSErr(dpReleasePubCB(thePubCB, thisApp));
thePubCB := NIL;
{ return an error code if any of the formats were not found }
FailOSErr(aliasErr);
FailOSErr(formatsErr);
FailOSErr(previewErr);
Success(fi);
END; { dp_GetStandardFormats }