DIRECTORY Commander, -- for debug expandname BasicTime, Convert, FS, FSBackdoor, FSName, IO, PFS, PFSNames, RefText, Rope; FSOnPFSImpl: CEDAR PROGRAM IMPORTS Commander, Convert, IO, PFSNames, PFS, RefText, Rope EXPORTS FS, FSBackdoor, FSName ~ BEGIN ROPE: PRIVATE TYPE = Rope.ROPE; STREAM: PRIVATE TYPE = IO.STREAM; Error: PUBLIC ERROR [error: FS.ErrorDesc] ~ PFS.Error; Wrap: PROC [inner: PROC, wDir: ROPE] ~ { IF wDir = NIL THEN inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], inner]; }; StreamOpen: PUBLIC PROC [fileName: ROPE, accessOptions: FS.AccessOptions ¬ $read, streamOptions: FS.StreamOptions ¬ FS.defaultStreamOptions, keep: CARDINAL ¬ 1, createByteCount: INT ¬ 2560, streamBufferParms: FS.StreamBufferParms ¬ FS.defaultStreamBufferParms, extendFileProc: FS.ExtendFileProc ¬ NIL, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, remoteCheck: BOOL ¬ TRUE, wDir: ROPE ¬ NIL, checkFileType: BOOL ¬ FALSE, fileType: FS.FileType ¬ FS.tUnspecified ] RETURNS [st: STREAM] = { StreamOpenInner: PROC ~ { st ¬ PFS.StreamOpen[ fileName: PFS.PathFromRope[fileName], accessOptions: (SELECT accessOptions FROM read => read, write => write, create => create, append => append, ENDCASE => ERROR), wantedUniqueID: UIDFromGMT[wantedCreatedTime], checkFileType: checkFileType, fileType: [fileType], createOptions: [keep: keep], streamOptions: [includeFormatting: NOT streamOptions[tiogaRead], closeFSOpenFileOnClose: streamOptions[closeFSOpenFileOnClose]], streamBufferParms: [bytesPerBuffer: streamBufferParms.vmPagesPerBuffer*512, nBuffers: streamBufferParms.nBuffers] ]; }; Wrap[StreamOpenInner, wDir]; }; OpenFileFromStream: PUBLIC PROC [self: STREAM] RETURNS [FS.OpenFile] = { RETURN [WrapOpenFile[PFS.OpenFileFromStream[self]]] }; StreamFromOpenFile: PUBLIC PROC [openFile: FS.OpenFile, accessRights: FS.Lock ¬ $read, initialPosition: FS.InitialPosition ¬ $start, streamOptions: FS.StreamOptions ¬ FS.defaultStreamOptions, streamBufferParms: FS.StreamBufferParms ¬ FS.defaultStreamBufferParms, extendFileProc: FS.ExtendFileProc ¬ NIL ] RETURNS [stream: STREAM ¬ NIL] = { stream ¬ PFS.StreamFromOpenFile[ openFile: BreakOpenFile[openFile], accessOptions: SELECT TRUE FROM (accessRights = read) => read, ((accessRights = write) AND (initialPosition = start)) => write, ((accessRights = write) AND (initialPosition = end)) => append, ENDCASE => ERROR, -- Can't get here streamOptions: [includeFormatting: NOT streamOptions[tiogaRead], closeFSOpenFileOnClose: streamOptions[closeFSOpenFileOnClose]], streamBufferParms: [bytesPerBuffer: streamBufferParms.vmPagesPerBuffer*512, nBuffers: streamBufferParms.nBuffers] ]; }; Open: PUBLIC PROC [name: ROPE, lock: FS.Lock ¬ $read, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, remoteCheck: BOOL ¬ TRUE, wDir: ROPE ¬ NIL, verifyNow: BOOL ¬ FALSE, checkFileType: BOOL ¬ FALSE, fileType: FS.FileType ¬ FS.tUnspecified ] RETURNS [file: FS.OpenFile ¬ FS.nullOpenFile] ~ { OpenInner: PROC ~ { file ¬ WrapOpenFile[PFS.Open[ name: PFS.PathFromRope[name], access: SELECT lock FROM read => read, write => write, ENDCASE => ERROR, wantedUniqueID: UIDFromGMT[wantedCreatedTime], checkFileType: checkFileType, fileType: [fileType] ]]; }; Wrap[OpenInner, wDir]; }; Create: PUBLIC PROC [name: ROPE, setPages: BOOL ¬ FALSE, pages: INT ¬ 0, setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1, wDir: ROPE ¬ NIL, fileType: FS.FileType ¬ FS.tUnspecified ] RETURNS [file: FS.OpenFile ¬ FS.nullOpenFile] ~ { CreateInner: PROC ~ { file ¬ WrapOpenFile[PFS.Open[ name: PFS.PathFromRope[name], access: create, fileType: [fileType], createOptions: [keep: IF setKeep THEN keep ELSE 0, fileType: [fileType]] ]]; }; Wrap[CreateInner, wDir]; }; BreakOpenFile: PROC [file: FS.OpenFile] RETURNS [PFS.OpenFile] = { WITH file SELECT FROM pfs: REF PFS.OpenFile => RETURN [pfs­]; ENDCASE => RETURN [NIL]; }; WrapOpenFile: PROC [file: PFS.OpenFile] RETURNS [FS.OpenFile] = { RETURN [IF file = NIL THEN FS.nullOpenFile ELSE [NEW[PFS.OpenFile ¬ file]]] }; GetInfo: PUBLIC PROC [file: FS.OpenFile] RETURNS [keep: CARDINAL ¬ 0, pages: INT ¬ 0, bytes: INT ¬ 0, created: BasicTime.GMT, lock: FS.Lock ¬ read, fileType: FS.FileType ¬ FS.tUnspecified] = { uniqueID: PFS.UniqueID; pfsFileType: PFS.FileType; [uniqueID: uniqueID, bytes: bytes, fileType: pfsFileType] ¬ PFS.GetInfo[BreakOpenFile[file]]; pages ¬ (bytes + 511)/512; created ¬ uniqueID.egmt.gmt; fileType ¬ [pfsFileType]; }; nameFormat: PFS.NameFormat ¬ brackets; RopeFromPath: PROC [path: PFS.PATH] RETURNS [ROPE] = INLINE { RETURN [IF path = NIL THEN NIL ELSE PFS.RopeFromPath[path, nameFormat]] }; GetName: PUBLIC PROC [file: FS.OpenFile] RETURNS [fullFName, attachedTo: ROPE] = { fullPath, attachedPath: PFS.PATH; [fullFName: fullPath, attachedTo: attachedPath] ¬ PFS.GetInfo[BreakOpenFile[file]]; fullFName ¬ RopeFromPath[fullPath]; attachedTo ¬ RopeFromPath[attachedPath]; }; SetByteCountAndCreatedTime: PUBLIC PROC [file: FS.OpenFile, bytes: INT ¬ -1, created: BasicTime.GMT ¬ BasicTime.nullGMT] ~ { PFS.SetByteCountAndUniqueID[BreakOpenFile[file], bytes, UIDFromGMT[created]]; }; FilePtr: TYPE = LONG POINTER TO INT; Copy: PUBLIC PROC [from, to: ROPE, setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, remoteCheck: BOOL ¬ TRUE, attach: BOOL ¬ FALSE, wDir: ROPE ¬ NIL ] RETURNS [toFName: ROPE] = { CopyInner: PROC ~ { Confirm: PFS.NameConfirmProc ~ { toFName ¬ RopeFromPath[fullName]; RETURN [TRUE]; }; IF attach THEN toFName ¬ RopeFromPath[ PFS.Attach[ attachment: PFS.PathFromRope[to], attachedFile: PFS.PathFromRope[from], wantedUniqueID: UIDFromGMT[wantedCreatedTime], remoteCheck: remoteCheck ! PFS.Error => { attach ¬ FALSE; CONTINUE } ] ]; IF NOT attach THEN { PFS.Copy[ from: PFS.PathFromRope[from], to: PFS.PathFromRope[to], wantedUniqueID: UIDFromGMT[wantedCreatedTime], confirmProc: Confirm ]; }; }; Wrap[CopyInner, wDir]; }; Rename: PUBLIC PROC [from, to: ROPE, setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, wDir: ROPE ¬ NIL ] = { RenameInner: PROC ~ { PFS.Rename[ from: PFS.PathFromRope[from], to: PFS.PathFromRope[to], wantedUniqueID: UIDFromGMT[wantedCreatedTime] ]; }; Wrap[RenameInner, wDir]; }; UIDFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [PFS.UniqueID] ~ INLINE { RETURN [[egmt: [gmt: gmt, usecs: 0]]] }; Delete: PUBLIC PROC [name: ROPE, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, wDir: ROPE ¬ NIL ] = { DeleteInner: PROC ~ { PFS.Delete[name: PFS.PathFromRope[name], wantedUniqueID: UIDFromGMT[wantedCreatedTime]]; }; Wrap[DeleteInner, wDir]; }; FileInfo: PUBLIC PROC [ name: ROPE, wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT, remoteCheck: BOOL ¬ TRUE, wDir: ROPE ¬ NIL ] RETURNS [ fullFName, attachedTo: ROPE ¬ NIL, keep: CARDINAL ¬ 1, bytes: INT ¬ 0, created: BasicTime.GMT ¬ BasicTime.nullGMT, fileType: FS.FileType ¬ FS.tUnspecified ] = { FileInfoInner: PROC ~ { fullPath, attachedPath: PFS.PATH; uniqueID: PFS.UniqueID; pfsFileType: PFS.FileType; [fullFName: fullPath, attachedTo: attachedPath, uniqueID: uniqueID, bytes: bytes, fileType: pfsFileType] ¬ PFS.FileInfo[name: PFS.PathFromRope[name], wantedUniqueID: UIDFromGMT[wantedCreatedTime]]; fullFName ¬ RopeFromPath[fullPath]; attachedTo ¬ RopeFromPath[attachedPath]; created ¬ uniqueID.egmt.gmt; fileType ¬ [pfsFileType]; }; Wrap[FileInfoInner, wDir]; }; SetKeep: PUBLIC PROC [name: ROPE, keep: CARDINAL ¬ 1, wDir: ROPE ¬ NIL] = { SetKeepInner: PROC ~ { [] ¬ PFS.PathFromRope[name]; -- Syntax check }; Wrap[SetKeepInner, wDir]; }; BytesForPages: PUBLIC PROC [pages: INT] RETURNS [bytes: INT] = { RETURN [pages * 512] -- Ignore real page size }; PagesForBytes: PUBLIC PROC [bytes: INT] RETURNS [pages: INT] = { RETURN [(bytes+511)/512] }; GetWDir: PUBLIC PROC [wDir: ROPE ¬ NIL] RETURNS [ans: ROPE] ~ { GetWDirInner: PROC ~ {ans ¬ RopeFromPath[PFS.GetWDir[]]}; Wrap[GetWDirInner, wDir]; }; DIRPtr: TYPE = POINTER; DirEntPtr: TYPE = POINTER; EnumerateForNames: PUBLIC PROC [pattern: ROPE, proc: FS.NameProc, wDir: ROPE ¬ NIL] = { EachPath: PFS.NameProc ~ { continue ¬ proc[RopeFromPath[name]]; }; EnumerateForNamesInner: PROC ~ { PFS.EnumerateForNames[pattern: PFS.PathFromRope[pattern], proc: EachPath]; }; Wrap[EnumerateForNamesInner, wDir]; }; EnumerateForInfo: PUBLIC PROC [pattern: ROPE, proc: FS.InfoProc, wDir: ROPE ¬ NIL] = { EachInfo: PFS.InfoProc ~ { continue ¬ proc[fullFName~RopeFromPath[fullFName], attachedTo~RopeFromPath[attachedTo], created~uniqueID.egmt.gmt, bytes~bytes, keep~1, fileType~[fileType]]; }; EnumerateForInfoInner: PROC ~ { PFS.EnumerateForInfo[pattern: PFS.PathFromRope[pattern], proc: EachInfo]; }; Wrap[EnumerateForInfoInner, wDir]; }; Close: PUBLIC PROC [file: FS.OpenFile] = { PFS.Close[BreakOpenFile[file]] }; nullCP: FS.ComponentPositions = [server: nullPos, dir: nullPos, subDirs: nullPos, base: nullPos, ext: nullPos, ver: nullPos]; nullPos: FS.Position = [start: 0, length: 0]; ExpandName: PUBLIC PROC[name: ROPE, wDir: ROPE ¬ NIL] RETURNS [fullFName: ROPE ¬ NIL, cp: FS.ComponentPositions, dirOmitted: BOOL] = { path: PFS.PATH; scratch: REF TEXT ~ RefText.ObtainScratch[100]; text: REF TEXT ¬ scratch; GetPath: PROC ~ { path ¬ PFS.AbsoluteName[PFS.PathFromRope[name]]; }; lastVersion: PFSNames.Version ¬ [none]; Append: PROC [t: REF TEXT, p: PFS.PATH, i: INT] RETURNS [REF TEXT] ~ { comp: PFSNames.Component ~ PFSNames.Fetch[p, i]; RETURN[ RefText.AppendRope[t, comp.name.base, comp.name.start, comp.name.len] ]; }; isADirectory: BOOL; comps: NAT; Wrap[GetPath, wDir]; comps ¬ PFSNames.ComponentCount[path]; isADirectory ¬ PFSNames.IsADirectory[path]; dirOmitted ¬ FALSE; cp ¬ nullCP; IF comps >= 2 THEN { text ¬ RefText.AppendChar[text, '[ ]; cp.server.start ¬ text.length; text ¬ Append[text, path, 1]; cp.server.length ¬ text.length-cp.server.start; text ¬ RefText.AppendChar[text, '] ]; cp.dir.start ¬ text.length; cp.subDirs.start ¬ text.length; }; SELECT TRUE FROM comps>3 OR comps=3 AND isADirectory => { text ¬ RefText.AppendChar[text, '< ]; cp.dir.start ¬ text.length; text ¬ Append[text, path, 2]; cp.dir.length ¬ text.length-cp.dir.start; text ¬ RefText.AppendChar[text, '> ]; cp.subDirs.start ¬ text.length; }; comps=3 AND NOT isADirectory => { cp.base.start ¬ text.length; text ¬ Append[text, path, 2]; cp.base.length ¬ text.length-cp.base.start; lastVersion ¬ path.ShortName[].version; dirOmitted ¬ TRUE; }; ENDCASE => dirOmitted ¬ TRUE; FOR i: NAT IN [3..comps) DO IF i ]; } ELSE { cp.base.start ¬ text.length; text ¬ Append[text, path, i]; cp.base.length ¬ text.length-cp.base.start; lastVersion ¬ path.ShortName[].version; }; ENDLOOP; IF lastVersion.versionKind # none THEN { text ¬ RefText.AppendChar[text, '! ]; cp.ver.start ¬ text.length; text ¬ SELECT lastVersion.versionKind FROM lowest => RefText.AppendChar[text, 'L ], highest => RefText.AppendChar[text, 'H ], all => RefText.AppendChar[text, '* ], numeric => Convert.AppendCard[text, lastVersion.version], ENDCASE => text; cp.ver.length ¬ text.length-cp.ver.start; }; FOR i: NAT DECREASING IN [cp.base.start..cp.base.start+cp.base.length) DO IF text[i] = '. THEN { cp.ext.start ¬ i+1; cp.ext.length ¬ cp.base.start+cp.base.length-(i+1); cp.base.length ¬ i-cp.base.start; EXIT; }; ENDLOOP; IF cp.base = nullPos THEN { cp.base.start ¬ text.length }; IF cp.ext = nullPos THEN {cp.ext.start ¬ cp.base.start+cp.base.length; IF cp.ext.start = CARDINAL[text.length-1] THEN cp.ext.start ¬ cp.ext.start+1}; IF cp.ver = nullPos THEN {cp.ver.start ¬ cp.ext.start+cp.ext.length; IF cp.ver.start = CARDINAL[text.length-1] THEN cp.ver.start ¬ cp.ver.start+1}; fullFName ¬ Rope.FromRefText[text]; RefText.ReleaseScratch[scratch]; }; EnumerateCacheForNames: PUBLIC PROC [proc: FSBackdoor.NameProc, volName, pattern: ROPE] = { -- This is pretty easy for Unix - it has no cache to enumerate. }; group: PACKED ARRAY FSBackdoor.ErrorCode OF FS.ErrorGroup = [ -- 4-- bug, bug, bug, bug, --14-- environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, -- 2-- environment, environment, -- 9-- client, client, client, client, client, client, client, client, client, --12-- user, user, user, user, user, user, user, user, user, user, user, user ]; codeAtom: ARRAY FSBackdoor.ErrorCode OF ATOM = [ $ok, $inconsistent, $software, $badFP, $wentOffline, $hardware, $volumeFull, $fragmented, $noMoreVersions, $serverInaccessible, $connectionRejected, $connectionTimedOut, $badCredentials, $accessDenied, $quotaExceeded, $invalidPropertyPage, $badBTree, $outOfPropertySpace, $lockConflict, $fileBusy, $noCache, $wrongLock, $globalWriteLock, $zeroKeep, $badByteCount, $unknownPage, $invalidOpenFile, $notImplemented, $fileTypeMismatch, $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified, $globalCreation, $badWorkingDir, $noKeeps, $cantUpdateTiogaFile ]; ProduceError: PUBLIC PROC [code: FSBackdoor.ErrorCode, explanation: ROPE] = { ERROR Error [ [group[code], codeAtom[code], explanation] ]; }; TranslateForRead: PUBLIC PROC [server: ROPE] RETURNS [LIST OF ROPE] = { RETURN [NIL] -- who uses this stuff? }; BangVersionFile: PUBLIC PROC [file: ROPE, version: FSBackdoor.Version] RETURNS [ROPE] = { length: CARDINAL = Rope.Length[file]; bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"]; RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, VersionPartFromVersion[version] ] ]; }; VersionFromRope: PUBLIC PROC [r: ROPE] RETURNS [v: FSBackdoor.Version] = { IF Rope.IsEmpty[r] THEN v ¬ FSBackdoor.highestVersion ELSE [v, ] ¬ ParseVersion[r, 0, FALSE]; }; VersionPartFromVersion: PROC [version: FSBackdoor.Version] RETURNS [r: Rope.Text] = { t: REF TEXT; SELECT version FROM FSBackdoor.lowestVersion, FSBackdoor.highestVersion => r ¬ NIL; ENDCASE => TRUSTED { r ¬ Rope.NewText[6]; t ¬ LOOPHOLE[r]; t[0] ¬ '!; t.length ¬ 1; [] ¬ Convert.AppendInt[t, version]; }; }; ParseVersion: PROC [name: ROPE, index: CARDINAL, pattern: BOOL ¬ FALSE] RETURNS [value: FSBackdoor.Version, vI: FSName.VersionInfo] = { c: CHAR; lastIndex: CARDINAL = Rope.Length[name] - 1; IF lastIndex IN [index .. index+4] THEN SELECT c ¬ Rope.Fetch[name, index] FROM IN ['0 .. '9] => { num: LONG CARDINAL ¬ 0; DO num ¬ num*10 + (c - '0); IF index = lastIndex THEN { IF num NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion) THEN EXIT; value ¬ [num]; vI ¬ number; RETURN; }; index ¬ index + 1; c ¬ Rope.Fetch[name, index]; IF c NOT IN ['0 .. '9] THEN EXIT; ENDLOOP; }; 'H, 'h => IF index = lastIndex THEN { value ¬ FSBackdoor.highestVersion; vI ¬ bangH; RETURN }; 'L, 'l => IF index = lastIndex THEN { value ¬ FSBackdoor.lowestVersion; vI ¬ bangL; RETURN }; '* => IF index = lastIndex AND pattern THEN { value ¬ FSBackdoor.highestVersion; vI ¬ bangStar; RETURN }; ENDCASE; IllegalVersion[name]; }; IllegalVersion: PROC [n: ROPE] = { ProduceError[ illegalName, QuotedName[n, " has an illegal version part"] ]; }; QuotedName: PROC [n, rest: ROPE] RETURNS [ROPE] = { RETURN [ Rope.Cat["\"", n, "\"", rest] ]; }; DebugExpandName: Commander.CommandProc ~ { ENABLE Error => {msg ¬ error.explanation; result ¬ $Failure; CONTINUE}; fullFName: ROPE; cp: FS.ComponentPositions; WHILE Rope.Match[" *", cmd.commandLine] DO cmd.commandLine ¬ Rope.Substr[cmd.commandLine, 1] ENDLOOP; cmd.commandLine ¬ Rope.Substr[cmd.commandLine, 0, cmd.commandLine.SkipTo[skip: "\l\r"]]; [fullFName, cp] ¬ ExpandName[cmd.commandLine]; IO.PutRope[cmd.out, fullFName]; IO.PutRope[cmd.out, "\n"]; IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.server.start]], [integer[cp.server.length]]]; IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.dir.start]], [integer[cp.dir.length]]]; IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.subDirs.start]], [integer[cp.subDirs.length]]]; IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.base.start]], [integer[cp.base.length]]]; IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.ext.start]], [integer[cp.ext.length]]]; IO.PutF[cmd.out, "[%g, %g]\n", [integer[cp.ver.start]], [integer[cp.ver.length]]]; }; Commander.Register["DebugExpandName", DebugExpandName]; END. ( FSOnPFSImpl.mesa Copyright Σ 1988, 1989, 1990, 1991 by Xerox Corporation. All rights reserved. Andy Litman May 24, 1988 7:58:10 pm PDT JKF October 31, 1988 9:23:34 am PST Eduardo Pelegri-Llopart March 16, 1989 1:28:42 pm PST Chauser, November 8, 1990 9:05 am PST Doug Wyatt, September 25, 1990 4:51 pm PDT Willie-s, August 20, 1991 5:52 pm PDT Michael Plass, November 27, 1991 11:04 am PST Stuff from FS (to be replaced by PFS) known clients are only interested in the create time takes advantage of the fact that all of the clients are only intersted in fullFName PROC [fullName: PATH, uniqueID: UniqueID] RETURNS [continue: BOOL _ FALSE]; Takes advantage of the fact that the only client is C2CPrincOps which just cares about the keep value, which is always 1 for UX PROC [name: PATH] RETURNS [continue: BOOL _ TRUE]; PROC [ fullFName, attachedTo: PATH, uniqueID: UniqueID, bytes: INT, mutability: Mutability, fileType: FileType ] RETURNS [continue: BOOL _ TRUE] n.b. the view component is not represented in CFS-style names. Form the server component Form the directory component Form the remaining the components ... subdirectories and ... the base. Form the version part Parse the base into base and extension Fix up the component positions so they are properly monotonic Stuff from FSBackdoor From FSPseudoServers: RETURN[UFSPseudoServers.TranslateForRead[server]] Lookup: PUBLIC PROC [server: ROPE] RETURNS [FSPseudoServers.PseudoServerList] = TRUSTED { RETURN [NIL] -- who uses this stuff? RETURN[LOOPHOLE[UFSPseudoServers.Lookup[server]]] }; From FSName Κ6•NewlineDelimiter –(cedarcode) style™šœ™Icodešœ ΟeœC™NKšœ$Οk™'Kšžœ ™#K™5K™%K™*K™%K™-—K˜šž ˜ K˜"K˜ K˜Kšžœ˜K˜ Kšœ˜Kšžœ˜Kšžœ˜Kšœ ˜ Kšœ˜Kšœ˜—K˜šΟn œžœž˜Kšžœžœ žœ˜K™šžœ žœ˜Kšœ%˜%Kšœ˜Kšœ˜Kšœ/˜/Kšœ%˜%Kšœ˜Kšœ˜Kšœ˜—K™šžœžœž˜šœžœ žœ˜)Kšœ%˜%Kšœ˜Kšœ˜Kšœ*˜*Kšœ%˜%Kšœ˜Kšœ˜—šœžœžœ˜"Kšœ˜Kšœ˜Kšœ+˜+Kšœ'˜'Kšœ žœ˜Kšœ˜—Kšžœžœ˜—K™%šžœžœžœ ž˜šžœ žœžœ˜#K™Kšœ˜Kšœ1˜1Kšœ%˜%Kšœ˜—šžœ˜K™ Kšœ˜Kšœ˜Kšœ+˜+Kšœ'˜'Kšœ˜—Kšžœ˜—K™šžœ žœ˜)Kšœ%˜%Kšœ˜šœžœž˜*Kšœ(˜(Kšœ)˜)Kšœ%˜%Kšœ9˜9Kšžœ ˜—Kšœ)˜)Kšœ˜—K™&š žœžœž œžœ/ž˜Išžœžœ˜Kšœ˜Kšœ3˜3Kšœ!˜!Kšžœ˜Kšœ˜—Kšžœ˜—K™=Kšžœžœ!˜:šžœžœ.˜FKšžœžœžœ ˜N—šžœžœ,˜DKšžœžœžœ ˜N—Kšœ#˜#K˜ K˜—K˜Kšœ™š Ÿœžœžœ/žœ ?˜›Icode2šœ˜—š œžœžœžœžœ˜=Lš œ˜Lš œΆ˜ΌLš œ˜ Lš œH˜NLš œH˜NL˜—šœ žœžœžœ˜0Lšœ&˜&Lšœθ˜θLšœ˜Lšœ…˜…LšœΘ˜ΘLšœ˜—šŸ œžœžœ+žœ˜MLšžœ6˜;Lšœ˜—K˜Kšœ™K˜šŸœžœžœ žœžœžœžœžœ˜GKšžœžœ ˜$Kšžœ+™1Kšœ˜K™—š Ÿœžœžœ žœžœ&žœ™YKšžœžœ ™$Kšžœžœ"™1Kšœ™—K™Kšœ ™ K˜š Ÿœžœžœžœžœžœ˜YLšœžœ˜%Lš œ žœžœ žœ žœ ˜RLšžœ[˜aLšœ˜—š Ÿœžœžœžœžœ˜Jšžœ˜Lšžœ˜"Lšžœžœ˜'—Lšœ˜—šŸœžœžœ˜ULšœžœžœ˜ šžœ ž˜Lšœ;žœ˜?šžœžœ˜L˜Lšœžœ˜L˜ L˜ Lšœ#˜#Lšœ˜——Lšœ˜L˜—šŸ œžœžœ žœ žœžœžœ8˜‡Lšœžœ˜Lšœ žœ˜,š žœ žœžœžœž˜Ošžœ˜Lšœžœžœ˜šž˜Lšœ˜šžœžœ˜šžœžœžœ8˜ELšžœžœ˜ —Lšœ˜Lšœ ˜ Lšžœ˜Lšœ˜—Lšœ˜Lšœ˜Lšžœžœžœ ˜Lšžœžœ˜ Lšžœ˜—Lšœ˜—šœ ˜ šžœžœ˜Lšœ0žœ˜9——šœ ˜ šžœžœ˜Lšœ.žœ˜7——šœ˜šžœžœ žœ˜'Lšœ2žœ˜;——Lšžœ˜—Lšœ˜Lšœ˜—šŸœžœžœ˜"LšœK˜KLšœ˜L˜—š Ÿ œžœ žœžœžœ˜3Lšžœ#˜)Lšœ˜L˜—šŸœ˜*Kšžœ7žœ˜GKšœ žœ˜Kšœžœ˜Kšžœ#žœ3žœ˜eKšœX˜XKšœ.˜.Kšžœ˜Kšžœ˜KšžœV˜XKšžœP˜RKšžœX˜ZKšžœR˜TKšžœP˜RKšžœP˜RKšœ˜K˜—L˜7K˜—Kšžœ˜—…—?δ[B