{ 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 Fix use count problem in dp_DeleteEditionContainerFile <33> 12/14/90 ngk Fix setting of type & creator til after copying of other Finder info. <32> 12/14/90 ngk 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 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 }