DIRECTORY AlpFile USING[ FileID, Handle, LockOption, Open, ReadProperties], AlpineEnvironment USING[ AccessList, ByteCount, NeededAccess, Outcome, OwnerName, PageCount, Principal, Property, PropertyValuePair, RName, UniversalFile, nullUniversalFile ], AlpineFile USING [ allProperties, PropertySet], AlpDirectory, AlpineDirectory, AlpineInterimDirectory, AlpInstance USING[ AccessFailed, Create, Handle, LockFailed, Unknown ], AlpTransaction USING[ AssertAlpineWheel, Create, Finish, Handle, Outcome], BasicTime USING[ GMT], Basics USING[ Comparison], Buttons USING[ Button, ButtonProc, ReLabel, SetDisplayStyle ], CedarProcess USING[ SetPriority ], Convert, FS USING[ ComponentPositions, Delete, EnumerateForInfo, EnumerateForNames, Error, ExpandName, InfoProc, NameProc], IO USING[ int, PutF, PutFR, PutRope, rope, STREAM, text, time ], List USING[ Reverse, Sort ], Menus, RefText, Rope, RPC, UserCredentials USING[ Get ], ViewerClasses USING[ Viewer ], ViewerTools USING[ GetContents, SetContents], YodelData; YodelUserImpl1: CEDAR MONITOR LOCKS d USING d: MyData IMPORTS AlpDirectory, AlpineInterimDirectory, AlpFile, AlpInstance, AlpTransaction, Buttons, CedarProcess, Convert, FS, IO, List, RefText, Rope, UserCredentials, ViewerTools, YodelData EXPORTS YodelData = BEGIN OPEN AE: AlpineEnvironment, YodelData; PageCount: TYPE = AE.PageCount; PropertyValuePair: TYPE = AE.PropertyValuePair; UniversalFile: TYPE = AE.UniversalFile; EnumProc: TYPE = PROC [ fileName: ROPE, universalFile: AE.UniversalFile, trans: AlpTransaction.Handle, flushState: BOOL ] RETURNS [ didCommitAndContinue: BOOL, quit: BOOL, nowTrans: AlpTransaction.Handle]; EnumeratePattern: PROC [ transHandle: AlpTransaction.Handle, commitALot: BOOL, transServer: ROPE, caller: AlpineEnvironment.Principal, key: RPC.EncryptionKey, assertWheel: BOOL, pattern: ROPE, enumProc: EnumProc] RETURNS [myTrans: AlpTransaction.Handle _ NIL]= { lastCommitedFullPathName: ROPE _ NIL; firstErrorThisXAct: BOOL _ FALSE; doFlush: BOOL _ TRUE; file: AE.UniversalFile; fullPathName, link: ROPE; tryRecovery: PROC RETURNS [doRetry: BOOL _ FALSE] = { IF ~firstErrorThisXAct THEN { inst: AlpInstance.Handle _ NIL; firstErrorThisXAct _ TRUE; inst _ AlpInstance.Create[fileStore: transServer, caller: caller, key: key]; myTrans _ AlpTransaction.Create[inst]; IF assertWheel THEN myTrans.AssertAlpineWheel[TRUE]; fullPathName _ lastCommitedFullPathName; doFlush _ TRUE; doRetry _ TRUE; }; }; myTrans _ transHandle; { ENABLE BEGIN AlpInstance.Unknown =>{ IF tryRecovery[] THEN RETRY; -- should retry the whole block }; AlpDirectory.Error => { IF type # transAborted THEN REJECT; IF tryRecovery[] THEN RETRY; -- should retry the whole block }; END; { DO didCC: BOOL; pathName: ROPE; quit: BOOL; [file, fullPathName, link] _ AlpDirectory.Enumerate[ transHandle: myTrans, pattern: pattern, previousFile: fullPathName]; IF fullPathName # NIL THEN { IF Rope.Find[fullPathName, "$$$.btree"] > 0 THEN LOOP; }; [quit: quit, didCommitAndContinue: didCC, nowTrans: myTrans] _ enumProc[ fullPathName, file, myTrans, doFlush ]; IF quit THEN EXIT; doFlush _ FALSE; IF didCC THEN { firstErrorThisXAct _ FALSE; lastCommitedFullPathName _ fullPathName; }; pathName _ fullPathName; IF pathName.IsEmpty[] THEN EXIT; ENDLOOP; }; }; }; ROPE: TYPE = Rope.ROPE; CompareProc: SAFE PROC [ref1, ref2: REF ANY] RETURNS [Basics.Comparison] = CHECKED { rope1: ROPE = NARROW[ref1]; rope2: ROPE = NARROW[ref2]; IF ~rope1.IsEmpty[] AND ~rope2.IsEmpty[] THEN { IF rope1.InlineFetch[0] = '- THEN { IF ~rope2.InlineFetch[0] = '- THEN RETURN[greater]; } ELSE IF rope2.InlineFetch[0] = '- THEN RETURN[less]; }; RETURN [Rope.Compare[rope1, rope2, FALSE]]; }; ParseSArgs: PUBLIC PROC [ d: MyData] RETURNS [user, password, srcServer, srcDir, srcFile: ROPE _ NIL, parseError: BOOL _ FALSE, errorExplanation: ROPE _ NIL] = { srcFileTemp: ROPE ; fullFName: ROPE; cp: FS.ComponentPositions; dirOmitted: BOOL; srcFileTemp _ ViewerTools.GetContents[d.src]; [fullFName: fullFName, cp: cp, dirOmitted: dirOmitted] _ FS.ExpandName[name: srcFileTemp, wDir: "[]<>" ! FS.Error => { parseError _ TRUE; errorExplanation _ error.explanation; CONTINUE ; }; ]; IF parseError THEN { [fullFName: fullFName, cp: cp, dirOmitted: dirOmitted] _ FS.ExpandName[name: (srcFileTemp _ srcFileTemp.Concat["*"]), wDir: "[]<>" ! FS.Error => { GOTO FSError ; }; ]; }; parseError _ FALSE; [user, password] _ UserCredentials.Get[]; srcServer _ fullFName.Substr[cp.server.start, cp.server.length]; IF dirOmitted THEN srcFile _ "" ELSE srcFile _ fullFName.Substr[cp.dir.start-1]; [srcDir] _ DecomposePattern[server: srcServer, pattern: srcFile, user: user]; EXITS FSError => {}; }; listFiles: PROC [trans: AlpTransaction.Handle, server: ROPE, pattern: ROPE, user: ROPE _ NIL, password: ROPE _ NIL , caller: AlpineEnvironment.Principal, key: RPC.EncryptionKey, displayProperties: AlpineFile.PropertySet _ AlpineFile.allProperties, d: MyData] RETURNS [LIST OF REF ANY] = { directory, restOfPattern: ROPE; resultList: LIST OF REF ANY _ NIL; noFiles: INT _ 0; noBytes: INT _ 0 ; AlpineListEnumProc: EnumProc = { intendReadLockOption: AlpFile.LockOption = [intendRead , IF d.breakLocks THEN wait ELSE fail]; readLockOption: AlpFile.LockOption = [read, IF d.breakLocks THEN wait ELSE fail]; IF fileName.IsEmpty[] THEN RETURN; nowTrans _ trans ; IF d.stopFlag THEN { resultList _ CONS[first: NARROW[" ** List aborted **", ROPE], rest: NIL]; noFiles _ 0; RETURN [quit: TRUE, didCommitAndContinue: TRUE, nowTrans: nowTrans]; }; quit _ FALSE; { file: AlpFile.Handle; fileID: AlpFile.FileID; properties: LIST OF AlpineEnvironment.PropertyValuePair; byteLength: INT; resultRope: ROPE ; resultRope _ IO.PutFR["%g\t", IO.rope[fileName]]; IF universalFile # AE.nullUniversalFile THEN { [file, fileID] _ AlpFile.Open[transHandle: nowTrans, universalFile: universalFile, lock: intendReadLockOption ! AlpInstance.LockFailed => GOTO lockError; AlpInstance.AccessFailed => IF missingAccess = AlpineEnvironment.NeededAccess.fileRead THEN GOTO skipFile ELSE REJECT; ]; properties _ AlpFile.ReadProperties[handle: file, lock: readLockOption ! AlpInstance.LockFailed => GOTO lockError; AlpInstance.AccessFailed => IF missingAccess = AlpineEnvironment.NeededAccess.fileRead THEN GOTO skipFile ELSE REJECT; ]; byteLength _ NARROW[properties.first, AlpineEnvironment.PropertyValuePair.byteLength].byteLength; UNTIL properties = NIL DO property: AlpineEnvironment.PropertyValuePair _ properties.first ; properties _ properties.rest; IF displayProperties[property.property] THEN { SELECT property.property FROM byteLength => { byteLength: INT _ NARROW[property, AlpineEnvironment.PropertyValuePair.byteLength].byteLength; resultRope _ Rope.Concat[resultRope, IO.PutFR["%g bytes ", IO.int[byteLength]]]; }; createTime => { createTime: BasicTime.GMT _ NARROW[property, AlpineEnvironment.PropertyValuePair.createTime].createTime; resultRope _ Rope.Concat[resultRope, IO.PutFR["%g ", IO.time[createTime]]]; }; highWaterMark => { highWaterMark: AlpineEnvironment.PageCount _ NARROW[property, AlpineEnvironment.PropertyValuePair.highWaterMark].highWaterMark; resultRope _ Rope.Concat[resultRope, IO.PutFR["HWM: %g ", IO.int[highWaterMark]]]; }; modifyAccess => { modifyAccess: AlpineEnvironment.AccessList _ NARROW[property, AlpineEnvironment.PropertyValuePair.modifyAccess].modifyAccess; resultRope _ Rope.Concat[resultRope,"modify access: ("]; IF modifyAccess = NIL THEN resultRope _ Rope.Concat[resultRope,"*none*"] ELSE { UNTIL modifyAccess = NIL DO accessItem: AlpineEnvironment.RName _ modifyAccess.first; resultRope _ Rope.Concat[resultRope,accessItem]; modifyAccess _ modifyAccess.rest; IF modifyAccess # NIL THEN resultRope _ Rope.Concat[resultRope,", "]; ENDLOOP; } ; resultRope _ Rope.Concat[resultRope,") "]; }; owner => { owner: AlpineEnvironment.OwnerName _ NARROW[property, AlpineEnvironment.PropertyValuePair.owner].owner; resultRope _ Rope.Concat[resultRope, IO.PutFR["owner: %g ", IO.rope[owner]]]; }; readAccess => { readAccess: AlpineEnvironment.AccessList _ NARROW[property, AlpineEnvironment.PropertyValuePair.readAccess].readAccess; resultRope _ Rope.Concat[resultRope,"read access: ("]; IF readAccess = NIL THEN resultRope _ Rope.Concat[resultRope,"*none*"] ELSE { UNTIL readAccess = NIL DO accessItem: AlpineEnvironment.RName _ readAccess.first; resultRope _ Rope.Concat[resultRope,accessItem]; readAccess _ readAccess.rest; IF readAccess # NIL THEN resultRope _ Rope.Concat[resultRope,", "]; ENDLOOP; } ; resultRope _ Rope.Concat[resultRope,") "]; }; stringName => { stringName: ROPE _ NARROW[property, AlpineEnvironment.PropertyValuePair.stringName].stringName; resultRope _ Rope.Concat[resultRope, IO.PutFR["stringName: %g ", IO.rope[stringName]]]; }; version => { version: LONG INTEGER _ NARROW[property, AlpineEnvironment.PropertyValuePair.version].version; resultRope _ Rope.Concat[resultRope, IO.PutFR["version: %g ", IO.int[version]]]; }; ENDCASE; }; ENDLOOP; }; noFiles _ noFiles + 1; noBytes _ noBytes + byteLength; resultList _ CONS[first: resultRope, rest: resultList]; EXITS lockError => { resultRope _ IO.PutFR["%g has lock set", IO.rope[fileName]]; resultList _ CONS[first: resultRope, rest: resultList]; }; skipFile => {}; }; RETURN [quit: FALSE, didCommitAndContinue: FALSE, nowTrans: nowTrans]; }; FSListEnumProc: FS.InfoProc = { resultRope: ROPE ; fName: ROPE ; componentPositions: FS.ComponentPositions ; IF d.stopFlag THEN { resultList _ CONS[first: NARROW[" ** List aborted **", ROPE], rest: NIL]; noFiles _ 0; RETURN [continue: FALSE]; }; [fullFName: fName, cp: componentPositions] _ FS.ExpandName[name: fullFName]; resultRope _ IO.PutFR["%g\t", IO.rope[fName]]; resultRope _ Rope.Concat[resultRope, IO.PutFR["%g bytes ", IO.int[bytes]]]; resultRope _ Rope.Concat[resultRope, IO.PutFR["\t%g ", IO.time[created]]]; noFiles _ noFiles + 1; noBytes _ noBytes + bytes; resultList _ CONS[first: resultRope, rest: resultList]; RETURN [continue: TRUE]; }; resultRope: ROPE _ NIL; outcome: AlpTransaction.Outcome; failureName: ROPE _ "" ; { [directory, restOfPattern] _ DecomposePattern[server, pattern, user]; IF trans # NIL THEN { IF d.assertWheel THEN trans.AssertAlpineWheel[TRUE]; [] _ EnumeratePattern[ transHandle: trans, transServer: server, caller: caller, key: key, assertWheel: d.assertWheel, commitALot: FALSE, pattern: Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]].Concat[restOfPattern], enumProc: AlpineListEnumProc]; outcome _ AlpTransaction.Finish[trans, commit]; } ELSE { pattern: ROPE; pattern _ Rope.Concat[Rope.Concat[Rope.Concat[Rope.Concat ["[", server], "]"], directory], restOfPattern]; FS.EnumerateForInfo[pattern: pattern, proc: FSListEnumProc ]; }; }; IF noFiles > 0 THEN resultList _ CONS[first: IO.PutFR["-- %g files, %g total bytes", IO.int[noFiles], IO.int[noBytes]], rest: resultList]; resultList _ List.Sort[resultList, CompareProc]; RETURN [resultList]; }; DecomposePattern: PUBLIC PROC [server: ROPE, pattern: ROPE, user: ROPE, addStar: BOOL _ TRUE] RETURNS [directory, restOfPattern: ROPE] = { rightAngleBracket: INT; dir: ROPE ; isAlpine: BOOL ; IF Rope.Match[pattern: "*.alpine", object: server, case: FALSE] THEN { isAlpine _ TRUE; dir _ user; } ELSE { isAlpine _ FALSE; IF server.IsEmpty[] THEN dir _ NIL ELSE { IF Rope.Match[pattern: "*.*", object: user, case: FALSE] THEN dir _ user.Substr[start: 0, len: user.SkipTo[1, "."]] ELSE dir _ user; }; }; IF NOT Rope.Match[pattern: "<*>*", object: pattern, case: FALSE] THEN { directory _ Rope.Cat["<", dir, ">"] ; restOfPattern _ pattern ; IF restOfPattern.IsEmpty[] THEN restOfPattern _ "*"; } ELSE { rightAngleBracket _ pattern.SkipTo[1, ">"]; IF rightAngleBracket > 1 THEN directory _ pattern.Substr[start: 0, len: rightAngleBracket+1] ELSE directory _ Rope.Cat["<", dir, ">"] ; restOfPattern _ pattern.Substr[start: rightAngleBracket+1]; IF restOfPattern.IsEmpty[] AND addStar THEN restOfPattern _ "*"; }; }; ListFilesProc: PUBLIC Buttons.ButtonProc = { resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, file, password: ROPE; directory, restOfPattern: ROPE; printedSomething: BOOL _ FALSE; parseError: BOOL; errorExplanation: ROPE; callList: YodelData.PerformProc = { RETURN[listFiles[trans, server, file, user, password, caller, key, d.displayProperties, d]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in List because %g\n", IO.rope[errorExplanation]]; }; d.stopFlag _ FALSE; IF d.background THEN CedarProcess.SetPriority[background]; [user, password, server, directory, file, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; [directory, restOfPattern] _ DecomposePattern[server: server, pattern: file, user: user]; d.out.PutF["\nList of [%g]%g%g\n", IO.rope[server], IO.rope[directory], IO.rope[restOfPattern]]; resultList _ PerformOp[performProc: callList, server: server, user: user, password: password]; printedSomething _ PrintResult[d.out, resultList]; IF NOT printedSomething THEN d.out.PutRope[" ** no files match the pattern **\n"]; EXITS badParse => {}; }; fileDelete: PROC [trans: AlpTransaction.Handle, server: ROPE, file: ROPE, user: ROPE _ NIL, password: ROPE _ NIL, caller: AlpineEnvironment.Principal, key: RPC.EncryptionKey, d: MyData] RETURNS [resultList: LIST OF REF ANY _ NIL, countDeleted: INT _ 0] = { directory, restOfPattern, directoryName: ROPE; firstNewNameLessVersion: ROPE _ NIL; topVersion: INT _ 0; allFullNamesSeen: LIST OF ROPE _ NIL; resultListBeforeCommit: LIST OF REF ANY _ NIL; countDeletedBeforeCommit: INT _ 0 ; fileCounter: INT _ 0; AlpineDeleteEnumProc: EnumProc = { fullFNameLessVersion: ROPE; bangPos: INT; thisVersion: INT; lastOne: BOOL _ FALSE; tryToDelete: PROC [ fullNameToDelete: ROPE] RETURNS [quit: BOOL _ FALSE] = { deleteIt: BOOL _ TRUE; IF ~d.AutoDelete AND ~ lastOne THEN { answer: YesNoAll; nowQuit: BOOL; ViewerTools.SetContents[d.oDeleteConfirm, fullNameToDelete, TRUE]; enableAnswer[d]; [nowQuit, answer] _ getAnswer[d]; disableAnswer[d]; ViewerTools.SetContents[d.oDeleteConfirm, ""]; IF nowQuit THEN { resultList _ CONS[first: NARROW[">> ** Delete aborted **", ROPE], rest: resultListBeforeCommit]; RETURN [quit: TRUE]; }; IF answer = no THEN deleteIt _ FALSE; }; IF deleteIt THEN { IF ~lastOne THEN [] _ AlpDirectory.DeleteFile[transHandle: nowTrans, name: fullNameToDelete]; IF d.AutoDelete THEN { IF ~lastOne THEN { countDeletedBeforeCommit _ countDeletedBeforeCommit + 1; resultRope _ IO.PutFR["%g deleted", IO.rope[fullNameToDelete]]; resultListBeforeCommit _ CONS[first: resultRope, rest: resultListBeforeCommit]; }; IF fileCounter > 10 OR lastOne THEN { outcome: AlpTransaction.Outcome; outcome _ AlpTransaction.Finish[handle: nowTrans, requestedOutcome: commit, continue: TRUE]; IF outcome # commit THEN ERROR AlpInstance.Unknown[transID]; -- raise ERROR to be caught by EnumeratePattern (our caller) for retry FOR rList: LIST OF REF ANY _ List.Reverse[resultListBeforeCommit], rList.rest UNTIL rList = NIL DO res: ROPE; res _ NARROW[IF rList.first = NIL THEN NIL ELSE rList.first]; IF res = NIL THEN EXIT; d.out.PutF[" %g\n", IO.rope[res]]; ENDLOOP; IF d.assertWheel THEN nowTrans.AssertAlpineWheel[TRUE]; -- wheelness does not survied commit and continue resultListBeforeCommit _ NIL; didCommitAndContinue _ TRUE; countDeleted _ countDeleted + countDeletedBeforeCommit; countDeletedBeforeCommit _ 0; fileCounter _ 0; }; } ELSE { outcome: AlpTransaction.Outcome; IF ~lastOne THEN { outcome _ AlpTransaction.Finish[handle: nowTrans, requestedOutcome: commit, continue: TRUE]; IF outcome # commit THEN { inst: AlpInstance.Handle _ NIL; inst _ AlpInstance.Create[fileStore: server, caller: caller, key: key]; nowTrans _ AlpTransaction.Create[inst]; IF d.assertWheel THEN nowTrans.AssertAlpineWheel[TRUE]; [] _ AlpDirectory.DeleteFile[transHandle: nowTrans, name: fullNameToDelete]; outcome _ AlpTransaction.Finish[handle: nowTrans, requestedOutcome: commit, continue: TRUE]; IF outcome # commit THEN { d.out.PutF["Alpine transaction aborted -- %g could NOT be deleted-- delete stopped\n", IO.rope[fullNameToDelete]]; quit _ TRUE; RETURN; }; }; IF d.assertWheel THEN nowTrans.AssertAlpineWheel[TRUE]; -- wheelness does not survied commit and continue countDeleted _ countDeleted + 1; d.out.PutF["%g deleted\n", IO.rope[fullNameToDelete]]; didCommitAndContinue_ TRUE; }; }; }; }; didCommitAndContinue _ FALSE; quit _ FALSE; nowTrans _ trans; IF d.stopFlag THEN { resultList _ CONS[first: NARROW[" ** Delete aborted **", ROPE], rest: NIL]; RETURN [quit: TRUE, didCommitAndContinue: FALSE, nowTrans: nowTrans]; }; IF flushState THEN { firstNewNameLessVersion _ NIL; topVersion _ 0; allFullNamesSeen _ NIL; resultListBeforeCommit _ NIL; countDeletedBeforeCommit _ 0; fileCounter _ 0 ; }; fileCounter _ fileCounter + 1; IF fileName.IsEmpty[] THEN { fileName _ "$$$Junk!1"; }; IF bangStarAndNonzeroDelVer THEN { bangPos _ fileName.Find["!"]; fullFNameLessVersion _ fileName.Substr[0, bangPos-1]; thisVersion _ Convert.IntFromRope[fileName.Substr[bangPos+1]]; IF Rope.Equal[firstNewNameLessVersion, fullFNameLessVersion, FALSE] THEN { allFullNamesSeen _ CONS[fileName, allFullNamesSeen]; IF thisVersion > topVersion THEN topVersion _ thisVersion; } ELSE { IF allFullNamesSeen # NIL THEN { FOR nowNameList: LIST OF ROPE _ allFullNamesSeen, nowNameList.rest UNTIL nowNameList = NIL DO nowBangPos: INT = nowNameList.first.Find["!"]; nowVersion: INT _ Convert.IntFromRope[nowNameList.first.Substr[nowBangPos+1]]; IF (nowVersion + d.DelVerCount) <= topVersion THEN quit _ tryToDelete[nowNameList.first]; IF quit THEN EXIT; ENDLOOP; }; IF fileName.Equal["$$$Junk!1"] THEN { lastOne _ TRUE; [] _ tryToDelete[fileName]; }; topVersion _ thisVersion; firstNewNameLessVersion _ fullFNameLessVersion; allFullNamesSeen _ CONS[fileName, NIL]; }; } ELSE { IF fileName.Equal["$$$Junk!1"] THEN lastOne _ TRUE; quit _ tryToDelete[fileName]; }; RETURN; }; FSDeleteEnumProc: FS.NameProc = { inner: PROC [fullFName: ROPE] RETURNS [continue: BOOL] = { deleteIt: BOOL _ TRUE; fullFNameLessVersion: ROPE; bangPos: INT; thisVersion: INT; IF d.stopFlag THEN { resultList _ CONS[first: NARROW[">> ** Delete aborted **", ROPE], rest: resultList]; RETURN [continue: FALSE]; }; bangPos _ fullFName.Find["!"]; fullFNameLessVersion _ fullFName.Substr[0, bangPos-1]; thisVersion _ Convert.IntFromRope[fullFName.Substr[bangPos+1]]; IF bangStarAndNonzeroDelVer THEN { IF (thisVersion + d.DelVerCount) > topVersion THEN deleteIt _ FALSE; }; IF deleteIt AND ~d.AutoDelete THEN { answer: YesNoAll; nowQuit: BOOL; ViewerTools.SetContents[d.oDeleteConfirm, fullFName, TRUE]; enableAnswer[d]; [nowQuit, answer] _ getAnswer[d]; disableAnswer[d]; ViewerTools.SetContents[d.oDeleteConfirm, ""]; IF nowQuit THEN { resultList _ CONS[first: NARROW[">> ** Delete aborted **", ROPE], rest: resultList]; RETURN [continue: FALSE]; }; IF answer = no THEN deleteIt _ FALSE; }; IF deleteIt THEN { FS.Delete[ name: fullFName]; countDeleted _ countDeleted + 1; d.out.PutF["%g deleted\n", IO.rope[fullFName]]; }; RETURN [continue: TRUE]; }; continue _ TRUE; IF bangStarAndNonzeroDelVer THEN { innerPattern: ROPE; thisBangPos: INT; outerContinue: BOOL ; anotherEnum: FS.NameProc = { continue _ inner[fullFName]; outerContinue _ continue; }; thisBangPos _ fullFName.Find["!"]; topVersion _ Convert.IntFromRope[fullFName.Substr[thisBangPos+1]]; innerPattern _ Rope.Concat[fullFName.Substr[0, thisBangPos+1], versionPattern]; FS.EnumerateForNames[pattern: innerPattern, proc: anotherEnum ]; continue _ outerContinue; } ELSE { continue _ inner[fullFName]; }; }; resultRope: ROPE _ NIL; outcome: AlpTransaction.Outcome; failureName: ROPE _ "" ; restBang: INT; versionPattern: ROPE; bangStarAndNonzeroDelVer: BOOL _ FALSE; patternForFS: ROPE; [directory, restOfPattern] _ DecomposePattern[server, file, user]; d.deleteConfirm _ wait; IF (restBang _ Rope.Find[restOfPattern,"!"]) > 0 THEN { versionPattern _ restOfPattern.Substr[restBang+1]; IF versionPattern.Equal["*"] AND d.DelVerCount # 0 THEN bangStarAndNonzeroDelVer _ TRUE; } ELSE versionPattern _ "L"; IF trans # NIL THEN { IF d.assertWheel THEN trans.AssertAlpineWheel[TRUE]; IF restBang < 0 THEN { restOfPattern _ restOfPattern.Concat["!L"]; }; directoryName _ Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]]; trans _ EnumeratePattern[ transHandle: trans, transServer: server, caller: caller, key: key, commitALot: TRUE, assertWheel: d.assertWheel, pattern: directoryName.Concat[restOfPattern], enumProc: AlpineDeleteEnumProc ]; outcome _ AlpTransaction.Finish[trans, commit]; IF outcome # commit THEN { resultList _ CONS[ first: Rope.Concat["Alpine transaction aborted -- deletes NOT done", IF resultList = NIL THEN NIL ELSE "; log before abort is:"], rest: resultList]; }; } ELSE { anotherPattern: ROPE; anotherBang: INT; patternForFS _ Rope.Concat[Rope.Concat[Rope.Concat[Rope.Concat["[", server], "]"], directory], restOfPattern]; IF (anotherBang _ patternForFS.Find["!"]) < 0 THEN patternForFS _ Rope.Concat[patternForFS, "!l"]; anotherPattern _ patternForFS; IF bangStarAndNonzeroDelVer THEN anotherPattern _ Rope.Concat[patternForFS.Substr[0, anotherBang], "!h"]; -- have to enumerate highest version, so we can do the delete versions FS.EnumerateForNames[pattern: anotherPattern, proc: FSDeleteEnumProc ]; }; d.stopFlag _ TRUE; setAnswer[d, wait]; resultList _ List.Sort[resultList, CompareProc]; }; DeleteFilesProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; saveDelVerCount: INT _ d.DelVerCount; resultList: LIST OF REF ANY _ NIL; srcServer, srcFile, srcDir: ROPE; user, password: ROPE; directory, restOfPattern: ROPE; keepMsg: ROPE; printedSomething: BOOL _ FALSE; parseError: BOOL; errorExplanation: ROPE; deleteNum: INT _ 0; callDelete: YodelData.PerformProc = { lora: LIST OF REF ANY; [lora, deleteNum] _ fileDelete[trans: trans, server: srcServer, file: srcFile, user: user, password: password, caller: caller, key: key, d: d]; RETURN[lora]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in Delete because %g\n", IO.rope[errorExplanation]]; }; d.stopFlag _ FALSE; IF d.background THEN CedarProcess.SetPriority[background]; [user, password, srcServer, srcDir, srcFile, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; [directory, restOfPattern] _ DecomposePattern[server: srcServer, pattern: srcFile, user: user]; IF Rope.IsEmpty[srcFile] THEN { d.out.PutF["\nDelete of [%g]%g%g NOT done -- MUST have explicit pattern\n", IO.rope[srcServer], IO.rope[srcDir], IO.rope[restOfPattern]]; RETURN; }; IF d.DelVerCount # 0 THEN { bangPos: INT _ Rope.Find[restOfPattern, "!"]; vers: ROPE _ ""; IF bangPos >= 0 THEN { vers _ restOfPattern.Substr[bangPos+1]; } ELSE restOfPattern _ restOfPattern.Cat["!L"]; IF ~vers.Equal["*"] THEN { d.DelVerCount _ 0; keepMsg _ NIL; } ELSE keepMsg _ IO.PutFR["with keep %g ", IO.int[d.DelVerCount]]; }; d.out.PutF["\nDelete [%g]%g%g %g\n", IO.rope[srcServer], IO.rope[srcDir], IO.rope[restOfPattern], IO.rope[keepMsg]]; resultList _ PerformOp[performProc: callDelete, server: srcServer, user: user, password: password]; d.DelVerCount _ saveDelVerCount; printedSomething _ PrintResult[d.out, resultList]; IF NOT printedSomething AND deleteNum = 0 THEN d.out.PutRope[" ** no files deleted **\n"]; IF deleteNum # 0 THEN d.out.PutF[" ** %g files deleted **\n", IO.int[deleteNum]]; EXITS badParse => {}; }; OptionsProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; d.displayOptions _ NOT d.displayOptions ; CreateButtons[d, p.parent.parent]; }; ChangeOptionsProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; count: INT ; option: AlpineEnvironment.Property; FOR count IN [0..NumberOfAlpineProperties) DO propertyName: ROPE ; [option, propertyName] _ PropertySetToRopeArray[count]; IF Rope.Equal[propertyName, p.name] THEN { d.displayProperties[option] _ NOT d.displayProperties[option] ; IF d.displayProperties[option] THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite]; EXIT; }; ENDLOOP; }; ChangeAutoDeleteProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; d.AutoDelete _ NOT d.AutoDelete ; IF d.AutoDelete THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite]; d.deleteConfirm _ yes; CreateButtons[d, p.parent.parent] }; YesProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; setAnswer[d, yes]; }; NoProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; setAnswer[d, no]; }; enableAnswer: ENTRY PROC [d: MyData] = { Buttons.ReLabel[d.yesButton, "Yes", TRUE]; Buttons.ReLabel[d.noButton, "No", TRUE]; d.deleteConfirm _ wait; }; disableAnswer: ENTRY PROC [d: MyData] = { Buttons.ReLabel[d.yesButton, "", TRUE]; Buttons.ReLabel[d.noButton, "", TRUE]; d.deleteConfirm _ wait; }; setAnswer: ENTRY PROC [d: MyData, answer: YesNoAll] = { DO IF d.stopFlag THEN { NOTIFY d.condition; RETURN;}; IF d.deleteConfirm # wait THEN WAIT d.condition ELSE { d.deleteConfirm _ answer; NOTIFY d.condition; RETURN; }; ENDLOOP; }; getAnswer: ENTRY PROC [d: MyData] RETURNS [stop: BOOL _ FALSE, answer: YesNoAll] = { DO IF d.stopFlag THEN { NOTIFY d.condition; RETURN[TRUE, wait];}; IF d.deleteConfirm = wait THEN WAIT d.condition ELSE { answer _ d.deleteConfirm; d.deleteConfirm _ wait; NOTIFY d.condition; RETURN; }; ENDLOOP; }; ChangeDelVerProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; propertyName: ROPE ; SELECT mouseButton FROM red => { -- increase number of versions to keep IF d.DelVerCount # INT.LAST THEN d.DelVerCount _ d.DelVerCount + 1; }; yellow => { -- keep 1 d.DelVerCount _ 1; }; blue => { -- decrease number of versions to keep IF d.DelVerCount > 0 THEN d.DelVerCount _ d.DelVerCount - 1; }; ENDCASE => ERROR; propertyName _ Rope.Cat["Keep ", IO.PutFR["%g", IO.int[d.DelVerCount]], " versions"]; ViewerTools.SetContents[p, propertyName]; }; ChangePriorityProc: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; d.background _ NOT d.background; IF d.background THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite]; }; InterimlistFiles: PROC [trans: AlpTransaction.Handle, server: ROPE, pattern: ROPE, user: ROPE _ NIL, password: ROPE _ NIL , displayProperties: AlpineFile.PropertySet _ AlpineFile.allProperties, d: MyData] RETURNS [LIST OF REF ANY] = { directory, restOfPattern: ROPE; resultList: LIST OF REF ANY _ NIL; AlpineEnumProc: PROC [fileName: REF TEXT, universalFile: AlpineEnvironment.UniversalFile] RETURNS [quit: BOOL] = { intendReadLockOption: AlpFile.LockOption = [intendRead, IF d.breakLocks THEN wait ELSE fail]; readLockOption: AlpFile.LockOption = [read, IF d.breakLocks THEN wait ELSE fail]; IF d.stopFlag THEN { resultList _ CONS[first: NARROW[" ** List aborted **", ROPE], rest: NIL]; RETURN [quit: TRUE]; }; IF Rope.Match[pattern: restOfPattern, object: RefText.TrustTextAsRope[fileName], case: FALSE] THEN { file: AlpFile.Handle; fileID: AlpFile.FileID; properties: LIST OF AlpineEnvironment.PropertyValuePair; byteLength: INT; resultRope: ROPE ; [file, fileID] _ AlpFile.Open[transHandle: trans, universalFile: universalFile, lock: intendReadLockOption ! AlpInstance.LockFailed => GOTO lockError; AlpInstance.AccessFailed => IF missingAccess = AlpineEnvironment.NeededAccess.fileRead THEN GOTO skipFile ELSE REJECT; ]; properties _ AlpFile.ReadProperties[handle: file, lock: readLockOption ! AlpInstance.LockFailed => GOTO lockError; AlpInstance.AccessFailed => IF missingAccess = AlpineEnvironment.NeededAccess.fileRead THEN GOTO skipFile ELSE REJECT; ]; byteLength _ NARROW[properties.first, AlpineEnvironment.PropertyValuePair.byteLength].byteLength; resultRope _ IO.PutFR["%g\t", IO.text[fileName]]; UNTIL properties = NIL DO property: AlpineEnvironment.PropertyValuePair _ properties.first ; properties _ properties.rest; IF displayProperties[property.property] THEN BEGIN SELECT property.property FROM byteLength => { byteLength: INT _ NARROW[property, AlpineEnvironment.PropertyValuePair.byteLength].byteLength; resultRope _ Rope.Concat[resultRope, IO.PutFR["%g bytes ", IO.int[byteLength]]]; }; createTime => { createTime: BasicTime.GMT _ NARROW[property, AlpineEnvironment.PropertyValuePair.createTime].createTime; resultRope _ Rope.Concat[resultRope, IO.PutFR["%g ", IO.time[createTime]]]; }; highWaterMark => { highWaterMark: AlpineEnvironment.PageCount _ NARROW[property, AlpineEnvironment.PropertyValuePair.highWaterMark].highWaterMark; resultRope _ Rope.Concat[resultRope, IO.PutFR["HWM: %g ", IO.int[highWaterMark]]]; }; modifyAccess => { modifyAccess: AlpineEnvironment.AccessList _ NARROW[property, AlpineEnvironment.PropertyValuePair.modifyAccess].modifyAccess; resultRope _ Rope.Concat[resultRope,"modify access: ("]; IF modifyAccess = NIL THEN resultRope _ Rope.Concat[resultRope,"*none*"] ELSE { UNTIL modifyAccess = NIL DO accessItem: AlpineEnvironment.RName _ modifyAccess.first; resultRope _ Rope.Concat[resultRope,accessItem]; modifyAccess _ modifyAccess.rest; IF modifyAccess # NIL THEN resultRope _ Rope.Concat[resultRope,", "]; ENDLOOP; } ; resultRope _ Rope.Concat[resultRope,") "]; }; owner => { owner: AlpineEnvironment.OwnerName _ NARROW[property, AlpineEnvironment.PropertyValuePair.owner].owner; resultRope _ Rope.Concat[resultRope, IO.PutFR["owner: %g ", IO.rope[owner]]]; }; readAccess => { readAccess: AlpineEnvironment.AccessList _ NARROW[property, AlpineEnvironment.PropertyValuePair.readAccess].readAccess; resultRope _ Rope.Concat[resultRope,"read access: ("]; IF readAccess = NIL THEN resultRope _ Rope.Concat[resultRope,"*none*"] ELSE { UNTIL readAccess = NIL DO accessItem: AlpineEnvironment.RName _ readAccess.first; resultRope _ Rope.Concat[resultRope,accessItem]; readAccess _ readAccess.rest; IF readAccess # NIL THEN resultRope _ Rope.Concat[resultRope,", "]; ENDLOOP; } ; resultRope _ Rope.Concat[resultRope,") "]; }; stringName => { stringName: ROPE _ NARROW[property, AlpineEnvironment.PropertyValuePair.stringName].stringName; resultRope _ Rope.Concat[resultRope, IO.PutFR["stringName: %g ", IO.rope[stringName]]]; }; version => { version: LONG INTEGER _ NARROW[property, AlpineEnvironment.PropertyValuePair.version].version; resultRope _ Rope.Concat[resultRope, IO.PutFR["version: %g ", IO.int[version]]]; }; ENDCASE; END; ENDLOOP; resultList _ CONS[first: resultRope, rest: resultList]; EXITS lockError => { resultRope _ IO.PutFR["%g has lock set", IO.text[fileName]]; resultList _ CONS[first: resultRope, rest: resultList]; }; skipFile => {}; }; RETURN [quit: FALSE]; }; resultRope: ROPE _ NIL; outcome: AlpTransaction.Outcome; failureName: ROPE _ "" ; { [directory, restOfPattern] _ DecomposePattern[server, pattern, user]; IF trans # NIL THEN { bangPos: INT ; IF d.assertWheel THEN trans.AssertAlpineWheel[TRUE]; IF (bangPos _ Rope.Find[restOfPattern,"!"]) >= 0 THEN { IF bangPos = 0 THEN restOfPattern _ "*" ELSE restOfPattern _ Rope.Substr[base: restOfPattern, len: bangPos]; }; AlpineInterimDirectory.EnumerateDirectoryUnderTrans[ transHandle: trans, directoryName: Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]], enumProc: AlpineEnumProc]; outcome _ AlpTransaction.Finish[trans, commit]; } ELSE { resultList _ CONS[first: NARROW["Use the List button for FS and IFS Files", ROPE], rest: resultList]; }; }; resultList _ List.Sort[resultList, CompareProc]; RETURN [resultList]; }; ListInterimProc: PUBLIC Buttons.ButtonProc = { resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, file, password: ROPE; directory, restOfPattern: ROPE; printedSomething: BOOL _ FALSE; parseError: BOOL; errorExplanation: ROPE; callList: YodelData.PerformProc = { RETURN[InterimlistFiles[trans, server, file, user, password, d.displayProperties, d]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in List because %g\n", IO.rope[errorExplanation]]; }; d.stopFlag _ FALSE; [user, password, server, directory, file, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; [directory, restOfPattern] _ DecomposePattern[server: server, pattern: file, user: user]; d.out.PutF["\nList Interim of [%g]%g%g\n", IO.rope[server], IO.rope[directory], IO.rope[restOfPattern]]; resultList _ PerformOp[performProc: callList, server: server, user: user, password: password]; DO nowRope: ROPE _ NARROW[IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; printedSomething _ TRUE ; ENDLOOP; IF NOT printedSomething THEN d.out.PutRope[" ** no files match the pattern **\n"]; EXITS badParse => {}; }; END. \YodelUserImpl1.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Yodel: User commands Last Edited by: Carl Hauser, April 17, 1986 10:01:23 am PST Hauser, April 19, 1985 4:45:25 pm PST Bob Hagmann January 6, 1986 4:48:38 pm PST PROC [ fileName: ROPE, universalFile: AE.UniversalFile, trans: AlpTransaction.Handle, flushState: BOOL ] RETURNS [ didCommitAndContinue: BOOL, quit: BOOL, nowTrans: AlpTransaction.Handle ]; loop through properties and print those enabled byteLength createTime highWaterMark modifyAccess owner readAccess stringName version PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOLEAN]; alpine files to list FS or IFS files to list PROC [ fileName: ROPE, universalFile: AE.UniversalFile, trans: AlpTransaction.Handle, flushState: BOOL ] RETURNS [ didCommitAndContinue: BOOL, quit: BOOL, nowTrans: AlpTransaction.Handle ]; PROC [fullFName: ROPE] RETURNS [continue: BOOL]; alpine files to delete FS or IFS files to delete loop through all the properties and find the one selected comparison is by Rope equivalent to option! parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL loop through properties and print those enabled byteLength createTime highWaterMark modifyAccess owner readAccess stringName version alpine files to list Bob Hagmann June 11, 1985 9:11:52 am PDT reformatted Bob Hagmann June 12, 1985 8:10:17 am PDT split from YodelUserImpl Bob Hagmann June 12, 1985 9:16:23 am PDT changes to: delete for delver and confirmantion Bob Hagmann July 9, 1985 changes to: copy - allow multiple file copy and other bug fixes Bob Hagmann July 26, 1985 9:51:26 am PDT changes to: tryToDelete (local of AlpineDeleteEnumProc, local of fileDelete) Bob Hagmann July 26, 1985 2:20:23 pm PDT changes to: tryToDelete (local of AlpineDeleteEnumProc, local of fileDelete) Bob Hagmann August 9, 1985 2:39:57 pm PDT changes to: tryToDelete (local of AlpineDeleteEnumProc, local of fileDelete) Bob Hagmann January 6, 1986 4:48:38 pm PST changes to: AlpineDeleteEnumProc (local of fileDelete) Bob Hagmann January 27, 1986 9:16:20 am PST Fix up deletes of $$$.btree and $$$Junk!1 (during delete with 0 keep) changes to: EnumeratePattern, AlpineDeleteEnumProc (local of fileDelete) Κ&Ο˜šœ™Icodešœ Οmœ1™<—Jšœ™šœ™K™+Jšœ%™%K™*—J˜J˜šΟk ˜ Jšœžœ4˜AJšœžœ˜˜―Jšœ žœ˜/J˜ J˜J˜Jšœ žœ6˜GJšœžœ6˜JJšœ žœžœ˜Jšœžœ˜Jšœžœ1˜>Jšœ žœ˜#J˜Jšžœžœj˜rJšžœžœ$žœ˜AJšœžœ˜J˜J˜J˜Jšžœ˜Jšœžœ˜Jšœžœ ˜Jšœ žœ˜-J˜ —J˜Jš œžœžœžœžœ ˜5J˜Jšžœmžœžœ>˜ΈJ˜šžœ ˜J˜—J˜J˜šžœžœžœ˜,J˜—Jšœ žœžœ ˜Jšœžœžœ˜/šœžœžœ˜'J˜—J˜JšΟnœžœžœ žœžœ:žœžœžœžœ#˜ΝJ˜šŸœžœ3žœžœ,žœžœ žœžœ#žœ˜†Jšœžœžœ˜%Jšœžœžœ˜!Jšœ žœžœ˜Jšœžœ˜Jšœžœ˜š Οb œžœžœ žœžœ˜5šžœžœ˜Jšœžœ˜Jšœžœ˜JšœL˜LJšœ&˜&Jšžœ žœžœ˜4Jšœ(˜(Jšœ žœ˜Jšœ žœ˜J˜—J˜—Jšœ˜šœ˜šžœž˜ šœ˜JšžœžœžœΟc˜=J˜—˜Jšžœžœžœ˜#Jšžœžœžœ‘˜=J˜—Jšžœ˜—˜šžœ˜Jšœžœ˜ Jšœ žœ˜Jšœžœ˜ Jšœy˜yšžœ˜Jšœ,žœžœ˜6Jšœ˜—Jšœp˜pJšžœžœžœ˜Jšœ žœ˜šžœžœ˜Jšœžœ˜Jšœ(˜(J˜—Jšœ˜Jšžœžœžœ˜ Jšžœ˜—J˜—J˜—J˜J˜—J˜J˜Jšžœžœžœ˜˜J˜—š Ÿ œžœžœžœžœ˜,Jšžœžœ˜'Jšœžœžœ˜Jšœžœžœ˜šžœžœžœ˜/šžœžœ˜#Jšžœžœžœ ˜4J˜—Jš œžœžœžœžœ˜6J˜—Jšžœžœ˜+J˜—J˜šŸ œžœžœžœ.žœžœžœžœžœžœ˜’Jšœ žœ˜Jšœ žœ˜Jšœžœ˜Jšœ žœ˜Jšœ-˜-šœ9žœ.žœ ˜vJšœ žœ˜Jšœ%˜%Jšžœ˜ Jšœ˜—Jšœ˜šžœ žœ˜šœ9žœJžœ ˜’Jšžœ ˜Jšœ˜—J˜J˜—Jšœ žœ˜J˜)Jšœ@˜@Jšžœ žœ ˜Jšœžœ,˜1J˜Mšž˜J˜—J˜—J˜š  œžœ(žœ žœžœžœ žœžœ-žœažœžœžœžœžœ˜ Jšœžœ˜Jš œ žœžœžœžœžœ˜"Jšœ žœ˜Jšœ žœ˜J˜š œ˜ Jšžœ žœžœ:žœžœžœžœ$™½Jšœ9žœžœžœ˜^Jšœ,žœžœžœ˜QJšžœžœžœ˜"Jšœ˜šžœ žœ˜Jš œ žœžœžœ žœ˜JJšœ ˜ Jšžœžœžœ˜DJ˜—Jšœžœ˜ ˜J˜J˜Jšœ žœžœ%˜8Jšœ žœ˜Jšœ žœ˜Jšœ žœžœ˜1šžœžœžœ˜.šœm˜mJšœžœ ˜+Jšœžœ8˜VJšœžœžœ ˜Jšœžœžœ˜—J˜J˜˜Fšœžœ ˜+˜Jšžœ9žœžœ ˜MJšžœžœ˜ ——J˜—šœ žœ˜%J˜;J™Jšœ/™/—šžœžœž˜J˜BJ˜šžœ&žœ˜.šžœž˜Jšœ ™ ˜Jšœ žœžœF˜^Jšœ%žœžœ˜P—˜Jšœ ™ —˜JšœžœžœF˜hJšœ%žœžœ˜K—˜Jšœ ™ —˜Jšœ-žœL˜Jšœ%žœžœ˜R—˜Jšœ ™ —˜Jšœ-žœJ˜}J˜8šžœžœžœ/žœ˜Ošžœžœž˜J˜9J˜0J˜!Jšžœžœžœ+˜EJšžœ˜—Jšœ˜—J˜*J˜Jšœ™—˜ Jšœ%žœ<˜gJšœ%žœžœ˜MJšœ˜Jšœ ™ —˜Jšœ+žœF˜wJ˜6šžœžœžœ/žœ˜Mšžœžœž˜J˜7J˜0J˜Jšžœžœžœ+˜CJšžœ˜—Jšœ˜—J˜*J˜Jšœ ™ —˜Jšœ žœžœF˜_Jšœ%žœžœ˜WJ˜Jšœ™—˜ Jšœ žœžœžœ@˜^Jšœ%žœžœ˜PJ˜—Jšžœ˜—Jšœ˜—Jšžœ˜—J˜—Jšœ˜Jšœ˜Jšœ žœ&˜7šž˜˜Jšœ žœžœ˜˜>šžœ;žœžœ˜JJšœžœ˜4Jšžœžœ˜:J˜—šœžœ˜šžœžœžœ˜ š žœžœžœžœ&žœžœž˜]Jšœ žœ˜.Jšœ žœ?˜NJšžœ,žœ'˜YJšžœžœžœ˜Jšžœ˜—J˜—šœ%˜%Jšœ žœ˜Jšœ˜J˜—Jšœ˜Jšœ/˜/Jšœžœ žœ˜'J˜—J˜—šœžœ˜Jšžœžœ žœ˜3Jšœ˜J˜—Jšžœ˜J˜J˜—š œžœ ˜!Kšžœ žœžœ žœ™0š  œžœ žœžœ žœ˜:Jšœ žœžœ˜Jšœžœ˜Jšœ žœ˜ Jšœ žœ˜J˜šžœ žœ˜Jšœ žœžœžœ˜UJšžœ žœ˜J˜—Jšœ˜Jšœ6˜6Jšœ?˜?šžœžœ˜"Jšžœ,žœ žœ˜DJ˜—šžœ žœžœ˜$Jšœ˜Jšœ žœ˜Jšœ5žœ˜;J˜Jšœ!˜!J˜Jšœ.˜.šžœ žœ˜Jšœ žœžœžœ˜UJšžœ žœ˜J˜—Jšžœ žœ žœ˜%J˜—šžœ žœ˜Jšžœ˜Jšœ ˜ Jšœžœ˜/J˜—Jšžœ žœ˜J˜J˜—Jšœ žœ˜šžœžœ˜"Jšœžœ˜Jšœ žœ˜Jšœžœ˜šœ žœ ˜Jšœ˜Jšœ˜J˜—Jšœ"˜"JšœB˜BJšœO˜OJšžœ>˜@Jšœ˜J˜—šœžœ˜Jšœ˜J˜—J˜—J˜Jšœ žœžœ˜J˜ Jšœ žœ˜Jšœ žœ˜Jšœžœ˜Jšœžœžœ˜'Jšœžœ˜J˜J˜BJšœ˜šžœ/žœ˜7Jšœ2˜2Jšžœžœžœžœ˜XJ˜—Jšœžœ˜šžœ žœžœ˜Jšœ™Jšžœžœžœ˜4šžœžœ˜J˜+J˜—J˜P˜J˜J˜Jšœ˜Jšœ ˜ Jšœ žœ˜Jšœ˜J˜-J˜—J˜J˜/šžœžœ˜šœ žœ˜˜DJš žœžœžœžœžœ˜<—J˜—J˜—J˜—šœžœ˜Jšœ™Jšœžœ˜Jšœ žœ˜Jšœn˜nJšžœ,žœ0˜bJšœ˜JšžœžœK‘F˜±JšžœE˜GJ˜—Jšœ žœ˜Jšœ˜J˜0J˜—J˜˜J˜—š œžœ˜.Jšœ žœ ˜Jšœžœ˜%Jš œ žœžœžœžœžœ˜"Jšœžœ˜!Jšœžœ˜Jšœžœ˜Jšœ žœ˜Jšœžœžœ˜Jšœ žœ˜Jšœžœ˜Jšœ žœ˜J˜š  œ˜%Jš œžœžœžœžœ˜Jšœ˜Jšžœ˜ J˜—J˜š œžœ˜Jšœ3žœ˜NJ˜—J˜Jšœ žœ˜Jšžœžœ&˜:J˜Jšœ[˜[Jšžœ žœžœ ˜7J˜_šžœžœ˜JšœLžœžœžœ˜‰Jšžœ˜J˜—J˜šžœžœ˜Jšœ žœ!˜-Jšœžœžœ˜šžœžœ˜Jšœ'˜'J˜—Jšžœ)˜-šžœžœ˜Jšœ˜Jšœ žœ˜J˜—Jšžœ žœžœ˜@Jšœ˜—Jš œ%žœžœžœžœ˜tJ˜cJšœ ˜ Jšœ2˜2Jšžœžœžœžœ-˜[Jšžœžœ*žœ˜Ršž˜Jšœ˜—Jšœ˜—J˜˜J˜—J˜š  œžœ˜*Jšœ žœ ˜Jšœžœ ˜)Jšœžœ˜)J˜"Jšœ˜—J˜š œžœ˜0Jšœ žœ ˜Jšœžœ ˜)Jšœžœ˜ ˜#Jšœ9™9Jšœ+™+—šžœžœž˜-Jšœžœ˜J˜7šžœ"žœ˜*Jšœžœ˜?Jšžœ˜Jšœžœ*˜/Jšœžœ+˜0Jšžœ˜Jšœ˜—Jšžœ˜—Jšœ˜—J˜š œžœ˜3Jšœ žœ ˜Jšœžœ ˜)Jšœžœ˜!Jšžœ ˜Jšœžœ*˜/Jšœžœ+˜0Jšœ˜J˜!J˜J˜—š œžœ˜&Jšœ žœ ˜Jšœžœ ˜)Jšœ˜J˜J˜—š œžœ˜%Jšœ žœ ˜Jšœžœ ˜)Jšœ˜J˜J˜—š  œžœžœ˜(Jšœ$žœ˜*Jšœ"žœ˜(Jšœ˜Jšœ˜—J˜š  œžœžœ˜)Jšœ!žœ˜'Jšœ žœ˜&Jšœ˜Jšœ˜J˜—š  œžœžœ"˜7šž˜Jšžœ žœžœžœ˜2Jšžœžœžœ ˜/šœžœ˜Jšœ˜Jšžœ ˜Jšžœ˜J˜—Jšžœ˜—J˜J˜—š   œžœžœ žœžœžœ˜Tšž˜Jš žœ žœžœžœžœ ˜>Jšžœžœžœ ˜/šœžœ˜Jšœ˜Jšœ˜Jšžœ ˜Jšžœ˜J˜—Jšžœ˜—J˜—J˜J˜š œžœ˜/Jš œžœžœžœžœ,ž™TJšœ žœ ˜Jšœžœ ˜)Jšœžœ˜šžœ ž˜šœ ‘'˜1Jšžœžœžœžœ#˜CJ˜—šœ ‘ ˜Jšœ˜Jšœ˜—šœ ‘(˜2Jšžœžœ#˜