4.5 KiB
message_api — architecture
Layers
┌──────────────────────────────────────────────────┐
│ Caller (BBS, tosser, editor, importer, …) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ ma.api (TMessageBase, factory, TUniMessage) │
├──────────────────────────────────────────────────┤
│ ma.events ma.lock ma.paths │
│ ma.batch (concurrent tosser helper) │
├──────────────────────────────────────────────────┤
│ Format backends — one .pas per format │
│ ma.fmt.hudson ma.fmt.jam ma.fmt.squish │
│ ma.fmt.msg ma.fmt.pkt ma.fmt.pcboard │
│ ma.fmt.ezycom ma.fmt.goldbase ma.fmt.wildcat │
├──────────────────────────────────────────────────┤
│ RTL: TFileStream, BaseUnix/Windows for locking │
└──────────────────────────────────────────────────┘
Polymorphism
Every backend descends from TMessageBase and implements the abstract
DoOpen, DoClose, DoMessageCount, DoReadMessage, DoWriteMessage
contract. Callers can either:
- Use the unified API —
MessageBaseOpen(format, path, mode)returns aTMessageBase. Read/write throughTUniMessage. Format-agnostic. - Drop down to format-specific class methods (e.g.
TJamBase.IncModCounter,TSquishBase.SqHashName) when they need behaviour the unified API cannot express. Each backend keeps its rich API public.
TUniMessage
A single message record covering every backend. Format-specific bit fields
(Hudson byte attr, JAM 32-bit attr, Squish attr, MSG word attr, PCB status,
EzyCom dual byte) are mapped into a canonical Attr: cardinal bit set
defined in ma.types (MSG_ATTR_PRIVATE, MSG_ATTR_LOCAL,
MSG_ATTR_RECEIVED, …). Conversion helpers expose the original encoding
when needed.
Dates land in TDateTime regardless of how the backend stored them
(Hudson MM-DD-YY strings with 1950 pivot, Squish FTS-0001 strings, JAM
Unix timestamps, PCBoard / EzyCom DOS PackTime).
The body is an AnsiString with kludge lines intact and CR-separated
(matches what Allfix already produces).
Locking
Three layers, applied in order on every Open:
- In-process —
TRTLCriticalSectionperTMessageBaseinstance. - Cross-process — advisory lock on a sentinel file
(
<base>.lckor, for Squish,<base>.SQLso we coexist with other Squish-aware tools).fpflock(LOCK_EX|LOCK_SH)on Unix,LockFileExon Windows. Retry with backoff up to a configurable timeout (default 30s). Lock acquire/release fires events. - OS share modes —
fmShareDenyWritefor writers,fmShareDenyNonefor readers. Same as the existing Allfix code.
Events
TMessageEvents lets callers subscribe one or more handlers to receive
metBaseOpened, metMessageRead, metMessageWritten, metLockAcquired,
metPackProgress, etc. Internally the dispatcher serialises calls so
handlers do not need to be reentrant.
Concurrent tossers
TPacketBatch owns a queue of .pkt paths and a worker thread pool.
Each worker opens its packet, reads messages, hands each to the
caller-provided processor. The batch caches one TMessageBase per
destination area so writes serialise through layer-1 locking; layer-2
keeps separate processes (e.g. an editor) safe at the same time.
Behavioural fidelity
Every format backend is a port of the proven Allfix implementation. Tests
read/write the same sample bases that Allfix tests use; outputs are
diffed against captures produced by the existing msgutil / domsg
code paths. Anything that works in Allfix today must keep working through
this library.