-- file DBLogImpl.mesa
-- created by Donahue, November 12, 1982 4:22 pm
-- last edited by Donahue, January 3, 1983 4:50 pm


DIRECTORY
DBLog,
DBLoad,
DBView,
FileIO,
IO,
Nut,
NutDump,
Rope;

DBLogImpl: MONITOR LOCKS data USING data: DBLog.Log
IMPORTS DBLoad, FileIO, IO, Nut, NutDump, Rope, DBView
EXPORTS DBLog = {

OPEN DBLog, Rope, IO, Nut, DBView;

ControlZ: CHARACTER = 032C;

LogList: LIST OF Log ← NIL;

LogRel: DBView.Relation = DeclareRelation[ "DBLog" ];

LogNameAttr: Attribute =
  DBView.DeclareAttribute[ r: LogRel, name: "name", type: StringType,
          uniqueness: Uniqueness[Key] ];
  
LogLengthAttr: Attribute =
  DBView.DeclareAttribute[ r: LogRel, name: "length", type: IntType ];

AcquireLock: ENTRY PROC [data: Log] = {
-- acquire the lock on the data
-- return the live flag
-- if FALSE is returned, the lock is not held
ENABLE UNWIND => NULL;
DO
IF data = NIL THEN RETURN;
IF NOT data.locked THEN {data.locked ← TRUE; RETURN};
WAIT data.condition;
ENDLOOP };

ReleaseLock: ENTRY PROC [data: Log] = {
-- release the data lock
-- then notify everyone else that the world is OK
data.locked ← FALSE;
BROADCAST data.condition };

InitializeLog: PUBLIC PROC[fileName: ROPE] RETURNS[ log: Log ] = {
log ← NEW[ LogObj ← [name: fileName] ];
InternalOpenLog[ log ];
LogList ← CONS[ log, LogList ] };

InternalOpenLog: PROC[ log: Log ] = {
 log.stream ← FileIO.Open[ fileName: log.name, accessOptions: write,
         closeOptions: FileIO.noCloseOptions ];
 log.stream.SetIndex[ log.stream.GetLength[] ] };

CommitLog: PUBLIC PROC[log: Log, reOpen: BOOLTRUE] = {
AcquireLock[log];
 SetLogLengthInDB[log];
IF NOT reOpen THEN { log.stream.Close[]; log.stream ← NIL };
 ReleaseLock[log] };

GetLogLength: PUBLIC PROC[ log: Log ] RETURNS[length: INT] = {
ENABLE UNWIND => NULL;
AcquireLock[ log ];
length ← log.stream.GetLength[];
log.stream.Flush[];
ReleaseLock[ log ] };

LogEntity: PUBLIC PROC[ log: Log, entryType: LogEntryType, entity: DBView.Entity ] = {
AcquireLock[log];
IO.PutRope[ log.stream, IF entryType = insertion THEN "+" ELSE "-" ];
NutDump.WriteEntity[ log.stream, entity ];
ReleaseLock[log] };

LogRelship: PUBLIC PROC[ log: Log, entryType: LogEntryType, relship: DBView.Relship ] = {
AcquireLock[log];
IO.PutRope[ log.stream, IF entryType = insertion THEN "+" ELSE "-" ];
NutDump.WriteRelship[ log.stream, relship ];
ReleaseLock[log] };

LogText: PUBLIC PROC[ log: Log, text: ROPE ] = {
AcquireLock[log];
[] ← InternalPutText[ log, text ];
ReleaseLock[log] };

InternalPutText: PROC[ log: Log, text: ROPE ] RETURNS[ len: INT ] = {
len ← log.stream.GetLength[];
IO.PutFL[ stream: log.stream,
  list: LIST[IO.rope["?"], IO.int[text.Length[]], IO.rope["\\"], IO.rope[text], IO.char[CR]]];
len ← log.stream.GetLength[] - len };

NoBreak: BreakProc = TRUSTED{ RETURN[ KeepGoing ] };

ReadString: PROC[ log: Log ] RETURNS[ string: ROPE ] = {
-- Terminates on reading a "\"; may return a null string if find immediately.
NameBreak: BreakProc = TRUSTED {
IF c='\\ THEN RETURN[StopAndTossChar]
ELSE RETURN[KeepGoing] };
string ← log.stream.GetRope[NameBreak, NoBreak];
IF string= NIL THEN string ← "" };

ReadName: PROC[ log: Log ] RETURNS[string: ROPE] = {
-- Terminates on reading a ":" or a CR; returns NIL in latter case.
NameBreak: BreakProc = TRUSTED {
lastBreak← c;
IF c = ControlZ THEN {[] ← SkipThruCR[log.stream]; RETURN[StopAndTossChar]}
ELSE IF c = '\\ OR c = CR OR c = ': THEN RETURN[StopAndTossChar]
ELSE RETURN[KeepGoing] };
lastBreak: CHAR;
string ← log.stream.GetRope[NameBreak, NoBreak];
IF string = NIL THEN string ← "" };

SkipThruCR: PROC[ stream: IO.STREAM ] RETURNS[ r: ROPE ] = {
ENABLE IO.EndOfStream => GOTO Quit;
c: CHARACTER;
WHILE (c ← stream.GetChar[]) # CR DO r ← Rope.Cat[r, Rope.FromChar[c]] ENDLOOP;
EXITS
Quit => RETURN[ IF r = NIL THEN "" ELSE r ] };

DumpLog: PUBLIC PROC[ oldLog: Log, newLog: Log,
         filter: PROC[entry: ROPE, pos: INT] RETURNS[copy: BOOLEAN] ] = {
-- just cycle through the log, applying the filter to all text log entries
 prefix: CHAR;
 entry: ROPE;
 currPos: INT ← 0;
 AcquireLock[ newLog ];
 AcquireLock[ oldLog ];
DO
  entry ← SkipThruCR[oldLog.stream];
IF entry.Length[] = 0 THEN EXIT;
  prefix ← Rope.Fetch[entry];
IF prefix = '? THEN
IF filter[ entry, currPos ] THEN currPos ← currPos + InternalPutText[ newLog, entry ]
ENDLOOP };

SetLogLengthInDB: PROC[ log: Log ] = {
logdata: Relship ←
 DeclareRelship[ r: LogRel, init: LIST[ AttributeValue[ LogNameAttr, S2V[log.name] ] ] ];
log.stream.Flush[];
SetF[ logdata, LogLengthAttr, I2V[ log.stream.GetLength[] ] ] };

AdvanceLogFromLastCommit: PUBLIC PROC[ log: Log ] = {
-- get length from the DB and then perform all the log actions from this point
DBRels: RelshipSet = RelationSubset[LogRel, LIST[AttributeValue[LogNameAttr,S2V[log.name]]]];
DBrel: Relship = NextRelship[ DBRels ];
{ ENABLE IO.EndOfStream => GOTO Quit;
pos: INT = V2I[ GetF[ DBrel, LogLengthAttr ] ];
prefix: CHAR;
log.stream.SetIndex[pos];
-- text entries are not touched, but the entity and relship entries are added or deleted
DO
  prefix ← GetChar[log.stream];
SELECT prefix FROM
  '? => { len: ROPE ← ReadString[log]; pos: INT ← log.stream.GetIndex[];
     log.stream.SetIndex[ pos+ IO.GetInt[ IO.RIS[len] ] + 1 ] };
  '+ => IF PeekChar[ log.stream ] = '/ THEN
    [] ← DBLoad.CreateEntity[ log.stream ]
    ELSE [] ← DBLoad.CreateRelship[ log.stream ];
  '- => IF PeekChar[ log.stream ] = '/ THEN [] ← DBLoad.DestroyEntity[ log.stream ]
      ELSE [] ← DBLoad.DestroyRelship[ log.stream ];
ENDCASE => [] ← SkipThruCR[ log.stream ];
ENDLOOP;
EXITS
Quit => { ReleaseRelshipSet[DBRels];
    SetF[ DBrel, LogLengthAttr, I2V[log.stream.GetLength[]] ] } } };

Notified: Nut.NotifyProc = {
FOR ll: LIST OF Log ← LogList, ll.rest UNTIL ll = NIL DO
SELECT whyNotified FROM
afterOpen => AdvanceLogFromLastCommit[ ll.first ];
beforeClose, beforeCommit =>
{ AcquireLock[ll.first];
ll.first.stream.Flush[];
SetLogLengthInDB[ ll.first ];
ReleaseLock[ll.first] };
afterCommit =>
{ AcquireLock[ll.first];
InternalOpenLog[ll.first];
ReleaseLock[ll.first] };
ENDCASE;
ReleaseLock[ ll.first ]
ENDLOOP };


Nut.Register[ domain: "Domain", notify: Notified ]     
}.