MODULE; IMPORT App, FileRd, FileWr, Fmt, FS, OSError, Rd, RdUtils, RegularFile, Thread, Time, Wr; REVEAL T = TPublic BRANDED "AppBackup.T" OBJECT threadWrite: Thread.T; cvChanged: Thread.Condition; deltaWait: Time.T; lastModified: Time.T; changed: BOOLEAN; OVERRIDES init := Init; modified := Modified; read := ReadDefault; write := WriteDefault; END; CONST InitialTime = 0.0D0; PROCEDURE AppBackup Init (self: T; fileName: TEXT; wait: Time.T; log: App.Log): T = BEGIN self.changed := FALSE; self.deltaWait := wait; self.lastModified := InitialTime; self.name := fileName; self.log := log; self.cvChanged := NEW(Thread.Condition); (* self.threadRead := Thread.Fork(NEW(BackupReadClosure, backup := self)); I now 2/5/96 think this is a bad idea. There should be a command for this. *) self.threadWrite := Thread.Fork(NEW(BackupWriteClosure, backup := self)); RETURN self; END Init; PROCEDUREModified (self: T) = BEGIN LOCK self DO self.changed := TRUE; END; Thread.Signal(self.cvChanged); END Modified; PROCEDUREReadDefault (self: T; <* UNUSED *> rd: Rd.T; <* UNUSED *> initial: BOOLEAN) RAISES {App.Error} = BEGIN self.log.log(Fmt.F("No read method give for %s", self.name), App.LogStatus.Error); END ReadDefault; PROCEDUREWriteDefault (self: T; <* UNUSED *> wr: Wr.T) RAISES {App.Error} = BEGIN self.log.log(Fmt.F("No write method give for %s", self.name), App.LogStatus.Error); END WriteDefault; PROCEDURELockFile (<* UNUSED *> file: RegularFile.T; <* UNUSED *> t: T) = CONST
MaxTry = 10; RetryInterval = 1.0D0; VAR try := 1;
BEGIN
Nov 16: file.lock broken in call to fcntl - int vs. long confusion
TRY
WHILE NOT file.lock() DO
IF try = MaxTry THEN
t.log.log(Fmt.F(Could not lock file: %s, t.name),
App.LogStatus.Error);
END;
INC(try);
Thread.Pause(RetryInterval);
END;
(* Check for Windows NT problem
(* IF file.lock() THEN
t.log.log("WARNING: Could lock file TWICE", App.LogStatus.Status);
END;
*)
EXCEPT
| OSError.E(cause) =>
t.log.log(Fmt.F("Could not lock file: %s (error: %s)",
t.name, RdUtils.FailureText(cause)),
App.LogStatus.Error);
END;
*)
END LockFile;
PROCEDURE UnlockFile (<* UNUSED *> file: RegularFile.T; <* UNUSED *> t: T) =
BEGIN
Nov 16: file.lock broken in call to fcntl - int vs. long confusion TRY file.unlock(); EXCEPTOSError.E(cause) =>t.log.log(Fmt.F(Could not UNlock file: %s (error: %s), t.name, RdUtils.FailureText(cause)), App.LogStatus.Error); END;
END UnlockFile; PROCEDURESynchronousRead (t: T; initial: BOOLEAN) RAISES {App.Error} = BEGIN LOCK t DO ReadBackupFile(t, initial); t.changed := FALSE END; END SynchronousRead; PROCEDUREReadBackupFile (t: T; initial: BOOLEAN) RAISES {App.Error} = VAR file: RegularFile.T; rd : Rd.T; BEGIN TRY file := FS.OpenFile(t.name, truncate := FALSE, access := FS.AccessOption.OnlyOwnerCanRead); EXCEPT | OSError.E (cause) => t.log.log( Fmt.F("WARNING: Could not open file: %s (error: %s)", t.name, RdUtils.FailureText(cause)), App.LogStatus.Status); RETURN; END; TRY LockFile(file, t); TRY rd := NEW(FileRd.T).init(file); IF Rd.Length(rd) > 0 THEN t.read(rd, initial); END; t.changed := FALSE; FINALLY UnlockFile(file, t); Rd.Close(rd); END; EXCEPT | OSError.E (cause) => t.log.log( Fmt.F("Problem reading backup file: %s (error: %s)\n", t.name, RdUtils.FailureText(cause)), App.LogStatus.Error); | Rd.Failure, Thread.Alerted => t.log.log(Fmt.F("Problem reading backup file: %s\n", t.name), App.LogStatus.Error); END; END ReadBackupFile; PROCEDURESynchronousWrite (t: T) RAISES {App.Error} = BEGIN LOCK t DO WriteBackupFile(t) END; END SynchronousWrite; PROCEDUREWriteBackupFile (t: T) RAISES {App.Error} = VAR file: RegularFile.T; wr : Wr.T; now := Time.Now(); BEGIN TRY FS.Rename(t.name, t.name & "-OLD"); EXCEPT | OSError.E=> END; TRY file := FS.OpenFile(t.name, truncate := TRUE, access := FS.AccessOption.OnlyOwnerCanRead); EXCEPT | OSError.E (cause) => t.log.log( Fmt.F("Could not open file: %s for writing (error: %s)", t.name, RdUtils.FailureText(cause)), App.LogStatus.Error); RETURN; END; TRY LockFile(file, t); TRY wr := NEW(FileWr.T).init(file); t.write(wr); Wr.Flush(wr); t.changed := FALSE; FINALLY UnlockFile(file, t); Wr.Close(wr); t.lastModified := now; END; EXCEPT | OSError.E (cause) => t.log.log( Fmt.F("Problem writing backup file: %s (error: %s)\n", t.name, RdUtils.FailureText(cause)), App.LogStatus.Error); | Wr.Failure, Thread.Alerted => t.log.log(Fmt.F("Problem writing backup file: %s\n", t.name), App.LogStatus.Error); END; END WriteBackupFile;
TYPE BackupReadClosure = Thread.Closure OBJECT backup: T; OVERRIDES apply := BackupRead; END;
PROCEDURE BackupRead (self: BackupReadClosure): REFANY = VAR lastMod: Time.T; BEGIN TRY LOOP TRY LOCK self.backup DO (* file rewritten?
TRY
lastMod := FS.Status(self.backup.name).modificationTime;
EXCEPT
| OSError.E =>
lastMod := InitialTime; (* file does not exist *)
END;
IF lastMod > self.backup.lastModified THEN
ReadBackupFile(
self.backup, self.backup.lastModified = InitialTime);
IF self.backup.lastModified = InitialTime THEN
Thread.Signal(self.backup.cvChanged);
self.backup.lastModified :=
FS.Status(self.backup.name).modificationTime;
END;
END;
END;
EXCEPT
| OSError.E (cause) =>
self.backup.log.log(
Fmt.F("Problem reading backup file: %s (error: %s)\n",
self.backup.name, RdUtils.FailureText(cause)),
App.LogStatus.Error);
END;
Thread.Pause(self.backup.deltaWait);
END;
EXCEPT
| App.Error =>
END;
RETURN NIL;
END BackupRead;
*)
TYPE
BackupWriteClosure = Thread.Closure OBJECT
backup: T;
OVERRIDES
apply := BackupWrite;
END;
PROCEDURE BackupWrite (self: BackupWriteClosure): REFANY =
BEGIN
LOOP
LOCK self.backup DO
WHILE NOT self.backup.changed DO
Thread.Wait(self.backup, self.backup.cvChanged);
END;
TRY
WriteBackupFile(self.backup);
EXCEPT
| App.Error =>
END;
END;
Thread.Pause(self.backup.deltaWait);
END;
END BackupWrite;
BEGIN
END AppBackup.