DIRECTORY BasicTime USING [GMT, Now, nullGMT], CedarProcess USING [DoWithPriority, Priority], Commander USING [CommandProc, Handle, Register], CommandTool USING [ArgumentVector, Failed, Parse], DFUtilities USING [DirectoryItem, FileItem, IncludeItem, ParseFromStream, ProcessItemProc], FS USING [Copy, defaultStreamOptions, EnumerateForInfo, Error, ExpandName, FileInfo, InfoProc, StreamOpen, StreamOptions], FSBackdoor USING [EnumerateCacheForInfo, InfoProc], FSPseudoServers USING [TranslateForWrite], GVBasics USING [Connect], GVNames USING [ConnectInfo, GetConnect], IO USING [Close, PutChar, PutF, PutF1, PutFR1, PutRope, STREAM], List USING [CompareProc], PrincOpsUtils USING [], Process USING [CheckForAbort, GetPriority, Pause, Priority, SecondsToTicks], RedBlackTree USING [Compare, Create, Delete, DestroyTable, EachNode, EnumerateIncreasing, GetKey, Insert, Lookup, Table], Rope USING [Cat, Compare, Concat, Equal, Fetch, Find, Flatten, Length, Match, Replace, ROPE, Run, SkipTo, Substr], STP USING [Close, Connect, Create, Error, Handle, IsOpen, Login, Open, Store], Tempus USING [Now, PackedSeconds, PackedToSeconds, Parse], UserCredentials USING [Get]; TrickleChargeServerImpl: CEDAR PROGRAM IMPORTS BasicTime, CedarProcess, Commander, CommandTool, DFUtilities, FS, FSBackdoor, FSPseudoServers, GVNames, IO, Process, RedBlackTree, Rope, STP, Tempus, UserCredentials = BEGIN GMT: TYPE = BasicTime.GMT; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Switches: TYPE = PACKED ARRAY CHAR['a..'z] OF BOOL; PairList: TYPE = LIST OF Pair; Pair: TYPE = RECORD [ src: ROPE, dst: ROPE, switches: Switches, timeRestriction: ROPE ]; FileEntry: TYPE = REF FileEntryRep; FileEntryRep: TYPE = RECORD [ name: ROPE, short: ROPE, date: GMT, len: INT, state: FileEntryState ]; FileEntryState: TYPE = {init, fetching, storing, moved}; doc: ROPE = "{srcDir dstDir}* moves files from srcDir (or DF) to dstDir -c: connect (to destination host) -d: debug (inhibits file transfer) -r: repeat (keep doing the transfer forever) -v: verify (no transfers, messages only for missing files) -q: quick (enumerate only the df files) See documentation for specification of time restrictions. "; maxRetries: NAT _ 10; retrySeconds: NAT _ 20; repeatSeconds: NAT _ 1800; secondsBetweenMoves: NAT _ 0; bytesPerIFSPage: NAT = 2048; maxPauseTime: NAT = 1800; DoIt: PROC [table: RedBlackTree.Table, out: STREAM, srcPrefix: ROPE, dstPrefix: ROPE, switches: Switches] = { debug: BOOL _ switches['d]; verify: BOOL _ switches['v]; enumerateForDfFiles: BOOL _ switches['q]; dfList: LIST OF FileEntry _ NIL; EachInfo: FS.InfoProc = { continue _ TRUE; Process.CheckForAbort[]; IF Rope.Run[fullFName, 0, srcPrefix, 0, FALSE] = srcPrefixLen THEN { new: FileEntry _ NIL; short: ROPE _ NIL; WITH RedBlackTree.Lookup[table, fullFName] SELECT FROM entry: FileEntry => { IF entry.date = created THEN RETURN; [] _ RedBlackTree.Delete[table, fullFName]; }; ENDCASE; short _ Rope.Substr[fullFName, srcPrefixLen]; IF Rope.Match["!*", short] THEN RETURN; IF debug THEN IO.PutChar[out, '.]; new _ NEW[FileEntryRep _ [ name: fullFName, short: short, date: created, len: bytes, state: init]]; RedBlackTree.Insert[table, new, fullFName]; filesSeenDuringEnumeration _ filesSeenDuringEnumeration + 1; bytesSeenDuringEnumeration _ bytesSeenDuringEnumeration + bytes; ifsPagesDuringEnumeration _ ifsPagesDuringEnumeration + 1 + (bytes + bytesPerIFSPage-1) / bytesPerIFSPage; }; }; EachEntry: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { IF entry.state # moved THEN { IF Rope.Match["*.df!*", entry.short, FALSE] THEN dfList _ CONS[entry, dfList] ELSE MoveFile[entry]; }; RETURN; }; ENDCASE => ERROR; }; MoveFile: PROC [entry: FileEntry] = { fullDstName: ROPE _ Rope.Concat[dstPrefix, entry.short]; fullSrcName: ROPE _ entry.name; isInCache: BOOL _ IsInFileCache[fullSrcName, entry]; isOnDst: BOOL _ FALSE; dstBytes: INT _ 0; dstDate: GMT _ BasicTime.nullGMT; Process.CheckForAbort[]; [created: dstDate, bytes: dstBytes] _ FS.FileInfo[fullDstName ! FS.Error => CONTINUE ]; IF dstDate = entry.date AND dstBytes = entry.len THEN { isOnDst _ TRUE; filesAlreadyThere _ filesAlreadyThere + 1; }; SELECT TRUE FROM debug => { ShowEntry[out, entry]; IF isInCache THEN { filesInCache _ filesInCache + 1; IO.PutRope[out, " (in local file cache)\n"]; }; IF isOnDst THEN IO.PutRope[out, " (already on destination)\n"]; }; isOnDst => { entry.state _ moved; }; verify => { ShowEntry[out, entry]; IF isInCache THEN { filesInCache _ filesInCache + 1; IO.PutRope[out, " (in local file cache)"]; }; IO.PutRope[out, " (NOT on destination)\n"]; }; ENDCASE => { srcStream: STREAM _ NIL; retriedCount: NAT _ 0; openName: ROPE _ NIL; streamOptions: FS.StreamOptions _ FS.defaultStreamOptions; streamOptions[tiogaRead] _ FALSE; entry.state _ fetching; IF isInCache THEN { openName _ fullSrcName; filesInCache _ filesInCache + 1; } ELSE { openName _ FS.Copy[from: fullSrcName, to: "///Temp/TrickleCharge.Temp$", wantedCreatedTime: entry.date, setKeep: TRUE, keep: 4]; }; srcStream _ FS.StreamOpen[fileName: openName, streamOptions: streamOptions, wantedCreatedTime: entry.date, remoteCheck: FALSE]; entry.state _ storing; CopyStreamToRemote[srcStream, fullDstName, entry.date ! STP.Error => { IO.PutF[out, "STP.Error when storing %g\n %g\n", [rope[fullDstName]], [rope[error]] ]; SELECT code FROM connectionRejected => IF retriedCount < maxRetries THEN { retriedCount _ retriedCount + 1; Process.Pause[Process.SecondsToTicks[retrySeconds]]; RETRY; }; connectionClosed => IF retriedCount < maxRetries THEN { retriedCount _ retriedCount + 1; RETRY; }; ENDCASE; IO.Close[srcStream]; GO TO failed; }; UNWIND => IO.Close[srcStream]]; IO.Close[srcStream]; entry.state _ moved; IO.PutF[out, "Moved %g\n to %g\n", [rope[fullSrcName]], [rope[fullDstName]] ]; IF secondsBetweenMoves # 0 THEN Process.Pause[Process.SecondsToTicks[secondsBetweenMoves]]; EXITS failed => {}; }; }; CopyStreamToRemote: PROC [stream: STREAM, remoteName: ROPE, date: GMT] = { IF Rope.Match["[*]<*>*", remoteName] THEN { hostStop: INT _ Rope.SkipTo[remoteName, 1, "]"]; dirStart: INT _ Rope.SkipTo[remoteName, hostStop, "<"]+1; dirStop: INT _ Rope.SkipTo[remoteName, dirStart, ">"]; host: ROPE _ Rope.Flatten[remoteName, 1, hostStop-1]; dir: ROPE _ Rope.Flatten[remoteName, dirStart, dirStop-dirStart]; nameSansHost: ROPE _ Rope.Substr[remoteName, dirStart-1]; IF NOT STP.IsOpen[stp] THEN DO getServerPupName: PROC [server: ROPE] RETURNS [pupServer: ROPE] = { IF server.Find[".", 0, FALSE] > 0 THEN { info: GVNames.ConnectInfo; connect: GVBasics.Connect; [info: info, connect: connect ] _ GVNames.GetConnect[server]; IF info = group OR info = individual THEN RETURN[connect]; }; RETURN[server]; }; userName: ROPE _ NIL; userPassword: ROPE _ NIL; [userName, userPassword] _ UserCredentials.Get[]; [] _ STP.Open[stp, getServerPupName[host]]; STP.Login[stp, userName, userPassword]; IF switches['c] THEN STP.Connect[stp, dir, ""]; EXIT; ENDLOOP; { ENABLE UNWIND => STP.Close[stp ! STP.Error => CONTINUE; ]; STP.Store[stp: stp, file: nameSansHost, stream: stream, fileType: binary, creation: date]; }; filesMoved _ filesMoved + 1; }; }; VisitEntry: PROC [name: ROPE, date: GMT] = { RemoveAngles: PROC [name: ROPE] RETURNS [short: ROPE _ NIL] = { angleCount: INT _ 0; lastAngle: INT _ -1; IF enumerateForDfFiles AND Rope.Run[ s1: name, s2: srcPrefix, case: FALSE ] # Rope.Length[srcPrefix] THEN RETURN[NIL]; FOR i: INT IN [0..Rope.Length[name]) DO SELECT Rope.Fetch[name, i] FROM '>, '] => { lastAngle _ i; IF (angleCount_angleCount+1) = dstAngleCount THEN RETURN [Rope.Substr[name, i+1]]; }; '! => EXIT; ENDCASE; ENDLOOP; IF lastAngle >= 0 THEN RETURN [Rope.Substr[name, lastAngle+1]]; }; new: FileEntry _ NIL; bytes: INT _ 0; short: ROPE _ NIL; Process.CheckForAbort[]; WITH RedBlackTree.Lookup[table, name] SELECT FROM entry: FileEntry => IF entry.date = date THEN RETURN; ENDCASE; [fullFName: name, bytes: bytes, created: date] _ FS.FileInfo[name: name, wantedCreatedTime: date ! FS.Error => IF error.code = $unknownFile OR error.code = $unknownCreatedTime THEN { IO.PutF1[out, "FS.Error[%g]\n", [rope[error.explanation]]]; name _ NIL; CONTINUE; } ELSE REJECT]; short _ RemoveAngles[name]; IF short = NIL THEN RETURN; new _ NEW[FileEntryRep _ [ name: name, short: short, date: date, len: bytes, state: init]]; WITH RedBlackTree.Lookup[table, name] SELECT FROM entry: FileEntry => { IF entry.date = date THEN RETURN; [] _ RedBlackTree.Delete[table, name]; }; ENDCASE; RedBlackTree.Insert[table, new, name]; filesSeenDuringEnumeration _ filesSeenDuringEnumeration + 1; bytesSeenDuringEnumeration _ bytesSeenDuringEnumeration + bytes; ifsPagesDuringEnumeration _ ifsPagesDuringEnumeration + 1 + (bytes + bytesPerIFSPage-1) / bytesPerIFSPage; }; stp: STP.Handle _ STP.Create[]; srcPrefixLen: INT _ Rope.Length[srcPrefix _ TranslateHost[srcPrefix]]; dstPrefixLen: INT _ Rope.Length[dstPrefix]; dstAngleCount: INT _ 0; filesMoved: INT _ 0; filesInCache: INT _ 0; filesAlreadyThere: INT _ 0; filesSeenDuringEnumeration: INT _ 0; bytesSeenDuringEnumeration: INT _ 0; ifsPagesDuringEnumeration: INT _ 0; RedBlackTree.DestroyTable[table]; -- clear the table from the last run FOR i: INT IN [0..dstPrefixLen) DO SELECT Rope.Fetch[dstPrefix, i] FROM '>, '] => dstAngleCount _ dstAngleCount + 1; ENDCASE; ENDLOOP; IO.PutF[out, "Moving files from %g to %g\n", [rope[srcPrefix]], [rope[dstPrefix]] ]; IO.PutF1[out, "{Building file table at %g}\n", [time[BasicTime.Now[]]] ]; SELECT TRUE FROM ~Rope.Match["*>", srcPrefix] => VisitClosure[srcPrefix, BasicTime.nullGMT, VisitEntry]; --A df file enumerateForDfFiles => { --Enumerate source directory for df files EachFile: FS.InfoProc = { doTheEnumerate: BOOL _ FALSE; Process.CheckForAbort[]; IF Rope.Run[s1: fullFName, s2: srcPrefix, case: FALSE]#srcPrefixLen THEN ERROR; [] _ FS.FileInfo[name: Rope.Substr[base: fullFName, start: srcPrefixLen], wantedCreatedTime: created, wDir: dstPrefix ! FS.Error => IF error.group=user THEN {doTheEnumerate _ TRUE; CONTINUE}]; IF doTheEnumerate THEN VisitClosure[dfName: fullFName, date: BasicTime.nullGMT, visitor: VisitEntry]; continue _ TRUE; }; FS.EnumerateForInfo[Rope.Concat[srcPrefix, "*.df!H"], EachFile]; }; ENDCASE => FS.EnumerateForInfo[Rope.Concat[srcPrefix, "*!h"], EachInfo]; IF debug THEN IO.PutChar[out, '\n]; IO.PutF[out, "Enumerated new files: %g, bytes: %g, IFS pages: %g\n", [integer[filesSeenDuringEnumeration]], [integer[bytesSeenDuringEnumeration]], [integer[ifsPagesDuringEnumeration]] ]; IO.PutF1[out, "{Moving files at %g}\n", [time[BasicTime.Now[]]] ]; RedBlackTree.EnumerateIncreasing[table, EachEntry]; FOR entryList: LIST OF FileEntry _ dfList, entryList.rest WHILE entryList # NIL DO MoveFile[entryList.first]; ENDLOOP; IF STP.IsOpen[stp] THEN STP.Close[stp]; IO.PutF1[out, "{Done at %g}\n", [time[BasicTime.Now[]]] ]; IO.PutF[out, "Files moved: %g, inCache: %g, alreadyRemote: %g\n\n", [integer[filesMoved]], [integer[filesInCache]], [integer[filesAlreadyThere]] ]; }; ShowTable: PROC [out: STREAM, table: RedBlackTree.Table] = { EachEntry: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => ShowEntry[out, entry]; ENDCASE => ERROR; }; RedBlackTree.EnumerateIncreasing[table, EachEntry]; }; ShowEntry: PROC [out: STREAM, entry: FileEntry] = { IO.PutF[out, "[name: %g, date: %g, len: %g, state: ", [rope[entry.name]], [time[entry.date]], [integer[entry.len]] ]; SELECT entry.state FROM init => IO.PutRope[out, "init]\n"]; fetching => IO.PutRope[out, "fetching]\n"]; storing => IO.PutRope[out, "storing]\n"]; moved => IO.PutRope[out, "moved]\n"]; ENDCASE; }; IsInFileCache: PUBLIC PROC [fullName: ROPE, entry: FileEntry] RETURNS [inCache: BOOL _ FALSE] = { cacheChecker: FSBackdoor.InfoProc = { IF bytes = entry.len AND created = entry.date THEN GO TO found; IF bytes > 0 AND entry.date = BasicTime.nullGMT THEN GO TO found; RETURN [TRUE]; EXITS found => {inCache _ TRUE; RETURN [FALSE]}; }; FSBackdoor.EnumerateCacheForInfo[cacheChecker, NIL, fullName]; }; TranslateHost: PROC [name: ROPE] RETURNS [ROPE] = { IF Rope.Match["[*]*", name] THEN { rPos: INT _ Rope.SkipTo[name, 1, "]"]; host: ROPE _ Rope.Substr[name, 1, rPos-1]; IF Rope.Length[host] # 0 THEN { nHost: ROPE _ FSPseudoServers.TranslateForWrite[host]; IF Rope.Length[nHost] # 0 THEN IF NOT Rope.Equal[nHost, host, FALSE] THEN name _ Rope.Flatten[Rope.Replace[name, 1, rPos-1, nHost]]; }; }; RETURN [name]; }; GetKey: RedBlackTree.GetKey = { RETURN [data]; }; Compare: RedBlackTree.Compare = { key: ROPE _ NIL; WITH k SELECT FROM ent: FileEntry => key _ ent.name; rope: ROPE => key _ rope; ENDCASE => ERROR; WITH data SELECT FROM ent: FileEntry => RETURN [Rope.Compare[key, ent.name, FALSE]]; ENDCASE; ERROR; }; CompareEntries: List.CompareProc = { WITH ref1 SELECT FROM ent1: FileEntry => WITH ref2 SELECT FROM ent2: FileEntry => RETURN [Rope.Compare[ent1.name, ent2.name, FALSE]]; ENDCASE; ENDCASE; ERROR; }; VisitClosure: PROC [dfName: ROPE, date: GMT, visitor: PROC [name: ROPE, date: GMT]] = { eachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM dir: REF DFUtilities.DirectoryItem => prefix _ TranslateHost[dir.path1]; file: REF DFUtilities.FileItem => { name: ROPE _ Rope.Concat[prefix, file.name]; IF prefix = NIL THEN name _ TranslateHost[name]; visitor[name, file.date.gmt]; }; incl: REF DFUtilities.IncludeItem => { file: ROPE _ TranslateHost[incl.path1]; visitor[file, incl.date.gmt]; VisitClosure[file, incl.date.gmt, visitor]; }; ENDCASE; }; prefix: ROPE _ NIL; in: STREAM _ FS.StreamOpen[fileName: dfName _ TranslateHost[dfName], wantedCreatedTime: date]; DFUtilities.ParseFromStream[in, eachItem, [FALSE, all, all, defining] ! UNWIND => IO.Close[in]]; IO.Close[in]; }; ParseTimeReference: PROC [ref: ROPE] RETURNS [valid, inRange: BOOL _ TRUE] ~ { IF ref=NIL THEN RETURN [TRUE, TRUE]; { ENABLE ANY => GOTO Fail; pos1, pos2, pos3: INT; pos1 _ Rope.Find[s1: ref, s2: "("]; IF pos1=-1 THEN GOTO Fail; pos2 _ Rope.Find[s1: ref, s2: "..", pos1: pos1+1]; IF pos2=-1 THEN GOTO Fail; pos3 _ Rope.Find[s1: ref, s2: ")", pos1: pos2+2]; inRange _ Tempus.PackedToSeconds[Tempus.Parse[rope: Rope.Substr[base: ref, start: pos1+1, len: pos2-pos1-1], search: FALSE].time] > Tempus.PackedToSeconds[Tempus.Parse[rope: Rope.Substr[base: ref, start: pos2+2, len: pos3-pos2-2], search: FALSE].time]; EXITS Fail => RETURN [FALSE, FALSE] }; }; TrickleCommandProc: Commander.CommandProc = { out: STREAM = cmd.out; switches: Switches _ ALL[FALSE]; timeRestriction: ROPE _ NIL; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; FOR index: INT IN [0..Rope.Length[arg]) DO char: CHAR _ Rope.Fetch[arg, index]; SELECT char FROM '- => LOOP; '~ => {sense _ NOT sense; LOOP}; IN ['a..'z] => switches[char] _ sense; IN ['A..'Z] => switches[char + ('a-'A)] _ sense; ENDCASE; sense _ TRUE; ENDLOOP; }; oldPriority: Process.Priority _ Process.GetPriority[]; table: RedBlackTree.Table _ RedBlackTree.Create[getKey: GetKey, compare: Compare]; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; pairList: PairList _ NIL; pairListTail: PairList _ NIL; FOR i: NAT IN [1..argv.argc) DO arg: ROPE _ argv[i]; Process.CheckForAbort[]; IF Rope.Length[arg] = 0 THEN LOOP; IF Rope.Fetch[arg, 0] = '- THEN { ProcessSwitches[arg]; LOOP; }; IF Rope.Fetch[arg, 0] = '( THEN { IF ~ParseTimeReference[arg].valid THEN { msg _ Rope.Cat["Invalid time restriction pair: \"", arg, "\""]; GO TO failed; }; timeRestriction _ arg; LOOP; }; { arg _ FS.ExpandName[arg, NIL ! FS.Error => { arg _ FS.ExpandName["$", arg ! FS.Error => { msg _ error.explanation; GO TO failed; }].fullFName; arg _ Rope.Flatten[arg, 0, Rope.Length[arg]-1]; GO TO ok; }; ].fullFName; EXITS ok => {}; }; IF pairListTail = NIL OR pairListTail.first.dst # NIL THEN { new: PairList _ LIST[[arg, NIL, switches, timeRestriction]]; IF pairListTail = NIL THEN pairList _ new ELSE pairListTail.rest _ new; pairListTail _ new; LOOP; }; IF NOT Rope.Match["*>", arg] AND NOT Rope.Match["*]", arg] THEN { msg _ IO.PutFR1["Destination not a directory (%g)", [rope[arg]] ]; GO TO failed; }; pairListTail.first.dst _ arg; pairListTail.first.switches _ switches; ENDLOOP; IF pairList = NIL THEN {msg _ "No arguments given.\n"; RETURN}; IF pairList.first.dst = NIL THEN {msg _ "Missing destination.\n"; RETURN}; { action: PROC = { DO FOR pair: PairList _ pairList, pair.rest WHILE pair # NIL DO IF ~ParseTimeReference[pair.first.timeRestriction].inRange THEN { IO.PutF[stream: out, format: "Not copying %g to %g this pass because %g is not in range %g.\n\n", v1: [rope[pair.first.src]], v2: [rope[pair.first.dst]], v3: [time[Tempus.Now[]]], v4: [rope[pair.first.timeRestriction]]]; LOOP; }; DoIt[table, out, pair.first.src, pair.first.dst, pair.first.switches ! FS.Error => { IO.PutF1[out, "FS.Error[%g], stopping this round.\n\n", [rope[error.explanation]]]; CONTINUE; }; ]; ENDLOOP; IF NOT switches['r] THEN EXIT; FOR timeLeft: INT _ repeatSeconds, timeLeft - maxPauseTime WHILE timeLeft > 0 DO Process.Pause[Process.SecondsToTicks[MIN[maxPauseTime, CARDINAL[timeLeft]]]]; ENDLOOP; ENDLOOP; }; CedarProcess.DoWithPriority[background, action]; }; EXITS failed => {result _ $Failure}; }; Commander.Register[ key: "///Commands/TrickleChargeServer", proc: TrickleCommandProc, doc: doc, clientData: NIL, interpreted: TRUE ]; END.  TrickleChargeServerImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) June 20, 1985 12:54:23 pm PDT Rick Beach, June 21, 1985 9:35:53 am PDT Dave Rumph, June 27, 1985 9:36:30 am PDT Bob Hagmann July 18, 1985 4:38:42 pm PDT Carl Hauser, March 11, 1986 2:07:25 pm PST Rick Beach, January 23, 1986 1:37:04 pm PST Eric Nickell February 25, 1986 12:10:10 pm PST TrickleChargeServer (switches) srcDir dstDir moves files from one remote directory to another. The behavior is governed by switches and options variables (see below). Implementation points: 1. Version numbers should be retained in order to keep the validity of the hints in DF files. This requires using STP (unfortunately). 2. Don't clutter the local disk with cached copies of transferred files. 3. Don't move files that are already there. 4. Support running this program in background to allow periodic updating of one directory from another. This is particularly useful for CedarChest. Types Source directory OR DF name (angle bracket syntax) Destination directory name (angle bracket syntax) switches in effect at argument capture time If timeRestriction#NIL, the it should contain Includes the srcPrefix, also includes version # Does not include the srcPrefix, does include version # create date of the file byte count of the file (useful redundancy) indicates the state of the file (obvious) Documentation Option variables # of times to retry connectionRejected from STP # of seconds between retry attemps # of seconds between repeats (when using the R switch) # of seconds to wait after a file transfer (to keep IFS load down) Command Procedures [fullFName: ROPE, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] This is likely to be the controlling file entry for an IFS (or it could just be a bogus file to be ignored) [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] The remote file is already present, so we don't need to move it We are verifying stuff and the entry is NOT on the desitnation Sigh, we actually have to ship the bits Force the transfers to happen on raw files It may be worth retrying later Retry, this time establishing the connection first Names with "." are GVNames (Grapevine names), so ask Grapevine to look them up If successful, use the connect as the server name for STP.Open This procedure is used to visit each file in a simple DF closure, where the imports are NOT followed, but the inclusions ARE followed. Phase1, build up data base. Don't move any files. [fullFName: ROPE, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] Phase2, move files. Don't change the entries (except for the 'moved' field). Phase2 1/2: move df files last. [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] [fullGName: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key] [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison] [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] When parsing the command line, be prepared for failure. The error is reported to the user Each argument can either be a switch specification or a genuine argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user. It is a good idea to periodically check for a process abort request. Ignore null arguments (it is not easy to generate them, even). This argument sets switches for the remaining patterns. By convention, switches are normally "sticky", in that they stay set until explicitly changed. This argument is a pair of time references, of the form: "( time1 .. time2 )" where time1 and time2 are parsable by Tempus, and therefore somewhat vague. Parse the argument. It must be either a directory OR a DF file name The argument is NOT a DF file name, so perhaps it is a directory The argument is not a valid name at all, so abort this nonsense Initialization Rick Beach, January 23, 1986 1:34:16 pm PST changes to: action (local of TrickleCommandProc) changed timeLeft: NAT to INT because timeLeft could go negative! Ê`˜codešœ™Kšœ Ïmœ1™ -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]šœ$˜$Kš¡:™:šžœžœž˜˜šžœžœ˜Kšžœ#žœžœ žœ˜MKšžœ˜K˜—Kšžœ˜K˜—Kšžœžœ˜—K˜—š œžœ˜%Kšœ žœ'˜8Kšœ žœ˜Kšœ žœ%˜4Kšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜!Kšœ˜šœ&žœ˜=Kšœžœ ž˜Kšœ˜—šžœžœžœ˜7Kšœ žœ˜Kšœ*˜*K˜—šžœžœž˜šœ ˜ Kšœ˜šžœ žœ˜Kšœ ˜ Kšžœ-˜/K˜—šžœ ž˜Kšžœ0˜2—K˜—šœ ˜ Kšœ?™?Kšœ˜K˜—šœ ˜ Kšœ>™>Kšœ˜šžœ žœ˜Kšœ ˜ Kšžœ+˜-K˜—Kšžœ,˜.K˜—šžœ˜ Kšœ'™'Kšœ žœžœ˜Kšœžœ˜Kšœ žœžœ˜Kšœžœžœ˜:šœžœ˜!Kšœ*™*—Kšœ˜šžœ ˜ šžœ˜Kšœ˜Kšœ ˜ K˜—šžœ˜Kšœ žœdžœ ˜€K˜——Kšœ žœjžœ˜Kšœ˜šœ5˜5šœ˜šžœ ˜šžœ/˜1Kšœ%˜%—šžœž˜˜Kšœ™šžœžœ˜#Kšœ ˜ K˜4Kšžœ˜K˜——˜K™2šžœžœ˜#Kšœ ˜ Kšžœ˜K˜——Kšžœ˜—Kšžœ˜Kšžœžœ˜ K˜—Kšžœžœ˜——Kšžœ˜Kšœ˜KšžœN˜Pšžœž˜Kšœ;˜;—Kšžœ˜K˜——K˜—š  œžœ žœžœžœ˜Jšžœ#žœ˜+Kšœ žœ#˜0Kšœ žœ,˜9Kšœ žœ*˜6Kšœžœ+˜5Kšœžœ8˜AKšœžœ'˜9š žœžœžœ žœž˜š œžœ žœžœ žœ˜Ccode2šžœžœžœ˜(N™NNšœ˜Nšœ˜šœ=˜=Nšœ6žœ™>—Nšžœžœžœžœ ˜:N˜—Nšžœ ˜N˜—Kšœ žœžœ˜Kšœžœžœ˜Kšœ1˜1Kšœžœ#˜+Kšžœ$˜'Kšžœžœžœ˜/Kšžœ˜Kšžœ˜—˜Kš žœžœžœ žœ žœ˜:K–n[from: STREAM, to: STREAM, closeFrom: BOOL _ TRUE, closeTo: BOOL _ TRUE, bufferByteCount: NAT _ 256]šžœW˜ZK˜—Kšœ˜K˜—K˜—š  œžœžœžœ˜,Kšœ†™†š   œžœžœžœ žœžœ˜?Kšœ žœ˜Kšœ žœ˜Kš žœžœ*žœžœžœžœ˜všžœžœžœž˜'šžœž˜šœ ˜ Kšœ˜šžœ+ž˜1Kšžœ˜ —K˜—Kšœžœ˜ Kšžœ˜—Kšžœ˜—Kšžœžœžœ"˜?K˜—Kšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœ˜šžœ"žœž˜1Kšœžœžœžœ˜5Kšžœ˜—šœ1žœ0˜cšžœ žœžœ"žœ˜SKšžœ9˜;Kšœžœ˜ Kšžœ˜ K˜Kšžœžœ˜ ——Kšœ˜Kšžœ žœžœžœ˜šœžœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜—šžœ"žœž˜1šœ˜Kšžœžœžœ˜!Kšœ&˜&K˜—Kšžœ˜—Kšœ&˜&Kšœ<˜˜@Kšœ˜—Kšžœžœ;˜H—Kšžœžœžœ˜#šžœB˜DKšœ&˜&Kšœ&˜&Kšœ'˜'—K˜KšœM™MKšžœ@˜BKšœ3˜3K™ š žœ žœžœ$žœ žœž˜RK˜Kšžœ˜—Kšžœžœ žœžœ ˜'Kšžœ8˜:šžœA˜CKšœO˜O—K˜K˜—š  œžœžœ ˜<–> -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]šœ$˜$Kš¡:™:šžœžœž˜Kšœ*˜*Kšžœžœ˜—K˜—Kšœ3˜3K˜K˜—š  œžœžœ˜3Kšžœs˜ušžœ ž˜Kšœžœ˜#Kšœ žœ˜+Kšœ žœ˜)Kšœ žœ˜%Kšžœ˜—K˜K˜—š  œžœžœ žœžœ žœžœ˜ašœ%˜%Kš œ žœžœ žœžœ™EKšžœ žœ™Kš žœžœžœžœžœ˜?Kš žœ žœ žœžœžœ˜AKšžœžœ˜Kšžœžœžœžœ˜0K˜—Kšœ/žœ ˜>K˜K˜—š   œžœžœžœžœ˜3šžœžœ˜"Kšœžœ˜&Kšœžœ ˜*šžœžœ˜Kšœžœ+˜6šžœž˜šžœžœžœž˜*K–M[base: ROPE, start: INT _ 0, len: INT _ 2147483647, with: ROPE _ NIL]šœ:˜:——K˜—K˜—Kšžœ˜Kšœ˜K˜—–< -- [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key]š œ˜Kš¡8™8Kšžœ˜K˜K˜—–R -- [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]š œ˜!Kš¡N™NKšœžœžœ˜šžœžœž˜Kšœ!˜!Kšœžœ˜Kšžœžœ˜—šžœžœž˜Kšœžœžœ˜>Kšžœ˜—Kšžœ˜K˜K˜—–> -- [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]š œ˜$Kš¡:™:šžœžœž˜šœ˜šžœžœž˜šœ˜Kšžœ%žœ˜3—Kšžœ˜——Kšžœ˜—Kšžœ˜K˜K˜—š  œžœ žœžœ žœžœžœ˜Wšœ)˜)šžœžœž˜Kšœžœ@˜Hšœžœ˜#Kšœžœ"˜,Kšžœ žœžœ˜0Kšœ˜K˜—šœžœ˜&Kšœžœ˜'Kšœ˜Kšœ+˜+K˜—Kšžœ˜—K˜—Kšœžœžœ˜KšœžœžœO˜^šœ+žœ˜EKšœžœžœ ˜—Kšžœ ˜ K˜—š  œžœžœžœžœžœ˜NKš žœžœžœžœžœžœ˜$šœ˜Kšžœžœžœ˜Kšœžœ˜K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]˜#Kšžœ žœžœ˜K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]˜2Kšžœ žœžœ˜K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]˜1–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]šœ ˜ Kšœkžœ˜wKšœmžœ˜z—Kšžœ žœžœžœ˜#Kšœ˜—K˜K˜—š œ˜-š œžœ žœžœžœžœ™:Kšœžœžœ™G—Kšœžœ ˜Kšœžœžœ˜ Kšœžœžœ˜š œžœžœ˜%Kšœžœžœ˜šžœžœžœž˜*Kšœžœ˜$šžœž˜Kšœžœ˜ Kšœžœžœ˜ Kšžœ$˜&Kšžœ.˜0Kšžœ˜—Kšœžœ˜ Kšžœ˜—K˜—K˜6K–@[getKey: RedBlackTree.GetKey, compare: RedBlackTree.Compare]šœR˜R–I[cmd: Commander.Handle, starExpand: BOOL _ FALSE, switchChar: CHAR]šœKž˜PKšœ)žœžœ ˜8KšœZ™ZK˜—Kšœžœ˜Kšœžœ˜K˜šžœžœžœž˜KšœÒ™ÒKšœžœ ˜˜KšœD™D—šžœžœžœ˜"Kšœ>™>—K˜šžœžœ˜!Kšœ—™—Kšœ˜Kšžœ˜Kšœ˜—šžœžœ˜!K™™šžœžœ˜(K˜?Kšžœžœ˜ Kšœ˜—K˜Kšžœ˜Kšœ˜—˜KšœD™Dšœžœž˜šœžœ ˜Kšœžœžœ(™@šœžœ˜šœžœ ˜Kšœ?™?K˜Kšžœžœ˜ Kšœ ˜ K˜/——Kšžœžœ˜ K˜—Kšœ ˜ —Kšžœ ˜K˜—š žœžœžœžœžœ˜