DIRECTORY AMBridge USING [TVForReferent], AMEventsExtra USING [Boot], Atom USING [GetPName, Gensym], ViewersSnapshot USING [Checkpoint, RollBack], CedarVersion USING [major, minor, patch], CIFS USING [Error, ErrorCode, GetFC, Open, OpenFile, Close, read, GetPathname, Reset], Commander USING [Register, CommandProc, Handle], CommandProcOps USING [CheckForAbort, EventFailed, GetTheFile], ConvertUnsafe USING [AppendRope, ToRope], Directory USING [DeleteFile, Error, GetProperty, GetProps, Lookup, PutProperty, Rename, ignore, PropertyType], File USING [Capability, GetSize], FileStream USING [SetLeaderPropertiesForCapability], FileIO USING [Open, OpenFailed, CapabilityFromStream, StreamFromCapability], GVBasics, GVNames USING [Authenticate], IO USING [AppendStreams, BreakProc, card, Close, CR, CreateEditedStream, CreateProcsStream, CreateRefStreamProcs, DeliverWhenProc, EndOf, EraseChar, ESC, GetBlock, GetLength, GetOutputStreamRope, GetRefAny, GetToken, GreenwichMeanTime, IDProc, int, LookupData, PeekChar, Put, PutBlock, PutChar, PutF, PutRope, RIS, ROPE, rope, ROS, SetEcho, SetLength, Signal, SkipOver, SP, StoreData, STREAM, time, TokenProc, tv, UserAborted, WhiteSpace], List USING [DReverse, Sort, CompareProc, Compare], MessageWindow USING [Blink, Append, Confirm], NameInfoDefs, PropertyTypes USING [tCreateDate], Rope USING [Cat, Concat, Equal, Fetch, Find, IsEmpty, Length, Replace, Substr, ToRefText], Runtime USING [GetBuildTime], System USING [GreenwichMeanTime, GetGreenwichMeanTime, SecondsSinceEpoch], Time USING [Current], UserExec USING [CheckForAbort, CheckForFile, CommandProc, ExecHandle, GetNameAndPassword, GetStreams, GetTheFile, HistoryEvent, RegisterCommand, RegisterTransformation, RopeSubst, SetNameAndPassword, TransformProc, UserAbort, UserAborted], UserExecPrivate USING [EventFailed, CallRegisteredProc, FileNotFound, GetRestOfStream, HistoryEventPrivateRecord, LookupCommand, ProcessProfile, RopeFromCMFile, StoreInSymTab, RunBCDFile], ViewerOps USING [EnumerateViewers, EnumProc] ; UserExecOpsImpl: CEDAR PROGRAM IMPORTS AMBridge, AMEventsExtra, Atom, CedarVersion, CIFS, Commander, CommandProcOps, ConvertUnsafe, Directory, File, FileIO, FileStream, GVNames, IO, List, MessageWindow, Rope, Runtime, System, Time, UserExec, UserExecPrivate, ViewerOps, ViewersSnapshot EXPORTS UserExec, UserExecPrivate = BEGIN OPEN IO; HistoryEventPrivateRecord: PUBLIC TYPE = UserExecPrivate.HistoryEventPrivateRecord; correct: ROPE; -- to be used in resume Run: Commander.CommandProc = { commandLineStream: STREAM = IO.RIS[cmd.commandLine]; anythingRun: BOOL _ FALSE; file, error: ROPE; DO next: ROPE; callDebuggerFirst: BOOL _ FALSE; IF file = NIL THEN file _ IO.GetToken[commandLineStream, IO.IDProc]; next _ IO.GetToken[commandLineStream, IO.IDProc]; IF Rope.IsEmpty[file] THEN EXIT; CommandProcOps.CheckForAbort[cmd]; IF Rope.Equal[next, "/D", FALSE] THEN { callDebuggerFirst _ TRUE; next _ NIL; }; [, error] _ UserExecPrivate.RunBCDFile[fileName: file, callDebuggerFirst: callDebuggerFirst, out: cmd.out ! UserExecPrivate.FileNotFound => { correct _ CommandProcOps.GetTheFile[file: name, defaultExt: defaultExt, handle: cmd]; RESUME[correct]; } ]; IF error # NIL THEN EXIT; anythingRun _ TRUE; file _ next; ENDLOOP; IF NOT anythingRun AND error = NIL THEN error _ "run what?"; IF error # NIL THEN CommandProcOps.EventFailed[cmd, error, file]; }; RunAndCall: PUBLIC UserExec.CommandProc = { commandLineStream: STREAM = event.commandLineStream; fileName, command, name, error: ROPE; fileName _ IO.GetToken[commandLineStream, IO.IDProc]; [name, error] _ UserExecPrivate.RunBCDFile[fileName: fileName, out: UserExec.GetStreams[exec].out ! UserExecPrivate.FileNotFound => {correct _ UserExec.GetTheFile[file: name, defaultExt: defaultExt, event: event, exec: exec]; RESUME[correct]; } ]; IF error # NIL THEN UserExecPrivate.EventFailed[event, error, fileName]; fileName _ Rope.Substr[base: name, len: Rope.Length[name] - 4]; -- strip off the bcd command _ UserExecPrivate.LookupCommand[fileName]; -- running the bcd may have registered a command. IF command # NIL THEN { UserExec.GetStreams[exec].out.PutChar['\n]; event.commandLine _ UserExecPrivate.GetRestOfStream[commandLineStream]; [] _ IO.RIS[event.commandLine, event.commandLineStream]; [] _ UserExecPrivate.CallRegisteredProc[command, event, exec]; }; }; AliasRecord: TYPE = RECORD [def: ROPE, args: LIST OF ROPE]; Alias: UserExec.CommandProc = { commandLineStream: STREAM = event.commandLineStream; name, def: ROPE; args: LIST OF ROPE _ NIL; name _ IO.GetToken[commandLineStream, IO.TokenProc]; IO.SkipOver[commandLineStream, IO.WhiteSpace]; IF NOT commandLineStream.EndOf[] AND commandLineStream.PeekChar[] = '( THEN { FOR l: LIST OF REF ANY _ NARROW[commandLineStream.GetRefAny[]], l.rest UNTIL l = NIL DO WITH l.first SELECT FROM r: ROPE => args _ CONS[r, args]; a: ATOM => args _ CONS[Atom.GetPName[a], args]; ENDCASE => ERROR; ENDLOOP; TRUSTED {args _ LOOPHOLE[List.DReverse[LOOPHOLE[args]]]}; }; def _ UserExecPrivate.GetRestOfStream[in: commandLineStream, includeLast: FALSE]; RegisterAlias[name, args, def]; }; RegisterAlias: PROCEDURE [name: ROPE, args: LIST OF ROPE, def: ROPE] = { PrintAlias: PROC RETURNS [doc: ROPE] = { out: STREAM = IO.ROS[]; out.PutRope["Alias "]; IF args # NIL THEN { out.PutChar['[]; FOR l: LIST OF ROPE _ args, l.rest UNTIL l = NIL DO out.PutRope[l.first]; IF l.rest # NIL THEN out.PutRope[", "]; ENDLOOP; out.PutRope["]: "]; }; out.Put[rope[def]]; RETURN[IO.GetOutputStreamRope[out]]; }; UserExec.RegisterTransformation[name: name, proc: ExpandAlias, doc: PrintAlias[], clientData: NEW[AliasRecord _ [def: def, args: args]]]; }; ExpandAlias: UserExec.TransformProc = { alias: REF AliasRecord _ NARROW[clientData]; synonyms: LIST OF REF SynonymRecord; SynonymRecord: TYPE = RECORD[key, val: ROPE]; RopeMemb: PROC [rope: ROPE, lst: LIST OF ROPE] RETURNS[BOOLEAN] = { FOR l: LIST OF ROPE _ lst, l.rest UNTIL l = NIL DO IF Rope.Equal[rope, l.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE] }; result _ alias.def; FOR l: LIST OF ROPE _ alias.args, l.rest UNTIL l = NIL DO new: ROPE _ IO.GetToken[event.commandLineStream, IO.IDProc]; IF RopeMemb[new, l.rest] THEN { -- e.g. args are (x y) and substituting y gorp. dummy: ROPE = Atom.GetPName[Atom.Gensym[]]; synonyms _ CONS[NEW[SynonymRecord _ [new, dummy]], synonyms]; new _ dummy; }; result _ UserExec.RopeSubst[old: l.first, new: new, base: result, case: FALSE]; ENDLOOP; FOR l: LIST OF REF SynonymRecord _ synonyms, l.rest UNTIL l = NIL DO result _ UserExec.RopeSubst[old: l.first.val, new: l.first.key, base: result, case: FALSE]; ENDLOOP; result _ Rope.Cat[result, UserExecPrivate.GetRestOfStream[in: event.commandLineStream, includeLast: TRUE]]; -- used to be FALSE. changed to TRUE because was not getting CR at end of event. }; DeleteFiles: UserExec.CommandProc = { commandLineStream: STREAM = event.commandLineStream; out: STREAM = UserExec.GetStreams[exec].out; file: ROPE; someMissing: BOOL _ FALSE; n: INT _ 0; numPages: INT _ 0; Delete: PROC [file: ROPE] RETURNS[deleted: BOOLEAN _ FALSE] = TRUSTED { found: BOOL _ TRUE; ls: LONG STRING = LOOPHOLE[Rope.ToRefText[file]]; cap: File.Capability; cap _ Directory.Lookup[fileName: ls, permissions: Directory.ignore ! Directory.Error => {found _ FALSE; CONTINUE}]; IF found THEN { numPages _ File.GetSize[cap] + numPages; deleted _ TRUE; Directory.DeleteFile[ls ! Directory.Error => {deleted _ FALSE; CONTINUE} ]; }; IF deleted THEN out.PutF["*nDeleted: %g\n", rope[file]] ELSE { out.PutF["*n*m%g not found.*s\n", rope[file]]; UserExecPrivate.EventFailed[event: event, offender: file]; -- and go on. }; }; DO UserExec.CheckForAbort[exec]; file _ IO.GetToken[commandLineStream, IO.IDProc]; IF Rope.Length[file] = 0 THEN EXIT; IF Delete[file] THEN n _ n + 1 ELSE someMissing _ TRUE; ENDLOOP; IF someMissing OR n > 1 THEN out.PutF["*nDeleted %d files, %d pages.", int[n], int[numPages]]; }; GetCreateDate: PROC [file: File.Capability] RETURNS [System.GreenwichMeanTime] = TRUSTED { createDate: System.GreenwichMeanTime; Directory.GetProperty[file: file, property: PropertyTypes.tCreateDate, propertyValue: DESCRIPTOR[@createDate, SIZE[System.GreenwichMeanTime]]]; RETURN [createDate]; }; SetCreateDate: PROC [file: File.Capability, createDate: System.GreenwichMeanTime] = TRUSTED { Directory.PutProperty[file: file, property: PropertyTypes.tCreateDate, propertyValue: DESCRIPTOR[@createDate, SIZE[System.GreenwichMeanTime]]]; }; RemoteNameProperty: Directory.PropertyType; GetRemoteFileNameProp: PROC[cap: File.Capability, remotename: LONG STRING] = TRUSTED { -- stolen from SubrImpl.mesa mlen: CARDINAL _ remotename.maxlength; IF mlen # 125 THEN ERROR; Directory.GetProperty[cap, RemoteNameProperty, DESCRIPTOR[remotename, SIZE[StringBody[mlen]]] ! Directory.Error => IF type = invalidProperty THEN GOTO leave]; LOOPHOLE[remotename+1, LONG POINTER TO CARDINAL]^ _ mlen; -- reset the maxlength!!! EXITS leave => remotename.length _ 0; }; SetRemoteFileNameProp: PROC[cap: File.Capability, remotename: LONG STRING] = TRUSTED { -- remotename should be of the form [ivy]name!vers IF remotename.maxlength ~= 125 THEN ERROR; Directory.PutProperty[cap, RemoteNameProperty, DESCRIPTOR[remotename, SIZE[StringBody[remotename.maxlength]]], TRUE]; }; CopyFiles: UserExec.CommandProc = { DoCopyFiles[exec, event, event.commandLineStream]; }; DoCopyFiles: PROC[exec: UserExec.ExecHandle, event: UserExec.HistoryEvent, stream: STREAM] = { out: STREAM = UserExec.GetStreams[exec].out; buffer: REF TEXT; bufferSize: CARDINAL = 512; bytes, totalBytes, numberOfFiles: INT _ 0; newF: STREAM; newN, oldN, arrow: ROPE; createDate: System.GreenwichMeanTime; capability: File.Capability; remoteName: ROPE; idProc: IO.BreakProc = { RETURN[IF char = '_ THEN break ELSE IO.IDProc[char]]; }; CopyOneFile: PROC = { r: ROPE; oldCap: File.Capability; oldF: STREAM; openFile: CIFS.OpenFile; oldLen: INT; r _ UserExec.GetTheFile[file: oldN, event: event, exec: exec]; IF r = NIL THEN { openFile _ CIFS.Open[name: oldN, mode: CIFS.read ! CIFS.Error => TRUSTED { out.PutF["*n*m%g: %g", rope[oldN], tv[AMBridge.TVForReferent[NEW[CIFS.ErrorCode _ code]]]]; -- converts enumerated type into rope, avoids big hairy select IF error # NIL THEN out.PutF[" %g", rope[error]]; -- CIFS error rope out.PutF["*s\n"]; UserExecPrivate.EventFailed[event: event, offender: oldN]; GOTO Error; }; ]; oldCap _ CIFS.GetFC[openFile]; oldF _ FileIO.StreamFromCapability[capability: oldCap, accessOptions: read, raw: TRUE]; }; numberOfFiles _ numberOfFiles + 1; IF oldF = NIL THEN { oldF _ FileIO.Open[fileName: r, accessOptions: read, createOptions: oldOnly, raw: TRUE]; oldCap _ FileIO.CapabilityFromStream[oldF]; }; createDate _ GetCreateDate[oldCap]; oldLen _ oldF.GetLength[]; IF newF = NIL THEN -- first time -- newF _ FileIO.Open[fileName: newN, accessOptions: overwrite, createLength: oldLen ! FileIO.OpenFailed => IF why = notImplementedYet THEN { out.PutF["*n*m%g not implemented yet.*s\n", rope[newN]]; UserExecPrivate.EventFailed[event: event]; GOTO Error; }; ] ELSE newF.SetLength[totalBytes + oldLen]; DO bytes _ oldF.GetBlock[buffer]; IF bytes=0 THEN EXIT; totalBytes _ totalBytes + bytes; newF.PutBlock[buffer]; ENDLOOP; oldF.Close[]; IF openFile # NIL THEN TRUSTED { remoteName _ CIFS.GetPathname[openFile]; -- the intent was to get the full path name, complete with version number. Unfortunately, CIFS does not currently support this CIFS.Close[openFile]; CIFS.Reset[oldN]; -- otherwise there would be two copies on the disk! }; EXITS Error => RETURN }; newN _ IO.GetToken[stream, idProc]; arrow _ IO.GetToken[stream, idProc]; IF Rope.Length[newN] = 0 OR NOT Rope.Equal[arrow, "_"] THEN { out.PutF["*eArgument missing or syntax error for copy!*s"]; RETURN; }; buffer _ NEW[TEXT[bufferSize]]; WHILE Rope.Length[oldN _ IO.GetToken[stream, idProc]] # 0 DO CopyOneFile[]; ENDLOOP; IF newF = NIL THEN RETURN; capability _ FileIO.CapabilityFromStream[newF]; newF.Close[]; out.PutF["*n*sCopied %d bytes", card[totalBytes]]; IF numberOfFiles = 1 THEN { IF remoteName # NIL THEN TRUSTED { cap: File.Capability = Directory.Lookup[fileName: LOOPHOLE[Rope.ToRefText[newN]], permissions: Directory.ignore]; remoteNameString: LONG STRING _ [125]; ConvertUnsafe.AppendRope[to: remoteNameString, from: remoteName]; SetRemoteFileNameProp[cap, remoteNameString]; }; SetCreateDate[capability, createDate]; } ELSE out.PutF[" from %g files", card[numberOfFiles]]; out.PutF[" to %g", rope[newN]]; }; FetchFiles: UserExec.CommandProc = { DO i, j: INT; localFile, remoteFile: ROPE; remoteFile _ IO.GetToken[event.commandLineStream, IO.IDProc]; IF Rope.Length[remoteFile] = 0 THEN EXIT; UserExec.CheckForAbort[exec]; IF (i _ Rope.Find[remoteFile, "/"]) # -1 THEN UNTIL (j _ Rope.Find[s1: remoteFile, s2: "/", pos1: i + 1]) = -1 DO i _ j; ENDLOOP ELSE IF (i _ Rope.Find[remoteFile, ">"]) # -1 THEN UNTIL (j _ Rope.Find[s1: remoteFile, s2: ">", pos1: i + 1]) = -1 DO i _ j; ENDLOOP ELSE UserExecPrivate.EventFailed[event, Rope.Concat["Not a remote file: ", remoteFile], remoteFile]; IF (j _ Rope.Find[s1: remoteFile, s2: "!"]) = -1 THEN j _ Rope.Length[remoteFile]; localFile _ Rope.Substr[base: remoteFile, start: i + 1, len: j - (i + 1)]; DoCopyFiles[exec: exec, event: event, stream: IO.RIS[Rope.Cat[localFile," _ ", remoteFile]]]; ENDLOOP; }; WhereFrom: UserExec.CommandProc = { commandLine: ROPE _ event.commandLine; out: STREAM = UserExec.GetStreams[exec].out; len: INT; stream: IO.STREAM; spellingCorrect: BOOL _ FALSE; file: ROPE; Lookup: PROC [file: ROPE] = TRUSTED -- Directory -- { txt: REF TEXT; name: LONG STRING; cap: File.Capability; failed: BOOLEAN _ FALSE; IF spellingCorrect THEN { r: ROPE = UserExec.GetTheFile[file: file, event: event, exec: exec]; IF r = NIL THEN GOTO NotFound; txt _ Rope.ToRefText[r]; } ELSE IF UserExec.CheckForFile[file] THEN txt _ Rope.ToRefText[file] ELSE GOTO NotFound; name _ LOOPHOLE[txt];-- Directory.GetProps may write into name, e.g. to change lower case to capitals. cap _ Directory.Lookup[fileName: name, permissions: Directory.ignore ! Directory.Error => {failed _ TRUE; CONTINUE}]; IF failed THEN ERROR -- because already did a CheckForFile ELSE { remoteName: LONG STRING _ [125]; GetRemoteFileNameProp[cap, remoteName]; IF remoteName.length # 0 THEN out.PutF["*n%g = %g\n", rope[file], rope[ConvertUnsafe.ToRope[remoteName]]] ELSE out.PutF["*n*mNo remote path for %g\n", rope[file]]; }; EXITS NotFound => { out.PutF["*n*m%g not found*s", rope[file]]; UserExecPrivate.EventFailed[event: event, offender: file]; }; }; -- of Lookup len _ Rope.Length[commandLine]; IF len > 1 AND Rope.Fetch[commandLine, len - 2] = '! THEN { -- special case check for ! here and in eval. If this occurs in several places, probably should handle it by filtering out at a higher point and setting the tryHarder field of the event to TRUE. spellingCorrect _ TRUE; commandLine _ Rope.Replace[base: commandLine, start: len - 2, len: 1]; }; stream _ IO.RIS[commandLine]; DO file _ IO.GetToken[stream, IO.IDProc]; IF Rope.Length[file] = 0 THEN EXIT; UserExec.CheckForAbort[exec]; Lookup[file]; ENDLOOP; }; ListFiles: UserExec.CommandProc = { [] _ DoListFiles[event, exec]; }; ListByDates: UserExec.CommandProc = { compare: List.CompareProc = TRUSTED { x, y: REF FileRec; x _ NARROW[ref1]; y _ NARROW[ref2]; RETURN[IF System.SecondsSinceEpoch[x.createDate] < System.SecondsSinceEpoch[y.createDate] THEN greater ELSE less]; -- more recently created files come first }; [] _ DoListFiles[event, exec, compare]; }; FileRec: TYPE = RECORD[name: ROPE, createDate: System.GreenwichMeanTime, byteLength, numPages: LONG CARDINAL]; DoListFiles: PROC [event: UserExec.HistoryEvent, exec: UserExec.ExecHandle, compare: List.CompareProc _ NIL] = { commandLine: ROPE _ event.commandLine; out: STREAM = UserExec.GetStreams[exec].out; len: INT; numFiles, numPages: INT _ 0; stream: IO.STREAM; spellingCorrect: BOOL _ FALSE; files: LIST OF REF FileRec; file: ROPE; Lookup: PROC [file: ROPE] = TRUSTED -- Directory -- { txt: REF TEXT; name: LONG STRING; cap: File.Capability; failed: BOOLEAN _ FALSE; IF spellingCorrect THEN { r: ROPE = UserExec.GetTheFile[file: file, event: event, exec: exec]; IF r = NIL THEN GOTO NotFound; txt _ Rope.ToRefText[r]; } ELSE IF UserExec.CheckForFile[file] THEN txt _ Rope.ToRefText[file] ELSE GOTO NotFound; name _ LOOPHOLE[txt];-- Directory.GetProps may write into name, e.g. to change lower case to capitals. cap _ Directory.Lookup[fileName: name, permissions: Directory.ignore ! Directory.Error => {failed _ TRUE; CONTINUE}]; IF failed THEN ERROR ELSE { createDate: IO.GreenwichMeanTime; byteLength: LONG CARDINAL; [, , createDate,byteLength,] _ Directory.GetProps[file: cap, name: name ! Directory.Error => IF type = invalidProperty THEN { out.PutF["*n*e%g has bad leader page*s", rope[file]]; GOTO Error; }]; IF compare = NIL THEN Print[file, createDate, byteLength, File.GetSize[cap]] -- so user gets to see them as they are processed ELSE files _ CONS[NEW[FileRec _ [name: file, createDate: createDate, byteLength: byteLength, numPages: File.GetSize[cap]]], files]; -- will be sorted first }; EXITS Error => {}; NotFound => { out.PutF["*n*m%g not found*s", rope[file]]; UserExecPrivate.EventFailed[event: event, offender: file]; }; }; -- of Lookup Print: PROC [name: ROPE, createDate: IO.GreenwichMeanTime, byteLength, nPages: LONG CARDINAL] = { out.PutF["*s*n%-30g %6d %g\n", rope[name], card[byteLength], time[createDate]]; numPages _ numPages + nPages; numFiles _ numFiles + 1; }; -- of Print len _ Rope.Length[commandLine]; IF len > 1 AND Rope.Fetch[commandLine, len - 2] = '! THEN { -- special case check for ! here and in eval. If this occurs in several places, probably should handle it by filtering out at a higher point and setting the tryHarder field of the event to TRUE. spellingCorrect _ TRUE; commandLine _ Rope.Replace[base: commandLine, start: len - 2, len: 1]; }; stream _ IO.RIS[commandLine]; DO file _ IO.GetToken[stream, IO.IDProc]; IF Rope.Length[file] = 0 THEN EXIT; UserExec.CheckForAbort[exec]; Lookup[file]; ENDLOOP; IF compare # NIL THEN TRUSTED { files _ LOOPHOLE[List.Sort[LOOPHOLE[files], compare]]; FOR lst: LIST OF REF FileRec _ files, lst.rest UNTIL lst = NIL DO UserExec.CheckForAbort[exec]; Print[lst.first.name, lst.first.createDate, lst.first.byteLength, lst.first.numPages] ENDLOOP; }; out.PutF["*n*sTotal of %d files, %d pages", int[numFiles], int[numPages]]; }; Rename: UserExec.CommandProc = { commandLineStream: STREAM = event.commandLineStream; out: STREAM = UserExec.GetStreams[exec].out; oldName, newName, r: ROPE; idProc: IO.BreakProc = { RETURN[IF char = '_ THEN break ELSE IO.IDProc[char]]; }; oldName _ IO.GetToken[commandLineStream, idProc]; newName _ IO.GetToken[commandLineStream, idProc]; IF Rope.Equal[newName, "_"] THEN { newName _ oldName; oldName _ IO.GetToken[commandLineStream, idProc]; }; IF Rope.Length[oldName] = 0 OR Rope.Length[newName] = 0 THEN UserExecPrivate.EventFailed[event: event, msg: "Empty file name"]; r _ UserExec.GetTheFile[file: oldName, event: event, exec: exec]; IF r = NIL THEN UserExecPrivate.EventFailed[event: event, msg: Rope.Concat[oldName, " Not Found"], offender: oldName]; oldName _ r; RETURN[RenameFile[oldName, newName, out]]; }; RenameFile: PUBLIC PROC [oldName, newName: ROPE, out: STREAM] RETURNS[success: BOOL] = { msg, r: ROPE; { Rename1: PROC [old, new: ROPE] = TRUSTED { olds: LONG STRING _ [60]; news: LONG STRING _ [60]; ConvertUnsafe.AppendRope[to: olds, from: old]; ConvertUnsafe.AppendRope[to: news, from: new]; Directory.Rename[olds, news]; FileStream.SetLeaderPropertiesForCapability[cap: Directory.Lookup[news], write: Time.Current[]]; }; Delete1: PROC [file: ROPE] = TRUSTED { Directory.DeleteFile[LOOPHOLE[Rope.ToRefText[file]] ! Directory.Error => CONTINUE]; }; Rename1[oldName, newName ! Directory.Error => { SELECT type FROM fileAlreadyExists => {IF Rope.Equal[oldName, newName, FALSE] THEN TRUSTED {Delete1["$$scratch" ! Directory.Error => CONTINUE]; Rename1[oldName, "$$scratch"]; Rename1["$$scratch", newName]; CONTINUE; }; r _ Rope.Concat[newName, "$$"]; IF Rope.Equal[r, oldName, FALSE] THEN -- e.g. user is in fact performing rename foo.mumble _ foo.mumble$$ {Delete1[newName]; RETRY; }; Delete1[r ! Directory.Error => CONTINUE]; Rename1[newName, r]; out.PutF["*s%g already existed, now renamed to %g\n", rope[newName], rope[r]]; RETRY; }; fileNotFound => msg _ "File Not Found"; invalidFileName => msg _ "Invalid File Name"; ENDCASE => msg _ "Directory Error"; out.PutF["*e%g, ", rope[msg]]; GOTO fail; }; ]; out.PutF["%g renamed to %g", rope[oldName], rope[newName]]; RETURN[TRUE]; EXITS fail => {out.PutRope["Unable to complete rename."]; RETURN[FALSE]}; }; }; LoginProc: Commander.CommandProc = { errorMessage: ROPE = LoginFromStreams[cmd.in, cmd.out]; IF errorMessage # NIL THEN CommandProcOps.EventFailed[cmd, errorMessage]; UserExecPrivate.ProcessProfile[]; }; Login: PUBLIC PROC [in, out: STREAM] RETURNS[name, password: ROPE] = { errorMessage: ROPE = LoginFromStreams[in, out]; IF errorMessage # NIL THEN { MessageWindow.Append[errorMessage, TRUE]; RETURN[NIL, NIL]; } ELSE { [name, password] _ UserExec.GetNameAndPassword[]; UserExecPrivate.ProcessProfile[]; }; }; LoginFromStreams: PROC [in, out: STREAM] RETURNS[errorMessage: ROPE] = { name, password: ROPE; UNTIL in.backingStream = NIL DO in _ in.backingStream; ENDLOOP; in _ IO.CreateEditedStream[in: in, echoTo: out, deliverWhen: IODeliverWhen]; name _ UserExec.GetNameAndPassword[].name; out.PutF["*sUser Name: " ! IO.Signal => IF ec = NotImplementedForThisStream THEN RESUME]; in.StoreData[key: $Count, data: NEW[INT _ Rope.Length[name]]]; IO.AppendStreams[in, RIS[name]]; name _ IO.GetToken[in, IO.IDProc ! IO.Signal => SELECT ec FROM Rubout => ERROR IO.UserAborted; EmptyBuffer => RESUME; ENDCASE; ]; out.PutRope["Password: "]; [] _ in.SetEcho[IO.CreateProcsStream[ streamProcs: IO.CreateRefStreamProcs[putChar: PutStar, eraseChar: EraseStar], backingStream: out, streamData: NIL]]; password _ IO.GetToken[in, IO.IDProc ! IO.Signal => SELECT ec FROM Rubout => ERROR IO.UserAborted; EmptyBuffer => RESUME; ENDCASE; ]; IF (errorMessage _ AuthenticateUser[name, password]) # NIL THEN RETURN; UserExec.SetNameAndPassword[name, password]; }; AuthenticateUser: PROC [name, password: ROPE] RETURNS [msg: ROPE _ NIL] = { IF Rope.Find[name, "."] = -1 THEN name _ Rope.Concat[name, ".pa"]; SELECT GVNames.Authenticate[name, password] FROM group => RETURN["... Can't login as group"]; notFound => RETURN[Rope.Concat[name, " invalid user name"]]; allDown => RETURN["Can't authenticate, no server responded"]; badPwd => RETURN["Invalid password"]; ENDCASE; }; IODeliverWhen: IO.DeliverWhenProc = { count: REF INT = NARROW[stream.LookupData[$Count]]; count^ _ count^ - 1; IF count^ > 0 THEN RETURN[FALSE] -- the idea is that if there are illegal characters, e.g. space, cr, etc. in user name, then don't want to have these terminate, or else could never change this again! ELSE RETURN[SELECT char FROM CR, SP, ESC => TRUE, ENDCASE => FALSE]; }; PutStar: PROC [self: STREAM, char: CHARACTER] = { SELECT char FROM CR, SP, ESC => NULL; ENDCASE => self.backingStream.PutChar['*] }; EraseStar: PROC [self: STREAM, char: CHARACTER] = { self.backingStream.EraseChar['*]; }; -- if default it, would print \ and char, causing two *'s. TypeVersion: UserExec.CommandProc = TRUSTED { out: STREAM = UserExec.GetStreams[exec].out; out.PutF[ "You are running Cedar %g.%g", IO.int[CedarVersion.major], IO.int[CedarVersion.minor]]; IF CedarVersion.patch # 0 THEN out.PutF[".%g", IO.int[CedarVersion.patch]]; out.PutF[" of %t\n", IO.time[Runtime.GetBuildTime[]]]; RETURN [TRUE] ; }; TypeDaytime: UserExec.CommandProc = TRUSTED { out: STREAM = UserExec.GetStreams[exec].out; time: System.GreenwichMeanTime = System.GetGreenwichMeanTime[]; out.PutF["The time is %g\n", IO.time[time]]; UserExecPrivate.StoreInSymTab[value: AMBridge.TVForReferent[NEW[System.GreenwichMeanTime _ time]], event: event, exec: exec]; RETURN [TRUE]; }; TypeUser: UserExec.CommandProc = TRUSTED { out: STREAM = UserExec.GetStreams[exec].out; user: ROPE = UserExec.GetNameAndPassword[].name; out.PutF["The logged-in user is %g\n", IO.rope[user]]; UserExecPrivate.StoreInSymTab[value: AMBridge.TVForReferent[NEW[ROPE _ user]], event: event, exec: exec]; RETURN [TRUE]; }; BootCurrent: UserExec.CommandProc = TRUSTED { IF NOT CheckForEdits[] THEN UserExec.UserAborted[exec]; AMEventsExtra.Boot[boot: [logical[]]]; }; RollBack: UserExec.CommandProc = TRUSTED { IF NOT CheckForEdits[] THEN UserExec.UserAborted[exec]; ViewersSnapshot.RollBack[]; }; CheckPoint: UserExec.CommandProc = TRUSTED { IF NOT CheckForEdits[] THEN UserExec.UserAborted[exec]; [] _ ViewersSnapshot.Checkpoint[]; }; CheckForEdits: PROC RETURNS[ok: BOOL] = { edits: BOOL _ FALSE; proc: ViewerOps.EnumProc = { IF v.newVersion THEN {edits _ TRUE; RETURN[FALSE]}; -- terminate RETURN[TRUE]; }; ViewerOps.EnumerateViewers[proc]; IF NOT edits THEN RETURN[TRUE]; MessageWindow.Append["Confirm discard of edits . . .", TRUE]; MessageWindow.Blink[]; RETURN[MessageWindow.Confirm[]]; }; CommandsFrom: UserExec.TransformProc = { commandLineStream: STREAM = event.commandLineStream; fileName: ROPE; contents, name: ROPE; fileName _ IO.GetToken[commandLineStream, IO.IDProc]; [contents, name] _ UserExecPrivate.RopeFromCMFile[file: fileName, event: event, exec: exec]; IF event # NIL THEN {private: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; event.dontCorrect _ TRUE; private.inCommandFile _ TRUE; private.inCMFile _ (Rope.Find[s1: name, s2: ".cm", case: FALSE] # -1); }; result _ Rope.Concat[contents, UserExecPrivate.GetRestOfStream[commandLineStream]]; }; -- of CommandsFrom TRUSTED {RemoteNameProperty _ LOOPHOLE[213B]}; Commander.Register["Login", LoginProc, "Supply user name and password."]; UserExec.RegisterCommand["Delete", DeleteFiles, "Delete a list of files."]; UserExec.RegisterCommand["Rename", Rename, "Renames a file", "Renames a file. Syntax is Rename old new, or Rename new _ old."]; UserExec.RegisterCommand["Copy", CopyFiles, "Copy contents of one or more files to another (new _ old1 old2 ...)."]; UserExec.RegisterCommand["Fetch", FetchFiles, "Copies contents of remote file(s) to corresponding local file(s)."]; UserExec.RegisterCommand["WhereFrom", WhereFrom, "Prints the name of the remote file(s) that the local file(s) came from, if any."]; UserExec.RegisterCommand["List", ListFiles, "Prints size, creation date for the indicated files."]; UserExec.RegisterCommand["ListByDates", ListByDates, "Prints size, creation date for the indicated files, sorted by create date."]; Commander.Register["Run", Run, "Load and Start the named programs.\n/d following a file name means just load the corresponding program, but do not start it. Instead, call the debugger, e.g. so that you can plant breakpoints. Proceeding from the corresponding action area will then start the program."]; UserExec.RegisterCommand["RunAndCall", RunAndCall, "Load and Start the named program. Then call the registered procedure, if any."]; UserExec.RegisterCommand["Alias", Alias, "Defines an Alias, Form is Alias (args) rest-of-line.", "Defines an Alias. Aliases provide a substitution macro facility for the first token on a line, e.g. Alias br bringover /o /a would cause 'br file df' to be transformed to 'bringover /o /a file df'. An optional argument list is permitted following the alias name, in which case tokens from the input line are substituted for the arguments, e.g. Alias both (file) compile file '; bind file would cause 'both foo' to be transformed to 'compile foo; bind foo'."]; UserExec.RegisterCommand["Version", TypeVersion, "Types the version number and build time of the Cedar boot file you are running."]; UserExec.RegisterCommand[ "Date", TypeDaytime, "Types today's date and time."]; UserExec.RegisterCommand["User", TypeUser, "Types the name of the logged-in user."]; UserExec.RegisterTransformation["@", CommandsFrom, "Treat the contents of the named file as a command file.", "If {file} ends in .cm (or {file} has no extension and there is a {file}.cm), treat the {file} as an old-style command file, i.e. when interpreting the contents, treat {fileName} by itself on a line as Run {fileName}. Similarly, treat @{fileName} by itself the same as CommandsFrom {fileName}. Interpreted {command}.~ to mean invoke the indicated registered command. \nOtherwise, if {file} ends in .commands (or the {file} has no extension and there is a {file}.commands), treat the file as a new-style command file, i.e. treat the contents of the {file} the same as though they had been typed."]; UserExec.RegisterCommand["BootCurrent", BootCurrent, "Boots current volume."]; UserExec.RegisterCommand["RollBack", RollBack, "Does a rollback."]; UserExec.RegisterCommand["CheckPoint", CheckPoint, "Does a checkpoint."]; END. August 12, 1982 4:15 pm fixed login to call userprofile.profilechanged September 9, 1982 11:03 am fixed listfiles to catch Directory.Error[type: invalidProperty] .UserExecOpsImpl.mesa; Edited by Teitelman on June 20, 1983 1:13 pm implements registered commands. See also viewerexecopsimpl connecting concrete and opaque types to access inCommandFile and inCMFile in CommandsFrom Run, RunandCall Aliasing File Operations: Delete, Rename, Copy, Fetch, List this is erroneous, because reading the property will set the maxlength Logging In Version, Daytime, User CheckPoint, Rollback, and BootCurrent Miscellaneous Initialization Edited on May 3, 1983 9:26 pm, by Teitelman fixed copy command to take remote file names changes to: CopyFiles, CopyOneFile (local of CopyFiles), ListFiles, DoList, Lookup (local of DoList), Print (local of DoList) Edited on May 12, 1983 5:59 pm, by Teitelman changes to: DIRECTORY, IMPORTS, CheckPoint, CheckForEdits Edited on May 23, 1983 4:38 pm, by Teitelman changes to: FetchFiles Edited on June 1, 1983 4:12 pm, by Teitelman changes to: DIRECTORY, RemoteNameProperty, GetRemoteFileNameProp, SetRemoteFileNameProp, CopyFiles, FetchFiles, WhereFrom, Lookup (local of WhereFrom), ListFiles, TRUSTED, Commander Edited on June 6, 1983 1:17 pm, by Teitelman changes to: AuthenticateUser Edited on June 20, 1983 10:16 am, by Teitelman changes to: CopyOneFile (local of DoCopyFiles), DIRECTORY, idProc (local of Rename), Rename, DoCopyFiles, DIRECTORY Ê&– "Cedar" style˜JšÏc-œ™BJš:™:J˜šÏk ˜ Jšœ žœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜-Jšœ žœ˜)JšžœžœL˜VJšœ žœ!˜0Jšœžœ*˜>Jšœžœ˜)Jšœ žœ_˜nJšœžœ˜!Jšœ žœ$˜4Jšœžœ@˜LJ˜ Jšœžœ˜Jšžœžœ)žœbžœŸžœžœžœ(žœ žœ0˜¸Jšœžœ(˜2Jšœžœ˜-J˜ Jšœžœ˜"JšœžœS˜]Jšœžœ˜Jšœžœ>˜JJšœžœ ˜Jšœ žœá˜ïJšœžœ§˜¼Jšœ žœ˜,J˜—J˜JšÐblœžœž˜J˜JšžœŒžœn˜ƒJ˜Jšžœ˜"J˜Jšœž œžœ˜headšœ$™$šÏnœžœžœ-˜SJšœ4™4—Jš œžœ˜&—™š œž˜ Jšœžœ˜4Jšœ ž œ˜Jšœ žœ˜šž˜Jšœžœ˜ Jšœžœžœ˜ Jšžœžœžœ2˜DJšœ1˜1Jšžœžœžœ˜ Jšœ"˜"šžœžœžœ˜'Jšœž˜Jšœžœ˜ J˜—šœk˜kšœ!˜!JšœU˜UJšžœ ˜J˜—J˜—Jšžœ žœžœžœ˜Jšœ ž˜Jšœ ˜ Jšžœ˜—Jš žœžœ žœ žœžœ˜˜>J˜—Jšžœ˜J˜——™Jš  œžœžœžœžœžœžœ˜;š œž˜ Jšœžœ˜4Jšœ žœ˜Jš œžœžœžœžœ˜Jšœ4˜4Jšœ.˜.šžœžœžœ#ž˜Mšžœžœžœžœžœžœ(žœžœž˜Xšžœ žœž˜Jšœžœ žœ ˜ Jšœžœ žœ˜/Jšžœžœ˜—Jšžœ˜—Jšžœ žœžœ ˜9J˜—JšœJžœ˜QJ˜Jšžœ˜J˜—š  œž œžœžœžœžœžœ˜Hš  œžœžœžœ˜(Jšœžœžœžœ˜J˜šžœžœž˜J˜š žœžœžœžœžœžœž˜3J˜Jšžœ žœžœ˜'Jšžœ˜—J˜J˜—J˜Jšžœžœ˜$J˜—Jšœ^žœ(˜‰J˜—š  œ˜'Jšœžœžœ ˜,Jšœ žœžœžœ˜$Jš  œ žœ žœ˜-š œžœžœžœžœžœžœžœ˜Cš žœžœžœžœžœžœž˜2Jš žœžœžœžœžœ˜6Jšžœ˜—Jšžœžœ˜ J˜—Jšœ˜š žœžœžœžœžœžœž˜9Jšœžœžœ#žœ ˜<šžœžœ/˜PJšœžœ ˜+Jšœ žœžœ*˜=J˜ J˜—JšœHžœ˜PJšžœ˜—š žœžœžœžœ"žœžœž˜DJšœTžœ˜\Jšžœ˜—JšœdžœP˜¾Jšœ˜——™2š  œž˜&Jšœžœ˜4Jšœžœ!˜,Jšœžœ˜ Jšœ žœžœ˜Jšœžœ˜ Jšœ žœ˜š œžœžœžœ žœžœžœ˜GJšœžœžœ˜Jšœžœžœžœ˜1Jšœ˜˜BJšœžœžœ˜2—šžœžœ˜J˜(Jšœ žœ˜šœ˜Jšœžœžœ˜.Jšœ˜—J˜—Jšžœ žœ(˜7šžœ˜Jšœ.˜.Jšœ< ˜IJ˜—Jšžœ˜—šž˜Jšœ˜Jšœ1˜1Jšžœžœžœ˜#Jšžœžœ ˜Jšžœžœ˜Jšžœ˜—Jšžœ žœžœB˜^Jšžœ˜J˜—š  œžœžœžœ˜ZJ˜%˜FJšœž œžœ˜H—Jšžœ˜Jšœ˜—J˜š  œžœAžœ˜]˜FJšœž œžœ˜H—Jšœ˜J˜—Jš œ˜+J˜š  œžœ#žœžœžœ˜sJšœžœ˜&JšœF™FJšžœ žœžœ˜šœ/ž œ žœ(˜rJšžœžœžœ˜+—Jš žœžœžœžœžœ ˜SJšž˜Jšœ˜Jšœ˜—J˜š  œžœ#žœžœžœ=˜”Jšžœžœžœ˜*Jšœ/ž œ žœ%žœ˜uJšœ˜—J˜š  œž˜#Jšœ2˜2šžœ˜J˜——š  œžœBžœ˜^Jšœžœ!˜,Jšœžœžœ˜Jšœ žœ˜Jšœ"žœ˜*Jšœžœ˜ Jšœžœ˜J˜%J˜Jšœ žœ˜J˜š œžœ˜Jš žœžœ žœžœžœ˜5J˜—š  œžœ˜Jšœžœ˜Jšœ˜Jšœžœ˜ Jšœ žœ ˜Jšœžœ˜ Jšœ>˜>šžœžœžœ˜š œ žœžœžœ žœ˜JJšœ=žœžœ>˜›Jšžœ žœžœ!˜FJšœ˜Jšœ:˜:Jšžœ˜ Jšœ˜Jšœ˜—Jšœ žœ˜JšœQžœ˜WJšœ˜—J˜"šžœžœžœ˜JšœRžœ˜XJ˜+J˜—J˜#Jšœ˜šžœžœžœœj˜šžœžœ˜!Jšœ8˜8Jšœ*˜*Jšžœ˜ J˜Jšœ˜——Jšžœ%˜)šž˜Jšœ˜Jšžœ žœžœ˜J˜ J˜Jšžœ˜—J˜ šžœ žœžœžœ˜ Jšœ žœ~˜¨Jšžœ˜Jšžœ3˜FJ˜—Jšž˜Jšœ ž˜J˜J˜—Jšœ#˜#Jšœ$˜$šžœžœ˜šžœžœž˜!J˜;Jšžœ˜Jšžœ˜——Jšœ žœžœ˜šžœ5ž˜˜EJšžœžœžœžœ ˜J˜J˜—Jšžœžœžœ˜CJšžœžœ ˜JšœžœR˜g˜DJšœžœžœ˜2—Jšžœžœž œ ˜;šžœ˜Jšœ žœžœ ˜ J˜'JšžœžœL˜iJšžœ5˜9J˜—šž˜˜ J˜+J˜:J˜——Jšžœ ˜—J˜Jšœ˜šžœ žœ'žœÂ˜€Jšœžœ˜J˜FJ˜—Jšœ žœžœ˜šž˜J˜&Jšžœžœžœ˜#Jšœ˜J˜ Jšžœ˜—Jšžœ˜J˜—š  œž˜$Jšœ˜Jšžœ˜J˜—š  œž˜&š œžœ˜%Jšœžœ ˜Jšœžœ˜Jšœžœ˜Jš žœžœQžœ žœ )˜J˜—Jšœ'˜'Jšžœ˜J˜—J•StartOfExpansion„ -- [event: UserExec.HistoryEvent, exec: UserExec.ExecHandle, clientData: REF ANY] RETURNS [ok: BOOLEAN _ TRUE, msg: ROPE _ NIL] -- š  œžœžœžœ>žœžœ˜nJ˜š  œžœWžœ˜rJšœ žœ˜&Jšœžœ!˜,Jšœžœ˜ Jšœžœ˜Jšœžœžœ˜Jšœžœžœ˜Jšœžœžœžœ ˜Jšœžœ˜ š  œžœžœžœœž˜5Jšœžœžœ˜Jšœžœžœ˜J˜Jšœžœžœ˜šžœž˜Jšœžœ>˜EJšžœžœžœžœ ˜J˜J˜—Jšžœžœžœ˜CJšžœžœ ˜JšœžœR˜g˜DJšœžœžœ˜2—Jšžœžœž˜šžœ˜Jšœ žœ˜!Jšœ žœžœ˜˜\šžœžœ˜ Jšœ5˜5Jšžœ˜ Jšœ˜——Jšžœ žœžœ81˜~Jšžœ žœžœp˜œJ˜—šž˜J˜ ˜ J˜+J˜:J˜——Jšžœ ˜—š  œžœžœžœ(žœžœ˜aJšœO˜OJšœ˜J˜Jšžœ ˜—J˜Jšœ˜šžœ žœ'žœÂ˜€Jšœžœ˜J˜FJ˜—Jšœ žœžœ˜šž˜J˜&Jšžœžœžœ˜#Jšœ˜J˜ Jšžœ˜—šžœ žœžœžœ˜Jšœžœ žœ˜6š žœžœžœžœžœžœž˜AJšœ˜JšœU˜U—Jšžœ˜J˜—J˜JJšžœ˜J˜—š œžœ˜!Jšœžœ˜4Jšœžœ!˜,Jšœžœ˜š œžœ˜Jš žœžœ žœžœžœ˜5J˜—J˜Jšœ1˜1Jšœ1˜1šžœž˜ šœ˜J˜Jšœ1˜1J˜——JšžœžœžœB˜J˜AJšžœžœžœg˜vJ˜ Jšžœ$˜*Jšžœ˜J˜—š  œžœžœžœžœžœ žœ˜YJšœžœ˜ šœ˜š  œžœ žœžœž˜*Jšœžœžœ˜Jšœžœžœ˜J˜.J˜.J˜J˜`Jšžœ˜—š  œžœžœžœž˜&šœžœ˜3Jšœžœ˜—Jšžœ˜—šœ.˜.šž˜šžœž˜˜šœžœžœžœž˜4šœ*žœ˜4Jšœ˜—Jšœ˜Jšžœ˜ J˜—Jšœ˜šžœžœžœC˜iJšœ˜Jšžœ˜J˜—šœžœ˜)Jšœ˜—JšœN˜NJšžœ˜J˜—J˜(J˜.Jšžœ˜#—J˜Jšžœ˜ Jšœ˜—J˜—Jšœ;˜;Jšž ˜ šž˜Jšœ4žœžœ˜C—J˜—Jšžœ˜J˜——™ š  œž˜$Jšœžœ%˜7Jšžœžœžœ/˜IJ˜!Jšžœ˜J˜—š  œžœžœ žœžœžœ˜GJšœžœ˜/šžœžœžœ˜Jšœ#žœ˜)Jšžœžœžœ˜J˜—šžœ˜J˜1J˜!J˜—J˜J˜—š  œžœ žœžœžœ˜HJšœžœ˜šžœžœžœ˜ J˜Jšžœ˜—JšœžœE˜LJšœ+˜+Jš œžœ žœ"žœžœ˜YJšœ žœžœ˜>Jšžœžœ˜ šœ#žœ ˜/šžœž˜Jšœ žœ˜Jšœžœ˜Jšžœ˜—J˜—J˜šœžœ˜%Jšœ žœ>˜MJ˜Jšœ žœ˜—šœ'žœ ˜4šžœž˜Jšœ žœ˜Jšœžœ˜Jšžœ˜—J˜—Jšžœ5žœžœžœ˜GJšœ,˜,Jšžœ˜J˜—š  œžœžœžœžœžœ˜KJšžœžœ!˜Bšžœ&ž˜0Jšœ žœ˜,Jšœ žœ*˜˜}Jšžœžœ˜šœ˜J˜——š œžœ˜*Jšœžœ!˜,Jšœžœ&˜0Jšœ'žœ ˜6Jšœ<žœžœ%˜iJšžœžœ˜Jšœ˜——™%š  œžœ˜-Jšžœžœžœ˜7J˜&J˜J˜—š œžœ˜*Jšžœžœžœ˜7Jšœ˜J˜J˜—š  œžœ˜,Jšžœžœžœ˜7Jšœ"˜"˜J˜——š  œžœžœžœ˜)Jšœžœžœ˜š œ˜Jš žœžœ žœžœžœ ˜AJšžœžœ˜ J˜—J˜!Jš žœžœžœžœžœ˜Jšœ7žœ˜=Jšœ˜Jšžœ˜ J˜——™ š  œž˜)Jšœžœ˜4Jšœ žœ˜Jšœžœ˜Jšœ5˜5Jšœ\˜\šžœ žœž˜Jšœ žœ@˜MJšœžœ˜Jšœžœ˜Jšœ9žœ˜FJ˜—JšœS˜SJšžœ˜——™J˜Jšžœžœ˜.J˜IJ˜KJ˜J˜tJ˜sJ˜„J˜cJ˜ƒJ˜®J˜„J˜®J˜J˜„J˜OJ˜TJ˜J˜ÜJ˜æJ˜NJ˜CJ˜IJ˜J˜—Jšžœ˜J˜J˜FJ˜ZJ˜™+J™,Jšœ Ïrœ¡œ¡œ™}—™,Jšœ ¡-™9—™,Jšœ ¡ ™—™,Jšœ ¡uœ¡™µ—™,Jšœ ¡™—™.Jšœ ¡ œ¡œ¡ ™s—J™J™J™J™J™—…—qê•>