From 9fad381d29e4533cd1ab6ccf9f8bff64f60b1b4c Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 13 Aug 2004 07:06:02 +0000 Subject: [PATCH] + Rework of buffer management by Joost Van der Sluis --- fcl/db/Dataset.txt | 5 +- fcl/db/dataset.inc | 491 +++++++++++++++++---------------------- fcl/db/db.pp | 19 +- fcl/db/dbs.inc | 6 +- fcl/db/odbc/testodbc.mdb | Bin 81921 -> 4295 bytes 5 files changed, 227 insertions(+), 294 deletions(-) diff --git a/fcl/db/Dataset.txt b/fcl/db/Dataset.txt index d84f929385..5bff522208 100644 --- a/fcl/db/Dataset.txt +++ b/fcl/db/Dataset.txt @@ -13,7 +13,6 @@ General remarks - All fields and descendents implemented. - No calculated fields. -- No Datasource yet. (although DataEvent is implemented in TField) - No persistent fields; this must be added later. @@ -38,7 +37,7 @@ The Buffers A buffer contains all the data for 1 record of the dataset, and also the bookmark information. (bookmarkinformation is REQUIRED) -The dataset allocates by default 'DefultBufferCount+1' records(buffers) +The dataset allocates by default 'DefaultBufferCount+1' records(buffers) This constant can be changed, at the beginning of dataset.inc; if you know you'll be working with big datasets, you can increase this constant. @@ -49,7 +48,7 @@ The following constants are userd when handling this array: FBuffercount : The number of buffers allocated, minus one. FRecordCount : The number of buffers that is actually filled in. FActiveBuffer : The index of the active record. -FCurrentRecord : The current Buffer. Should be phased out. +FCurrentRecord : The current record in the underlaying dataset. So the following picture follows from this: diff --git a/fcl/db/dataset.inc b/fcl/db/dataset.inc index 5908dbebb8..9f106d1c70 100644 --- a/fcl/db/dataset.inc +++ b/fcl/db/dataset.inc @@ -56,13 +56,12 @@ begin Inherited Destroy; end; - +// This procedure must be called when the first record is made/read Procedure TDataset.ActivateBuffers; begin FBOF:=False; FEOF:=False; - FRecordCount:=1; FActiveRecord:=0; end; @@ -74,7 +73,7 @@ end; Procedure TDataset.BindFields(Binding: Boolean); -Var I : longint; +// Var I : longint; begin { @@ -313,7 +312,6 @@ end; Procedure TDataset.DoInternalOpen; begin - FBufferCount:=0; FDefaultFields:=FieldCount=0; DoBeforeOpen; Try @@ -323,20 +321,10 @@ begin InternalOpen; FBOF:=True; {$ifdef dsdebug} - Writeln ('Setting buffer size'); + Writeln ('Calling RecalcBufListSize'); {$endif} -{$ifdef dsdebug} - Writeln ('Setting buffer size'); -{$endif} - (* - SetBufListSize(DefaultBufferCount); - {$ifdef dsdebug} - Writeln ('Getting next records'); - {$endif} - GetNextRecords; - *) + FRecordcount := 0; RecalcBufListSize; - //SetBufferCount(DefaultBufferCount); {$ifdef dsdebug} Writeln ('Setting state to browse'); {$endif} @@ -350,16 +338,6 @@ begin end; end; -Function TDataset.RequiredBuffers : longint; -{ - If later some datasource requires more buffers (grids etc) - then it should be taken into account here... -} - -begin - Result:=0; -end; - Procedure TDataset.DoInternalClose; begin @@ -431,7 +409,7 @@ end; Function TDataset.GetCanModify: Boolean; begin - Result:=True; + Result:= not FIsUnidirectional; end; Procedure TDataset.GetChildren(Proc: TGetChildProc; Root: TComponent); @@ -514,43 +492,40 @@ begin //!! To be implemented end; + Function TDataset.GetNextRecord: Boolean; -Var Shifted : Boolean; + procedure ExchangeBuffers(var buf1,buf2 : pointer); + + var tempbuf : pointer; + + begin + tempbuf := buf1; + buf1 := buf2; + buf2 := tempbuf; + end; begin {$ifdef dsdebug} Writeln ('Getting next record. Internal RecordCount : ',FRecordCount); -{$endif} - Shifted:=FRecordCount=FBufferCount; - If Shifted then - begin - ShiftBuffers(0,1); - Dec(FRecordCount); - end; -{$ifdef dsdebug} - Writeln ('Getting data into buffer : ',FRecordCount); {$endif} If FRecordCount>0 Then SetCurrentRecord(FRecordCount-1); - Result:=GetRecord(FBuffers[FRecordCount],gmNext,True)=grOK; - If Result then + Result:=GetRecord(FBuffers[FBuffercount],gmNext,True)=grOK; + + if result then begin - If FRecordCount=0 then - ActivateBuffers - else - If FRecordCount0; - If Shifted Then + If FRecordCount>0 Then SetCurrentRecord(0); + Result:=GetRecord(FBuffers[FBuffercount],gmPrior,True)=grOK; + if result then begin - SetCurrentRecord(0); - ShiftBuffers(0,-1); - end; - Result:=GetRecord(FBuffers[0],gmPrior,True)=grOK; - If Result then - begin - If FRecordCount=0 then - ActivateBuffers - else - begin - If FrecordCount0)And(ActiveRecord>AValue-1) Then + If Not IsCursorOpen Then + Exit; +{$ifdef dsdebug} + Writeln('Recalculating buffer list size'); +{$endif} + ABufferCount := DefaultBufferCount; + for i := 0 to FDataSources.Count - 1 do + for j := 0 to TDataSource(FDataSources[i]).DataLinks.Count - 1 do begin - // ActiveRecord Will be pointing to a deleted record - // Move Buffers to a safe place and then adjust buffer count - ShiftCount:=FActiveRecord - Avalue + 1; - ShiftBuffers(0, ShiftCount); - FActiveRecord:=AValue-1; - End; - FRecordCount:=AValue; - // Current record Will be pointing to a invalid record - // if we are not in BOF or EOF state then make current record point - // to the last record in buffer - If FCurrentRecord<>-1 Then - Begin - FCurrentRecord:=FRecordCount - 1; - if FCurrentRecord=-1 Then - InternalFirst; - End; - End; - SetBufListSize(Avalue); + DataLink:=TDataLink(TDataSource(FDataSources[i]).DataLinks[j]); + if DataLink.BufferCount>ABufferCount then + ABufferCount:=DataLink.BufferCount; + end; + + If (FBufferCount=ABufferCount) Then + exit; + +{$ifdef dsdebug} + Writeln('Setting buffer list size'); +{$endif} + + if (ABuffercountNil) do + While (I<(Value+1)) and (FBuffers[i]<>Nil) do begin FreeRecordBuffer(FBuffers[i]); Inc(i); @@ -797,7 +776,7 @@ begin end; end; If Value=-1 then - Value:=0; + Value:=0; FBufferCount:=Value; {$ifdef dsdebug} Writeln (' SetBufListSize: Final FBufferCount=',FBufferCount); @@ -842,13 +821,13 @@ end; Procedure TDataset.SetFilterText(const Value: string); begin - //!! To be implemented + FFilterText := value; end; Procedure TDataset.SetFiltered(Value: Boolean); begin - //!! To be implemented + FFiltered := value; end; Procedure TDataset.SetFound(const Value: Boolean); @@ -892,12 +871,6 @@ begin end; end; -Function TDataset.SetTempState(const Value: TDataSetState): TDataSetState; - -begin - //!! To be implemented -end; - Function TDataset.TempBuffer: PChar; begin @@ -926,7 +899,7 @@ begin {$ifdef dsdebug} Writeln ('Active buffer requested. Returning:',ActiveRecord); {$endif} - Result:=FBuffers[ActiveRecord]; + Result:=FBuffers[FActiveRecord]; end; Procedure TDataset.Append; @@ -1032,9 +1005,6 @@ begin writeln ('Delete: Internaldelete succeeded'); {$endif} FreeFieldBuffers; -{$ifdef dsdebug} - writeln ('Delete: Freeing field buffers'); -{$endif} SetState(dsBrowse); {$ifdef dsdebug} writeln ('Delete: Browse mode set'); @@ -1062,9 +1032,45 @@ end; Procedure TDataset.DoInsertAppend(DoAppend : Boolean); -Var Buffer : PChar; - BookBeforeInsert : TBookmarkStr; + procedure DoInsert; + + Var BookBeforeInsert : TBookmarkStr; + TempBuf : pointer; + + begin + // need to scroll up al buffers after current one, + // but copy current bookmark to insert buffer. + If FRecordcount > 0 then BookBeforeInsert:=Bookmark; + + if FActiveRecord < FRecordCount-1 then + begin + TempBuf := FBuffers[FBuffercount]; + move(FBuffers[FActiveRecord],FBuffers[FActiveRecord+1],(Fbuffercount-FActiveRecord)*sizeof(FBuffers[0])); FBuffers[FActiveRecord]:=TempBuf; + end + else + inc(FActiveRecord); + + // Active buffer is now edit buffer. Initialize. + InitRecord(FBuffers[FActiveRecord]); + cursorposchanged; + + // Put bookmark in edit buffer. + if FRecordCount=0 then + begin + fEOF := false; + SetBookmarkFlag(ActiveBuffer,bfBOF) + end + else + begin + fBOF := false; + if FRecordcount > 0 then SetBookMarkData(ActiveBuffer,@BookBeforeInsert); + end; + // update buffer count. + If FRecordCount0) and FEOF) or ((Distance<0) and FBOF) then exit; + DoBeforeScroll; Try If Distance>0 then Scrolled:=ScrollForward @@ -1445,13 +1432,13 @@ begin {$ifdef dsdebug} WriteLn('ActiveRecord=', FActiveRecord,' FEOF=',FEOF,' FBOF=',FBOF); {$Endif} - If FRecordCount<>PrevRecordCount then - DataEvent(deDatasetChange,0) - else - DataEvent(deDatasetScroll,Scrolled); - DoAfterScroll; + If FRecordCount<>PrevRecordCount then + DataEvent(deDatasetChange,0) + else + DataEvent(deDatasetScroll,Scrolled); + DoAfterScroll; + Result:=TheResult; end; - Result:=TheResult; end; Procedure TDataset.Next; @@ -1491,18 +1478,16 @@ begin CheckRequired; DoBeforePost; If Not TryDoing(@InternalPost,OnPostError) then exit; + cursorposchanged; {$ifdef dsdebug} writeln ('Post: Internalpost succeeded'); {$endif} FreeFieldBuffers; -{$ifdef dsdebug} - writeln ('Post: Freeing field buffers'); -{$endif} + Resync([]); SetState(dsBrowse); {$ifdef dsdebug} writeln ('Post: Browse mode set'); {$endif} - Resync([]); DoAfterPost; end; end; @@ -1522,33 +1507,6 @@ begin Resync([]); end; -procedure TDataSet.RecalcBufListSize; -var - i, j, MaxValue: Integer; - DataLink: TDataLink; -begin -{$ifdef dsdebug} - Writeln('Recalculating buffer list size - check cursor'); -{$endif} - If Not IsCursorOpen Then - Exit; -{$ifdef dsdebug} - Writeln('Recalculating buffer list size'); -{$endif} - MaxValue := DefaultBufferCount; - for i := 0 to FDataSources.Count - 1 do - for j := 0 to TDataSource(FDataSources[i]).DataLinks.Count - 1 do - begin - DataLink:=TDataLink(TDataSource(FDataSources[i]).DataLinks[j]); - if DataLink.BufferCount>MaxValue then - MaxValue:=DataLink.BufferCount; - end; -{$ifdef dsdebug} - Writeln('calling Setbuffercount'); -{$endif} - SetBufferCount(MaxValue); //SetBufListSize(MaxValue); -end; - Procedure TDataset.RegisterDataSource(ADatasource : TDataSource); begin @@ -1559,66 +1517,56 @@ end; Procedure TDataset.Resync(Mode: TResyncMode); -Var Count,ShiftCount : Longint; +var i,count : integer; begin // See if we can find the requested record. {$ifdef dsdebug} Writeln ('Resync called'); {$endif} - If rmExact in Mode then - begin + +// place the cursor of the underlying dataset to the active record + SetCurrentRecord(FActiverecord); + +// Now look if the data on the current cursor of the underlying dataset is still available + If GetRecord(Fbuffers[0],gmcurrent,True)<>grOk Then +// If that fails and rmExact is set, then raise an exception + If rmExact in Mode then + DatabaseError(SNoSuchRecord,Self) +// else, if rmexact is not set, try to fetch the next or prior record in the underlying dataset + else if (GetRecord(Fbuffers[0],gmnext,True)<>grOk) and + (GetRecord(Fbuffers[0],gmprior,True)<>grOk) then + begin {$ifdef dsdebug} - Writeln ('Exact resync'); -{$endif} - { throw an exception if not found. - Normally the descendant should do this if DoCheck is true. } - If GetRecord(Fbuffers[0],gmcurrent,True)<>grOk Then - DatabaseError(SNoSuchRecord,Self); - end - else - { Can we find a record in the neighbourhood ? - Use Shortcut evaluation for this, or we'll have some funny results. } - If (GetRecord(Fbuffers[0],gmcurrent,True)<>grOk) and - (GetRecord(Fbuffers[0],gmprior,True)<>grOk) and - (GetRecord(Fbuffers[0],gmNext,True)<>grOk) then - begin -{$ifdef dsdebug} - Writeln ('Resync: fuzzy resync'); -{$endif} - // nothing found, invalidate buffer and bail out. - ClearBuffers; - DataEvent(deDatasetChange,0); - Exit; - end; -{$ifdef dsdebug} - Writeln ('Resync: Center in resync: ',(rmCenter in Mode)); + Writeln ('Resync: fuzzy resync'); {$endif} + // nothing found, invalidate buffer and bail out. + ClearBuffers; + DataEvent(deDatasetChange,0); + exit; + end; + FCurrentRecord := 0; + + +// If we've arrived here, FBuffer[0] is the current record If (rmCenter in Mode) then - ShiftCount:=FbufferCount div 2 + count := (FRecordCount div 2) else - // keep current position. - ShiftCount:=FActiveRecord; - // Reposition on 0 -{$ifdef dsdebug} - Writeln ('Resync: activating buffers'); -{$endif} - ActivateBuffers; - try - Count:=0; -{$ifdef dsdebug} - Writeln ('Resync: Getting previous ',ShiftCount,' records'); -{$endif} - While (Count=BufferCount then - Exit; - try - MoveSize:=SizeOf(Pchar)*Abs(Distance); - GetMem(Temp,MoveSize); - If Distance<0 Then - ShiftBuffersDown - else If Distance>0 then - ShiftBuffersUp; - Finally - FreeMem(temp); - end; + TempBuf := FBuffers[0]; + move(FBuffers[1],FBuffers[0],(fbuffercount)*sizeof(FBuffers[0])); + FBuffers[buffercount]:=TempBuf; +end; + +Procedure TDataset.ShiftBuffersForward; + +var TempBuf : pointer; + +begin + TempBuf := FBuffers[FBufferCount]; + move(FBuffers[0],FBuffers[1],(fbuffercount)*sizeof(FBuffers[0])); + FBuffers[0]:=TempBuf; end; Procedure TDataset.UnRegisterDataSource(ADatasource : TDatasource); @@ -1760,7 +1684,10 @@ end; { $Log$ - Revision 1.17 2004-08-03 19:08:48 michael + Revision 1.18 2004-08-13 07:06:02 michael + + Rework of buffer management by Joost Van der Sluis + + Revision 1.17 2004/08/03 19:08:48 michael + Latest patch from Micha Nelissen Revision 1.16 2004/08/02 15:13:42 michael diff --git a/fcl/db/db.pp b/fcl/db/db.pp index ac89ca48de..8d372e65f9 100644 --- a/fcl/db/db.pp +++ b/fcl/db/db.pp @@ -793,7 +793,6 @@ type FBeforePost: TDataSetNotifyEvent; FBeforeScroll: TDataSetNotifyEvent; FBlobFieldCount: Longint; - FBookmark: TBookmarkStr; FBookmarkSize: Longint; FBuffers : TBufferArray; FBufferCount: Longint; @@ -826,23 +825,24 @@ type FRecNo: Longint; FRecordCount: Longint; FRecordSize: Word; + FIsUniDirectional: Boolean; FState : TDataSetState; Procedure DoInsertAppend(DoAppend : Boolean); Procedure DoInternalOpen; Procedure DoInternalClose; Function GetBuffer (Index : longint) : Pchar; Function GetField (Index : Longint) : TField; - procedure RecalcBufListSize; Procedure RegisterDataSource(ADatasource : TDataSource); Procedure RemoveField (Field : TField); Procedure SetActive (Value : Boolean); - procedure SetBufferCount(const AValue: Longint); Procedure SetField (Index : Longint;Value : TField); - Procedure ShiftBuffers (Offset,Distance : Longint); + Procedure ShiftBuffersForward; + Procedure ShiftBuffersBackward; Function TryDoing (P : TDataOperation; Ev : TDatasetErrorEvent) : Boolean; Procedure UnRegisterDataSource(ADatasource : TDatasource); Procedure UpdateFieldDefs; protected + procedure RecalcBufListSize; procedure ActivateBuffers; virtual; procedure BindFields(Binding: Boolean); function BookmarkAvailable: Boolean; @@ -898,7 +898,6 @@ type procedure Loaded; override; procedure OpenCursor(InfoQuery: Boolean); virtual; procedure RefreshInternalCalcFields(Buffer: PChar); virtual; - Function RequiredBuffers : longint; procedure RestoreState(const Value: TDataSetState); procedure SetBookmarkStr(const Value: TBookmarkStr); virtual; procedure SetBufListSize(Value: Longint); @@ -1010,13 +1009,14 @@ type // property Fields[Index: Longint]: TField read GetField write SetField; property Found: Boolean read FFound; property Modified: Boolean read FModified; + property IsUniDirectional: Boolean read FIsUniDirectional write FIsUniDirectional default False; property RecordCount: Longint read GetRecordCount; property RecNo: Longint read FRecNo write FRecNo; property RecordSize: Word read FRecordSize; property State: TDataSetState read FState; property Fields : TFields Read FFieldList; - property Filter: string read FFilterText write FFilterText; - property Filtered: Boolean read FFiltered write FFiltered default False; + property Filter: string read FFilterText write SetFilterText; + property Filtered: Boolean read FFiltered write SetFiltered default False; property FilterOptions: TFilterOptions read FFilterOptions write FFilterOptions; property Active: Boolean read FActive write SetActive default False; property AutoCalcFields: Boolean read FAutoCalcFields write FAutoCalcFields; @@ -1500,7 +1500,10 @@ end. { $Log$ - Revision 1.19 2004-07-25 11:32:40 michael + Revision 1.20 2004-08-13 07:06:02 michael + + Rework of buffer management by Joost Van der Sluis + + Revision 1.19 2004/07/25 11:32:40 michael * Patches from Joost van der Sluis interbase.pp: * Removed unused Fprepared diff --git a/fcl/db/dbs.inc b/fcl/db/dbs.inc index e00c1708c8..11fd66a929 100644 --- a/fcl/db/dbs.inc +++ b/fcl/db/dbs.inc @@ -39,6 +39,7 @@ Const SNoDatasetRegistered = 'No such dataset registered : "%s"'; SNotConnected = 'Operation cannot be performed on an disconnected database'; SConnected = 'Operation cannot be performed on an connected database'; + SUniDirectional = 'Operation cannot be performed on an unidirectional dataset'; SNoSuchRecord = 'Could not find the requested record.'; SDatasetReadOnly = 'Dataset is read-only.'; SNeedField = 'Field %s is required, but not supplied.'; @@ -48,7 +49,10 @@ Const { $Log$ - Revision 1.5 2003-08-16 16:42:21 michael + Revision 1.6 2004-08-13 07:06:02 michael + + Rework of buffer management by Joost Van der Sluis + + Revision 1.5 2003/08/16 16:42:21 michael + Fixes in TDBDataset etc. Changed MySQLDb to use database as well Revision 1.4 2002/09/07 15:15:23 peter diff --git a/fcl/db/odbc/testodbc.mdb b/fcl/db/odbc/testodbc.mdb index 2dcc6dde289fd0c601cc604265ac49745d37e4fc..fafa587a2fd374102e03f2aa84e75e31e9462047 100644 GIT binary patch delta 8 PcmZo{U_Gw5;kW<*55og? literal 81921 zcmeHQdypJQdGDT?-Py<0J%t}+maPAl)@ znB6_;Y>YNe07F$`S9nvrl1fpPqAJ))$RCsehcXrk4kqTG1X6KIaivUxlRz18cx`@P z_w?@Uy(C+@z0+Oy-Oha7)BW|=zwYVBH~nCov9Y3)OF3RDakpDc4DMxQzLGP?Pei}@ zjvxK`l$X4Vedc|if3))cPw)KJ>{nmved76NK0EQxFP;1R@UK4n#DNd|>c_X;JNTJj zKK<^k|IzU`A3ky9qYvEr>FMVmKmBsYz_VX|yl=NN`>sF!%6qqke)8T=@#{YMoA!fu ze>C%!6I+gd?3M3*Xw&&j4l8(*aP;@!^>FEG;583MnEH=5zq)|1cC@W8LzCIG5FB|66Ps` zu?}CVu{IWGZRiqEw*Us+0`7MWHuzrXZuvBeS}#}#YcY#@5@du6>?|f2_%7{?RhVg_ zyIJ)UK@1Tq=n`WQ76}Di+Tjun2V8a$AIp})$+$WZR}(Q)oDmmeO;KjnyToF?*d(El zCmLs=pq~nhH&KdVd_RoEW6Yl8(hQ;M{j{*?9G6xWt9LQ>r6PwM&&?J0rK|%^*0qkz zPP?%|r|1rNF0O)n$8-v0=-}y`>xG6h&Qu`+AI`W_aP5J7F6T}ZBN7LbK zabIpSKXx+j7302TB%hlyMrOhzGl#tVbYx^^G@mMETzh0@=uFXb(A_l{uT{SgbGVxLA^UMfcC%L2 zj4J`(!kXv_e$C<<2ZcPs)y7XoNzrPf5zq)|1g<0meogh`3L4e^KM&Au^?@6s23a=B zqFQf4^^PAI>LSc<_oKoa9#M-aTW2&cQNR747ak6BkPD(ZrpE9u(E1C_z`Xu|Ro5SgpjQzK>H);q zJQk`S(N}2rS2v=Ei#>Nl-(0x=u$ys;>3ptmGCdud^2XCedtxG=DP?nZTHYgt`(^h7 z#DI9xPQfN!jLs+Alzdj_Bz-O2Er|i979e3DDNFQTpkcu97T%!MSi)NqytR=x0#g7Q z=;Tm-i98Fq!AY8cTQ#9|iIM4>gOu(rDg5+ZBcKt`2&{bsRQrFIY_LUw$EZoQMTdCOziaF>p5k}$ud+Rhq>Ki!+N=@;Rz+s43L#cWhG;P~0vdr;j6iiH zyOJ`ltPq^%#nd*A_J1W&&~a-7Gy)m{jetgAy&W6 z^`U%)gt7R;p@l?f#RRn^qkmco5P_|gl^IKjiEB_<;9EJmE#yug=xblj*%e~WJ_13{ zrcRtb;hdP6oPnjCOSdVnlo$3UY0rD9Gg;}QMY@zGvnNiU@}_6frwYQSq1+2z>9lZ( zNtc=N(y4-UY4%+noI!Aku2nif0G~S}2Yz;{kea-oJakBVe&)fM6XQ~VP|YbIR(zi8 z?@)Ll13ydQJ?Fm3aVdvY^ISYLSvn;dhik6$QfJdM^l&B3Z^ZF#)0>0yQX-voanGLqR}& z(HL?6OF*iA&^+PsDm+MoVMZy+uyFB zvG~T2+)<}ah~Th)z9L|0mpVqP)j|V!n2xK7K+D;24H0O^J8o1&LJ>D9Vj~eitD{R1 zn-sBG5nG5rmUeV2VyhxU?Upc?4d?h~C%5nK@l5zsYsnu@R#5mJQEERf8M6beK% zDMBbFNK{TPL$R* z8zH=ilnABr=oGUm2yi(O0fd~?077X)IyQ(o18@iN(CvTYu+}kY1T+E~0gZr0V6`B?dD6th zF}8vIo&AXYcDvbrF8rzRz2WP^-w%BvbWiAp(2Le%))^~lMXl&le{Fi0a;1$%V7(#G zhDnSxPqc-zN51*9S3W*?J~{f?uT|(yPz)bRxicoe6FX)#Z6F86cJ11=MVd`F#w$tI zhV`OlylM+@`qrjz*vX;uNebq3`i8}lEf?U_5-)EtHa+nX$nXc*?tfPt`X?rE^wo$n zn=chjz6~PTJ|i6RnKU+0EQ}4^amNm6wv%|g4RcM)aBnf$x8MBz&l6L1C+}mk1stkr z^6skHj9^F&CJ!wUY&4%LW!!?vw^q#-84e#Emk2jD;iadG3xzY640qU_&U*`n1M*A6 zH=Osfh|SM1+$hhGI!tf%b6tUBpfl4)nw^YqG85Xls2eJCBKUpj8T z{dS)f@kz2aWRahMKl{gLg$(LWF;L=z=d2bC6Gh%Py)qRvvrN7;Dx|tA4sS2vz;_p6 z=cNJ}eSxt3149USbJcXqNGWHQj#M7$wos%9+Dfn+ObPnf&%P&;t^^(&H~A1yvwYP0 z`uhA=f?f!q2Dw|{8Sjx|q3*%D%<7T7Nh#SA=AL94(`}i^bBlZLK9rgrx zFPE{X?x|E-a6oQ6%3Ca|QiRp2vzql!`42z%k;LyE{L<~uAML!l>(u@>@0pnwPG=tX zlzp-L#rhNB^K}AA38?A{$fvBne9GED`BaoXekk}s`IOa{Pgxr%pNi6#k1As_bF2*% zNJ-#BLN)x$N+8=()`kgGi&6+=NRuY^uY85=d)9B|te~N3hhWfr+MEXmFH>v9-CesgzMx%bJqF ze^-G4+H0I=PmP0Z(c8%Dt-pl_L$+n=P4O!pvId?~DW)TAeO6S4>Ny9kgnjKP=WjMf zj`ojWAK!1&-^c7j_Mvl!&TX-`oD2R<8Nv2nu(+Zxw0dme{eS1}f9vn=J8z@jd<_7J zY0bVO6bi+%n*I9+*$)vz8QVS|cHdJ)A}4!Mu02nH_a9>6BB%(aVQ1 z^z-eP5?1g!;+`o+RU`*8z7ElyNXrj*Yt>+*I-8e!#*33oXJ-r=oP)H0&HlM2iEg={RYk>0E6xQ9O;%7zI9! zIEYG&aT6~)%hFm)7!1Z?$X|&3y&V=V&Zv%vu^%0EeKE|17!(U{B{sEK4hx1fUZB($ zibGi^ow+2A_XI@Tn=VeK-Aw9&4BHux#UR!ZujIaA*vS-Jg?mypC<^x}~eDx2L!FO_-*>_90CrNYGI#g-Gn(^@K+g8*O?VMVu4%%{dDu7jPxA5Bl3blgnhsFOe)hZ-=Go)8U(f`2&UOclaXTW3)_5TOvH1w*PJKP9BHz<&rSzZ%ii_GzTr zoCJXU;eEN3duAs(aXvmFX8089dY_WMDTFo}fd(Q#Z&#V#S)nkILkvCsuN!U?MDx@E z4IK*00e^LnD6KUKAYY@t1} zjCQZCp}!4|s+;gC+IQ+WNPcsAAJ+!>)pa{Ke}=Ldt?UE#&%^Ht|2Q-iI&Z1(|7aEi z`~S*qe*~v1{&H5P0Q>)xY0$R3Tj)U7fcK~&JKkLJ8KV=R5m*ic%FDr2^L)(+)W%b< znZ>0CMAN$RW<`at_)o)9kx>ssvfjGpY~olvoLeWF|MJ;NIs%+`hN-6&V}EGB&(7N??3cp-8h$qX?9=r3 zWOy|EmhilTfaje5b0uO_`~DB<>Erw|oc~i^{+R`c0>g|~dpM8&=x`+#SuOK=N5I+$ z1gbMQ?ZvE@uD$SAM$cafy;zj~3{xfFGD;xVY%7IM4FiAe8|`8dSYxfM{<=A3ZJ2y5 zH2GZ$B-emrerHfVW%cD#*2c-l`DRAz|2W&pE^7Thy@Tm?OMD;bJkE}?y{w-dXNTBf zc97l8hS&gBb~5%T(*2BsRT)^^6op!ielO7aU*B-71~AxFd>cE)-pU?ez2a{duDiiw znC{j+5>tcPZ2mZR)9rz8hHzI~Z%wu^rg{y-lRtfM=vkY@t z0nY~TB#Xa1%guQ*3SScujS)dq3hHU=;Qs zQn(jk7s99?s7(lT3rM4kDS;5<>=axl1dbz!WnAQk$7bu&`}%ohVh>V7sjKCBkh-`s zv3p*6m#a(+0;PjMFNOTh1NS|Uy$7}o{uahdhDeBVy9bC}EK+p-p#g=qWnkej?lFUw zrpV86bp2iXw|C<8yF|n5az|RCS!#zW70x4w_Yxz}!Pfu+XHYQ8qHu(`jU{29+(Z_G zg)O-X&a{s1?f=F3Ru;0}na=IJ^>5$jlYP7Fv#rduMxBYj`b#1P`wxEZp;i{L)Oxn< zf6>ZNvN`XSEa#Lre64{?1TR9RqTYZYpfu4?mf=VWm5v9i4Ldg6e9M}KjJxkN zVprjnY#wawKlm2L_u{=y;$@DpPqGbvga&Whv9~_Z+uNJmwP&bz=MEO>z23A2@YY6o zdv7lquLrw!_w;t5Mjm|Qu|r-S&6Sf)V0|^(sbg68m(Az6)r3`jUcP{ex|kR{>3FV{ zI>t^POF8+z@}AzFR3_8)K`hv4O7!*YNgPCt>rNCCBkA$SJnZ?z-o|)~@qvdiVtmMq zW?O0DBY)bKAF7@c%j)ntvnAI5;VIJN8-JCaUjL`p|EVIO@2dy_s@%#{zv=aVG@un* za#8&@Z^7R(eE(l`1U+~BiXmT6AZe}5K)}<0rBD68%;5Zd@ z`jMgT&v&SK$JPL3y1Kfq?r4eIJaU)CpA2u5uEA?{* zKqH_LSdR#-id_g+QV1IVUq3(R*TwiBjd((C002Ua|IrIdi19!A1UHFC(8t{*9--fK zlX!&gQkQsyZ0i!e288O^Ox*(_wh)2f9o>r9s)(Bvv5g3D?%3`VouW4amrlWu2!kFW z(oWG4A;MBFAw>u^1e81-Dis|~q?jO=b|Od~m9CB$xsYH&uR%nsa%m%iekg`tI11oOE+N2_`sVt>D zNypywq_WPMf6?clV4KkLutRAVXR{szNJeOyxU>8kYYmk!RioZ*l;0ngv&J)4IJsn zH0g3JETJeav|K^>xenf~7+m7Yr3;*mbSgr}Y11J>(dtoX5p{%Fw0h!LJaBkeJaE=m zw0bmGtnMVOSpC6Q!9%H#YOK#%?B4*^TS!D4)UcQzxSQ}MzT6P#^aDdU!%)9NnVK7K z3W^XpwEy_g!LGwSoE* z6{T3e)?!`bm&-ZU1`4Dk@FAgvvoGW4udh?qh6!X_DoP=cZJ`@jrp}-*5JU@Fk=-q7 z_ZW1R8G9>+|Fem~(s({ym_IaH^Da}A{N!nO{B+*Cuh5gt^yIy%HJ|Eg0&Sc}4IPk+ zZA2TOi#`vk^PxeK8G`q-E}eO59BWN|K~%r{}1X0%>VyssM&rl{Hd@y z|L>mA4WSpU$E-6}(u!K=%n!qxHW~qqz~x7v4YhF@Jsup@vc=f+#773tC)J%IliR3_ zIJ5au(d0X-X8VnCD1F+f50BO#TYo(K@(;gEfwTXY5KI{R`>z{92S-Yx;La$DCicUJ zQtk{9uhm@Enn5#r_1T~>ql4o~3;ITsr9DC~D0xwClU0`niO1!*D$FkjFY$_A7DmKG zNp_*q!1)hI_0Fv7`M+eNx?`w^%2K&XL^z2h$O{#cDPT)4Ih*K+vFehiNGu zLh1E?P>pw7PpkcixIr!Z3#|XcBMeeIqx5Ja2(ccKh`3%Fh(e^znnxh9=2Lh@B`~=f zq+MwtINu*aC5)+YpR-5oUxvRB-XE@n9t*ibD~B*X@iYBecsbOkZDL zw=|ofsZX*6)cn6Ve6z2qpR4BI21d5esM%miwn;J_y5kQ20|2T2lY9a7fAI&&db#>c z-pW}rwwPS#;FiI?#boN3T6L#5v8U)^H)C=?Ex1u~@ZkaV#1g?q^C_&RDwuq0)ohXB ztR=&ZO?c_);zHrj*P!|rt6TQHolWVqqHmqlz_s%Ddvk|un}{8nJ7JNO09@Lgc* zU7DLA!^UYYGnwR`^GU{@#E;C@WK$Cxp)WTu@W}#ea^|m38S2|=RHou;2DW5{vnGV; zO}4hO)^2GbR>7$q3(j-cw;SwE$7u=x8OiS7lET7nNj5jLo{iE%?1Hld=6^CDCSFr< z)nrIsU+{TtO2%UBls|Ju@CvRvS$qw~!98I5&N#}6Fp_E6XPQVxBJAM~{~2y`mN6_j zt^dev;QWR-N|G>=b22Gy*CfMX_Qhstq3hg*(hIE|9=Uz1@r$Xr;fInOSyEbClbDeG v6n&EM&D>?T3#~)-ITBuE{M6qiZ8(X*VCNMwa9dyz=LHuw9(xmHwqySf(4S;x