-- HackedAlpineBackupImpl.mesa
-- Last Edited by
-- MBrown on July 22, 1983 4:30 pm
-- Kolling on January 4, 1984 3:21 pm
-- Bob Hagmann October 5, 1984 10:19:56 am PDT

DIRECTORY
AlpineCmds
USING[WheelGetCreateTime],
AlpineFS
USING[StreamOpen],
AlpInstance
USING[Create],
AlpTransaction
USING[AssertAlpineWheel, Create, Finish, Handle],
BasicTime
USING[GMT],
Commander
USING[CommandProc, Handle, Register],
CommandTool
USING[DoCommand],
FS
USING[Error, FileInfo, StreamOpen],
FSRemoteFile
USING[ConfirmProc, Store],
GVBasics,
GVSend,
IO,
Process,
Rope,
UserCredentials
USING[Get],
ViewerIO;

HackedAlpineBackupImpl: CEDAR MONITOR
IMPORTS AlpineCmds, AlpineFS, AlpI: AlpInstance, AlpT: AlpTransaction, Commander,
CommandTool, FS, FSRemoteFile, GVSend, IO, Rope, UserCredentials, ViewerIO

= BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;

typescriptStream: IO.STREAMNIL;

PutLog: PROCEDURE[format: Rope.ROPENIL, v1, v2, v3, v4, v5: IO.Value ← [null[]]] =
BEGIN
typescriptStream.PutF[format, v1, v2, v3, v4, v5];
typescriptStream.PutRope["
"];
typescriptStream.Flush[];
END;
OpenLog: PROCEDURE =
BEGIN
typescriptStream ← ViewerIO.CreateViewerStreams["AlpineBackup.log"].out;
END;
CloseLog: PROCEDURE =
BEGIN
typescriptStream.Close[];
END;

crockCmd
: Commander.Handle ← NIL;
AlpineBackupProc
: Commander.CommandProc =
BEGIN
crockCmd ← cmd;
DoAlpineBackup[NIL, NIL];
END;

DoAlpineBackup
: PROC [alpineFiles: ROPE, messagesTo: ROPE] = TRUSTED {
IF alpineFiles.IsEmpty[] THEN
alpineFiles ← "[indigo]<Alpine>Backup>AlpineFilesToBackup.txt^";
IF messagesTo.IsEmpty[] THEN messagesTo ← "Hagmann.pa, Donahue.pa";
DoBackup[alpineFiles, "Cyan", "AlpineBackup", messagesTo];
};

DoBackup: PROC [alpineFiles: ROPE, server, directory: ROPE, messagesTo: ROPE] = {
-- alpineFiles is either "<full path name>^", meaning contents of the named file, or
-- "<full path name> <full path name> ... <full path name>", meaning itself.
-- if a full path name is prefixed with '= then file is backed up even if create dates agree
-- server is of form "Ivy" or "Cyan"
-- directory is of form "AlpineBackup"
-- messagesTo is of form "RWeaver.pa, Kolling.pa, MBrown.pa"
in, err: STREAM;
toList: LIST OF RName ← NIL;
counts: ARRAY BackupFileInfo OF INTALL[0];
name, pwd: ROPE;
OpenLog[];
[name, pwd] ← UserCredentials.Get[];
err ← IO.ROS[];
err.PutF["Starting backup of Alpine files %g to [%g]<%g>\n",
IO.rope[alpineFiles], IO.rope[server], IO.rope[directory]];
PutLog["Starting backup of Alpine files %g to [%g]<%g>", IO.rope[alpineFiles], IO.rope[server], IO.rope[directory]];
IF alpineFiles.Fetch[alpineFiles.Length[]-1] = '^ THEN {
alpineFiles ← alpineFiles.Substr[len: alpineFiles.Length[]-1];
in ← FS.StreamOpen[alpineFiles];
}
ELSE in ← IO.RIS[alpineFiles];
{ -- Parse the messagesTo ROPE into a LIST OF RName.
s: STREAM = IO.RIS[messagesTo];
to: RName;
DO
to ← s.GetTokenRope[IO.IDProc! IO.EndOfStream => EXIT].token;
toList ← CONS[first: to, rest: toList];
ENDLOOP;
s.Close[];
};
DO
alpineFile: ROPE;
info: BackupFileInfo;
alpineFile ← in.GetTokenRope[IO.IDProc! IO.EndOfStream => EXIT].token;
info ← BackupFile[alpineFile, server, directory, err];
IF info = alpineError THEN {
-- retry once
err.PutRope["Retrying ...\n"];
info ← BackupFile[alpineFile, server, directory, err];
};
counts[info] ← counts[info] + 1;
ENDLOOP;
{ body: ROPE;
body ← IO.PutFR["Summary:\n%g file(s) copied\n%g file(s) found unchanged\n%g file(s) not found\n%g error(s) in IFS operations\n%g error(s) in Alpine operations\n\nLog:\n\n",
IO.int[counts[fileCopied]], IO.int[counts[fileUnchanged]], IO.int[counts[fileNotFound]],
IO.int[counts[ifsError]], IO.int[counts[alpineError]]];
body ← body.Concat[IO.RopeFromROS[err]];
[] ← SendMessage[
from: "Alpine backup process",
sender: name, senderPwd: pwd,
returnTo: "Hagmann.pa",
to: toList, cc: NIL,
subject:
IF (counts[ifsError] + counts[alpineError]) # 0 THEN
"Ending backup (file server errors)"
ELSE IF counts[fileNotFound] # 0 THEN
"Ending backup (files not found)"
ELSE "Ending backup",
otherHeader: NIL,
body: body,
validate: FALSE, sendIfValidateFails: TRUE];
};
err.Close[];
in.Close[];
PutLog[format: "Ending backup of Alpine files"];
CloseLog[];
};

BackupFileInfo: TYPE = {
fileNotFound, ifsError, alpineError, fileUnchanged, fileCopied };

BackupFile: PROC [alpineFile: ROPE, server, directory: ROPE, err: STREAM]
RETURNS [BackupFileInfo] = {
ifsServer: ROPE = Rope.Cat["[", server, "]"];
ifsDirectory: ROPE = Rope.Cat["<", directory, ">"];
fullName, longName, shortName: ROPE;
errorCode: ATOMNIL;
found: BOOL;
alpineCreateTime, ifsCreateTime: BasicTime.GMT;
copyOnlyIfCreateTimeChanged: BOOLTRUE;
IF alpineFile.Fetch[0] = '= THEN {
copyOnlyIfCreateTimeChanged ← FALSE;
alpineFile ← alpineFile.Substr[1];
};
[longName, shortName] ← BackupFileName[alpineFile, ifsDirectory];
fullName ← Rope.Cat[ifsServer, longName];
BEGIN ENABLE ANY => GOTO alpineError;
[found, alpineCreateTime] ← AlpineCmds.WheelGetCreateTime[
file: alpineFile, enableWheel: TRUE];
EXITS alpineError => {
err.PutF["Alpine error in examining %g\n", IO.rope[alpineFile]];
PutLog["Alpine error in examining %g", IO.rope[alpineFile]];
RETURN [alpineError];
}
END;
IF NOT found THEN BEGIN
err.PutF["Alpine file %g not found\n", IO.rope[alpineFile]];
PutLog["Alpine file %g not found", IO.rope[alpineFile]];
RETURN[fileNotFound];
END;
BEGIN
ifsCreateTime ← FS.FileInfo[name: Rope.Cat["[", server, "]", longName, "!h"],
wantedCreatedTime: , remoteCheck: , wDir:
! FS.Error => BEGIN errorCodeerror.code; GOTO errorFound; END;].created;
errorCode NIL;
EXITS errorFound => NULL;
END;
IF
((errorCode = $serverInaccessible) OR (errorCode = $connectionRejected)) THEN BEGIN
err.PutF["IFS error in examining %g on %g\n",
IO.rope[longName], IO.rope[server]];
PutLog["IFS error in examining %g on %g", IO.rope[longName], IO.rope[server]];
RETURN[ifsError];
END;
IF errorCode = NIL AND
copyOnlyIfCreateTimeChanged AND alpineCreateTime = ifsCreateTime THEN BEGIN
err.PutF["File found unchanged %g\n", IO.rope[alpineFile]];
PutLog["File found unchanged %g", IO.rope[alpineFile]];
RETURN[fileUnchanged];
END;


BEGIN

transHandle: AlpT.Handle ← NIL;
BEGIN ENABLE
ANY => GOTO alpineError;
confirmProc: FSRemoteFile.ConfirmProc = BEGIN RETURN[TRUE]; END;
alpineStream: STREAM;
transHandle ← AlpT.Create[instHandle: AlpI.Create[fileStore:
alpineFile.Substr[start: 1, len: alpineFile.SkipTo[1, "]"] - 1]], createLocalWorker: TRUE];
transHandle.AssertAlpineWheel[TRUE];
alpineStream ← AlpineFS.StreamOpen[name: alpineFile, accessOptions: $read,
streamOptions: [tiogaRead: FALSE], keep: , createByteCount: , streamBufferParms: ,
extendFileProc: NIL, wDir: NIL, transHandle: transHandle];
FSRemoteFile.Store[server: server, file: longName, str: alpineStream, created: alpineCreateTime,
proc: confirmProc
! FS.Error => GOTO ifsError];
alpineStream.Close[! ANY => CONTINUE];
[] ← transHandle.Finish[requestedOutcome: commit, continue: FALSE];
EXITS
alpineError => BEGIN
err.PutF["Alpine error in retrieving %g\n", IO.rope[alpineFile]];
PutLog["Alpine error in retrieving %g", IO.rope[alpineFile]];
IF transHandle # NIL
THEN [] ← transHandle.Finish[requestedOutcome: commit, continue: FALSE];
RETURN
[alpineError];
END;
ifsError => BEGIN
err.PutF["IFS error in storing %g on %g\n",
IO.rope[longName], IO.rope[server]];
PutLog["IFS error in storing %g on %g", IO.rope[longName], IO.rope[server]];
IF transHandle # NIL
THEN [] ← transHandle.Finish[requestedOutcome: commit, continue: FALSE];
RETURN
[ifsError];
END;
END;
END
;




DelVerAndSetNoBackup[server, directory, longName];
err.PutF["File transferred okay %g\n", IO.rope[alpineFile]];
PutLog["File transferred okay %g", IO.rope[alpineFile]];
RETURN [fileCopied]
};

BackupFileName: PROC [alpineFile, ifsDirectory: ROPE]
RETURNS [longName, shortName: ROPE] = {
-- Transforms "[Luther.alpine]<MBrown.pa>Walnut.DBLog", "<AlpineBackup>"
-- into "<AlpineBackup>Luther>MBrown.pa>Walnut.DBLog", "Walnut.DBLog".
IF NOT Rope.Match[pattern: "[*.alpine]<*>*", object: alpineFile, case: FALSE] THEN
ERROR;
{ -- derive longName from alpineFile and ifsDirectory
longName ← alpineFile.Replace[
start: alpineFile.Find[s2: ".alpine]<", case: FALSE], len: 9, with: ">"];
longName ← longName.Replace[start: 0, len: 1, with: ifsDirectory];
};
{ -- derive shortName from longName
pos1: INT ← 0;
pos2: INT;
UNTIL (pos2 ← longName.Find[s2: ">", pos1: pos1+1]) = -1 DO
pos1 ← pos2;
ENDLOOP;
shortName ← longName.Substr[start: pos1+1];
};
RETURN [longName, shortName];
};

DelVerAndSetNoBackup: PROC [server, directory, longName: ROPE] = {
-- server is like "Ivy"
-- directory is like "AlpineBackup"
-- longName is like "<AlpineBackup>Luther>MBrown.pa>Walnut.DBLog"
ksFile: STREAM = FS.StreamOpen["Backup.ks", $create];
ksFile.PutRope[Rope.Cat[
"Connect ", directory, "\n",
"Delete ", longName, ",\nKeep 2\nConfirm\n\n"]];
ksFile.PutRope[Rope.Cat[
"Change Attributes ", longName, "\nNo Backup\n\nQuit\n"]];
ksFile.Close[];
[] ← CommandTool.DoCommand[Rope.Cat["Chat ", server, " -l >Backup.ChatLog <Backup.ks -d -s"], crockCmd];
};

RName: TYPE = GVBasics.RName;

SendMessageInfo: TYPE = {
ok, invalidRecipient, noValidRecipients, badPwd, badSender, badReturnTo, allDown};

SendMessage: PROC [from: ROPE, sender: RName, senderPwd: ROPE, returnTo: RName,
to, cc: LIST OF RName, subject: ROPE, otherHeader: ROPE, body: ROPE,
validate, sendIfValidateFails: BOOL]
RETURNS [sent: BOOL, info: SendMessageInfo] = {
NoticeInvalidRecipient: PROC [rNumber: INT, rName: RName] = {
state ← $invalidRecipient;
};
h: GVSend.Handle = GVSend.Create[];
state: SendMessageInfo;
aborted: BOOLFALSE;
DO
ENABLE GVSend.SendFailed => IF notDelivered THEN LOOP ELSE EXIT;
SELECT h.StartSend[senderPwd, sender, returnTo, validate] FROM
ok => state ← $ok;
badPwd => { state ← $badPwd; EXIT };
badSender => { state ← $badSender; EXIT };
badReturnTo => { state ← $badReturnTo; EXIT };
allDown => { state ← $allDown; EXIT };
ENDCASE => ERROR;
AddRecipients[h, to];
AddRecipients[h, cc];
IF validate THEN {
IF h.CheckValidity[NoticeInvalidRecipient] = 0 THEN state ← $noValidRecipients;
IF NOT sendIfValidateFails AND state # $ok THEN {
h.Abort[]; aborted ← TRUE; EXIT;
};
};
h.StartItem[Text];
h.AddToItem[ConsMessage[sender, subject, from, to, cc, otherHeader, body]];
h.Send[];
EXIT;
ENDLOOP;
RETURN [state IN [$ok .. $noValidRecipients] AND NOT aborted, state]
};

AddRecipients: PROC [h: GVSend.Handle, recipients: LIST OF RName] = {
FOR r: LIST OF RName ← recipients, r.rest UNTIL r = NIL DO
h.AddRecipient[recipient: r.first];
ENDLOOP;
};

ConsMessage: PROC [
sender: RName, subject, from: ROPE, to, cc: LIST OF RName, otherHeader, body: ROPE]
RETURNS [ROPE] = {
dateRope: ROPE = ConsField["Date", IO.PutFR["%g", IO.time[]]];
senderRope: ROPE = ConsField["Sender", sender];
subjectRope: ROPE = ConsField["Subject", subject];
fromRope: ROPE = ConsField["From", from];
toRope: ROPE = ConsListField["To", to];
ccRope: ROPE = ConsListField["cc", cc];
header: ROPE = Rope.Cat[dateRope, senderRope, subjectRope, fromRope, toRope, ccRope];
RETURN[Rope.Cat[header, otherHeader, "\n", body]];
};

ConsField: PROC [fieldName, fieldContents: ROPE] RETURNS [ROPE] = {
RETURN [IF fieldContents.IsEmpty[] THEN NIL
ELSE IO.PutFR["%g: %g\n", IO.rope[fieldName], IO.rope[fieldContents]]];
};

ConsListField: PROC [fieldName: ROPE, fieldContents: LIST OF ROPE] RETURNS [ROPE]= {
result: ROPENIL;
IF fieldContents # NIL THEN {
result ← fieldName.Concat[": "];
DO
result ← result.Concat[fieldContents.first];
fieldContents ← fieldContents.rest;
IF fieldContents = NIL THEN EXIT;
result ← result.Concat[", "];
ENDLOOP;
result ← result.Concat["\n"];
};
RETURN [result.Flatten[]];
};


Commander.Register[key: "DoAlpineBackup", proc: AlpineBackupProc, doc: NIL, clientData: ];



END
.


CHANGE LOG

Created by MBrown on May 28, 1983 9:20 pm

Changed by MBrown on July 8, 1983 11:53 am
-- Added code for server operation (periodic wakeup, grapevine message sending.)

Changed by MBrown on July 19, 1983 8:35 am
-- Added code for triggering backup from MiscServerTool

Changed by MBrown on July 22, 1983 4:30 pm
-- Added code for passing "alpineFiles" as a value as well as a file reference; added
--"messagesTo" button to aid in debugging.

Changed by Bob Hagmann on June 1, 1984 7:47:40 am PDT
-- Changed keep from 1 to 2 so that backup will be more meaningful.

Changed by Bob Hagmann on October 5, 1984 10:20:14 am PDT
-- Moved backup to Cyan from Ivy, and added Donahue to message list.