From 3b6545e738f0c33aa7bba0c2d3081f6d743ac1e9 Mon Sep 17 00:00:00 2001 From: ClemensF Date: Wed, 19 Nov 2014 21:11:14 +0100 Subject: [PATCH] Version 2.4.0. - Added ImageFileCache and FileDbCache for WinRT - Improved TileImageLoader - Removed TileContainer, TileLayer can be added as MapBase child - Removed attached property MapPanel.ViewportPosition --- Caching/FileDb/FileDb.dll | Bin 0 -> 101376 bytes Caching/FileDb/FileDb.xml | 1870 +++++++++++++++++ Caching/FileDb/FileDbPcl.dll | Bin 0 -> 90624 bytes .../FileDb.XML => FileDb/FileDbPcl.xml} | 1246 ++++++----- Caching/FileDb/Help.html | 983 +++++++++ .../FileDbCache.WPF.csproj} | 16 +- .../FileDbCache.cs | 309 ++- .../Properties/AssemblyInfo.cs | 6 +- .../FileDbCache.WinRT.csproj | 74 + Caching/FileDbCache.WinRT/FileDbCache.cs | 278 +++ .../Properties/AssemblyInfo.cs | 14 + Caching/FileDbCache/FileDb/FileDb.dll | Bin 70144 -> 0 bytes Caching/FileDbCache/FileDb/FileDb.txt | 5 - .../ImageFileCache.WPF.csproj} | 7 +- .../ImageFileCache.cs | 189 +- .../Properties/AssemblyInfo.cs | 6 +- .../ImageFileCache.WinRT.csproj | 66 + .../ImageFileCache.WinRT/ImageFileCache.cs | 94 + .../Properties/AssemblyInfo.cs | 14 + MapControl.sln | 76 +- MapControl/BingMapsTileLayer.cs | 3 +- MapControl/HyperlinkText.cs | 2 +- MapControl/IMapElement.cs | 4 +- MapControl/ITileImageLoader.cs | 14 + MapControl/ImageCache.WinRT.cs | 22 + MapControl/ImageFileCache.WinRT.cs | 67 - MapControl/ImageTileSource.WPF.cs | 15 +- MapControl/Int32Rect.cs | 2 +- MapControl/MapBase.Silverlight.WinRT.cs | 39 +- MapControl/MapBase.WPF.cs | 46 +- MapControl/MapBase.cs | 371 ++-- MapControl/MapControl.PhoneSilverlight.csproj | 9 +- MapControl/MapControl.Silverlight.csproj | 5 +- MapControl/MapControl.WPF.csproj | 7 +- MapControl/MapImage.cs | 4 +- MapControl/MapImageLayer.cs | 10 +- MapControl/MapItemsControl.WPF.cs | 59 - MapControl/MapPanel.Silverlight.WinRT.cs | 13 +- MapControl/MapPanel.WPF.cs | 13 +- MapControl/MapPanel.cs | 22 +- MapControl/MapPath.cs | 11 +- MapControl/MapPolyline.Silverlight.WinRT.cs | 1 - MapControl/MapRectangle.cs | 8 +- MapControl/PanelBase.cs | 6 +- MapControl/Properties/AssemblyInfo.cs | 4 +- MapControl/Settings.cs | 32 - MapControl/Themes/Generic.xaml | 2 +- MapControl/Tile.Silverlight.WinRT.cs | 18 +- MapControl/Tile.WPF.cs | 13 +- MapControl/Tile.cs | 5 +- MapControl/TileContainer.Silverlight.WinRT.cs | 49 - MapControl/TileContainer.WPF.cs | 49 - MapControl/TileContainer.cs | 151 -- MapControl/TileImageLoader.Silverlight.cs | 2 +- MapControl/TileImageLoader.WPF.cs | 287 +-- MapControl/TileImageLoader.WinRT.cs | 277 ++- ...inRT.cs => TileLayer.Silverlight.WinRT.cs} | 11 +- MapControl/TileLayer.WPF.cs | 17 + MapControl/TileLayer.cs | 267 ++- MapControl/TileSource.cs | 38 +- MapControl/WinRT/MapControl.WinRT.csproj | 20 +- MapControl/WinRT/Properties/AssemblyInfo.cs | 4 +- SampleApps/PhoneApplication/MainPage.xaml | 3 +- SampleApps/PhoneApplication/MainPage.xaml.cs | 3 +- .../PhoneApplication/Package.appxmanifest | 8 +- .../PhoneApplication/PhoneApplication.csproj | 1 + .../Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/SilverlightApplication/App.xaml | 2 +- .../SilverlightApplication/MainPage.xaml | 5 +- .../SilverlightApplication/MainPage.xaml.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/StoreApplication/MainPage.xaml | 3 +- SampleApps/StoreApplication/MainPage.xaml.cs | 5 +- .../StoreApplication/Package.appxmanifest | 6 +- .../Properties/AssemblyInfo.cs | 4 +- .../StoreApplication/StoreApplication.csproj | 9 + SampleApps/WpfApplication/App.config | 12 - SampleApps/WpfApplication/MainWindow.xaml | 22 +- SampleApps/WpfApplication/MainWindow.xaml.cs | 28 +- .../WpfApplication/Properties/AssemblyInfo.cs | 4 +- .../Properties/Settings.Designer.cs | 35 - .../Properties/Settings.settings | 9 - .../WpfApplication/WpfApplication.csproj | 19 +- 84 files changed, 5504 insertions(+), 1940 deletions(-) create mode 100644 Caching/FileDb/FileDb.dll create mode 100644 Caching/FileDb/FileDb.xml create mode 100644 Caching/FileDb/FileDbPcl.dll rename Caching/{FileDbCache/FileDb/FileDb.XML => FileDb/FileDbPcl.xml} (83%) create mode 100644 Caching/FileDb/Help.html rename Caching/{FileDbCache/FileDbCache.csproj => FileDbCache.WPF/FileDbCache.WPF.csproj} (85%) rename Caching/{FileDbCache => FileDbCache.WPF}/FileDbCache.cs (68%) rename Caching/{FileDbCache => FileDbCache.WPF}/Properties/AssemblyInfo.cs (75%) create mode 100644 Caching/FileDbCache.WinRT/FileDbCache.WinRT.csproj create mode 100644 Caching/FileDbCache.WinRT/FileDbCache.cs create mode 100644 Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs delete mode 100644 Caching/FileDbCache/FileDb/FileDb.dll delete mode 100644 Caching/FileDbCache/FileDb/FileDb.txt rename Caching/{ImageFileCache/ImageFileCache.csproj => ImageFileCache.WPF/ImageFileCache.WPF.csproj} (91%) rename Caching/{ImageFileCache => ImageFileCache.WPF}/ImageFileCache.cs (53%) rename Caching/{ImageFileCache => ImageFileCache.WPF}/Properties/AssemblyInfo.cs (75%) create mode 100644 Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj create mode 100644 Caching/ImageFileCache.WinRT/ImageFileCache.cs create mode 100644 Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs create mode 100644 MapControl/ITileImageLoader.cs create mode 100644 MapControl/ImageCache.WinRT.cs delete mode 100644 MapControl/ImageFileCache.WinRT.cs delete mode 100644 MapControl/Settings.cs delete mode 100644 MapControl/TileContainer.Silverlight.WinRT.cs delete mode 100644 MapControl/TileContainer.WPF.cs delete mode 100644 MapControl/TileContainer.cs rename MapControl/{IObjectCache.WinRT.cs => TileLayer.Silverlight.WinRT.cs} (52%) create mode 100644 MapControl/TileLayer.WPF.cs delete mode 100644 SampleApps/WpfApplication/Properties/Settings.Designer.cs delete mode 100644 SampleApps/WpfApplication/Properties/Settings.settings diff --git a/Caching/FileDb/FileDb.dll b/Caching/FileDb/FileDb.dll new file mode 100644 index 0000000000000000000000000000000000000000..73f4061bfe488ca26868f202d1a93cb62d69eabb GIT binary patch literal 101376 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%0Dgexa|foLJb4b&J)$ z7Y@X}1#6KNZ!_^*jUTC#?Z-ccFNOo2V0{A5XgJ24^&G-#Y>^q@)4x{ueY|42m7|#u zTxuv!JcKYb0Oz0I-gA{%H&=~P9eE^`zWYJ4tyv|}9J+Tk8bcYU2tv|TdC`^#zw_jz zfR68AEQ!5687P*%;bymcU$Rhq5{RV}B1`xEmgS^_p=2jud|z0x>j97Q+UMCioI8^i zBnvZl?!NvcvxodfaEHoS39(Rsb?+AdGTKe9N)E*@Yf-|_&Oy6P)^@t>{^N}(UgzeQ z*6HqPz|62SUTKvsFeA$_4dEiEBOn$BTOca(1C*tXMK%d=Y~>>ekWmQ_5d!0W_2KN)!zJ477e3fI$&n*GGvMM`c69zC0wBpzg^m_WlKaI)txm z1?os2%}#!+DFV)fO{;)2vs&92Cv{2+d-lEjJYi7*!(A>6x5SYiOedZ& zM_*rR{>y!OI-kR|E3oWe0tJt&utUJ3tVg zo43o;@d88m;uldOiov)}T6ffd=W3qi_jZOy3{}0LQi}3FGTnSf%n^}A4y6qn8eTq~ z$C;$kXuyOd#fdH@<182`j_qS~ayS*A4hBx=O91O+UAGH-&GI4s$w11&=CLx8yizkF z8$2CJ3Cy_(2ZUZ3N}dj-XF@vn_BWK$CA6*8kk+9D)ixxC%2;Y74kn0(3DLon*1`0p zu#j}sVDeoE9jtObkCRPgseY;xOcU}&CQ(?PPT@!uQjjMflA|n@7WsLm2wox(q+kIR9)+en;sm*N@*h zqH;9URy~R?BQYOh0DwV)rLswsfHkCB);0O!R{|QMe&;(+EIr$-e+cNz=6pkh zfg;Y&K>8uWITg4SC36aRtv3?ACZ;*UU2WM=6LVaa`(np$o zhtzWoe3jWCnQw3!;>Ev{Rw1#8dLcG|Nx|vtL~9Ggj{4L1P!Az|!e$Iu~JFYY~zRCma%0ODnZafocfY!Rn zt=)C#?llOFB;ts2WvtLHA#Q0f^^Xa{9D8VhP@Jf(Zf@AtLd{gTvwSj`rVfAf>q}|$}^0h5% z6#(fWj{~1%WAvbJR3LYU3FHA5Iy4K#uSzE6+H4OEkF2R(T`3P~R~5H{_2`Pp8EM^Q zltt<(MWpRFy5g-~^i+WTu!TbSMO7S-@bi`s9bMLlUz z9Scg5Mp?1kP_(r6n`}|NU7)H(YsP>yG5deyyQUfc3gt|V$hvH|C(v#ODc#R3pBe7o z?zi2_%_;?#--?mdx~+E<`oJxeujA0S3*}ad#=_KHvG(Yq+|&1KkhCp1i*3x730CAm zLayZ@@#bptS*P>^WC*Aje3Uax9} zSRJN{-(`B3Qgnh54*@s44})?dorsV?gGBOx28oZbi&P9iFbqlx%Q2mjnuEm%Uy*wV zW%~}6YX}d>Z473ZWWIYAaT{^y2~|eNn@rqqfFy7NS9v`i7Btg5Phu^Pj1@9cOcz)c zS=K}NnXyG$fW0?uILMDX1{=(IoDuXO1d{K_W1UjE7q3BgC4%0HNpIEKkkUE3>ly`> z@7y#r?eMNp7?GX`ReAgE2&y=!^VV)i3~hR@8KCK8_MTJF?wFtc7usFcfgEeJ%=Ev3 zApyP=!_4`I<{D|@x1dqadHn3~9Q43OAf#xCI)wdpJJO8%**dcs7c_i+P?8qE36h51 z1PQrDJrU{0YhNv2adf!#FXPtCnw@DRib=*!b{Qtn$x<$Kj9PpY3E7?(*id_Nf1h-4 zR2M)U<)=u`AF@` z27C}=@~vP|VvZq<(APbmY9@$Ov*wk80$wWsWp^(4l50PzH2HSy)cD42PxFKLO3>(*VmXz zqjbt}FM@V3wps7apUn)-RJGp!YZI4}j~Nk}ez1OzO_ReqN>^4_AS7E?!V<#5#?n^$ zT$r=R8xlINCC$9n%w~-1O+7;=HXH-7L0EB}+CUDSpF(O!R$KcaOB!He4UvZm^)y1p z;$RoGk>tu)Tn*`<%t#%S(GIG`A}q}wN&|&j(?#_NsP|#j4!V3V=@Q3H;+>;e6c+3p z-$A!(KY@VD7(_U!!mdV3i0+cRlzPEWT~v3OD!&~`GfAOy7*2SA8cV{2q8P_GNp{e_ zfy8?|ew3^q#;*?V{39)_hkv%x!nf=-Lv;GpJWP5F@<+6{(xu-35cFH@t=d>Bj(szc zLE^R;u-bk==rX&JX;}qYVRn8JZwKrY-ZSLrkpk(I3{M6GQzdi{Lpmj}k*yM#kV-}k zcD;O#eP~DGP)f)W`95=^z|WXYNo3=3!5OKbV8`|$@&x<>Yj^1qiZQ6HbVvd{sT~5v z99lV0CMTprJh!StcpHdxh|GVcPfBP5t}}VBK&4OO)-w1H(LUMo!l|LnSZ6)b$I3G( z$U%4D$xteYcoBr|Kqz?YUy%+v!JQ&?@-1yuJ>r7DN=ZCN|(f20W%em&h7Fzv}0i|*KEN}T;-eCtq=_JA^~Me zMGs13>#7SUW+SZ)bI(QU{#ytdJd5WQv47QI~4YgNkUA%EI;6TB%8RhWk=7_53vK(a_n zY7T`V#+3ynqvG+%N;yGpEb~+O`E2Xv5~;5-pfuUNh9oAFOYwWCfu}81=%onfc76qJ zAyfJSJsQ)?{0RCWgSsGn25l?r9Zk|ACU^SY)Vp2NU%~W+-f3Zm!dx!fG*xpXM`Q0| z&2bEKH1~q4M@ZFT@Ibp-ZeuT}N`=)Jfz{MYQZ8id@&q(?XB?g+WadmVIG;VMX3jd@ z|Dg>1AQB&{W8&49Cz|z6!PAz{=KXwNNHiaCjznVqIH=6~f8pIiSA7UShOL2@6VgxM z=VFRsoALDXup}A{Pw6l?cJbZpZATtB#5$J?ghXCw!vu)Y~m;k5LZmD)HQR?`P`#o$K?2g%z0vr}FNRB(eH;|=^0nz|oX@*4ZW+XT^BF9Pq zNn!_PUIa*QNGI=*E@3-SR>cultEBDp;ExYIr)s@cy}H0UZ9U}6ofD8zc8Q}ht5^C4 zO2lNe6-7d3bs{qBI}n5OF;4U*%wX|~ZJmUjO~Xz>R|y!BIA#>A67+<9Wbqmtv9A_L z^>jVv9xPfZkV344kQxZ2VyWO`%o~9emL5)(K*~JnU@a%YsUN=&)fhlEx~50x)G^_l z>UNGojpCgtwlQuK3x!ZU)6;A19c%c!YkdTXI<uZ-9n>@3T8LFwQae-bUu6xm*mvZV1?uJ4oY2%_?2yz*8_P)* zl9nVNrvxr{@0*cov9KY-RPVxFDW zDJ9;O8ll9x;U%7g5(5n6E`muFKhLGZwwI$mNFgB&YB5X$-=S3stbCJtZL!8Ci@yNx zcY~xnnkf28^gz#tXufN^PA*q`9%;~V`_NfmTUIt_J#cFbYUGx4aPST?bjx{t2X}VM zP5KUj#VupBxEiLp`8W@Hgm4O1*%a%k=wbmfBQ< zE5;^+;MHn4X;jO3YQ5e+d-w#?CQ zZ=Lpck@92?YXb@(8IF@vZklKGkwvf+ukRAnr}$C4VZ5?!r^awBL({HYd8M&|>~BWf zW8Xf94G!Jj?W7E;oAeod)VfKBSIWSw$S&^1Qw`;GmmH^gAZWUsl%#T5uRn__!?G)1 z{5C?fao7!FG{Vj-AxsI4nSk=q5BAov?pOc}2d_eV)Im+4SA?YZxgZ2>IGXTk(}9B6 zjp+d<*1dPNvBQFxOdV_;-E=1!vUInUm@Glv#)}VdVKPdjp-))Y$bgJikAS`kK=IB~ z00H_POqr-cN@mnLou+an%I|fKC{z73rj%V$jgcV-N>T#;0swOp9%IvO$MYdHK&<#J z_`1G;L6uY*(W@griO{rLh8*VZZ9{SGZW)}}Y|t|}I5t|nK@Vlo1!$x!EEP2kIfI|U z85We4^fU5INgmkuVq+fm1|I`3?8_%A9^~P=oqrrcSEGZ!fL|Nlfduw76S9t+QwgYjK%6fH?-6&LM;3{f-yZFEhAi`%Id=r<^f4%BBC>DMCP=J zye@KnQZCi5O=+3Ibvv@Ah4qgd<#u#@e>NQ7PWAXs;AZOR`2LY~a)-(WykCAXYR5l} z?<;RUHr(sb(?$^6a2^lLltRH8v_P;|D{5Ua_0WK!6$h`Tjo66QoH3_azu8ZDq(HtIhYeq|WJF`$nlfb%zDA3?zQ1ME3_nnHa}ASRpxTxIK3b1FrA zi4-x#%#MKW2z_#0k+86ovbvSdu7BYC@C?oyhsWUnbXhVw_-s5dMpofb8`!Ssup|9r ze^$qW*eSsPNuRtpUB->?B=s5B0hG*zSs*XRt#sOVKPQBDUsNm|Gh$8SY)F#j(uRfO|RMcJa2B@M`*&P&74nbTwNW zZ_s&!I4`qh1;cqHv(YnG>Dqem0At?50l7nv##W6S<^l|FN|t3TZ9LsZ$K{;ZMz4rS zqqHw=QD1p(u%_Y+zX&rCf8+$_{cya4hOI9L@n6FGukd>Xe%{~2bX+F5SW{72~& z>Im9Pf5dxLN?%hx!!)w6)wO+l=|2%7$HCf5j}FZQbco0UlIgv4WeM0`dgsuTn3I`u zKvG^RDR-ge+e_R@2oNDZ&|Vq3j;?B;! zM!xpGy7(S0MzqA2kc2x$_=5#d1a!W`;YU&Z?Z zNKI?Fm_NMoHxtt{ld;ET1_081Z7zc3^y-iwFcqHu|Zv6#>EztH~M=$KST}D z5?EJo_PY#jV4}f1K~oUxh@q@zd=eR9EvXMA(YgVU4o9b?tIFw2Ye&Q4_Deri8&@K?Ha3 zh_9=+?YOk2UFVcPjRe&=r%Fd48TN7}A}Slt!8{+k7G&spHV{Kl&b&zo@#cPoiUinj zgyF#N4er4=KR!YF1Lp$FDDDB;oa<8g3T?A4smM)}kmzq*UvEdS_i@iZI~F2#Lhu)Q zi$POx@;h`ci&?+5Rvcq=D-LZYDT{DkyK(wz(~r)$m7X(hRXx_Px1FkL#%EvuhS z+iv16)nk$lrfR->cQ=^3lF-E1=Ev}*{V3*K;)10uTM`*aHy=gZk(zJcJGK~eny{J2 zBjvc2M#2=|s(d#)hqUS>cB18LPIePC7pl;bQ-XTPGD0YNK602r4{Qbj?33D*^oScv z;;0)C1tkR{=;y=Us1iZBbhmVEx!78Aw^>^PDf}O$b-Cw56HVLYJ!9^wOf&2IelWHF z3V<$0>+r{3H}73|Czn1K%#UHW^`6U^a=fF*mMn^@#Lq=oe=*C;Ds(M_o+t&k^jldPwjr-f$Vg@8BQA!~`Q95wuR$ zqBJL#@Y#ku?k44Mt z92;CudmexY`_6CZKAETpST%5u4TQ05z1fH!(nS_L1e_8Ws*HR*hG&SZrv?&95ey)>L;MZky`RwRYk?+`G zx#=*B$xU=PPKpu)z9atdp=r_qIOz{tV^op~5v!>Ao?i!DXnA|)($Oea)!H3L1LK1Q%tp-Ne zJu3yhRNU%M!WId09GIlq6P(C}o#08jt zkU#yFH1_pBgK&R38h^S!-9HG8{{kAnBd)zXkxiFJ3bmF8Te0H0iCRKQR{Q}b$bfkh z3MwY!WS*2vh6Rh%pyX6`Mm~G6hMP zV3t42pE(4B+10gI4DKQZ$p8bU$R=SWCV&hEy@G*o)s#&JqZLhMXXmqrh+yZ>_7Az^ zLfnE1E27U5g+qYCA^su$Y@qOyNF%h3gaRZ^Z0!apFhw?PYE zoP73BMPZJA=zA`LIt|ukpCbx~0)<2UL;X2JP{6t(N(0&oyNE(vfC5uw>ug-cNNW((^`n1E#uW_k7Lf{9NCUkvUMSkz6@f5GTa^q7pM5AqB*M zYa8kY6NCW;&ueEz&}^?l!}hbon#8pDcL_vWe8^0A9Ak7Q%-g))RvROq?aF88mRb?( zclmQ~Iuy8XqLVGe{DNKlGiE@4E}CVoKiBU_4+yd?F~3D28Qjrn<+aGT*7@zG9o4wnge$yOe-b5!m0X3RX`~!Q|G<&yc_5c^Ai*S7%@%Iv! zi2*JGX(Av^6i5b{A4@x<+TI?tVU#*fWQ@uOlWc#IL32{JhiGCJvHTvON$EgEgvoZp zt=0u(wTnW{<@eU5Gnsnk%>=O{9Aad(wIN7g+! zRpJzmbm~A$a=xCzz%giJ$Rp>tQ^K`h@loIa@gRSj@B^I3H!qFmrAc2p^bACA1y1tw z31h$2ypuCA&dk}Ov$@ADn3qQL(qvxxzlb8`?ov|jv5M0{e_n5FQ*A6l1Q`b=k-|0- z$z4EEEOXo;BDLsnurd&x2~ZS0{Z)AK4|RRVVQbC#NoQ0|@i7#ALHSE?>H9LCPYdU# z@9`?%SE_tpt@3@X%J+5np5j-71m=H+aa?nI)@tB^da~VL_{~ytKda5p-=N*gtMI^= zE%<-|9Us`;;sfH5KI3-zoAkppP{I=pNjH&%MH-b8IIE3G*7K8S&$#tgl(k69$}Zht z34N1XWz%hz?l z;Un3FIS+cT}ts|~%ba%KBOAtvm0f2%7CyvO7xCpr? zjlg7z3U=Dvc8u<*I`zr@nDK}Lk0q|LhtCerqXtdpG+6j*%|B3Sty{usL;9(j{`kKi zYW5dQ6C-_GlhZzA$}>%7BRfn0^;QI)YZYoN?q9L|smr$f(9a^wGz40BYnxxfAr7=gQF;DDDDfRXb8cYqp3V zH-h!jzy-)t_0^F~Qf>pRugOs_w+Yd54`NRW8F6XOJ~W0}BcR!^jom5yH9l@7bZCz6 z-a@0KajW1_breF*?i$0af*qvx1t^y{4B(P1d^6*9fVSl?5%< zHahhIYw5^sq%FjA!0dE14>VgPo3Ht$xin6rQ5?e7R$=z!+-oqwLuHAfl?TEM6;(8N*s8el>=aqF9)39NrfYu7cLWoSKEm`?I1 z1q;(%Aj!o@${!b|=AB~CP56yLJsI{Zyu%j9+6@1zL7pct)ci?Um_CWXQZG^k(p57O z2Q9_k=&9}v<#S?EXrzkHFazv4aD;3M{7IQ|Sp2I~9 zz6`u0LwKSP_T%}U83^*@J7;scq;;O?n8{$DxB0b7?;c~1vC)rj=;0maJ_JIE5<^7; z8WbJWEt|~2IuF=RBk1JLHiAy&Vcibt1UIYrVE<17@@^lOn#rAR$)2ARE>UL04(1pE zZaRnC%+dke^iElO$>%ZzF3z^t{PLxDZ+I*vt#C7d%Lp;OG;kwLQJ2F9H0pw;sN0GD zf}Vy4nR3Jz2br{I3j1pA^35@%I&F$k0L|G6abo&upbsLawtv>o;R8T^zUO=b^LL8a zF6uLIJPf0dx6BjfEnhgoA_{6XS(ErVSu$MBY${co7>t$w!H|-eP{HQ+@X|oPp(6_T zAVKC}ph-0ozisWwc86@^?I7e17y*AC!RiSht0y4cNcZM*_)!*^D&~zXGtL~g&WBg`<-pD5{eFkOe}^a3#$;zzYk z&$7M_X7=DKmzzU~c0ONz3T3q0a!0WWxGf^@*?D)zhHDYR*ktFBd9sn=eMQo$Q0kO?bqZ7^-H1;ua*sL?gDJ9Nt)m9gwGK|@Gl z%oQzALIxr~w2_|%zCphr0r!{LFDKR<8k*&r8+*$rtg&YiL6)CJuF-S5<(=&Q0uPhy zn7Ja&wl?%mMnp`9om}@TEKM0xI)*|?oDvb)SXP@7evxkFC6?^_Ql9-XAT3jzmS(q^ zySA}kCi{odazH}9BaLCqHqO_^ zjIA4Xcly7JvOKvoYbQGOG==%)8xAt} zD+PzOFF+)Y>ZNvhyniCk2|OXZINyCNa5AR?$KwQ%dEd6L248C;zbu!aDlp^m(0!(nld-iBgRbI&h4j=Ue2%dQ^LKZw0l$glfDNj5Q%Y_l+Y$V5#Q(`c4>N zvP9m)fACFSc;A^WY~;#x>9S&u6psayc0!$xoC2;MhNLG!?5v_zU761OL0k-`GpaXiXvD zS_x><001jVe>&qdAdP@?Bt^3Z)FJ>g2w=8oKpg^lC7?B>A)^p7Lqf)CKs^G+N`Tl& zv^Ky`Lh_wfkHWImx>in}C6#U}hx}=QsnbZRphlZQD)B=37{=h1tj%FF(U_rdcA=NU z24{4oi}V8BUWRWd6N|={TF+Cc zx98C6jI3BpRsU#NMq(>UxNvmaK+Y?oQ%5?zIHY13bi2F47Lc zloFR7FU*d%Rq32>813if^rGG;^C1p~CzlxIhMAIXV+pTr*AiX^f4;FWwhg9FS!-N9o(0kzc0I91L zgj&|%>LTpgUP|N#2-YZnz{v;~-n~GI_oK5kB(0we4PGoL~#{;5{ng$SkD!hN}(x z?*TpQ=VfD{Uez|m7Nmf!-B>=L$K#C!v3PMjANk@$JUZm;(9avj8-DkO^Vh`{0PiRL z!}`lYz2U%i)Bt?%41|AVQhTVRvIjMS>8IbC55J`|aCIg9=^xk-~&e)3l+x6V`?`!~%p-Dfb^1E!=Kdo1^7D5o}+E53-Q zenn$e9WYPow$uiMD*kr)CA{N&8CV1b7My

qsZ1U0@-G3UR&01uijBxkJhqpM}e2m++-G{%4_<6gAxX>!zhZ$w+6vGIL0o#(+>~E41gfb z82ioklfpBs_Mk@a+{xqj~{w>f7aG5^ttjayhj72!d<$1_@wzlI()7#9kTa0VD7{%?-o5AzxSVN6l zoC(-&FMwciJOjbOpeGJ6o+L8}68;W2B)!tt(9{?P7@My3A&CF0wakNGa}6)x zm3v&MVhaS6vL@XT5QhHxB zb+LC!h>bK7u7H5DIjuLS!%9e6t0_2oA7F?}C4m_1P?B;U-4v~e&y0YlL5SA{`Lu%G z*>fjGz1!xJxJSa)><>BN#Xq{-2`>I|PUVru-g3 z81g^BwDaazB+XwC(z>|!M9o)NuAL*HOL`C2(8g%!vfl9;+7yJcBh@&0@}j2Y%L=u7 z8-Qb2q<}iE_nr|7SloLjfpO zD9*xY7=w)s5yuT>3dT!&AsP{nswO$AcWsb2+9b!w%`r%8nnWNR)mzfE&C#^Wd&%sP zwk6tpbqGThTb3^y3v8az6p^ktqW23LR)x)y-uG*0RR=BZy;(!6uvup6Q-#d}Qy+s( z&|Bngz;R3z^TnnoL-?jkl;#+kfwt=*ZvJ+l#V6 z_A-tqXB(FJ4az=bOAOhh)9_SH*Ze^LtHIo)Dn`ML@9hD&09JOHpM6uX3bj>z(K=r^ z0t@+yHjv^luNPd|BB_iFJ?OO7?p`V-1es+30_c$IBQys10U>~EwxASD)>bo~r)n}S zt-H6IQ2u3UQD98~!m=CjBvUiZsJOs({A?R+rO?ZRa(-e=@28OF-x*rHQLc}RJm}R= zgy2&{Ith$o@woL5;;Pp=wf8x&Cll9$8Pn?90$GXOdmR!K+n6dBs2IWPGxj3o_WW8s zmO3!?#?f=waxN>KkA6>fJr58Ni2jOva!J%1uSJk6-+dlHg!DCa?l6|xd6bsHWjQVl zz;ya5-o*F>dWBR!*GX9(7l-NRbi7w!K_3oFXH1%yY95Ionva$^#oRD;yQDC=%YQ>5 z?WL=$lb!=_g%svN4vx~(l-@MsWfm@xnddX8?MZ8A|-ijy5246-9#+zahK;)6a zf3K|#I1qp|iSf(i+DTq#;2xMHOl|7JUa0ST4D?fFVVcq#@9O)$Q5{pjG5-UDhnY~6 zpaTjXMiBDN9|}Fe0kx5v%ZNce^?#)~Q12 z#uqml<;HkBKmV5)qrtEoJ}QxgvgulswMJq7@WPZ?uFCpMWJP>l@9!XBu}u)JU~ZyT zuJ9v(!RHW!D?~)Y$0NbKpQCk3?*arRIw@`88f(d1SfO;!%?xELO82_CQCf|p=$K7- z6eefVr3;uf-g;2)F?dOI5nf8i1g=;Z4WC`;EijEMx8Y%V#%6&}DI zK}N|G(V?h43}{0NA;jjMvV z6`+Ncpex>s{7b+-S7E#Bb7pB}=a=<;8>5RwKpAcgDS{)$P3$!rl^d@&8rrnpsYXGBWYY&n-T>ct*yj7rLj_Q=Db)#zz3D@>k-3 zum7J21v%kxA$GAk(Fk`z$Iq|V<8YqbF9LJRo=z zn08}{nA&YsMimnTqz3 z+}(*0%3&H@#5EtH;GJ4EEfF zSOL0;G0UlFw+bC@Qor($#Idg+C=VT{&ASe)^A�ywPS~L20{jJ{Pw7o}bTYr|*8!Ytx9dH>vq^) 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 be$rS+m&#h;wIO(?{O(d^#W + + + FileDb + + + +

+ Represents an open FileDb database file. None of the FileDb classes are re-entrant - + access to the class objects must be syncronised by the calling application. + + + + + + Constructor for FileDb + + + + + + ToString override - returns the DB filename or a string indicating its a memory DB + + + + + + + Open the indicated database file. + If dbFileName is null an in-memory database will be created. + + The filename of the database file to open. + It can be a fully qualified path or, if no path is specified the current folder will be used. + Indicates whether to open read only or not + + + + + Open the indicated database file for encryption. Encryption is "all or nothing", + meaning all records are either encrypted or not. + If dbFileName is null an in-memory database will be created. + + The filename of the database file to open. + It can be a fully qualified path or, if no path is specified the current folder will be used. + A string value to use as the encryption key + Indicates whether to open read only or not + + + + + Close an open database. + + + + + + Create a new database file. If the file exists, it will be overwritten. + If dbFileName is null an in-memory database will be created. + + The full pathname of the file or null/empty to create a memory DB + Array of Fields for the new database. + + + + + Create a new database file. If the file exists, it will be overwritten. + If dbFileName is null an in-memory database will be created. + + The full pathname of the file or null/empty to create a memory DB + List of Fields for the new database. + + + + + Delete an existing database. + + The pathname of the file to delete. + TODO: make static + + + + Indicates if the DB filename's existence + + + True if the file exists, false otherwise + + + + + Start a transaction - a backup of the whole database file is made until the transaction is completed. + Be sure to call either CommitTrans or RollbackTrans so the backup can be disposed + + + + + + Commit the changes since the transaction was begun + + + + + + Roll back the changes since the transaction was begun + + + + + + Add a new record to the database using the name-value pairs in the FieldValues object. + Note that not all fields must be represented. Missing fields will be set to default + values (0, empty or null). Note that only Array datatypes can NULL. + + The name-value pairs to add. + The volatile index of the newly added record. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new Table with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Sometimes you may need to get an empty table just for the field definitions. + Use this method because its much more efficient than using a contrived filter + which is designed to return no results. + + An empty table containing all fields + + + + + Returns a single Record object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + + Returns a single Record object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A Record object or null + + + + + Returns a single Record object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + Update the record at the indicated index. To get the index, you would need to first + get a record from the database then use the index field from it. The index is only + valid until a database operation which would invalidate it, such as adding/deleting + a record, or changing the value of a primary key. + + The record values to update + The index of the record to update + + + + + Update the record with the indicated primary key value. + + The record values to update + The primary key value of the record to update + + + + + Update all records which match the search criteria using the values in record. + + The search expression, e.g. ID = 100 + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the compound search criteria using the values in record. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the filter expression using the values in record. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Delete all records in the database + + The number of records deleted + + + + + Delete the record at the specified index. You would normally get the index from a previous + query. This index is only valid until a record has been deleted. + + The zero-based index of the record to delete. + true if the record was deleted, false otherwise + + + + + Delete the record with the specified primary key value + + The primary key value of the record to delete + true if the record was deleted, false otherwise + + + + + Delete all records which match the search criteria. + + The search expression, e.g. ID = 100 + The number of records deleted + + + + + Delete all records which match the compound search criteria. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + The number of records deleted + + + + + Delete all records which match the filter criteria. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + The number of records deleted + + + + + Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + + + + + + Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord + + + + + Call this to remove deleted records from the file (compact). + + + + + + Call this to write the index and flush the stream buffer to disk. + Flushing will be done automatically if AutoFlush is On (and only writes the index + if necessary), whereas this call always writes the index. + You can use this to periodically write everything to disk rather than each time + as with AutoFlush. Flush is always called when the file is closed, however in that + case the index is only written if AutoFlush is set to Off. + + + + + + Call this method to reindex the database if your index file should be deleted or corrupted. + + + + + + Add the specified Field to the database. + + The new Field to add + A default value to use for the values of existing records for the new Field + + + + + Add the specified Field to the database. + + The new Fields to add + Default values to use for the values of existing records for the new Fields. + Can be null but if not then you must provide a value for each field in the Fields array + + + + + Delete the specified Field from the database. + + The name of the Field to delete + + + + + Delete the specified Fields from the database. + + The Fields to delete + + + + + Rename the specified Field. + + The name of the Field to rename + + + + + Allows you to set an encryption key after the database has been opened. You must set + the encryption key before reading or writing to the database. Encryption is "all or nothing", + meaning all records are either encrypted or not. + + A string value to use as the encryption key + + + + + Encrypt a string value. + Not syncronized. + + The key to use for encryption + The value to encrypt + The encrypted value as a string + + + + + Decrypt a string value. + Not syncronized. + + The key to use for decryption + The value to decrypt + The decrypted value as a string + + + + + Return a List of custom objects filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new List of custom objects with the requested Records + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new List of custom objects with the requested Records and Fields + + + + + Return a List of custom objects filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new List of custom objects with the requested Records and Fields ordered by the specified list + + + + + Return a List of custom objects filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new List of custom objects with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Returns a single custom object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A new object or null + + + + + + Returns a single custom object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A new object or null + + + + + Returns a single custom object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A new object or null + + + + + Static event fired when a record has been updated. + + + + + Static event fired when a record has been inserted. + + + + + Static event fired when a record has been deleted. + + + + + Fired when a record has been updated. + + + + + + Fired when a record has been inserted. + + + + + + Fired when a record has been deleted. + + + + + + The full filename of the DB file + + + + + + A value which can be used to keep track of the database version for changes + + + + + The fields of the database (table). + + + + + + The number of records in the database (table). Doesn't include deleted records. + + + + + + The number of deleted records which not yet cleaned from the file. Call the + Clean method to remove all deleted records and compact the file. + + + + + + Configures autoclean. When an edit or delete is made, the + record is normally not removed from the data file - only the index. + After repeated edits/deletions, the data file may become very big with + deleted (non-removed) records. A cleanup is normally done with the + cleanup() method. Autoclean will do this automatically, keeping the + number of deleted records to under the threshold value. + To turn off autoclean, set threshold to a negative value. + + + + + + Specifies whether to automatically flush data buffers and write the index after each + operation in which the file was updated. When AutoFlush is not On, the index is not + written until the file is closed or Flush is called. + You can set AutoFlush to Off just before performing a bulk operation to dramatically + increase performance, then set it back On after. When you set it back On after it was + Off, everything is flushed immediately because the assumption is that it was needed, + so you don't need to call Flush in this case. + + Setting AutoFlush On is most useful for when you aren't able to guarantee that you will + be able to call Close before the program closes. This way the file won't become corrupt + in this case. + + + + + + Tests to see if a database is currently open. + + + + + + Allow ability to store meta data in the DB file. MetaData must be one of the supported + DataTypes: String, Byte, Int, UInt, Float, Double, Bool, DateTime and also Byte[] + + + + + + Handler for static DbRecordUpdated event. + + The name of the updated database + The record index + The fields and new values which were updated + + + + + Handler for static DbRecordAdded event. + + The name of the updated database + The record index + + + + Handler for static DbRecordDeleted event. + + The name of the updated database + The record index + + + + Handler for RecordUpdated event. + + The record index + The fields and new values which were updated + + + + + Handler for RecordAdded event. + + The record index + + + + + Handler for RecordDeleted event. + + The record index + + + + + Specifies the data type for database Fields + + + + + + Specifies the type of match for FilterExpressions with String data types + + + + + Specifies the comparison operator to use for FilterExpressions + + + + + Boolean operands to use to join FilterExpressions + + + + + Constructor + + + + + Open the database files + + + + + + + + Flushes the Stream and detaches it, rendering this FileDb closed + + + + + ---------------------------------------------------------------------------------------- + + record must have all fields + + + + + ---------------------------------------------------------------------------------------- + + record must have all fields + + + + + + Configures autoclean. When an edit or delete is made, the + record is normally not removed from the data file - only the index. + After repeated edits/deletions, the data file may become very big with + deleted (non-removed) records. A cleanup is normally done with the + cleanup() method. Autoclean will do this automatically, keeping the + number of deleted records to under the threshold value. + To turn off autoclean, set threshold to a negative value. + + number of deleted records to have at any one time + + + + ---------------------------------------------------------------------------------------- + + Read all records to create new index. + + + + + + Remove all deleted records + + + + + + Removes an entry from the database INDEX only - it appears + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + Int32 or string primary key used to identify record to remove. For + databases without primary keys, it is the record number (zero based) in + the table. + true if a record was removed, false otherwise + + + + + Removes an entry from the database INDEX only - it appears + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + The record number (zero based) in the table to remove + true on success, false otherwise + + + + + Removes entries from the database INDEX only, based on the + result of a regular expression match on a given field - records appear + deleted, but the actual data is only removed from the file when a + cleanup() is called. + + + number of records removed + + + + move to the first index position + + + + + + Move the current index position to the next database item. + + true if advanced to a new item, false if there are none left + + + + + Return the current record in the database. Note that the current iterator pointer is not moved in any way. + + + + + + + retrieves a record based on the specified key + + primary key used to identify record to retrieve. For + databases without primary keys, it is the record number (zero based) in + the table. + + if true, an extra field called 'IFIELD' will + be added to each record returned. It will contain an Int32 that specifies + the original position in the database (zero based) that the record is + positioned. It might be useful when an orderby is used, and a future + operation on a record is required, given it's index in the table. + record if found, or false otherwise + + + + + retrieves a record based on the record number in the table + (zero based) + + zero based record number to retrieve + + + + + + + + Searches the database for an item, and returns true if found, false otherwise. + + rimary key of record to search for, or the record + number (zero based) for databases without a primary key + true if found, false otherwise + + + + + Returns the number of records in the database + + the number of records in the database + + + + + Returns the number of deleted records in the database, that would be removed if cleanup() is called. + + the number of deleted records in the database + + + + + Returns the current database schema in the same form + as that used in the parameter for the create(...) method. + + + + + + + Flush the in-memory buffers to disk + + + + + + Helper + + + + + + + + Helper + + + + + + + + Use this version if you will need to add/remove indices + + + + + + function to write the index values. We assume the + database has been locked before calling this function. + + + + + + + + + + + to keep track of null fields in written record + + + + + + Use this version only if all of the fields are present and in the correct order in the array + + + + + + + + + Use this version only if all of the fields are present and in the correct order in the array + + + + + + + Write a single field to the file + + + + + + + + ------------------------------------------------------------------------------ + + Private function to perform a binary search + + file offsets into the .dat file, it must be ordered + by primary key. + the left most index to start searching from + the right most index to start searching from + the search target we're looking for + -[insert pos+1] when not found, or the array index+1 + when found. Note that we don't return the normal position, because we + can't differentiate between -0 and +0. + + + + + Helper + + + + + + + + Private function to read a record from the database + + + + + + size does not include the 4 byte record length, but only the total size of the fields + + + + + + + + + function to read a record KEY from the database. Note + that this function relies on the fact that they key is ALWAYS the first + item in the database record as stored on disk. + + + + + + + + + Reads a data type from a file. Note that arrays can only + consist of other arrays, ints, and strings. + + + + + + + + + Write the database schema and other meta information. + + + + + + + Helper + + + + + + + + Use this class for single field searches. + + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + Operator negation + + + + + Parse the expression string to create a FilterExpressionGroup representing a simple expression. + + The string expression. Example: LastName = 'Fuller' + A new FilterExpression representing the simple expression + + + + + Utility method to transform a filesystem wildcard pattern into a regex pattern + eg. december* or mary? + + + + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A Table to use to build the IN FilterExpressions + The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of custom objects to use to build the IN FilterExpression + The name of the Property of the custom class which holds the value to be + used to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. + All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple + FilterExpressionGroups to form any combination of And/Or logic. + + + + + + Parse the expression string to create a FilterExpressionGroup representing a compound expression. + + The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' + A new FilterExpressionGroup representing the compound expression + + + + + Summary description for HexEncoding. + + + + + Creates a byte array from the hexadecimal string. Each two characters are combined + to create one byte. First two hexadecimal characters become first byte in returned array. + Non-hexadecimal characters are ignored. + + string to convert to byte array + number of characters in string ignored + byte array, in the same left-to-right order as the hexString + + + + Determines if given string is in proper hexadecimal string format + + + + + + + Returns true is c is a hexadecimal digit (A-F, a-f, 0-9) + + Character to test + true if hex digit, false if not + + + + Converts 1 or 2 character string into equivalant byte value + + 1 or 2 character string + byte + + + + Represents a column of the database (table). + + + + + Use this constructor when creating a new database. The ordinal index of the field will be the + order it was added to the field list, unless a primary key field is specified and wasn't the + first in the list. In this case the primary key field will be moved to the first in the list + so that its always first. + + The name of the field + The data type of the field + + + + + Use this constructor when you need to create a Records list manually rather than + using one retured from a query. In this case, you must set the field ordinal index + for each field, starting at zero. + + The name of the field + The data type of the field + The zero-based ordinal index of the field + + + + + Clone this Field + + A new Field with the same values + + + + The name of the field. + + + + + The type of the field. + + + + + The zero-based ordinal index of the field. + + + + + Indicates if this is the one and only primary key field + + + + + Indicate if this is an Array type field + + + + + Used for auto-increment fields. Set to the number which you want incrementing to begin. + Leave it null if not an auto-increment field. + + + + + Returns true if this is an auto-increment field, false otherwise + + + + + Comment for the field + + + + + User property to associate a value with this Field + + + + + Represents data for a row, a Record consists of name-value pairs. + Used when adding data to the database and by the Record object + to store data returned from queries. + + + + + Represents a single row returned from a query. It will + contain one or more fields of the database (table). The last column may be the index of the row (if requested), + which is the zero-based position of the record in the index. If there is no primary key specified for + the database (table), then the index is the record number in the order in which the row was added to the database. + + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Tests to see if the indicated field is in this Record + + + true if the field is in this Record + + + + + Return the Typed field value. + + The name of the field + The Type field value + + + + + Return the integer field value. + + The name of the field + The integer field value + + + + + Return the integer field value. + + The ordinal index of the field + The integer field value + + + + + Return the unsigned integer field value. + + The name of the field + The unsigned integer field value + + + + + Return the unsigned integer field value. + + The ordinal index of the field + The unsigned integer field value + + + + + Return the String field value. + + The name of the field + The String field value + + + + + Return the String field value. + + The ordinal index of the field + The String field value + + + + + Return the Byte field value. + + The name of the field + The Byte field value + + + + + Return the Byte field value. + + The ordinal index of the field + The Byte field value + + + + + Return the Single field value. + + The name of the field + The Single field value + + + + + Return the Single field value. + + The ordinal index of the field + The Single field value + + + + + Return the Double field value. + + The name of the field + The Double field value + + + + + Return the Decimal field value. + + The ordinal index of the field + The Decimal field value + + + + + Return the Decimal field value. + + The name of the field + The Decimal field value + + + + + Return the Double field value. + + The ordinal index of the field + The Double field value + + + + + Return the Boolean field value. + + The name of the field + The Boolean field value + + + + + Return the Boolean field value. + + The ordinal index of the field + The Boolean field value + + + + + Return the DateTime field value. + + The name of the field + The DateTime field value + + + + + Return the DateTime field value. + + The ordinal index of the field + The DateTime field value + + + + + The number of fields in the Record + + + + + + A property which is used for integrating with the binding framework. + + + + + + Used by the Record class for enumerating in foreach contructs + + + + + + A List of Records + + + + + + A list of Fields + + + + + + Represents a data table returned from a query. A table is made up of Fields and Records. + + + + + + Create a table with the indicated Fields. + + The Fields list to use (a copy is made) + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + Indicates whether to make a copy of the Fields object and each Field. + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + The record data + Indicates whether to make a copy of the Fields object and each Field. + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself and the Fields in + the Record objects match the data in the Record. + + The Fields list to use + The record data + Indicates whether to make a copy of the Fields object and each Field. + + + + + Add a new Record to this Table with all null values. + + + + + + + Add a new Record to this Table with the specfied values, which must be in the order + of their corresponding fields. + + + + + + + Add a new Record to this Table with the FieldValues + + + + + + + + Save this Table to the indicated file as a new database. If the file exists + it will be overwritten. The new database will be just as if you had created + it from scratch and populated it with the Table data. + + The full path and filename of the new database + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + The Fields of the Table + + + + + + A simple class used to communicate property value changes to a Record. + Used by WPF databinding. + + + + + diff --git a/Caching/FileDb/FileDbPcl.dll b/Caching/FileDb/FileDbPcl.dll new file mode 100644 index 0000000000000000000000000000000000000000..1789b1cefeef4a0f8e21524faade15a6de83cadc GIT binary patch literal 90624 zcmeFad3YSfwKm@B?&+E7(a36zG$UEIC1Fb*WaAA3BgvLzvzx^%#&}_|!8X_!kCAK^ z3xQ1_31G8@B{-~33}FdbF4+kQkUb%B$i_`@0twm4%}uyB2_Z4x`<|-m>5;}P`90r1 zzvt7V>8|C}sZ&*_PMtcn^a;yuvK-5@T>Rd6!?HeuH~;$O`R0$Uh^}e*T#fa3^4Ye} zR2~0p+oJQ=4z_L_D4sX4^1{}0S8mu)+|+u`>ehkH8(PXVMNtmCU(Yy2P2TV!&3(W+@nRe6@RsLHZ@6?($}+^ukL#hcX8Z7aIr68ibJUPL?# zgFnaWzs9m^<-hc{tT8HDp8ddk99{QSS(|jaNVH{rsmiKGz+W$_vc{D~yoIOLvJw%H z1pLQD{L?nAzH}4(x1Iue5hrCGkw1c0OdDLaa??u7S^^L$JIZV=L7K>4Kd78GFgS27 z!lVo+i-qd5mW_hzpEj_1eGz~x6U)Xrv{sCQo6qR~_xJz*8raBwg+KD9ZN;p|UqY9= z(Xx^fYFSTitP+`UG5CM04tuj#W`=7O006APlw|&&dm3ieXLojF4|?qi1i8MKiraZ~ zNuP{rS6|7%L_Ckz6yJ;Y?|tKqH#$e)xOL(&(?{c|dwV#*-lXGCpB_Bh;6PB6j+oHF zK+u8LXgV~GkB&KZ?r0oB$6Mooj3phbru!!w90-cifewrMU?AwgYcw4i$4Q5ra?WTR zLdToq=tjGs)=9_rcg;CX z>sOb@eOBT~OR#$VnPqWK5h{kF<+GnyIs)hJFx?rm;wOzr_kc-v=>=yVRu)(Mprm8@ zKHj-vZF$^-5|^3bSf_#iQH^Zh3)UTWv<7p!W+U7wW*|^}Zx5KJDqjzG&v*oTdmx#b zl|KzXrRBz!v4b}uYKuL@MsbQCmK=Zsed8hk@eg@?>fC12$MO$WS)M)k5CDgn#?Ch& z2RoO~B1ECF_j%3m3|@{$@s?;X)DCZOD}!&127^3r@M;F%jt9CX@{ff&2J-*tD^nJS zRl(le#S*CQ?zqKaq!O9jl|!(&6eYrC06WbZwyX4o#HjbNvWuC(p84@GKG_Ef8K4AjtCL4 znst8}K-}NX_g0v=T(K32Y*xO{dixaOe{!QZx26a|Y??vWFg?ja00DKk3JKL(Us>hQ3z)z^t^YMm2A}>BaeEbJvteGB-d*ogGSk0WcR*aT;Ffelq>=f}e1d&Sj&N!juI6hL$!80_PBa{%32BXIk& zX!PbH*auW78m3wYEd?R?YX+|z0cM5}A`LIhPj+h>>it+ZOPwts+RCNstKF`f;c{OlG}j-RArJuG8?$Nf@B^X z>$fyS`;G`7B)pczJr0jdED^+t?}00bCEIgL(#5%SCvr->BxeO@9^c}X@1Z9{1H&%%21_hrG@y53@4D1((4_!hf; zG9md49!hFSW5US|?!epkbp5_$O-)yY_1lA_U>Vl0|JzHJ>iW(4c6+MM%A=lqMXFO@ zUwj0m*vtOu3CqRv6A=yOQSDZ1jX%g@EZWVnx=MJR?T5|ZFWy}7vHf5*JXvcsPceAa zxUT8l^6|r_?H4a(1G%yixgovdrk3#bmFT_cfld8yhBsvSO2|!Jw9PcQ9nm#4&6oU# zbK-t+7k^rrVuN#RXGNT!U0G-Jo6+r7eBs~1`SI;b7w(_Vy>A~U`75RPYyD;O%`i(k ztIPST_z56QMXEHR*ehP*FVkLYz^voHHGhL9&<1a2^V5+Wa*T1q*IgctuZ~+D_t^6* zLfyh~V_~}H8J^6sF;uWG{P2b2j3~~g?DlkBRkSa-HEyi<4P@xs9^ItWs}|bh(lwHb z_C$&HsdH4@k-p#l^MFv16xt6i(LVUQW$VInk@iU3%gy<4U=!MP+z8rxmp9HbZMPrV zzxi*`PW=S(kp*ELef`1qB|0A`Kba!N_VP1SQA7uaz%#Ii9G^OMB&?gCsem((^ALgt z_KYWLx<;gf*+-d)mR(=(ggDt2$ACV~M}OOKhSAY0@KMYy?g1^RHz(%qpNUNW zKd_6%=KMyD?GWN@ht{SgMy5oDmE7QVCH2)fdL+`prbPIDyV}5yrpYaSRa4~V=fPE( zt4p%E<4{Et$Lga?dAvURh;S^az=PT1*Htd4)&1vU%AGG%%;T23>JQd!Ke`;~AH}cf zJYw%Kk7KLE634oF?fsLDieK+LT{98$|1^)ct$7T>I7-C>e4*O%H`dgQ(LKt?^469$ zrY%S59?&%(M^i?|hEVmWIF@tnugBY$#!NKrH+pOY-v4+fcp*2;SoZTLCM}52J7O$z zi=P7Cii4Tq>5{JGS`oYxel--zi_v4GTm1Ap!FxF94OE}+UeCZ1>TkD|Ny8Od=b_09E&;wtPkvenIl&pvR}MT@k{$f zZ0I#Q19j2}-oeDhp$r^FuT%WuJI1T)VZ*U`Gf%9f<2?26;(hU;Y*Z&5!EeQ<-x1#V zCBC@s9LLxkqcXIdF9dyMwg~&*#DmNELbu<$Up~!DcSqMp@yq)qJH%Vk{T07yyuG(B zU$kGoD1POg;f>5yzH`?YqgIZV7un(yKwMD_Rirn9_m1Nd=SS!*pIhaMKY2%Z3ur$r z-yS~u+;9vWB{z$ouZ-68jnaG~CTlkt)#o1keNQNhAuFo2?pyCG^ykQFLvNDxE=Wp6V6Ty6DB@5 z8NunKTg>CFP{fM|5`G6j$!hs&)GMSTCe?gPS&*P)2~mtT^9oxEMA_nc%|G-kj>N; zPGS~8P`9%>o>11VHiSg2U6~-0P3#IXWyY>}WbTkq#x5~&)xoS)7t{p-%v!r?);bf| zBCuj^G9*O9F{AS{^#vBZu0G!iUpk&t_N;nk&-ztp&#E77&x&h%R(ym#YY*&x%GllV zx8gy}gA9RsoiaF`zF52{IX4uhI1mGw#2}`mJ}rE84MN2*W}SZE28nKIljJ@RGfRK) zU_!xumj^3O$tUs1c!AgTYnw>oL2`)6irr}POg6|iEj_Xp1)~{l5+L}a)G_XjaNm#L zb2`kyykRzeFo#;V<43r!;T>dKR`vV|jzSh8CX;?-BE;Fd`9j%!5y8-a&A~Vh)EW#)=t;G z^k*8gnWn(m2N4Vwz~7u2OOyf3}t6T519fX z&$wqkd74r7p`yTe6p1@{@xqdgk9I~KXKZyaB2&Ik{833SjLgfF9Cb$78PVIOR93{z z*dMhs-v8rbxX>(iMsy`Z`#;k|Mln*fN8%RFJ;O}85C__I+(_CdZ;9%Om29Nn#Gq(w zq;H@05q8FJ<;NVSo50LVygb^DMHy*ld~$ul%nL$TH;>Lh;1PDl>B}YGG2_6~jfuNLIozn-aR0fOJVx3LbsjfwJlRxtr97f` z!*`a)(~u!p_hi4m_TCj{9$9DYYoO?Ix;}*XGA&NlEwvVBRtDXSpUtF{7Uu`4o#{lf zvKChzRA-aBg6c9YE)mhdk`XN~1(BG7F!hZVcQ)mon0;h@^UW70(*>}dRhJh1NiCyN zH$*@iz4kYAneDhZA@^q2a|nHIgLsNaCStFb1l1)4uniEL6-D4?Sm1 zeoYM%T)S_vFE%!RwOK-TNOW5B5qH5&=Nn<@P%bg7_qD^#9v2B)jIhrfxZEt(*+b_; ze+Te&2+tgMjG4$Q+zz-G(;s(O0o?$YqaI2fqdTC6iRF)p0FBu9w7{Eof(EyT)*>rz zY%AYC#n89hKlQJHptMJ$ga>`Y-vs53`;6Wk50oL9%Tyg^=3e&Dx^jA;=n@Y@-*W%d z-H%T$Z7V9_5qjPh9`r`i(>HXa>7(}01?BW46+H}n%l%VR&uA%a5h~#kdfpTd_k4Wy zu$0-BVM$LKRuSBHb>~;+g=NowlqDIuim4bkt7Ce&hO6VYFpl_I=Z-Opv$iuV z!4}i}eCYzS=w$0Omzgxb&W^p%bOvWwf-Ta#=;UQ)0nr{>U!wWcmj=RcXIMoLz9U_0 z49E6RQNjNj;eY+epy|H~kEpK-q#H^?smXrAf;z)GEpa~d`q;2ML+?>IcOm?c@3%}Z z(aZ=tzZ{q9 zhI+u~F?PXf!G80NAFK>jVyQAik1%8XFj>Xv2C!wfa%cPCv5*wQo zyRS+Sa|t9w(&Kz3D4s3NaTC1PkwG?U#6m9q#;Et&cYO!Hfci{l!$u6R$ju1t1P;oM33#Kpysn^x&C3RXqES;==co2bRKU$O3Qm~k%#r|^F`>M+OdAIP{ za@*RNRju49a7$}emkPzIlQte0j>2(1vf8#-Z>+PYCND7a5iLS=V@kgoyc%4iXuskl z);#IY`URSmTyJM8F1D%agtkmg2fY{!yL^JW@}gJB8{Yz;gEibIpX%0%(F z+AG(r3LP(E0_}C(ffV)-DH8_nLUmdyO-r(8R>@P>LlH3WdWc&V5aMDOz$$`tnsEj* zty^6q;h`pG4SXyLZ>!Ma$)=9%_^`CGj4g37Ktkp7Jy=frp4s=t8(;-WEWFt2|2G9! zvl2Vm;jPTCvard@*aShzVGS_sU-`dN2x|9h%1y2x9*)4+6e{^Q7~8UH@D!Gwvc+yv z?svn@e%d@EZY^;v?zbV&UH8`(vsb9W*MnyMV?^TQf5w;8nzE>-3k!fi&jP@2N3mUC zd_s$JJe7_Rj~C}7xOgNUj%tZ`5qmhjOif)4*M@vyp5j6Tnd!sBm8TC8KI#TVI+twk zhv2TSc2sonWFV3~!zSQF1XL%wgN^}5_3xv z`Ts)UvBuTQzA*52;AvDgx7wgK#8aC~$P&h`pjI$WP#BY|b4$}$8XIEsWMZ~c=jML} zbQ!N911mclHrsn1@N#!5MkADrtrH}Qry=u9Qi<0f84iM_u`S~V{%Sq~ua8Paki-&0 z0@mP0dh`xdg-%M+cR%V@ozp6GunMp|u^4oe)rViG3FEQr zwFsLY43Z7K&=d{z@!}GuYM1immMZ>@a8DpbtqbzV$>&^D@lOF5f>gWWk-ECN{8B`V z@w+awuK~qs4l^G{Xo;u!#0o55^ZHApWdoE=oF z&eJZen_HrKU8IZc#h8oj(YsMQM!n%M^n#kX$aZw)^khf*Ud$=1j=gw5C7ZVjN#UA- z-wOP0!jJXGKZb9>g9FO<@Q#L0MA$xrwV{79d-K-^e*n)(crzc=N)_!>kHOuFDD>m> z(k-FMPN}}C7ptC0RvB33Vta5#v1mP-wJMb9uN$Jkz#IYSZw{++3IB3p4&f1}_kg(1KNoYLMe)mfU7#UP1 zf%Zc?Op=N>b2;oF{UlIEo5@q!LjH3qE0K3sq23NqdEN8SLNg7h@v^7Xs1-Ra?6BHe zs+Df^Q_Bbykuqx$V8_t=DeZDgO&kS|h>KJ_+Xq$AiszQ5;%*OAkT;PW(bScMh@0&r zYoMqqx88Q5`wQw;Z&}9@U)EJ4aS(|3$*DSrv5#^D>`s+C5%&6$6!D$3Q$z!xxK{y0 zY(M*4Xj&YFyPrZvb|RbqZ`2?cFk$OI0YyZTvyh~CHXc}IYyuRz9+ zlkh;qNFDT5{C`n;nc9HSDukS1?Y>lo0V9_}Muk!n8nMA_BQUNuY-t zUj%l{g(O}Lf@{3oDWh11+K0EgxCZemuEh_j7S%)zXBq$<#MRSYI>T zY!F}_S2cZ$+6QP`w^bdp*SMSPGcUHaNL=gG4LP2Yb*`-0fOyJ_XTEiySy~x901V-K zS*+y4fz8oDOM5q({Wle4jE!2!cG|9}JZ{CV;b&7>oGB?Z04G#2 z4wOmhXS$=fPJq73?v($Q?l@h)0$MlYdEK9fBY?gs9Vvr-cBP)=7setICp=b=_u?3n z<1QGz)RlbIm3|WPInI7m?nei}DsjdV=DKv^wUqVG%VGx(Uqn#!;eK6_Fh;Q<7dW62lLM(5zasG$eHJFF?(oW zYeAn!U=j|#jAIIC0Dt)dlE7g6a`_TYKonPJK?)j91U&I(E$ls@w8 z^-@j>oziHK$kw@avHV}itI+7WPZF4`0n^n5X8;*Qk75$2t#Qn@79qRFr9oUqJMNW$ zqMJ$JXjMWKLQz7o=Y-m0fwSVMJi^hdu}hXz3#WK}8^CDT?n9}pNFP_CnlcBIIA+$K zNs3|?B(;Q*I4!yrfG=__@S^}C?NvZrCP8~e=gTQ3RCW;ZT?c+VU$zcELhz6CqhG+^ zuG;4BOO80%*d|1;a%7sLS^^90XdCKMtlQ%Gpm?nqyAT5H!G)w}39CJ|qbZ_MO9N(T z49U12>013Bi;P3-E zK*G6fHQLn38hGU_$GHf51V3hE#-oLBgMZk6&-im&XvP*!a7xU)iA@192diYZmlD%k zt=T04M6ULPU%Boav#3N)R=DTpj}1+75xt(8Dy3~V+1TqG*@x_GdrImdZss)<1-iRY zju$Url`0iToROIiYUw9Hiprs!dN0bISG4^0(`}*YD?&?zl58i82oV)}8mQBQWl3!o z>4EAHQ)M=_m-i}oxut#qgQ7m-ya$3(Kp)f zL_3CY*3*{QsA{Zf@uW-T@?VhjN`A6dRCr|e>!~@dH0nqRrDu*y+tX6VWfIEhTWDchs<`C6XEs)f$wZS)x%u4v31XPq|KvnX^%)DC~jwwW8*Z#m|g^2(m7OpIRr+ zN_%*dUunN@h97Cjufc1J%{jaZ;|-B#I~`TMn59&zUTR#bdc6wO>+Gj`Rd}R&owrlH zDXHFgRKADRTP?Ml_;)H-9omlQra9~#g(S1l^FIAEQ>AuQkf{+t@a z>)%qJbc%IKJ1s)<@LR6vAA_p(a>WZN4xC&uN0n7XvnMP3-5iew9tE%KDIP{xXtqSM z3;Plwef*YcJ+Icuu0{vl{_AzMm>jvbzpX zr&x~=nR@01Hvl*t#~d!1|2E;de16p^bfbHW&L`5Q2w4JTumh2CU>;FQna|o7xhZ_#8 z;A@ujH5-!Z&tt2kimyUvd-i$X?0c|>Fwxb_ASMf@za20??Whi6)ZmmI7SQ}NU})Gj z0Lc9jCjh=pwbgG9uL;+;C5ahTyHc!hN!Z19#{k&bJZwM!-&0xIh2W znc@jMw#485MRJ-G0r{O)nlP9Z8i^*aDX)IAV&yR!j1v=h2>~0JI3kT|}M* zW4v{c%KOh}ofYoaki5&Recgw$a4Me>gEB_VIV^8|XCk*0os(mtGD@ck9B|P zfL&E-0`S$QM#(Fl^*2O9g`vE+-@DS>#iGW{5d);E1XeUFO^tMDx)@Fg{24i*8g2!$ z%GE&Ot?#Nh2SDns3TVq$Apz9?inV}(WP@0EVMW=nfHYDIFw42D1eZH}#d3yW1b%Z# z1-CjSQ3oZqasXai6;=ZEKPnqo17RJ!ys*mLg9Y*jrhNCy_>)25A#_cCZ$Bz{{k+tnM~2u8ag@4cT^LlQMQSc#clx-5umVH+HA36t%Sls z#g>|8*67C+L_u6B$TWp26`AHxX(ddFD~tqMKQ12*m_}NOvd(krrAw#d-I!NeN$ipi2vasu@h9{j zMM6cdPbbwjqAkFgjEFm@m_^m+dN@DSHz*Y^W-~+C%!*c3T{sG9jL_@>+jJkdYVlT(L`$v$EO3cAt-WE1b5XW2y z%mFjLC-zER7yUFQsG`WukyPNtxq5*jpl73p@K8-lUH|a_i`1k_p>W8C$ed!NJQ_i^ zD|gi+0mkaNtn0A_@?WFoHMOK3SJ?8;q6EIGsa!wRLaz&otCg!RJVB3&>1TQbJ;b1H zs=g{*W>Cu6Ec@P+yQzPxf=BJ4z56jo(Ft-r(7&{MY$Qcv|A{Ka(M&P6AADT_s@c$i zdWFW`&p}n?*cidp)K6B3t%Ub>{C*BUCuj`Ygtr{IYk;S4C>~B8 zs=S|a<%I*2JsfP}iAv2C7jW3-{Ir|rE8{}Jl1`*s-7owS894c^jKqa5@`m{{@OosB zhNBYV$0X4{#%?G7E+#^eKkJ! z_KlrPm46R2t##EKdQT82O5vzfyw#HEy-Wo4LEaghT+QUK4JoDXy8TevA<4f3md@0> zd3iU~)dvmv>yaSZyE)^dot8vmD51{t>C3yr^(MKcF%{p_WsI02Tw*Om*q)C+vc=rDp>3pPa}dn)AXD8P2Oy!W3`ccYzwkq3fPv^-WC>l?jp(v>A_k`? z-RSLwdNPcKpSY7rVQ+$(OQ1XAxQd=jsFU2ICc`)ZnGJB1J6&lmp-ypQ&WlMf6;-I4 zP+qm7Dti^645y5uioG6gnWD-xDK(SuL}|r*6NOrZLUp%9M^|V&M)~O)gYw0?lB{6d zX{D7@b(W^z`ok0>?>*}i06}Z8u7CT#j#2aPk@ULkauT{p6sF6S3k08-hd zWykc$RfKcUVOr996=9?m11%X{K)}|&getq*=Bl+5D1sn|XAM43Hzz#w5t-!3*@Xp5 z*Nj-S5hG+LZCMwdJUDW41WtG59!-`sqt_q7*Q}e^aV^HI<#wS1aU!aerZEYWrbU~B z6$N_B7x4|g8)Gi*jMspt2jlSILWTIwVTcd6zB<2phU&t= zLS&=O(bNt#JX9E%VAMX{mZTygiV7_YRO}+zUP1He5*jFnlh*%MDUbGvuMFKW3M%mPzY zhGx0Kv8Vu=(RRZ8auFJ`3o{lA24TAM7!UmJHdfIO%3CD9E70wrH^Z96Uv7WtdlX_r z2{g5%I9})nj{54sBr;AXVXbKe;$bwp>n4QF!3RF#25&}qb%H1Ar^o_4YL`&zI*E85 zr1;&Bhe9b{wpQ(U^%&1}eJfe?32Sje$s*eCKZ*Lo2*UdN{C#Up*Bu;-`o%pPUcFnK z3?~Fy{zrI6me#B7{Ff2Z1ul}CCQ=|3wuNl|s|ZbFb72w1V`VPEJBhl)49l1vV(J(p zhC{aodw9dkD6jynic344Dlq2|FpUloRxQ@MvIox8Z4y;`4+>x4=)x0|(l7r135cUC#-svR3tkzq>I+ap;0fY_8(Z>r?jJ9>p3{3+ zJ^H=?yf|TrKJ0ZQcylxi;9r5K`>W^>o;rs55%^PkS=J7(n1e@DE{`|Kc;Om8rbzVL z@p>45AAyVg)SPRX0QWjH1=p;J2R#OBpK+OgL%6YhtNR;_9oOx`z=aBZAE*(tN=)X| zF$qvIc7_ib+qyp)tL_q3-5|1RM=^;_=2|#v%dE;q;qK=YMs)*MtQ5W%D|VsTVU^zu ziewjva>_Oby}e|u)Y=;?v+hGEuP>J!ql?NQ(t-eR8wEL5{#xOyR$IW_X3@GuTTjR( z#_6QWQ(ws6v)t^2a_NQ2Re~QQow(SiJ*DHrGI}8$d|u{F!*|A%gK{a~q z1x&2?X(Rb*vKi+D=|Un^M^CVhgS&q~F!+PzySULa_e&WabbqKWMHZlq(dNJxx`?NH z2xZ(Z;O$j-?>H=Fj$=ml^j6Cf4Av`FfFj?M+jkV#bS;odB!}rW8FsYDnhcaBZnwYZuN&xZAaq zPnk4z6iyfknL^C~@(_WXE|6EDL>+~RBOx)ZA!JG*V*>dufqV@lmF13JK}eFT&ZViT zc}L;92$9oe9fdECOa#K2=ut`Z0H0#u=qT(Q30b2cuM)`11#+7ZvatloAxbq+WG-A* zH-|IfCSTz?l%B2`e2VR*qcEu=E|yI0-RW1jbV>gvl@1dwOTd=W#TIi$M*+Hlb;a#M z;lEZlf#hsdnHxF^ zkOo%%4oP$boCAI;a@0$JJ+hq`cO_ai4KP;a>fW)m|$9;D0uh3LkEpHoJVFm<#B1LPKyUhjLQKigXX zNVd8FjSbTN-3S_qluIm_OB1?Jwo;cNB?0-@K@0B4r&3@|vkqEr@mm*QvMa1vA5;4h zIQ~$@f;LDwwE4<{`NRkINeu&sDJG6(a^P`edPmtBlugbYsyR_iHn-;AkI=s7Q9{%N zbbJy`()CsjrqNYFsw10TRl@HeA$F|)15`VX3|>6qFW-uwyu1&T&r@`sd*TvvCr&Io zhdHSYk=+n2mhFyFr_*X3PP?x{R!TQdC^aG>Y6LGTi5$$3*^UX|R&^Y>OA=WPE`ak<1(Z~&tX1a+6xh(uJ4s82iC04qB)MG=eDV1}@yWz`ZHm~oRHYT2)?Dxj}e(4=L; zy1LeJVzQUVsd0-t0Z>kJvT!({u|m^|s7$OLTkWwD!O5yRCx{*@OX?Crx@8Gbs==X| zHvd~gds5Nxf=w6T-8+*f4HSM8W>U1{#Ua3?JH-3e#bVlH`eDt-mTjO*UK;<=RL z#qXkR(&0Wevp@BLfyL&`6RH>s{^-8khl)X&v|u{PnnVKf%|{T2N+f^(W9Dk3e}vUc zm2<*!qi>4u%xo_khrH^>cXXi7ob1K9t*FFHuFN4|25Uuf>{fFk2NTrcO&!2CNhd`Q z_v}P6|7#EmPI6Uid_HUqH`*F92T(f0iPplqRXtLd`UeUMG0L*cX*#M@7PDr43Y>oGJMGK}jEL4X1Oegqa4BSa4)V*xnU zS3`a(-gO>gJn+R5*frsV+3K_sEQJqY!II5bi|-*U7WT(31N1a_#`=ZHqoFzQqt?=d z#w<>tEd~7=h|zR3YTOtYo4F?hydaQ20toPacQho&ac-f(3~mA`coZNL{KD=M0eA-k@38A%beYD2p{6vnd7dJsLL^{9?ysy%KnkTz)q2-<{8{u;7LmxJm^ChY}j z)IcpO=8uSIsYUtmu2BAUDxGReYAC9fFGR4riGH4Y>!J=2)MVGRbwS#v&jcgLJ=%M11hE=Xb9?2G0(DM*dtt74$0JN_}UWuo74vzV<1*-w0N!x zLYJWpPuXgNtg?e^)rW$C-*Gw#$@m93@VSq-l#cVTu4Vde%dNG;^h3qqOGY2z4!OWo z1x7?Y3klm>%$l9BcM8k4@T|>2L}hUko(UgMV~k#_pQu5rE_OwCgtEv4sn#{E@HXbp$Jg9S7&BtGl!_$ zX9tJexjvJ2uxspI=6(orKO{ILm^~u*Lr3Jkjk(w8+!>jvad1;7ahTknV%bNE`s1WR ze743ICm3T$8k0FRn>kF*8U=?2huxH^b%NUHqL<>v!$93(!C}Fnpza5ex&iBJ@uOOs z{!HNT5@JYSiX;p_s=uAnO@nARRR4-%tA%LK^u*k-U-mFOL@ z-E>0CuV*nfqlZ;#&ZaX{AJh+AkHp$~My}+TK$;M9M=9nYgyay@PBe;%A@Q)=frqLy zqCs|)k>LcHQMF;}`5Q!{>bW%79m~K?GT3FEAZzL&o9WJGdI~Lw4Z4G#8!-{8x`>ZV z&JJ=;{uK;zK@V!AC+G>fQ6thrPPLSnNWG%F;ABG$SB%Mnaww<^PNb}&xE zmf_dpNU*A*d%W!g~@9)`ph@sT+Ig*hPqJGP>!=U!9Knhe4z$qlgt?t3rWXQC!UkW2)UiGsw?@jXJ0 z9O+MBpc?5XIl&}D+@wq&iGxJ4f<6#e8tmJhy7tnz9TM0F0-vkMW-13^vkoWdFgbK6 zHj^BL%_i67gUR_FD2P*+uaE;-O>!t>1zjN0r+6sK0x3P?K&S4&5~9)UhN-?)hhVac z&bTuIUzShADwF9C`m<_HW>?UkQ{#QaFcb7QDysA)&HZKi3Fj=x&&{>DHH~a-=qen{ zk3vmDpZL~yr<$wT-25&8VtCKqDm~Nja>mhU98Jmrorie(OL*RO&!$!S4C~k(9B{?@ zc{~u_$ish3S%Fl4^};Et)ve+XHhU)+r+eGlOMoCtUN9xU^HxXkxZ zneX8;-y`sSulOjQ#PX3aj`L;TS^+waQDuDg_zC9BuTy*sa9DH#=klutd|^WyU$_s0 zFX%Yx9dn8wr5{`MBs^Z1@Zt&V5TjuMXQn@f$RHs${%epI>XdD~ef>PsHZU5kLWCPz z(zx7*8cL%Srs$_3UH*R|08+57#PffGtJ;nAHvu#u`!#TA17Z_rt(L6r#DlLyH)rH9 z!gL}MV>6sfYaMA41lHPatoJr_P`FpKE!f640#aT6POL=j19P?Sq z_duB!BOjxcjY^ITqOLq;nYhy{?ZfCl0J-Q>B(A7;xZSo7N~D!Y&V_2sjjx>zodePH zo(Schh&H^TYp~ANxs$_d5p^}Wbr!x-@+u0d$`iL*0guj1_{?V<^L+|at$>dyaPG-? z>WWjsdm{}(n4=>2JPU{QEc6uOJM7h|=BihX1=%_5-8He?DK*%p zyh5?1qBa#0b(Csn{&XE%I?E)j=8;z7X%m}>CI-2oJUFrI}0nirKk*Xnx zpgC-JR1Yjs1&2<25ITiZX4JC4_bhfQj{f3Wm`ZX@k$AVIk1*evQdQDTA+MWZ>=I*u zEkJ~tP!m?ln7JX>dYH>38(rlI`GyCo6+DEjb?rv0mZa&b?Z8J&-skvdbi&WhNZ8@W{R8vAcG3$Bo#6nfxM`~u6v*mg+IVqT=CzCtDk|zR` zKTal%n;OoI_>Dt38TNa)=L42YXq;3s>>KE9!6Zy3zk|R+KSH!3#kskzDYiymd21-y zCR_WADuLlvCxBrON5dw=pO9fj4yCdaKnN<7_n6b1iN4>0ZIalt&h8?UcZ2VU5T6)u zv012Z8iIn@4w;d0mz>yyDO6u{_|;JV9vm8wtp*!{*t$O6Mjb%+lPK9RXi(29p-$9e zUmU2NLez;J6FK@Ov)ExCFaXSh3T!ReOl^Ut zHhdkSR<%rSU)5bfEl9>SbwQ1)iZMXV5ead!1=C>1f`x9lOi+(cyad_4^N1|iAliB8$o^kKH%TRWJ-ZHG3YCQL|2oT&^{LQ@xs5Lt}}{w zst|*xPg9ZFf`b-YCA=SKD`PNe=m(4V)=FJ7r zc9_?$pb>S|h;LS8RWsyJSBx-q6hYZkpBWo8X_QSn`q=_wvzcavax7*t#ZMyTy+Jcj zHV4f?lR`NbC>gPxDGXkWWTx)N)n~>BNBlDOSX@KA!yk_1v%52&9upM)oG~} zA(%NJXbsv#yAKZFN16O>1Pt1cSzFK+w1~zXw04FGCI%A)PBzhYM(uL#K|8eKh+VF| zOz-_F1o^Zd%ryei$!7VzM8Ct4i+h!V+bsH?la&_)GS?Yj1pOzW2uS{;ZF9``44rW% zxmsOQ>D5}Faf8>DAqqnKU3Al`0{2C9=aoAglqFGgBV-!+hT{I8XokqzMoO+ff%4G` zEIwkc2Fe~aPP%I<2RUql!dRtd#a?9j9a7M8#_8eQ@o8UI3%w@)*9$&lJCmprYW>2vQbwKxpH%5 zH8!;@MUd=GbQPGo169SRaU42K0yIa{gi86QoFgVnV0Y1ZkHLG9D$gU;z2=)0ypZoR z$jhhWvv8!0LmxcFvU5C<--kkBIWRF8Lteaai*vb{^$?mAqJe$r?)P49bToYk`)Wku zz+7@Cz7)XQ&Tz&yF0we=OIplHy*8ZUG43n&RpBE%?*sZ=?Wv z7T%_2CxE^d&|KzKjmxe)a3qqo9H1bau5AcHG}najqR?s>#C8)TqN>5RfFXIOhR~Kk zs#{b*igN*2ppIiy5Nz)6`!=PrI&$40Q=Wkcp}6@}&abDGNYsR4j>wNo;s_7KtvRKB z5jICMf9^Mb^py3k3+k+u&-)2 zMWh~)r-BXo<{@M4zX|?1?53)h;@%~;ceWQZ7|6oQDNuQr4tBa!Gm+Aeh8b-7HRY`7 z(xr>6VKyMgDXh?p9s`bWF7pkbnuCE8Q-Lc#fLa8M7ZBO= z&{79K*(z{bd@9mb%PKh;mQY+&I>^txm1Ew)8rMK&68BtyK$vm2#aFh?uSlbo&uw#L3Xje+vDcnl(>k%E|7xnzK{ z&Gc;sw^bO7ko*=f*7c8F7;bi{G~kj?c@M|S=PfgnW2dF2G%|-HFaSBHdjmXsI?9G5 zA8yU1NmXi+7O21cz(% zh4<%5)YJi9WH1`hv2lwxB%fq?KsnSZRtF>1ztm zPfZ$52UHps$5tkP)ThiYYNX4QnN{UX;gjIf3TFyO6z9uCC`t?#oz%NFCP&#h6;hqc zFWdtt`qha;zi@+)(a)L-oZa%qtS6{bl>kSjO2DWN+EFa5<2dSFk466a{1-z?>AQ|T z53nOB9V^MZn7qMJrs!;41Jv=|ey0U90lutm@)Ud^291at^1p<8bqBm$#k1 zCZ-VZuF&P*UlH#89=Zu7=(ObfH`k||!$bbgu!r= z{##4%)*lXyz^e$tA9DSsP$wC>tpxvtO*P>J#Tix+)X!(4iP4|Pi!-0j9^QR?PGd_ihP4qm_p^@CV)lrEXvc9{l>ZKX4np zA243*XP2n2B1(1}dP;|BTG~|n8p05NFJSXu7dM(s_QHX3YnjULt_Rl}JH1o91qABD z9~6fJyOA_p11J+bz~bTS29SF&lBhlMsx(gVn+Stq4DkV_xA-mkco2R&QP&s0 zO@E9xn@D*8@hn2Rxmu^bfgD$oCSIXHsyr*K!u!p)kX`bNb%rX>QO~-@OmE8L6u$%P zY1yNbEPfYme)L#!PO6M)wHy7R=t}iC-kLPeq9P}@hO_WKQ{`K8$7-`4RLXac#I`Al!_w8Wr@y}goGE{PC@Hyl#d^>uWtv@*@->&-l>AsP7 z_Rt-r{`a?USBD?pafVd{*V|5H;qZqn4n5p6Ydo&>GC2X|ZUXZ`XUA?bPpD)Kz);JaFSmLOkU%np{77q6*Lgfiz%aBn5>A`~hf4_=T^asv*YFcAkYZiPqUF z&p$L?XpYY%vk{AFId=e+85-OEOHjuGRc`U3Ak)$ zflOz4{7+qKUq~a^>xS40BU_fsG5=i3AjtUfu4zDMQn#Ghk73iYVOw4h_)GbDc)$2O zsc=;iIZ+To=ldsz)JP@a$_BZlx19}uok3DtP5QXJrVy7*a;lWWE~dqitQFxiq9Omu zqRfhfw4B`oWwJ!=6+L9`O4ORQBz%o?J+L58_8Ls#WzwE;k$aHAX`h^|z_eCLPL# zR2z@m7c?zdluPgBZM0#Qg6i0QDv$^(%izR*=wi_fgeQWsu$FxPxd=e%8<#8!kxHJ2 zBmAA$K}){J*EuZBABgrJ2LXJ3g!%G9@hSaV6jT|-NA|DMX`@wgwA}Op;!KqYh9mn6 z3U+K1c1b^lJz$%o)mMWs6tQ{9qVb>xtu&(k4)2FSV^sYu?0-OomNn3c{m_aCTSmj%l!!s{g&0 zL@FO0pdWdI?Svna^C zBV4(3SzENs6^X!1piz}FnbQvmX_2`rW7jOy(%WBah*<KsveN>Ft+Er^@!OA-#s% z4(u5vV7rb~d2}ET+}Q?gdoNtFRRvau_l8Nv=16?1A3XSn76zAWoYlE&uSBde<0M>H5gU%k>#8b3g=ZAA$X9%*9_eFh zm8;UE@B8&%xv{AKRFW7U*TXrVDOD_?Ogk|#*~qCwZBIu}#sv!d<3uoKG6=* zv=vbs_>NMK9qS?wsMJPbt6q~C5 zd0AzV$f?0Wudy^PPbb9~$emoW9(6cUH11$7ydNaXmg1;%H*WTYTQdiIgD?oWIk{u zYJ#8t@RiMkr~l$M*1pKnoQKig!%=3n!gux$!*847PCtH>W&$GdX?qf{TS;!`L{DXR zmhZ$QR6}vgI#J4eauMEZ6wb@qHyayL%RC-C^;0b~z0OnkUw!dXBR^CpY@Az-Smn;n<-jy>4XR=AW#qBN zZskdTesfIhx)-Aozdz}yLK*)$ZbP0+i@JWO6QQTFzR$U}tD;#qFZY7Dd7K3E>^e(* z5z)(Im4g;cu08@6=id)ZJJjT5=ed@t!@GYo!63GW?kw2|7Jp}UXghF*RRr;@{mv!f z<>5m+OYk?2Iok9^d{9Y65Z>7~&)6#Lp%0Yc*L-13_>l=`SVa*2{^zEIw#1>kO7Pux zO$;vzafVd{;V*x6L1;T2y1NAbiEp=u7pyzODuVFedEv?s&(J+3_^)l=Y!MMV()yq}IY3~dkHUxGj7ing$AonaNh{<;zQtX2H;#}~&MJBMn1 zJ5Qs#o#%CLvJi>ADMDnCyaV?Uc*zz8xVi5^77gL?E0kAReOc0syX2*$EpK%`0))m7vbfC*t|V$P}A6m^2~AHPKZR*1k_c z4Dy>iaqE*vd4Zy5=23^4c7J>HP`Iib>PO}9GIDUcmXX5Ze}P~%&S7iIpGZ9{bo>y3 z&-b74cC=r1SP1F)vq3+=|AN?Vj&o|elf5r{5EL|J@MWDUo99aF$OlSx>)%0mVl1F6 z{L$4@O&vSB!5nPEAwQ>+dTIDuKts9BUpnGG7;bbyxtWD=SHis;KQ0HKiXX#n$2*CY z;n(pl@J1E2%Y6^WHFc2YEC$LENPUZ?x|Jn^a+Nl-_Y?%`Dg=t<7e}*8xS;uHTy+8k z7BZJtR+XS8hET777Ikr~z7_MQ3{Z=&hlWsJ6{u~*#X!Hno69s*B80k+f$A*VJQQ6m zkf!3%lWZKvvid^KivS*v9%fsCfSis*HvnBMBQ8vo<7_HoF9sysDiS?er|Bq!bTl%p zqC@l?Q9$$ek$M98g};G{vL#@RA{L_fA?cTNe&HM9mNw`Q0Ul?doV?o3Kwsa=B2cgd z>NdDRpx(=z8R!>=8L+tv?(P9`GE1e6Np6EX2+Me1_J@>F89o0amwl<5grEoOJ&!OK z*th@;n@mZEUPRINfklocOFyuz_f<7LTBXnb#4TiOU8wrOqeom~OzL8-<^+tL(;nJ| z9*XZ;0C}}C6l8nZqTav{p^g+^MRwX_8zMn8;`{JE4Zqd+ZN~2!{O-f=Hv9@ul&n2rSFV;%HvR6=tIT>&v`zo0>yt78D@h#e6g$dzGzzs$(>krkfNXZ)~zr=J073j?&m3%sgZz zYEBvf%I~N=2L6cdrEi7h9RvLU;2%&ucIU>@oUt!JU=)?iJEEyIT1^h;y+8l$hOj@C zXy}xEfD6}OP+qBy?1voLpMKvv6t0g)c=`Wsf8zQxO%V98{kPuJQQ96LU0Aduev`7i zPq!k?h(3*to1Dbtxj3%A_pJLjPl>-(|2}l3>fiH0MB4@9Lq1?zQ=iM@4Y5xeU!B|> z-X*sg;Wj=rS^N+(^@<;4{?usXPTxl_SU%SXC`ZWJ?LoRoTyXx9l0L8E8cp1NeKSh) zjy#4|Z+94Xx#fftOpSA7%UwTP5*~I_=}K|A zhb}RS6>zd@ch55U{rDG`n#FaKUl`YRVAsqMWk1-Yd-Cb^W>b+#7skDN@#1iQi_UMh zNjLjF$41H^<%1Yg_4$EoSC7mujQhc?e8~U0UP7D;|GRx;`5@{P&R3?5iOdBgzc6mq z%H<>54{Cx+H{(a+jiJw!PvUlgdFH&)YuZmK-wngNDV1c69&>^Z!9$+=6kyx$UJ>GH zA0ik&CMMA0v4;qJ!`gjjkIpUIlA)|qXgg)BZTs=~a4wj?lH3VOF^(~xLB-Qmua6I> zNrKjvEOz>PF>qKNga3rs+ro6z!@09E(`Lobf|s)Au9HqQb16Nc_?JjY?m?bY8#BD6 z;+*_GMsgfI3Ya#j{6D|uN^@tm0z%pGr#b1Zx$EB^+ND1#3SNOC)f zvLCTbn-$K_CbyU~1`20RFgmUvL8lcYNWlGcubg?FsXJW;tBN?NsyG^CYi_b z*edV2U94rB~W{QjKbxtG7PW`Q1x1~OyJqAOU;&a)k4BCeTD$;;`7lx$2=7sAI5zrAi` z%U8$6Pcy@tfYg@>73#HR@P9n*K*Lt5fX}*%|*A~1r>^B>G%=XR@ z)~(OqE6^igr(JS+c)rfyMVC;p=OE+UtMfuzrGx9B@6vfP?!_0*Esgug!Nf5SGK{9D zr*4(G-$LyIOO%dmddM4t7ph8pYFp#EcCSCf3uy zeRYOhex#91ECO0xpPhwa4#JTGFgwrLbmB{tm|qYOOLlVPs1?BjhlEUrBeVFBA2v#= z5BaI>kztB!Q6Jm~$?eg!TqVT_EB9-wp{{QyIzr)i;Ea%iqrzTcMi_?09ZvEyaoi-S z4k*RdUTS616>MDe;c1aX-`9b#1;d;Or1vA;Af*yMA(C$*l1u?I)~d_M##3x~!?!?| zAYSI=iUjq=S)8Qmx*U*+9gWZlu}7#s3;1!F)nz5tYv(LNreqyyn1#srpmMM7qjgglYL zqDIJ$Zi9`f&l3g-@X38Zj78(pabKi(A$9e9qw~M%Svt1; zh_iI}v%+8q@H?LbQ$6*NRj!m6#qYyNiM{v_^W_ddgcoE;%=!Wff|?~iPB`ed}!!Vz(C+I{ODC34=<~C@kurv-~5>+KV2O zFr+(Cp4M$EJrMB_Rx`tl-{8Ze!xr#T8L-C8<$jQABE?G|8=^_!{9l3e|V1m z*T8=9T>p4OD1X$qR5)){8rVpB-gD1bGe0RS7uPDD{^B{IaJJRAEr=+JLdx{a2M-;g zuc*2qjwy>TF|@$qa3dS8B90$TT5i_VG#&Wp1nQS{cI?hc%k_Ywb?G_ZEVd6>pn)}%EMK6p<&`n2^&;N1@Y`D5Te?Nn>WW*s+wJ-?r6 z-2gwQGJrjnu=l{f9x`tt;?IGfe>RewelKQXz+wJ~pZX2;i;v^?5CV8py9akI+^6Ba z06zq=-^x3QH;rQL_#Ka*gCC3J;zxWj{U$xo`Q$qk*pY&Hgk_=oAZ-iq&%=BvF5jZ| zu)F;-gj|mh&$`2qS zAk9*oo+iwCxNEG-(pRTFYgd|-d_%&2CC^vn`Kml$m#169RAXz1VV1Zr8N-mP<@qUj z{!*TaT83YmWsZNz9%W^!NZX*b4l~j^>piuZM$h^yAnUB78Z(VG*3*s4ViN_%vwq#k zw5!JQStWSRY!+^t_j9t;Xq1CgR+ZcOLTX&mh z`4j1$K9P`@;5n%4>U$Fj|AR@4{V#bw&`y}`9iOm1KsG}7gB@(&QV2sgkWImZRn}Ha zATF`5vsa3*3Vq{4_9NOyMtmHQtHFsIE&BFq-zM?>Mf)xlUlQz1$jikyLHo9fZ>IKL zZ8^yADEL+a^1b5Y&BiOy@2>+zwbp=hjk6M3ANoAe2)>n|5?>3U+sawk4dNu2ZGTZ8X( z9524j61NMBmW;bxd>^ug#J2;!8tVbHGvn@p&xh}7@qG}!o2&<|>m~PxVoxCV4_o(3 z+;;`bBi08IXIVc5B`a~?_XFZp>@z%R-KK~@{=b>IM-uhQ@ zzb?-waH??e)?bRds+R8O1m;)JS{H9kNJx!5U%*udi?_Za;jhZ`b$MP7c;;Ax*oNA5 zcs`Nj^Er#pU#0m>NcdQJzK~$ZD-!;yJYSdR^#bqKFt!ztrWBpEw;*H|o+e~&?d=FT zT%O0vbBR1Jkmn|OUM z`5k$_EKfTl806U_&*SBJraU*x^G12@mgf`l{H8pAj%StiKk{s`YyQduJ6V3!}W*b`5)qbvY!0;je7Fu z59IlCdH!CW|0_?cfw3uhX64x?W^0MB6!C*V21;TEsTI-%htxR=QD+=kQrz}nET z%vWt!WfjLCgl@HE{GoVWgQsUbIi7u?^sKV}Hhw2cZBJk+(i2X{vvI;L2$?wHc08v| zV5$Gna&LW=6>BwIQDt4wM)-@{)&XW)8>!viM#wwlxvT9za6I-S^8AcIKGR0bD<_iv z^%IG0$E422D(n4|nD)f>MQ|@|XY421N%I%lN%Pm``Tch0^|SWV{Y+K5V^LFJwRPNQ zXR5k8PH)Ooo!D^(p69@QQY+?}*6oR_;~xahTea^6_-yNW?fXZ34QSBtHFKOSq;Pb7- zLvJg5+_|80%K;Tap~K$59+h#qB{4&-i?fWr&_14$L z_f_kqG!hHubrux2ZtRqOx5%37Pm_b#i+A9k|V-^I7h zy4e4!(`YR@P-wAk#`h-1Ss&BBk0IT7E77UqKIP}!@z#pacYxbsJ)wPH24tI6J4Hc$ zSA46q@2BGXjQ0J`pXvf~s)GE7-|Ze?O$~kjr@eQ9ldGum$E$94Pxrif-j5_bNhg^= zB1xS0V+oL+?s@mTduDn9N_wVyX1b@RyQjP7(GYdA2^bYMXmk-#!%JaB7FeH-+uo8&*#6H+;i*HLcfFIKgpAw%{rieFS91GPwH#fp(J*bzJcAB z#O}~HvQtUyUVSsm!6vPw@{oQP+ofVP*~j&}nLCO75Li5koz&ad;UsoSZ)bNUv0v*Q zEFVi4qG<<}cD7r^T-k2{yG_M>**S(zX2R|g<=mHDV(4Z;f$e9X%-&$=WzXOVBc**V zJE9w4+wuI5u&-rz8iv?G75h&1fMJ+DpTzbUMp+#;h$!cGvL_5|=)aGiSZbsn5CIa3#A-#R_w}b$b~E zEU0+02)jbXEIDV{UUr{~Z4y`xp4Cx(TLrdP#j0{XYuL-4QL&z!yA0!Ody~vLn{ym% z!b2){Am`f#A3LRDpU-*BFv;k*Oo--v$T`gptJu>yPa6De7k2(BtvvTh!wfquu>I`P z+}|5!+0t&Ab~dZpILD48u}h3mc25%PHpbbBBzCzGD=3>*?rP)JtUrl;*tm}clh|jB zA7qD;*zLys?DZt}4dXS;(xOp0X1tblB(WbF53zU>d%^fIcAJXTWdFwaN%lw*`vb7& zlbFtQn7x(63QV73#dcXkO}54KX||+duI!6UpJmUhm@oTM(+zC5L+0F<-EO*(-6k;D z3G;yI3yl8hgw47mGmY{`MhtN{$9n%*4>fu)nr;v-s7gP zC9#>j@0?caqpl-m|7-NsP)p zu40nUCz5GBc_-QNBsP=xlIeR%jLMx@QSJxHG%EL_Bu3?)N@7&*X?FS-Le^pqc!rry zNy&dB_wO>EVRaG{X+L58YTB~-?=pVE4z^Lw14w(!aFQLB82fIXG4nZgpNjn$*iYGU zfn95Qrzk(`U)d>%p(Jsz${tMv#kO^6ZNFg;b$1neOd`&r2@V9%)7%Oy75ui5J=_C`rNu$(T*+1n*2*{@lp ziWy7Wb+549Dppu}lD)#*Dppb2t$US4RBUT$OnH?ZQn6a3y~d8J*riB&jXkeoouz%c ze`jY@%vCxB%-St#7d>{Gxh2M~EIrOnvt9+hc^; z%YK)2hCQWXJ!OXM-?LpKlICbxLH1kXBN~+Rqh$kzcUavXS?);L9s}c*S4ixOWhb$# zu%u#ll(p-0{II~Vx-2`3wfj?Q+Cu^hzF(GmqAaE$=U!Rg&&poT*7MT>JHTE7l?=Y* zkvV@?wrt4Y^kz2k@~5)6!N?DKC6-x!jlslknv~d3`Ns^I{Ou{i_M6^U-mS~x)@hkG zEwBz1iwf*E728){lY^Xone*E6OLMY$JRq^d<(KD}`O=KUZY*ESDd0zEC3ai+134vp zcbKqGv%AY*%~``wL?re=`Py6yKNTbFdiH$zOs*U4 zAC=qB9$Isac?Y-Pu>{rd-8COKzn5Qu{aZ@=mo<->tNE^FnfAh(H_Q$EuKf~wWlc=hNu7ufCtlIFU#>+|sJ?4ZOhSo>MS#r&oZORRToZ{8*R z&>@M9t=$W(>LU_cTzggCZvNIsCHA*#@5*cCRUebs$JaiP*UqO^?1r_E=5_J9>tx!k zYhTE7@?D1|_D^dq`MvxG6?=T`CSb4JAk$8+eJp=~SA9-mude-B{t(}%VrSOAmha-v zsMw#@p3NWOdvBCE1M7+l-28-!eRQ3@V2tnmyiEJdy5WNNadwl$j;@<5xRO_?*jLv@ z3q1S^75m1zs|&n*S;da6yS8AO--fA^`r+iduNBPlw^i)Lb>A+C@Y}v9(@wAZQ9+cS zRDEiC2DErj1)JFHG<= zDmH7my6|e=@fAw@G&^ef`@#=$cBjnwJ%8BjUMm%l30UO^4l@HpZ zrv&zC_KxL03O~%BzngMiUtUtNq3C0rrg7*X?BFH9ngsSD|6Ijz!6*1WIgbe&d6?fI zG4^Q1MA2dXSX$ck{FIvZOhs?*_1uJMN6uAe*{67=#MrNq_9?EvM@tJ7eVSJ$v1rj} zxi^XJE4q>2Ca?phE7pIw=u7+|6`NXr9k6F)PE&M!o$e?_e*+D)HW z->18oTU6|(^+UkwRP689f2!yf?o_b{*8d$auZlgf{tHF7@_j1y^!i(X9Z|7U>$`Qg z@w-&)we>OOHvX8vKFgk3dq>gjT>o{d{j=;(>+di6GLNfR!-gk{zQVV{Nuo6WhF6RJ zp1+mEE-C&RkHBS;Y5wAW;HL#9*PHk9;`=G>fGOYl;i7x_b`@J^y$+Z|VAryp<}Vk2 zogbB$snzpvIY&$m7zv2R-SB@gg}D)y)~zvQ3znPZ|p z(eDrO{6`7)7=3k{|Hh zDmJ$1*TCEYJ75YR?T0*~V)ID*AwQ(%Tt#DAq?U*6QG zdy2oUVs~#E0#^K8!3+DwrZ-A{%nwUU^yZKGJxS~&`!Rn;Vy15M*3y6Br&Vl!*^bhu zx&FAMveUey^e5b+VlH!U?z6m3#dz^X*TG75!yh_EWeJ}F8$uwixFZt1AT29%o z_-Pe;3VzSa-1K8Xg}t!3H}_>;DKXQ(Z!RqRHNQhmJG*%uu%>^J^<`G}>3+?fDppuI z1Pq(r#0z_1LsQu+{3aD!S9w|4tNei^)>-x%e@4YBD~HPdoxh&Myk)0(&eM|0&dN~9 zZ}`?EX36;v?ohF&%23&V^1VsyLuIe?eJZxQ@*`z$@EelYjb*>(_o-M{3?=_Lt%jyjqf(c|4wah}cM>~Xo~7(dVxKS1RoG9d z+yNHea=1KS*(|PGTp3-IK(g zDKAw{sn|!hyii`I^;UxAq`t{0PNvuV`K{=kp zocfK*+evIrU#XNouho#yZ&B)!*a7{8%I=?2+J5%=E#+(8qZ|>K^h0(ij|uE$es|u^ zHC4)Cx$-vs5$m`bvxJx;yB_&*E3pGoH)2=Wy$BTI`(PnBVv$f;NJe^%rqsIZ4uh2Io0rfi}{P+@uJ zgmrAqd1Lf!OEUidjk1zYf{MzamrKAc^aN0Mv<{Ez0h*DUXi9F}0u#y^;(*GYWC3RBrnF{L! zhkw2!e25q-wKD(znV6LI z%g6Dt6wP!CDdW>)R)=-WQ%1c;6byh0PW+soT6)>PTw{7_dRS7>tPR!jmt(YXeQwS_ z7oXnVmGi>66jpP!y6oyat2s|El^#y7ousPi!0Ps{u4N@1sh{WapX#rblvkG3>Qz)Z zr>CZe|6IG$^Q;czc|9~Iy+y10WpydauV6fCyh#c&Mp5-Jy%gn?@ruyT^!Bc%qot-( zkl{ZECI9L9&*hD*2XU)sU$xK}`37JHI|lfdX=)f&iQN6WSCxyv;Z>Ze=aP0uwe^HK(^wtv~ar! zr&2ji%~8&DK3B`cn?`}C>DZr(Ny%#U=)>70K}Y(ZpAzA}*33Yv&*uM>MYnZx?5MthO>?V(jLQrIDfN^EoZ%{D`p?gY6qk|+eM!3BF`AVJu6>7 z#=n}ir1Nm1d<>t=X$NIG$>T?iPVeW1xyPA@UzYWPf%3d=m=p1Hyf(KET)jVcGUIxF zQ*I<<7kE3#9D+lKIFadK^xW8qm^+Z?soc8(Pv#!aNbpSa`*lC&w1@dqq#kFxu(SL7 zjGF|7pYmO1gRz)(oAZskn9E!W*lo5L6MV0^TbJOo=FP^NkpCj%ZKAH*L|wNrdJcV; z2ybI|=l#KSp9mBEEm_3*NoEriY&=#P&zj>0@CG);9Lp(&a3hDzR7dsu6Rl zv`+Vs;O8;+$I@d6lMEjcTsU@>>A^RQ?q})0(>g z3)b8VxMs~a05`4qR_-yrW6h(0&42?+$C?v>{cC=l`yk?<18fHT9^bR(-vCLM2p(q^ zK+=#0*36ix@4jxX6fq~%meT&DjlZ)-r{BnFANm--WbN*}@A0;^9eHDle{DF=#%I^g z=aJlq=G|*wFi`mJe1iWd;4bKMVeSL@$M{og9~Lq5c@Odz*FK&9AnN)R;#7lRF{?-T#JaBlDwYj}b3E75 z1WhZqY({vqT6dS@9^O zms#;#?tbN>ip@m{x{yU@#(#SYQS3I4!_hT@wQvK3Ad?-KD>h_F|L&j^h=BRDxD`r=M#+F4eu zTxET;m~@Zmf7n{5t5&WxL@ zn-$u5eO0v70{Uke!IOZpUnu4=lqx7CXaOXC9z$PP&}(lROO21gZWAO9ALC>D zgbXe0gO<%}-Vl`kOF*t8ngxKz*%}cx!6KGqn%Hi(gj1*vwie+I{9>62?+{f2_Ooq( zF18cU&1wO!U`>E~*(HEp)&e-q+5v;i2^e7mfN?egxWq08+{g9;E;C;y-U?zN!0Xr? z;9(XAJi-Ux`+{B3f};wBn2?^_D2bILSfJNe(7Xa>(H%hkQ5J$zdxeIc(=7hbm5TsN*DuCQfqL#YqmkImyAnZvpJ!K0qh`3Sd9K z3(&>y1$6Uo0A9hr1-O?#0_f$B15WcN0fYQUfD!%-V4VLHaEbpMa3B9A;4*&&@F4#U z;3587!0Y%Q01xxG0grH=Wnwq*48WUsc9z19>8{V($u7tH5j*is*`EP+vkw6dv0DJI zX0yspR+GB~ID;4YcjA2{JK#F@1GW?1$|1~rvvnwCXBz=8V(%cP7BO}n zGc73-0_t=W-YlS9hV@sQJZtO%-YVc`1Le8ZZ~%}OQh1|)HUT{X?icW80bdkwV-e-F z3Fr}UzkoLjcuc?-1?0torhse{g{=ba7I3eC%K{!1@DTw|3CK2!`~vP4aIb`w6u&GW zyHMm2aJPVa1zZ;JsDO_McuGLFRpb|Nw}5*ETo&-CfTsjx+eA(QcMHfF7EO>d$9pjJ zrr@2*N#!-=9i>orfzGY_u;DLfC3SKN|D0CDK75WOVEqtQzUkm?GI9POd(Njfl6?GQR7k{hxL~&M$rR3t0 z&Juq~tmLYakCr@H@`sX+(mka&mwvVM$m(18oV_&21*0G1pSUj`uo2`J)nkxZ&D=22Hf?~WCl~`qP zc(z&4>ulKFe8?1U1mOM1O^DeHYzsmcAhcC{m*FKdePr-;^I^c73`YRpHs1(n%p+JR z;Fi2EBK%$f8}e>JxU_gXR((I%KxLoZKrt_F_zal*_YF4!p4mY8e_-8N!q`KbD9^D? ze?<7nO+@owHc{#?1pM_Tr4;!^_#KfmV>3~^VskCxHE2P@7b>aj2P!ure5jK07`G7I zDc}{G_ak2Ne^$h>3$8Qao5c7tK^}C^g=9>)^pJ@je;hx2WI((b(1a@k@frA2a5o}` zu6$f3#1vrFV#1Y$m@ND$cylouG1++UE+1Epka;fRi%^dV@4uN5|7k!4J(-8pQowwC zcaA6?0aUOyDnR(Npp?)44v@1oD6Oz^0Y8ay9Pdq*A*K)z8o}2fTnxxr8DEERDIiMm z3MvK2VV^c2yjH*q_(sGu^Ua890_5ysz6IgG0pwVhU5M~5KEP9fX$R!2i`OFD3CLL=uSd8Skh3d!Bf{?o#10v6MtHA)iANZ{9 z2Hlr+59rS7CNlmu<6{|L%=k&ha~VI&_<6>Q8NbSSHRC@r-pF_-;~LZJ=(Jn1ewpRU zp31&D`#|=+Ik~w-xkI_$+-kGO{D8SN@5Vw)@y_DQi^q#UT>OdRrPAw4Unu>3>HEr- z%069ociFMB*UF6L?=SCOgUJZ*@0kGqfo%o6hcyA-%k;~s_mDY8AiWbx>t7j5$X%=k zn8Xbtl!{xK?=bch&Mn&}O5ke%;vXTB-k<(NVXAB40@|C?L4$SBULEvS>Uk#aS-7%s zl|q}!aFyd)gKI6W3i8U>4%pcppi+##4%A6 zR_~9i_wTCr@2U6itM?zM_aCYEAFKDL)%#D>`?Ko(r|SJ@>iq@v{tI!(ujneoH)B7> zeKY&MvYGt?*PFPEy3K4IuJ`KR$M$9Tan0abWZA}taNURNF*y@7wp}2q6w^L^KMTdOR1^)zU?F_|hYgkJ-9Q1iY zY*1#STp%>=5BlSY6fKQIhc7k}^+%}A)rt0SXwpA59~F$OPIg5-Uf--II+IFrd7@Lk zcw5vn>st(`C#d37ncAre1p-UT%H@xz%fHt*ACG#1gT7dJK8n^kJ)x<2&(!K3O6EvX z5={=Br?LIfuy=kUo?=&%f;}8bMEz6KX-Vz#es3z{IqXByzGyg-lssx2@yGlt6ey`) zJH5dm+Xcm#5Be@;!;b3F@d;a}Jv1GQ&-XV4CfQhreQv0#slOw`l767J_rT%#NrNL&^LvYWJa6Uo64q@rd(;I z&r4E9t|%^}&MCa>(upgtbogVDpeJDudSbDfhP~Bnb?hW!7g|TciMHzD&e^4w_P%&5 zF~MS0VVgfRA4&|j*oQ)HTirPGRQGxonxoOWj!xg6@li)tJqv{vx`(?4h8t#mP5xS2 zUtK?I4b9IoS0duGW56+oC+;Cc#P)dN6VpjdC_~gA3y1n5K4@|{Dl*Y1>5GVZd0XOAEE*wX<=%bHUrf`a|`u8K=G7GuqYPJT}xnImlciQ^SeA z1&4dQdvP&7G~PPE+#Ox53y~@3z({n_HaT3i;9D)H^N6l~b*^U82xwsIQY7k&k#$)W z(H;%YM_9bh?{BT{ndzVOt`w69k4h< z_4c;lR1fR4&&T57*&bLiUlhY;!S98hIXim?`aND$<_oHD#1ou{>2dglCG$iRduue) zX%7cg0~g!b?hE;%{s|^^NL6nVwCoZ!KE|B>SR9%nDu^)`nPk?|7m3!p{f*&>%bOS< z>U6l-M1NvD+|*m=9dL{{cw;>tw=6Q`i$g4ISAW#MfML)%8wvVmeWAE8U=Cm0;}6Cz zz1mjO(ArW{?by*$)7r43uC3Ow!&cSSxTDQp)mYQghK3JJL+2OjR@J>cGNaE zRkbwK*4V4t>Oa7)ymE-zJYkDQJ&DecKTgA8$bXgZ5=;=H%P8V6WUFpqwyBySmn}Lx z$yz4r+wHCqpN%<}IvPT)aa$7`^>hTMVxh@VHr&`Y*4b&RpJPK)d%Q8vw8O@_{UdFC zzL}{yw%}RX(>m+%M3`rAV6rdT;T&h~$wc3LOI@p*dA!~$HLIlBG?aiwVM0|c8OHCp zKjfpy%@+-ZCuV%wq`T8K9rby6DR?!zFR#x&t$QQvHM!MBR^pom^ktFUnA=9iJ_ z%&AuI+>BZ@WnE!LwTvqxQ`uw&SBTVyb0?4##{^by{UZ z5DG?4?3`%IrVcF%_Ri1Bs$+rx&~);)`FPk)o&y&+LY55^!FYmqC72xV;d2`y#ny)7D^mGhhC$xtperX_(n4oc+BP=j%u-7VKKOf-84H!&9%=*qp&%w2P)X>TZ*$z$$&6s{#xhUp2n`& zC~FHxXFYKt6?+)I8WTc_$truo7_{&)TS@CN`S|0^9;7zd!4cX%NftQ9T6|Of5Ztg3 zHon5Mvl!?wOd87!*r(vm9D6HiWS?5!bqYQcNY1BAH(X|H2n-NrzQqL z)Xy_%2v!zb8AoN(V=(^+mnMxh8L^Vc$~YpD9upHC*5;2w1VZnb)Zjs%?zX{V@ykpu~f3gpDZT&P7GUFy@-v;4xs25X2THfGPg!g6u|t}8k&eE zgrhf8d|k0wT=eo`&;M29CfExafZ1(}EnR3b9-AnYtM z(@l+ccm`{yh5~a#*msA20M{yx9-a)dw)xNmd;ri@qy1E$O@rFB zh>9`eX_(X)uj(&pd8L}moHDHCOPeFo=Z7@QpJw&XW7<<@T{YrKn;n)blv!VVI_yOs zEQDuZO;b~n=q}M_)l8{bI&HB;Xab8ij2x`k5Vm`ywPF)mpf%)Gb7SVAd=YJiSC`5u zr=HnbT5TSCVz8b~*7S||8mF84rJcL-O41BCGQvJFM@#QK)?xN(Pm~Rb`Ga%=$$$-Y zLQ!y|WdX*07>0QmmTD57hnGQeb`NZCCw2m>1sd4?XG)$7P^hC}q?I?}Md<`=T=0 z2`62oX#p*v6U%hp6wIQgILsFEvc4!N(`-O9<+!MstY+VQyl-+)_&1OarZ2CSKIESY zdE)ala6F+nmF)E``l60;%^%P*3Wp&aZ^Q5seMUq`@eZ3urO@|2<^dY zuEj@KA7=KrPr5Fx(P%iT7DD$5nZYX&yby!vCdp2!lVqBJWPv+lQamDpx=D*r<<%!$ ziDa@$RgLbGeI;oMQvlVKn@wb$!}IW=q~-}n#v_)PLYksh3WZRo>ZZvylDQR*R&QAK zMAFK#x#sEk@Sd2XPgrMs?h>4dYtu%0ee*! z3)!2UU6YRan1c-m<4#}0Xh&Bqn+Z9cQ{FavpotAm&o~$R>ZX=PSgf(i+0xbKakjIi z(HZA#SEHjY$YSoev)UJ_i%c};Bv@9d1SXrk5G z+OW_b?P9%)PG|3;ZM3nGjV#tV!{ZCX{%)2S9&&b$F3r?TvYxhjr)#W^hJRwo>Fn+v zS&Y;$N0r^_nQ8Hi^`kbsbD$~gb~)H$>yUH7@0sp#v#@)?*ZgM)77uj_CIT`jZ2-K@(# z;`D-@fhsmU;&AS9Hq|=ESo`9%vo99v@3x~m+MFZvgG=Eh)*XmB?T(I~svvXsx$LtY zz9A25sp@gp^mWhgX=DBIdiSusIpV8l-r=x2F*MQK(ZyybtKHF>=GM7(=5UX?2S)>) z6YZ>h#^s*zH8&^5SVx`Ry)@YDZ*Z|;-@N;(##z_mFmr+W3(=`+ zMwDr6z?mZFM6;1tH^#vLt(a|cJpyAT6XC^)K+2(_g8ku`U+h~uqHWcQs;Su^^F*d& z?luqlF*INskH_pbI}5@-2yY*DHMJKcszyvqECGer_=H>aICvA&OE|STAz`|#K95Vo|8pZV8CpQk6qjh#P(d23xXYsZLXZv`xy0wih z&5Sv!I+nt7U~92|kA0}Oo5ib|YigSpX4;wC@0`G3vb)(-{m5{~q%>+V<~KHJa}K(?+iDic zv7k8t+91>mH}&xVR>$BUGm|T4`#un)5o?xw=gY({zcwG(a?6WP+G&OWIu%33?Rc+OM-cII<1l)Db zS+9SF#iD+wPV;v2IeNB)vSGb!9C=eipOckD3Sn!@C4oM zWAlmnb~falcQ-_P$0I|`*IDHbPB?;Nc4oJS-D8fXk?t8eWyIvv+2aq%5dud`%(O$U zIk&ggRp)JDp2-GxTf$b?gpGL*v=MnR=rLso-JEfS-FssG=7vr-;2m~Pjs^y&9BgtV z;qI$;&a?*DTy2NjHZ(Xo+rUE8-R_ZCzpt@I?LS$$XLivY@2_{a^|P_aq}yIU(mCg2 z{ZLH6R+qcVIo9PzYklz+KYXGw@0h#UQRjjQbSE6{dT%J~8e!di9qz8iVb{=vE8a(Lt-rUMUPWD<7jvG+h}L)Za;3n>ctgF!!blQp~>YrW(OOCYh@Dz~qr zdaOOVxG&u0o=UX#%wsFM#p7Od)sOadvA(GhcT3k~M|UqvEcCi(YcZ3~ zG3R)PySl5r%QeBKho{{!*Gyz#QV=8kjiBe8Al?6dw+Gn4`PbJ>A?K zh)gh7G$A#jb!y5zIy5`mxQ7i-jk+DNJ0$Ula?7yD(Bc{N$Er0&Qqjd`4HOpaF0zog7d7m${BD@ z$J_d_`s#280^x!7Kp$I5v;_Q#y6!!VY_e`DFt;ZNG3R`Bb@P~yjWy3;*WA}-YZ0SGuqGBXF$|*=CI^*9n9_!l)re81J=Bsr zr%8;`l9ri@B@s%2Hvw)aT84b72S%g*pm)L(^}51x1&W8nSh$2(Yiz=UM+1XCJki5y z1}m5ttxISk!Fn&b^O1V=L>sko_fIIss(*f`G8G`KPl80i?j{}dZMl2D$FOs zL1>wWoZ*G3N=>c>X6eysY`zUeNWd;EwGrbKPp;r+!7-#95*J;kP8>FPbFi!0Mi?rsd?P<3FcdD!otWRudU>x<*_BCzp3(*NxqCSEnBewu(EF=+1!CF*gA8G0cj5s^J^Ydby zcBXG$$N`jE1*;=U8bcyN+hm=g2|RRzRj1r5BD#C$0`VSyaK4@O&o2b*dz`)+hgyr8 zQDC%%PY(`=fEFg|F3*&RqdT4g;9;0(SF#ye6ISrtsd_3aI6H_Y>o2sT z-{X&BpO`8`6!Wx<1!Daj^{qbEJ>L}=4|jH8op!#dAhjusr^@NiUNn0^JH4=0Vk1@< zSW!vR)C$EEn5vz`0?AUySOh>kOw?52N>hyVpb=6X1`4T8FlAF+ZHm<# zX>~BCaU*JMbJYjJ!D)IfG-2Bl=$!4C@pr%oLlyp#5MUj5PLZ~#kDRn^7i}9QHGt&` zA-RK?O85&RCh8hx?T*R8GeJ{cRDA{{?`&qmA8>W{IIy)f5NHo9%;EWv2MQ9A`)qJr zx_ScL!_^~hO!|Wf`0~EU>sappxQK*+PKuc0|G(9)3TO{82TsGDbe zD$tP_3w718_P}Iddf4ZhZDk8EE~7Qg)vZmed!{Min5vnw1=v;N&OrbCm@gn#wA%i@ z7&vnJSP@lODL1mKmKv(6s;WKpJF1zxDiEmgwe}2qS+LdLnwSX$oUv9n-Z@y2xvIJr znODcvtcsgNKH9Vrht76R?ZoD`JljO05huTVQE^NHs*-l{NPUtapGl++6VSQ1iB1;6 z&k}m@T7w_2Iat{&er_TT+zQO2hT`}sf(YWRI21@Z=5a_qj?lP>8$vjW(s(+HxG=(2 z)`Ajoq+U)xSLka)st+{~{Sc^99ac7hPzZnFG+LPbkT!w*lcEfjh~uZ{{D`A6REt&c zDmfs2u4YyEIRutXX!qltLAS zky8^3y&ysC??j9huRT!ZRDDP^<=iq@Wr&iZVH*`=~?zM(`)}W%=`@3w{wTWj}*V)CW@a zq`F?Hs@H{xKUeWn6%h+GAf)oI91zqQa$sr0W~Iv8gh)N2W{o1d@~`1dD*pKgQV3lY z#Gl5~tPm}AFAY;R<3M^u3?v$cq^nlE|03JJfLvjuQM{%OG|*DoPhw1$45f}Be-KoC zLdcX#igO`jopLNnt@Q}?A!Q`xptuPkH5?29E;}r)j@?xXWB+TUB8?gGA#0(2nM57) zq92%TFs)8mqcj>MDLF!=rNA*aAuFPjGBZ2zLzvXt=_XT?Te{w8J!!=o73r-cp5~!R zWICgGcklcrHC0+_RxO6~CH3FB(GPNWkQg z?Ac{2%_(Us^}d`x(&ra#G~)0JsI`L@O{i@7tDR!vkqnXSG%F{EKm^R4&j?8?tTp4@ zp|BlPv@}iqR+}d|X;A}dfKLggR}Q+B>c}p0sY5SamB}-ZJt=kLT+>396~{y&GB!o! zWv+8MwhGZw`^l1%nVm+620i&2e@+VNd~8)s@iqwc)_1oAM4A+n`k90&rw!?{5DPTw z$u5(tp$SuV&;Jt}l{UdFuGAb^=gQfIxFH@%4@oh!E=_NCYNXR3OBW#NzU-P<`e?s= z4DFoDsAZq#5!{A&~KrSWqT?~-Mx}1mDSg+_y zQW`R2a@I_@_|kBZG^uaN?vuNdo;zK(sW}X$hK75(S=Tf;eUNJe&rd`e^VKFPH5sPd zz$vgswvf`uU&2I*g$OlDvb@sJXhtf%spsQG>xz^blHZmpCF5ygW|`VJW}d&MGKhXnte>~Gp$UFG26K{^r3DVEK#wFSvg=e`KG%B z%(il%pIcs9a%qUpuZRpk#qu7!yGXATUxs(Wt{|v`t6|tl@fWwDUE2Cdn`X43Cbh_q zUP+k`TE9srfZ^fcyOq`K@5*J7bb_Q4$J)=85m61QdO(JT+DLN*^%@OVnpfn+o9-ww zqmA`qiNg$iY>?hlGq}mI>7#jS31!Zm!Kx7_M?(!l)T1OnX%MJ?QZ6HD98Ke7)|ep| zueh-SDXBD#-$Jc;H)oG+>_r=9!QV>P^b&9&DYEKwrID^#=hYDW?%>x$x4iuIOZtxf z;ceqZA3tMc%SY)=Tm3S_i=X&(7QOQad~nEC&Ghnx!GBhEH-6{}+ed~Pf7<-Z(%;s>|e*X zu%3$yH{Nyav5%C!+IHri=?{LR-tn{Q;;}Qo>iSSOi#W%|i+=3-!q~v}nxAc%dH=0% z#lP^@v6|M5SHD|(^&^YVzVLw!_icUllO}pYmj0RcoxbMTZ+|8634@jM0({hj8SC*| zvei4Q&;Uf4krOZ7;vZqA_4Kx*ODP?VdLlSWmE0!68SwC8jO(n7Tkx`~GmNu(R@$$j zBU0AMaMZf9x7CI2<3tJ{;@?hqKOR=mfrIVpIkZdYcWk#~<7EU#XluFG7U zd})kMOXF=K+NvogDprk*l^(nM;+r_DklQF$+B*{*?6CIRomM>Q7B9o#Hz=8c6X*EO z0lTR6Di`+QV%DMXWPA~ij;vc37Z-Q>u8LFaj+h#|b0RzoW4DvDZ7OY1b+Uri5Dskk ztmo7N&THvOvUqpQ`skh3Rh8|q;!vYiq{pn-@WH!!*dk-w@LPMe<6fW7GcmEFx?#L| zN8Ln&cgHxM8SSW`Sh2~l)~j}5t-yMO-<>mD3-^ zm6Hk;S}4v!e-vjCaaM}6(jUcHMO-DtRni~DRYG&jxN@i=IR+~$Ts{KSgiFC?p=b+! zQlxPCdXacN-tk7bVEHpjg~4c5xT$dYpdO8~WCAH%zM*jW=6qDJd3Y3*h-VAEUNUUcVb&9Dl$7n^V z8w!I+7xxNtwiS&)Ooh3^oFh1rVI|wB2X7*qRxF|xizJI7lq@E~3hDzrvmT<1m_#ke zktHfYfC^HpMNkovzCrR-VV1-xkEDV25Ni~nh@QDY&^rV)3%n6)hYE9KoQaB4Z;&uW zxkEy(LaK-UtW>FEEszd~miRjQYX|Ne&94m`I9{H-$;@P)5KAB1~o? z2GLt`jRs;a2Mnk^LH7!a5rq+JCha7sa5F)(*_>lEpsstt=KU20=thMh*O*~8nJNs3 zEnI$3lrR)jaXl&6dZy{j9^+bS8tDtFc(|l+PC@u#@&Edg!sTNS(Zfnf;Z~GUOIp@5 z6oGm@3`wQwm!@Y0yTCT|Oj*w|t(>S9EI*!|56cXCKL8yA2GjDxz+_z+lDa+vMA2*sYNMrmO+E(?qHyYV3>7G_1cEb>rc@|Og-NN%R4Q_mid=m~!SV|PUc}&_`%5I> zB7DC9Rgw@4`CQr$1g%Ig0V+idOcVh#T6NrFsSxI&h-FuCE=v^#bE}9QqCkqCSH*QH ztiqh16tX%D%yJm0d2-SyMbaoi_tPkek&s47j*P4%6Xu*G4^VD%k_9m;Ku7Infhw7p zLuBo#x+~17x@ml*NGmC7V&Iri6BNd*WQrk!M(4;V8ZoGNJ%0EaBSwV8h}mj13l&Lf z71ZTnOG)8&2yBJMpc^Pow-t7EE6Xu#6()5nOzKvc)UC{dpWejmjxl7mLXY1N!NTR= zS?jyVy350r05F) zKzNs;V@d{7G8ulSS;0hxZ*t%x9C=K^CpQY2;$_O8K7@R`aXD~x;Bx8DLfgOJE%V`s50JKNA37_+zLLNe z0{BXU_(4y~SK!PbgHqrWQZH~8&I-J67C(wv;8ZfqG(vAC@F0Qv3EWHIh?0?$A%+yD zZA|7B2Az?UF-0HUY%Z*V)iM?K!+KejtgM3l227R)J>v>Y7djjh5aoj#XUsrXEerEO z!;Uo8^f$(0inPFxnZ!RJ$r-GqV1Fiu1xn3vGDgA-^@c{7iWn-*lIHawnNduH6{@*C zXac)RzBL1E9E4Rut%{PbR2xfBA)=HDi!hHW18RNy@qwP?)GAD$2_k?+vt(nkF5F)L zLsG;sfg>d23Q$DGQOFFHq&eSYHRdQL1yo`94pK!5_)aHYXv~&efgSK-!R0tg7LUxvl6(o8W85R-!Dne;{l<%|ZRV4_KgZUx3ns?b!pzl7rPAG46o1YkCi ztjq=bE2xY}gl$9?cneh0it;q-i;UTXQk;+x%?Bb8wm~U0W&vA~2KN9{1KCBG-~>0-KF_pr#g9TOdkG$%=CL*oaaXA>kY;H)bm) zi&D^07%2==%_5toYHEL2a$y0G1|BWor^1+B;IJrWlNmj^k)tw#_Sfh!bEDWK!wpv)3)eM+>-*5@<%rsjd~~%2*ABSd*JqQI3oyESPG2L#Umz5;a(bz z^rubX%SsU@@3h%lOxwZ*SQg_khg?dyJ#cF*f(j%Jw*%_qT`^hUt4J{27*HY+Kgeg! zw;E6k^-3Np%rR(lm^a;MQy|H^O4Uc{w5=Sl7l1MoE^@s1#O#fSg|r z<{9-;3SvA~K#1@erG3(8K=w*S7G?+GSBUx5lxY;60Ay&E)=wLt#Jq_H7SGBhu9xoz zkFaUVW@DDHZ^BDM<6vq;9UrLhuD#LgPs3NZ3Hpzrrlv56zH#E?bsAWVBTn44{PW z&&&k19MH=JMUXULa1dER8H0fWB3zM`B`T4+tyYDq)9W;tK(iv#RFPw<$Td~KiXjH2 zp>rt?g<0b8YB|p&kSj+3dHcj6=wqn>L&X4qn5-BdFK9k5Y?a7`B>`D|vx!Qf1{5S( z83c@Ial!t2Q4k3a;#W;G&|HJTh*;#s+>wFHm}@n0h=Bef0e**Om$B^AnJjQ0VP8l~ zFwXEFcGgPPPobVuq*=tok_8)x$b3`20$m`>^q|BFjSvLTd&wm+nc(qY8Av@(gA~|< zD1lK68KHwkF|1F~(a4VlkjR0?Q36U{V!S{I6TG6!NWdgv60sDp8NEj9gDio+ARsy) zHIss)wVAkb{7dhG8!QvF-NCEQY>L} zD$GT4yiium0K%tM#Jo?g5-;4}T)6*|b&MwFBc!#IX+_>5V~*LZh-KEYG*g%%j7GG} zT(Ao{O&Ktf=KLIdZxS5?KkPzdxyb<8W@Z`;WQ5VV_-{6!Yk}eZag~LF{jD^!i<+gE zqZFeBt9Z%BNT%fi`W7{VDTsxnMTW<0DR5+3_2OQzI|~+oVhVOEdx$!-v4Ehr}-v4quZ!69=#r#wjNgsUw~ zY%(8|^kMSeX;$k&DmX@ru(7mGqnV5n#6EESdFVl^hF zLttrn;cq1KV&ot%2^M+jMi@Doy9qd?pfS^6taf0U73S4K!ibomXV@3Oe+I$kBX9wx z)wqI=#)LggVlC*PWHUsDX+8+m7Tp5rDLMe1j@r3@SO*!x(Geqxngkv&eVb9-NZcWe zh5G(slMb`aR>=TLoA4=Xok<641KWq{bKyhjP=$d86OlAqU_h}vN0^(j7B|8^!pNA> ziF!px^&z{7C=oLTGv*xwh#BB)z0X7qCE;bM0kR@$&|*w;XrbOhuQ1?DMlMGeY0WOG z8j&a?#vK;mYN{~m%fi=QCK2y4p-m!>0bf+y@5!Q^h5LQz?QBS$BtT(sZjl=e7X4NP zC+Ure5VQVbZ zF~V%aw-R#3Ad#z*gJNe6tH`9a6QVnsffYE05#%a`mkMEGk#U(&eG+VDG9sB|P?Io? z%e;nUQjVIGqb21elXBIh+-$WJO>)w(B9s=HWF|LRmfTWe2JV8aQNemyhRE<~R9;Mr z#4vW9>`ViJMu<|lY zq+sW1sCVSt1^tat=$F~IVn(QSZ`E9xF)_!~z!asrY$1Z%mAau!iW22{j zqQ0TJxwg(%)zrLsb!GCSdRm=BzPJLWw153J2mL3W|LfvUZMgHoC;t0%?C9+6Kf3pc z-?q-z_SfCs^{BgY`P4OAfB3tv8=lq;J)ygdB;PrCm*D>`hubn$k6X!9GFy!i2%3+t}?=Gk9f z`_Koz7{2cxU%Te|tuKG-g`4G6kJDVEujsutfrcp}!_dyCrqEA*yF*AZ=yGRKJg7a z%KxV=%({DJ!T-hnM5JyyL3CZ_MK9c#)8Kf+(K0wW9kTY6Z(6v&wxF;!XEuwFFW$V(V_CT0u!g(; zN16U#;{QVukYD1)2I%t23glOA@lbeGJmu@aa(0vG} z?)q>@03TifY(tp-J*@xFcj%!w9%bT6-EmAp{3DX-MwB8QxHzcNkpX#J0bgdo0RTES zL;ornm2!y#1$1~J{k*Hl!0yrSqjd;)qJs^7all|TCAY{^g+u7_uMS5qAOp0w9A)jO zZ5DrYJOOXzAl``>I+;a>U$i4esRPj3p`M*+eJi+QI2f@#Nrz51g>ZPT7k8pb|618q z$S04}(1C~{geOrKoimr`thORde>+j)Dx_$6cYvRjdFc!J#P2rH_2SVCPfE^#vUHG| z5E=v>$SC;=c00IG(NJfk3K`5#&!DL8rFW zgXbz-)#A=@{?m;7)Qj>65*@a{JKKO#8tJ_BgWDj=;mr&DG5i_>eUsXWI;KP^>X`^+ zNiCYfv5`3Ptd6td_loFj=)1|7T1mW6ucjzce^4KgB%-4AUTPMkMD3u1LR2?t7M0bw zC+ywZ6UWgYuD;LURgc}IByn!PX!>-%9%@DXl8bliuR76RboP=C-^-&u=l0sUebyw( p*dPOv0-b!8Co$ixoDkIig8y$wAj0uAd&UmqwATOr`~RN={x7X>#-jiL literal 0 HcmV?d00001 diff --git a/Caching/FileDbCache/FileDb/FileDb.XML b/Caching/FileDb/FileDbPcl.xml similarity index 83% rename from Caching/FileDbCache/FileDb/FileDb.XML rename to Caching/FileDb/FileDbPcl.xml index 47c4a542..3c65bc3d 100644 --- a/Caching/FileDbCache/FileDb/FileDb.XML +++ b/Caching/FileDb/FileDbPcl.xml @@ -1,7 +1,7 @@ - FileDb + FileDbPcl @@ -10,6 +10,495 @@ access to the class objects must be syncronised by the calling application. + + + + Constructor for FileDb + + + + + + ToString override - returns the DB filename or a string indicating its a memory DB + + + + + + + Open with an existing database stream + + The database stream to use - normally a FileStream + + + + + Close an open database. + + + + + + Create a new database using the passed stream, or if null and in-memory DB + + The stream to use or null to create a memory DB + Array of Fields for the new database. + + + + + Create a new database using the passed stream, or if null and in-memory DB + + The stream to use or null to create a memory DB + List of Fields for the new database. + + + + + Start a transaction - a backup of the whole database file is made until the transaction is completed. + Be sure to call either CommitTrans or RollbackTrans so the backup can be disposed + + + + + + Commit the changes since the transaction was begun + + + + + + Roll back the changes since the transaction was begun + + + + + + Add a new record to the database using the name-value pairs in the FieldValues object. + Note that not all fields must be represented. Missing fields will be set to default + values (0, empty or null). Note that only Array datatypes can NULL. + + The name-value pairs to add. + The volatile index of the newly added record. + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpression representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified fields. + + + + + Get all records matching the search expression in the indicated order, if any. + + Represents a single search expression, such as ID = 3 + The list of fields to return or null for all fields + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex. + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A FilterExpressionGroup representing the desired filter. + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A FilterExpression representing the desired filter. + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields in the specified order + + + + + Get all records matching the FilterExpressionGroup in the indicated order, if any. + + Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + A new Table with the requested Records + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A new Table with the requested Records and Fields + + + + + Return a Table of Records filtered by the filter parameter. Only the specified Fields + will be in the Table. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order. + A new Table with the requested Records and Fields ordered by the specified list + + + + + Return a Table of Records filtered by the filter parameter. + + A string representing the desired filter, eg. LastName = 'Fuller' + The desired fields to be in the returned Table + If true, an additional Field named "index" will be returned + which is the ordinal index of the Record in the database, which can be used in + GetRecordByIndex and UpdateRecordByIndex + A list of one or more fields to order the returned table by, + or null for default order. If an orderByField is prefixed with "!", that field will sorted + in reverse order + A new Table with the requested Records and Fields + + + + + Return all records in the database (table). + + A table containing all Records and Fields. + + + + + Return all records in the database (table). + + The list of Fields to return or null for all Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all rows. + + + + + Return all records in the database (table). + + Specify whether to include the Record index as one of the Fields + A table containing all rows. + + + + + Return all records in the database (table). + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A list of one or more fields to order the returned table by, + or null for default order + A table containing all Records and the specified Fields. + + + + + Sometimes you may need to get an empty table just for the field definitions. + Use this method because its much more efficient than using a contrived filter + which is designed to return no results. + + An empty table containing all fields + + + + + Returns a single Record object at the current location. Meant to be used ONLY in conjunction + with the MoveFirst/MoveNext methods. + + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + + Returns a single Record object specified by the index. + + The index of the record to return. This value can be obtained from + Record returning queries by specifying true for the includeIndex parameter. + The list of fields to return or null for all fields + A Record object or null + + + + + Returns a single Record object specified by the primary key value or record number. + + The primary key value. For databases without a primary key, + 'key' is the zero-based record number in the table. + The list of fields to return or null for all fields + Specify whether to include the record index as one of the Fields + A Record object or null + + + + + Update the record at the indicated index. To get the index, you would need to first + get a record from the database then use the index field from it. The index is only + valid until a database operation which would invalidate it, such as adding/deleting + a record, or changing the value of a primary key. + + The record values to update + The index of the record to update + + + + + Update the record with the indicated primary key value. + + The record values to update + The primary key value of the record to update + + + + + Update all records which match the search criteria using the values in record. + + The search expression, e.g. ID = 100 + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the compound search criteria using the values in record. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Update all records which match the filter expression using the values in record. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + A list of name-value pairs to use to update the matching records + The number of records which were updated. + + + + + Delete all records in the database + + The number of records deleted + + + + + Delete the record at the specified index. You would normally get the index from a previous + query. This index is only valid until a record has been deleted. + + The zero-based index of the record to delete. + true if the record was deleted, false otherwise + + + + + Delete the record with the specified primary key value + + The primary key value of the record to delete + true if the record was deleted, false otherwise + + + + + Delete all records which match the search criteria. + + The search expression, e.g. ID = 100 + The number of records deleted + + + + + Delete all records which match the compound search criteria. + + The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" + The number of records deleted + + + + + Delete all records which match the filter criteria. + + The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". + This filter string will be parsed using FilterExpressionGroup.Parse. + The number of records deleted + + + + + Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + + + + + + Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord + + + + + Call this to remove deleted records from the file (compact). + + + + + + Call this to write the index and flush the stream buffer to disk. + Flushing will be done automatically if AutoFlush is On (and only writes the index + if necessary), whereas this call always writes the index. + You can use this to periodically write everything to disk rather than each time + as with AutoFlush. Flush is always called when the file is closed, however in that + case the index is only written if AutoFlush is set to Off. + + + + + + Call this method to reindex the database if your index file should be deleted or corrupted. + + + + + + Add the specified Field to the database. + + The new Field to add + A default value to use for the values of existing records for the new Field + + + + + Add the specified Field to the database. + + The new Fields to add + Default values to use for the values of existing records for the new Fields. + Can be null but if not then you must provide a value for each field in the Fields array + + + + + Delete the specified Field from the database. + + The name of the Field to delete + + + + + Delete the specified Fields from the database. + + The Fields to delete + + + + + Rename the specified Field. + + The name of the Field to rename + + + + + Allows you to set an encryption key after the database has been opened. You must set + the encryption key before reading or writing to the database. Encryption is "all or nothing", + meaning all records are either encrypted or not. + + A string value to use as the encryption key + + + + + Encrypt a string value. + Not syncronized. + + The key to use for encryption + The value to encrypt + The encrypted value as a string + + + + + Decrypt a string value. + Not syncronized. + + The key to use for decryption + The value to decrypt + The decrypted value as a string + @@ -223,422 +712,48 @@ A new object or null - + - Open the indicated database file. + Static event fired when a record has been updated. - The filename of the database file to open. It can be a fully qualified - path or, if no path is specified the current folder will be used. - - + - Open the indicated database file for encryption. Encryption is "all or nothing", - meaning all records are either encrypted or not. + Static event fired when a record has been inserted. - The filename of the database file to open. It can be a fully qualified - path or, if no path is specified the current folder will be used. - A string value to use as the encryption key - - + - Close an open database. + Static event fired when a record has been deleted. + + + + + Fired when a record has been updated. - + - Create a new database file. If the file exists, it will be overwritten. - - The full pathname of the file. - Array of Fields for the new database. - - - - - Delete an existing database. - - The pathname of the file to delete. - - - - - Add a new record to the database using the name-value pairs in the FieldValues object. - Note that not all fields must be represented. Missing fields will be set to default - values (0, empty or null). Note that only Array datatypes can NULL. - - The name-value pairs to add. - The volatile index of the newly added record. - - - - - Return a Table of Records filtered by the filter parameter. - - A FilterExpression representing the desired filter. - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields ordered by the specified fields. - - - - - Get all records matching the search expression in the indicated order, if any. - - Represents a single search expression, such as ID = 3 - The list of fields to return or null for all fields - If true, an additional Field named "index" will be returned - which is the ordinal index of the Record in the database, which can be used in - GetRecordByIndex and UpdateRecordByIndex. - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. - - A FilterExpressionGroup representing the desired filter. - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A FilterExpression representing the desired filter. - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields in the specified order - - - - - Get all records matching the FilterExpressionGroup in the indicated order, if any. - - Represents a compound search expression, such as FirstName = "John" AND LastName = "Smith" - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. - - A string representing the desired filter, eg. LastName = 'Fuller' - A new Table with the requested Records - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - A new Table with the requested Records and Fields - - - - - Return a Table of Records filtered by the filter parameter. Only the specified Fields - will be in the Table. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order. - A new Table with the requested Records and Fields ordered by the specified list - - - - - Return a Table of Records filtered by the filter parameter. - - A string representing the desired filter, eg. LastName = 'Fuller' - The desired fields to be in the returned Table - If true, an additional Field named "index" will be returned - which is the ordinal index of the Record in the database, which can be used in - GetRecordByIndex and UpdateRecordByIndex - A list of one or more fields to order the returned table by, - or null for default order. If an orderByField is prefixed with "!", that field will sorted - in reverse order - A new Table with the requested Records and Fields - - - - - Return all records in the database (table). - - A table containing all Records and Fields. - - - - - Return all records in the database (table). - - The list of Fields to return or null for all Fields - A table containing all rows. - - - - - Return all records in the database (table). - - The list of fields to return or null for all Fields - A list of one or more fields to order the returned table by, - or null for default order - A table containing all rows. - - - - - Return all records in the database (table). - - Specify whether to include the Record index as one of the Fields - A table containing all rows. - - - - - Return all records in the database (table). - - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A list of one or more fields to order the returned table by, - or null for default order - A table containing all Records and the specified Fields. - - - - - Returns a single Record object at the current location. Meant to be used ONLY in conjunction - with the MoveFirst/MoveNext methods. - - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A Record object or null - - - - - - Returns a single Record object specified by the index. - - The index of the record to return. This value can be obtained from - Record returning queries by specifying true for the includeIndex parameter. - The list of fields to return or null for all fields - A Record object or null - - - - - Returns a single Record object specified by the primary key value or record number. - - The primary key value. For databases without a primary key, - 'key' is the zero-based record number in the table. - The list of fields to return or null for all fields - Specify whether to include the record index as one of the Fields - A Record object or null - - - - - Update the record at the indicated index. To get the index, you would need to first - get a record from the database then use the index field from it. The index is only - valid until a database operation which would invalidate it, such as adding/deleting - a record, or changing the value of a primary key. - - The record values to update - The index of the record to update - - - - - Update the record with the indicated primary key value. - - The record values to update - The primary key value of the record to update - - - - - Update all records which match the search criteria using the values in record. - - The search expression, e.g. ID = 100 - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Update all records which match the compound search criteria using the values in record. - - The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Update all records which match the filter expression using the values in record. - - The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". - This filter string will be parsed using FilterExpressionGroup.Parse. - A list of name-value pairs to use to update the matching records - The number of records which were updated. - - - - - Delete the record at the specified index. You would normally get the index from a previous - query. This index is only valid until a record has been deleted. - - The zero-based index of the record to delete. - true if the record was deleted, false otherwise - - - - - Delete the record with the specified primary key value - - The primary key value of the record to delete - true if the record was deleted, false otherwise - - - - - Delete all records which match the search criteria. - - The search expression, e.g. ID = 100 - The number of records deleted - - - - - Delete all records which match the compound search criteria. - - The compound search expression, e.g. FirstName = "John" AND LastName = "Smith" - The number of records deleted - - - - - Delete all records which match the filter criteria. - - The filter to use, eg. "~LastName = 'peacock' OR ~FirstName = 'nancy'". - This filter string will be parsed using FilterExpressionGroup.Parse. - The number of records deleted - - - - - Move to the first record in the index. Use this in conjunction with MoveNext and GetCurrentRecord + Fired when a record has been inserted. - + - Move to the next record in the index. Use this in conjunction with MoveFirst and GetCurrentRecord - - - - - Call this to remove deleted records from the file (compact). + Fired when a record has been deleted. - + - Call this to write the index and flush the stream buffer to disk. - Flushing will be done automatically if AutoFlush is On (and only writes the index - if necessary), whereas this call always writes the index. - You can use this to periodically write everything to disk rather than each time - as with AutoFlush. Flush is always called when the file is closed, however in that - case the index is only written if AutoFlush is set to Off. + The full filename of the DB file - - - - Call this method to reindex the database if your index file should be deleted or corrupted. - - - - - - Allows you to set an encryption key after the database has been opened. You must set - the encryption key before reading or writing to the database. Encryption is "all or nothing", - meaning all records are either encrypted or not. - - A string value to use as the encryption key - - - - - Encrypt a string value. - Not syncronized. - - The key to use for encryption - The value to encrypt - The encrypted value as a string - - - - - Decrypt a string value. - Not syncronized. - - The key to use for decryption - The value to decrypt - The decrypted value as a string - - A string value which can be used to keep track of the database version for changes + A value which can be used to keep track of the database version for changes @@ -700,6 +815,51 @@ DataTypes: String, Byte, Int, UInt, Float, Double, Bool, DateTime and also Byte[] + + + + Handler for static DbRecordUpdated event. + + The name of the updated database + The record index + The fields and new values which were updated + + + + + Handler for static DbRecordAdded event. + + The name of the updated database + The record index + + + + Handler for static DbRecordDeleted event. + + The name of the updated database + The record index + + + + Handler for RecordUpdated event. + + The record index + The fields and new values which were updated + + + + + Handler for RecordAdded event. + + The record index + + + + + Handler for RecordDeleted event. + + The record index + @@ -712,7 +872,7 @@ Specifies the type of match for FilterExpressions with String data types - + Specifies the comparison operator to use for FilterExpressions @@ -722,90 +882,24 @@ Boolean operands to use to join FilterExpressions - - - Use this class for single field searches. - - - - - - Create a FilterExpression with the indicated values - - The name of the Field to filter on - The Field value to filter on - The Equality operator to use in the value comparison - - - - - Create a FilterExpression with the indicated values - - The name of the Field to filter on - The Field value to filter on - The Equality operator to use in the value comparison - The match type, eg. MatchType.Exact - - - - - Parse the expression string to create a FilterExpressionGroup representing a simple expression. - - The string expression. Example: LastName = 'Fuller' - A new FilterExpression representing the simple expression - - - - - Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used - to filter the query. - - The name of the Field which will be used in the FilterExpressions - A Table to use to build the IN FilterExpressions - The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions - A new FilterExpression - - - - - Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used - to filter the query. - - The name of the Field which will be used in the FilterExpressions - A List of custom objects to use to build the IN FilterExpression - The name of the Property of the custom class which holds the value to be - used to build the IN FilterExpression - A new FilterExpression - - - - - Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. - All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple - FilterExpressionGroups to form any combination of And/Or logic. - - - - - - Parse the expression string to create a FilterExpressionGroup representing a compound expression. - - The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' - A new FilterExpressionGroup representing the compound expression - - Constructor - + Open the database files - + + + + + Flushes the Stream and detaches it, rendering this FileDb closed + + ---------------------------------------------------------------------------------------- @@ -865,7 +959,7 @@ deleted, but the actual data is only removed from the file when a cleanup() is called. - The record number (zero based) in the table to remove + The record number (zero based) in the table to remove true on success, false otherwise @@ -1101,6 +1195,118 @@ + + + + Use this class for single field searches. + + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + + + + + Create a FilterExpression with the indicated values + + The name of the Field to filter on + The Field value to filter on + The Equality operator to use in the value comparison + The match type, eg. MatchType.Exact + Operator negation + + + + + Parse the expression string to create a FilterExpressionGroup representing a simple expression. + + The string expression. Example: LastName = 'Fuller' + A new FilterExpression representing the simple expression + + + + + Utility method to transform a filesystem wildcard pattern into a regex pattern + eg. december* or mary? + + + + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A Table to use to build the IN FilterExpressions + The name of the Field in the Table which holds the value to be used to build the IN FilterExpressions + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of custom objects to use to build the IN FilterExpression + The name of the Property of the custom class which holds the value to be + used to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Create a FilterExpression of type "IN". This type is a HashSet of the values which will be used + to filter the query. + + The name of the Field which will be used in the FilterExpressions + A List of strings to use to build the IN FilterExpression + A new FilterExpression + + + + + Use this class to group FilterExpression and FilterExpressionGroup to form compound search expressions. + All expressions in the group will be evaluated by the same boolean And/Or operation. Use multiple + FilterExpressionGroups to form any combination of And/Or logic. + + + + + + Parse the expression string to create a FilterExpressionGroup representing a compound expression. + + The string compound expression. Example: (FirstName ~= 'andrew' OR FirstName ~= 'nancy') AND LastName = 'Fuller' + A new FilterExpressionGroup representing the compound expression + @@ -1165,6 +1371,12 @@ The zero-based ordinal index of the field + + + Clone this Field + + A new Field with the same values + The name of the field. @@ -1193,7 +1405,7 @@ Used for auto-increment fields. Set to the number which you want incrementing to begin. - Set to -1 if not an auto-increment field. + Leave it null if not an auto-increment field. @@ -1232,8 +1444,17 @@ Create a Record object with the indicated Fields and values. If creating a list of Record objects (for a Records list) be sure to use the same Fields list for each Record. - - + List of Field objects + Array of values. Each value will be converted to the Field type if possible. + + + + + Create a Record object with the indicated Fields and values. If creating a list of Record objects + (for a Records list) be sure to use the same Fields list for each Record. + + List of Field objects + Array of values. Each value will be converted to the Field type if possible. @@ -1244,7 +1465,15 @@ true if the field is in this Record - + + + Return the Typed field value. + + The name of the field + The Type field value + + + Return the integer field value. @@ -1252,7 +1481,7 @@ The integer field value - + Return the integer field value. @@ -1260,7 +1489,7 @@ The integer field value - + Return the unsigned integer field value. @@ -1268,7 +1497,7 @@ The unsigned integer field value - + Return the unsigned integer field value. @@ -1331,6 +1560,22 @@ The name of the field The Double field value + + + + Return the Decimal field value. + + The ordinal index of the field + The Decimal field value + + + + + Return the Decimal field value. + + The name of the field + The Decimal field value + @@ -1407,6 +1652,24 @@ Represents a data table returned from a query. A table is made up of Fields and Records. + + + + Create a table with the indicated Fields. + + The Fields list to use (a copy is made) + + + + + Create a table with the indicated Fields and records. If copyFields is true, a new + Fields list is created and a copy of each field is made and its ordinal adjusted. + Otherwise the original Fields object is adopted. You should pass false for copyFields + only if you created the Fields list and its Field objects yourself. + + The Fields list to use + Indicates whether to make a copy of the Fields object and each Field. + @@ -1433,21 +1696,36 @@ Indicates whether to make a copy of the Fields object and each Field. - + - Create a new Record with all of the fields of the Table. The Record is not - added to the Table. To do this, call Records.Add. + Add a new Record to this Table with all null values. - + - Save this Table to the indicated file as a new database. If the file exists - it will be overwritten. The new database will be just as if you had created - it from scratch. + Add a new Record to this Table with the specfied values, which must be in the order + of their corresponding fields. - The full path and filename of the new database + + + + + + Add a new Record to this Table with the FieldValues + + + + + + + + Save this Table to the Stream as a new database. If the Stream is null + one will be created and it will be a memory DB. The new database will be just as if you had created + it from scratch and populated it with the Table data. + + The full path and filename of the new database diff --git a/Caching/FileDb/Help.html b/Caching/FileDb/Help.html new file mode 100644 index 00000000..c14fb2bd --- /dev/null +++ b/Caching/FileDb/Help.html @@ -0,0 +1,983 @@ + + + + + +FileDb Overview + + + + + +

Overview

+
+

FileDb is a simple database designed as a simple +local database solution for Xamarin Cross-Platform phone (Android, IOS, WinPhone) +or any .NET application.  FileDb is a No-SQL database + meant for use as a local data store for applications.  + Here are some important points about FileDb:

+ +
    +
  • + Stores one table per file, including its index
  • +
  • + Extremely Lightweight - less than 50K
  • +
  • + Supports field types Int, UInt, Bool, String, Byte, + Float, Double and DateTime and also arrays of the same + types
  • +
  • + Index supports a single Primary Key field (optional)
  • +
  • + Compiled versions for Windows Phone + RT/PCL, + .NET
  • +
  • + FileDb is VERY FAST
  • +
  • + FileDb is FREE to use in your applications
  • +
  • + Use with LINQ to Objects to achieve full relational + capability
  • +
+ +

FileDb was specifically designed to use only native + .NET data types so there would no need to translate + between database storage and the CLR data types.  + So you can just as easily read/write a String[] field as + you would an Int field.  Another feature is that a + database file created on any .NET platform will work on + any other.  So you can create a database file on + your Windows machine and it can be used in a Silverlight + or Windows Phone app.

+ +

LINQ + FileDb gives you full + relational database capability

+

Even though FileDb is a "flat-file" database, using + LINQ it becomes fully relational!  LINQ + to Objects allows you to join Tables together just as + you would do in SQL. All of the power of LINQ is + available to you: Joins, Grouping, Sum - the lot.  + (See + the examples below.)

+

FileDb also has a built-in + query filter parser so you can write SQL-like + filter expressions to make filtering data easy, like + this:

+
+

+ + string filter = "FirstName IN ('Cindy', 'John') AND Age > 32"

+
+

Use FileDb in your .NET and mobile + applications where you need a searchable, updatable + local database.

+Use FileDb with Xamarin cross-platform app +development

Most phone apps only require a +simple database.  By purchasing a source code license you can compile +FileDb into your IOS, Android and Windows Phone projects and use the same exact +data layer code for them all.  This is much easier than using Sqlite +wrappers.  With FileDb's built in encryption you can have everything you +need to have a secure data layer.

+

+FileDb Database Overview

+ +

FileDb is a simple database designed for use on any .NET platform such as Windows +Phone and Silverlight, but its also great for any .NET app where simple local +database storage is needed. For example, instead of using XML config files you +could use a FileDb database to store and retrieve application data much more +efficiently. FileDb allows only a single table per database file, so when we +talk about a FileDb database we really mean a single table with an index. The +index is stored in the file with the data, and allows an optional Primary Key.

+

FileDb is NOT a relational database - it is NO-SLQ, +meaning you can't directly issue SQL commands for querying, adding or updating. However, +you CAN use LINQ with +FileDb to get full relational query capabilities.   And FileDb does include an Expression Parser which parses SQL-like filter +expressions, which makes searching, updating and deleting very easy - see below for an example.

+

And FileDb supports using powerful Regular Expressions for +filtering.

+

FileDb supports AES encryption at the record level. This +means the database schema is not encrypted (field names, etc.), but each record +is entirely encrypted. Encryption is "all or nothing", meaning it expects that +either all records are encrypted or all records are not encrypted. You turn +encryption on by passing an encryption key when opening the database.

+ +

FileDb is thread-safe for multithreading environments, so it can be accessed from +multiple threads at the same time without worrying about database corruption.

+

FileDb databases can only be opened by a single +application. Any attempt to open the file when already open will fail.  +This makes sense since its meant for use by a single application at a time +(FileDb is not meant as a multi-user database, such as SQL Server Express).

+

FileDb Classes

+

The main FileDb classes are: FileDb, Table, +Field and Record.

+ + +
  • FileDb: Represents a database file. All database operations are + initiated through this class.
  • + + +
  • Table: Represents a two + dimensional dataset returned from a query. A Table consists of Fields + and Records.
  • + + +
  • Field: Defines the + properties of the table column, such as Name and DataType.
  • + + +
  • Fields: A List of Field + objects.
  • +
  • + + + Record: A list of data objects represents a single row in a Table.  + Implements IEnumerable and the Data property which is used for DataBinding.
  • + + +
  • Records: A List of + Record objects.
  • + + +
  • FieldValues: A simple + Name/Value pair Dictionary. Use this class when adding and updating records.
  • + + +
  • FilterExpression: Used + to filter records for query, update and delete.
  • + + +
  • FilterExpressionGroup: + Used to create compound expressions by grouping FilterExpressions and + FilterExpressionGroups.
  • +
    +

    Database Fields

    +

    Fields (or Columns) can be of several common types: +String, Int, UInt, Bool, Byte, Float, Double and DateTime, or can also be an array of any of +these types.

    +

    Int Fields can be AutoIncrementing, and you can optionally +specify one field to be Primary Key (it must be of type Int or String).

    +

    FileDb doesn't support the notion of NULL fields for the +non-array type. Only array type fields can have NULL values. The non-array field +values will always have a value, either zero or empty.

    +

    FileDb Records

    +

    FileDb supports two methods of data retrieval.  You can say the +"default" way is with the built-in Record and Records classes.  Think of +Record as the .NET DataRow class, and think of Table as a DataTable.  Table +is a list of Records, and a Record holds the actual values. You access Field +values using indexing just as you would a DataRow, like this:

    +
    + + + + + +
    + FileDb employeesDb = new FileDb();
    + employeesDb.Open( Employees.fdb" );
    +
    + Table employees = employeesDb.SelectAllRecords();
    + Record record = + employees[0];
    + int id = (int) record["EmployeeId"];
    + // or
    + id + = (int) record[0];
    +
    +
    +

    To use a Table with LINQ, you do this:

    +
    + + + + + +
    + var recs = from e in + employees
    +           where (string) + e["FirstName"] == "John"
    +           select e;
    +
    +
    +

    Notice we have to cast the record value to a string.  + This is because, just like with the DataRow, Record + values are all type object.

    +

    Records and Custom Objects

    +

    Records are great because they require no additional + programming and they work with LINQ, albeit with some + casting.  But you can use your own custom classes + if you want because FileDb has template (generic) + overloads for each of the SelectRecords methods.  + You only need to create a class with public properties + which match the names of the fields you want to use.  + Here's an example using the Employees table.

    +
    + + + + + +
    + public class Employee
    + {
    +    public int EmployeeID { get; set; }
    +    public string LastName { get; set; }
    +     + public string + FirstName { get; set; }
    +     + public string Title { + get; set; }
    +     + public string + TitleOfCourtesy { get; set; }
    +     + public DateTime + BirthDate { get; set; }
    +     + public DateTime + HireDate { get; set; }
    +     + public string Address + { get; set; }
    +     + public string City { + get; set; }
    +     + public string Region { + get; set; }
    +     + public string + PostalCode { get; set; }
    +     + public string Country + { get; set; }
    +     + public string + HomePhone { get; set; }
    +     + public string + Extension { get; set; }
    +     + public Byte[] Photo { + get; set; }
    +     + public string Notes { + get; set; }
    +     + public int ReportsTo { + get; set; }
    + }
    +
    +
    +

    The templated SelectRecords versions return a IList<T> + where T is your custom type.

    +
    + + + + + +
    + + IList<Employee> + employees = employeesDb.SelectAllRecords<Employee>();
    + Employee employee + = employees[0];
    + int id = Employee.EmployeeId;
    +
    + var emps = from e in + employees
    +           where e.FirstName + == "John"
    +           select e;
    +
    +
    +

    As you can see, this is much cleaner code.  And + its actually more efficient since the Record class has + more overhead because its not as simple.

    +

    Searching and Filtering

    +

    FileDb uses FilterExpressions and + FilterExpressionGroups to filter records in queries and + updates. We use FilterExpressions for simple queries + which consist of a single field comparison (field = + 'value') and we use FilterExpressionGroups for compound + expressions, where multiple expressions and grouping are + required. You can add either FilterExpressions or + FilterExpressionGroups to a FilterExpressionGroup, thus + creating complex expresssions (FileDb processes + FilterExpressionGroups recursively).

    +

    You can either create your own manually in code or + use the built-in Expression Parser to create them for + you. The Expression Parser recognizes standard SQL + comparison operators. You can see it used in the + examples below. It also recognizes REGEX, which + uses Regular Expressions, and CONTAINS which uses + String.Contains. See the section on + Regular Expressions below for more info. Field names + prefixed with ~ specifies no-case comparison (for + strings only).

    +

    Each time you use () around an expression, a new +FilterExpressionGroup will be created. The inner-most expressions are evaluated +first, just as in SQL.

    + +

    Example 1: Create a FilterExpression

    +
    +
    + + + + + +
    + // build an expression manually
    + FilterExpression searchExp = new FilterExpression( "LastName", "Peacock", + Equality.Equal );
    +
    + // build the same expression using the + parser
    + searchExp = FilterExpression.Parse( "LastName = 'Peacock'" );
    + Table table = employeesDb.SelectRecords( + searchExp, + new string[] { "ID", "LastName" } );
    +
    + // Or you can simply pass the string filter + directly - a FilterExpression will be + created in the same way as above
    +
    + table = employeesDb.SelectRecords( "LastName + = 'Peacock'", new string[] { "ID", "LastName" + } );
    +
    + foreach( Record record in table )
    + {
    +    foreach( object value in record )
    +    {
    +         + Debug.WriteLine( value );
    +     + }
    + }
    +
    +
    + +


    +Example 2: Create a FilterExpressionGroup

    +
    +

    This example creates two identical FilterExpressionGroups, +one using the Expression Parser and the other with code.

    +
    + + + + + +
    // For string fields there are + 2 ways to specify no-case + comparisons: you can prefix fieldnames with ~ or you can use ~= as + demonstrated below
    + // The first form is needed when using the IN operator, eg.
    + FilterExpressionGroup filterExpGrp = + FilterExpressionGroup.Parse( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy') + AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // equivalent building it manually
    + var fname1Exp = new FilterExpression( "FirstName", "andrew", Equality.Equal, + MatchType.IgnoreCase );
    + var fname2Exp = new FilterExpression( "FirstName", "nancy", Equality.Equal, + MatchType.IgnoreCase );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal ); + // this constructor defaults to MatchType.UseCase
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    + // or just pass the filter string directly
    +
    + table = employeesDb.SelectRecords( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy') + AND LastName = 'Fuller'" );
    +  
    +
    +
    +


    +FileDb supports these comparison operators:

    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    = Equality
    ~= No-case equality - strings only
    <> Not Equal
    != Not Equal (same as <>)
    >= Greater than or Equal
    <= Less than or Equal
    REGEX Use Regular Expression
    CONTAINS Use the .NET String's Contains method
    IN Creates a HashSet of values to use like SQL + IN operator
    NOT Logical Negation, e.g NOT CONTAINS
    +
    + + +
    + +

     

    +
    +

    REGEX - +Regular Expressions in searches and filtering

    +

    FileDb supports using Regular Expressions. You can use any RegEx supported by +.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.  +Internally, FileDb uses FilterExpressions to evaluate fields.  You don't +need to use them because you can pass in filter strings and they'll be parsed +into FilterExpressions/FilterExpressionGroups for you.  This is just to +show you how can create them manually if you want to.  In +the example below, both FilterExpressionGroups are identical.

    + +
    + + + + + +
    // Using the Expression Parser
    +
    + // You can use brackets around fieldnames if there are spaces in the + name
    + FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName] + REGEX 'NANCY') AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // we can manually build the same FilterExpressionGroup
    + var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );
    + var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Regex );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    +
    +
    + +

     

    +
    +

    Using CONTAINS in searches and filtering

    +

    FileDb supports using Regular Expressions. You can use any RegEx supported by +.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.  +Internally, FileDb uses FilterExpressions to evaluate fields.  You don't +need to use them because you can pass in filter strings and they'll be parsed +into FilterExpressions/FilterExpressionGroups for you.  This is just to +show you how can create them manually if you want to.  In +the example below, both FilterExpressionGroups are identical.

    + +
    + + + + + +
    // You can use brackets around fieldnames if there are spaces in the + name
    + FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName] + CONTAINS 'NANC') AND LastName = 'Fuller'" );
    + Table table = employeesDb.SelectRecords( filterExpGrp );
    +
    + // we can manually build the same FilterExpressionGroup
    + var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );
    + var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Contains );
    + var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );
    + var fnamesGrp = new FilterExpressionGroup();
    + fnamesGrp.Add( BoolOp.Or, fname1Exp );
    + fnamesGrp.Add( BoolOp.Or, fname2Exp );
    + var allNamesGrp = new FilterExpressionGroup();
    + allNamesGrp.Add( BoolOp.And, lnameExp );
    + allNamesGrp.Add( BoolOp.And, fnamesGrp );
    +
    + table = employeesDb.SelectRecords( allNamesGrp );
    +
    +
    +
    +

     

    +

    Sort Ordering

    +

    Query methods allow for sorting the results by fields. To +get a reverse sort, prefix the sort field list with !. To get a no-case sort, +prefix with ~. To get both reverse and no-case sort, use both ! and ~.

    +

    Example:

    +
    + + + + + +
    + + Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName", + "Age" }, false, new string[] { "~LastName", "~FirstName", "!Age" } );
    +
    +
    +

    Selecting a Table from a Table

    +

    Another very powerful feature of FileDb is the ability to select a Table from +another Table.  This would allow you to be able to select data from a Table +after the database file has been closed, for example.

    +

    Example:

    +
    + + + + + +
    + + customersDb.Open( path + "Customers.fdb" );
    +
    +// select all fields and records from the database table
    +Table customers = customersDb.SelectAllRecords();
    +
    +Table subCusts = customers.SelectRecords( "CustomerID <> 'ALFKI'",
    +new string[] { "CustomerID", "CompanyName", "City" }, new string[] { "~City", "~CompanyName" +} );
    +
    +
    +

    Encryption

    +

    Using encryption with FileDb is simple. You only need to +specify a string key when you open the database. After that everything is +automatic. The only caveat is you must set a key before you add any records. +Once a single record has been added without a key set you cannot later add +records with a key. Its all or nothing. Likewise, you cannot add records with +encryption and later add records without.  You must set the encryption key +each time you add any records.

    +

    Persisting Tables

    +

    You can easily save a Table as a new database using +Table.SaveToDb.  This method creates a new database file using the Fields +in the Table then populates it using the Records in the Table.  For +example, you can select subsets of your data, save it as a new database and send +it over the Internet.

    +
    + + + + + +
    Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName" + } );
    + table.SaveToDb( "Names.fdb" );
    +
    +
    +

    You can also save a Table to a database from the + FileDb Explorer. Just right-click on the Grid to show + the context menu and select the "Create database from + Table..." menu item.

    +

    Using LINQ to Objects with FileDb

    +

    Microsoft has done an amazing job with LINQ.  + They have invested a huge amount of time, effort and $ + in this technology which allows you to query just about + any kind of data in a SQL-like way.  We use LINQ + with FileDb to join Tables as we would using SQL.  + The difference is that instead of doing it all in a + single step with SQL, we must do it in two steps.  + First we select the data Tables from the database files + then we use LINQ to join them together.

    +

    LINQ to Objects produces a list of anonymous types as + its result set.  This is good because we get + strongly typed data objects which we can easily use in WPF/Silverlight apps.

    +

    Here is an example of doing a simple select using + LINQ:

    +
    + + + + + +
    // + Using the IN operator.  Notice the ~ + prefix on the LastName field. This is how +
    + // you can specify case insensitive searches
    +
    + Table + employees = employeesDb.SelectRecords( "~LastName + IN ('Fuller', 'Peacock')" );
    +
    + var query = + from record in employees
    + select new
    + {
    +    ID = record["EmployeeId"],
    +    Name = record["FirstName"] + " " + record["LastName"],
    +    Title = record["Title"]
    + };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

    The only thing LINQ did for us in this example was + gave us a typed list of anonymous objects.  Here's + the same thing but with custom objects:

    + + + + + +
    IList<Employee> employees + = employeesDb.SelectRecords<Employee>( + "~LastName + IN ('Fuller', 'Peacock')" );
    +
    + var query =
    + from e in employees
    + select e;
    +
    + foreach( var emp in query )
    + {
    +    Debug.WriteLine( emp.ToString() );
    + }
    +
    +
    +

    Now lets tap into LINQ's real power to join tables together +like a SQL inner join.  Notice in the following example we use the +FilterExpression.CreateInExpressionFromTable +method.  We do this to get only the records we are going to need with LINQ.  +So using FileDb with LINQ is a two step process.  You first select the +records you will need then use them in the LINQ query.  If your database +files are large, you can filter the records like this.  Otherwise you can +just select all records.

    +
    + + + + + +
    FileDb customersDb = new FileDb(),
    + ordersDb = new FileDb(),
    + orderDetailsDb = new FileDb(),
    + productsDb = new FileDb();
    +
    + customersDb.Open( "Customers.fdb" );
    + ordersDb.Open( "Orders.fdb" );
    + orderDetailsDb.Open( "OrderDetails.fdb" );
    + productsDb.Open( "Products.fdb" );
    +
    + // get our target Customer records
    + // Note that we should select only fields we + need from each table, but to keep the code
    + // simple for this example we just pass in null for the field + list
    +
    + FilterExpression filterExp = + FilterExpression.Parse( "CustomerID IN( 'ALFKI', + 'BONAP' )" );
    + FileDbNs.Table customers = + customersDb.SelectRecords( filterExp );
    +
    + // now get only Order records for the target + Customer records
    + // CreateInExpressionFromTable will create + an IN FilterExpression, which uses a HashSet +
    + // for high efficiency when filtering + records

    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "CustomerID", customers, "CustomerID" );
    + FileDbNs.Table orders = + ordersDb.SelectRecords( filterExp );
    +
    + // now get only OrderDetails records for the + target Order records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "OrderID", orders, "OrderID" );
    + FileDbNs.Table orderDetails = + orderDetailsDb.SelectRecords( filterExp );
    +
    + // now get only Product records for the + target OrderDetails records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable( + "ProductID", orderDetails, "ProductID" );
    + FileDbNs.Table products = + productsDb.SelectRecords( filterExp );
    +
    + // now we're ready to do the join
    +
    + var query =
    +    from custRec in customers
    +    join orderRec in orders on custRec["CustomerID"] + equals orderRec["CustomerID"]
    +    join orderDetailRec in orderDetails on + orderRec["OrderID"] equals + orderDetailRec["OrderID"]
    +    join productRec in products on + orderDetailRec["ProductID"] equals + productRec["ProductID"]
    +    select new
    +    {
    +        ID = custRec["CustomerID"],
    +        CompanyName = custRec["CompanyName"],
    +        OrderID = orderRec["OrderID"],
    +        OrderDate = orderRec["OrderDate"],
    +        ProductName = productRec["ProductName"],
    +        UnitPrice = orderDetailRec["UnitPrice"],
    +        Quantity = orderDetailRec["Quantity"]
    +    };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

    Here's the same thing again using custom objects:

    + + + + + +
    // get our target Customer records
    +
    + FilterExpression filterExp = + FilterExpression.Parse( "CustomerID IN( 'ALFKI', + 'BONAP' )" );
    + IList<Customer> customers = + customersDb.SelectRecords<Customer>( filterExp );

    + filterExp = + FilterExpression.CreateInExpressionFromTable<Customer>( + "CustomerID", customers, "CustomerID" );
    + IList<Order> orders = + ordersDb.SelectRecords<Order>( filterExp );
    +
    + // now get only OrderDetails records for the + target Order records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable<Order>( + "OrderID", orders, "OrderID" );
    + IList<OrderDetail> orderDetails = + orderDetailsDb.SelectRecords<OrderDetail>( filterExp );
    +
    + // now get only Product records for the + target OrderDetails records
    +
    + filterExp = + FilterExpression.CreateInExpressionFromTable<OrderDetail>( + "ProductID", orderDetails, "ProductID" );
    + IList<Product> products = + productsDb.SelectRecords<Product>(( filterExp );
    +
    + // now we're ready to do the join
    +
    + var query =
    +    from custRec in customers
    +    join orderRec in orders on custRec.CustomerID + equals orderRec.CustomerID
    +    join orderDetailRec in orderDetails on + orderRec.OrderID equals orderDetailRec.OrderID
    +    join productRec in products on + orderDetailRec.ProductID equals productRec.ProductID
    +    select new
    +    {
    +        ID = custRec.CustomerID,
    +        CompanyName = custRec.CompanyName,
    +        OrderID = orderRec.OrderID,
    +        OrderDate = orderRec.OrderDate,
    +        ProductName = productRec.ProductName,
    +        UnitPrice = orderDetailRec.UnitPrice,
    +        Quantity = orderDetailRec.Quantity
    +    };
    +
    + foreach( var rec in query )
    + {
    +    Debug.WriteLine( rec.ToString() );
    + }
    +
    +

     

    +
    +

    + Creating a Database

    + +

    You create your database programmatically by defining +Fields and adding them to an array then calling FileDb.Create, similar to below. +Notice we set the ID field to be AutoIncrementing and PrimaryKey. This code +creates a database with every type of field.

    + +
    + + + + +
    Field field;
    + var fieldLst = new List<Field>( 20 );
    + field = new Field( "ID", DataType.Int );
    + field.AutoIncStart = 0;
    + field.IsPrimaryKey = true;
    + fields.Add( field );
    + field = new Field( "FirstName", DataType.String );
    + fields.Add( field );
    + field = new Field( "LastName", DataType.String );
    + fields.Add( field );
    + field = new Field( "BirthDate", DataType.DateTime );
    + fields.Add( field );
    + field = new Field( "IsCitizen", DataType.Bool );
    + fields.Add( field );
    + field = new Field( "DoubleField", DataType.Double );
    + fields.Add( field );
    + field = new Field( "ByteField", DataType.Byte );
    + fields.Add( field );
    +
    + // array types
    + field = new Field( "StringArrayField", DataType.String );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "ByteArrayField", DataType.Byte );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "IntArrayField", DataType.Int );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "DoubleArrayField", DataType.Double );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "DateTimeArrayField", DataType.DateTime );
    + field.IsArray = true;
    + fields.Add( field );
    + field = new Field( "BoolArray", DataType.Bool );
    + field.IsArray = true;
    + fields.Add( field );

    +
    + var myDb = new FileDb();

    + myDb.Create( "MyDatabase.fdb", fieldLst.ToArray() );
    +
    +
    +


    +Adding Records

    +

    You add records to a database by creating a FieldValues +object and adding field values. You do not need to represent every field of the +database. Fields that are missing will be initialized to the default value (zero +for numeric types, DateTime.MinValue, +empty for String and NULL for array types).

    + +
    + + + + +
    +var record = new FieldValues();
    + record.Add( "FirstName", "Nancy" );
    + record.Add( "LastName", "Davolio" );
    + record.Add( "BirthDate", new DateTime( 1968, 12, 8 ) );
    + record.Add( "IsCitizen", true );
    + record.Add( "Double", 1.23 );
    + record.Add( "Byte", 1 );
    + record.Add( "StringArray", new string[] { "s1", "s2", "s3" } );
    + record.Add( "ByteArray", new Byte[] { 1, 2, 3, 4 } );
    + record.Add( "IntArray", new int[] { 100, 200, 300, 400 } );
    + record.Add( "DoubleArray", new double[] { 1.2, 2.4, 3.6, 4.8 } );
    + record.Add( "DateTimeArray", new DateTime[] { DateTime.Now, DateTime.Now, + DateTime.Now, DateTime.Now } );
    + record.Add( "BoolArray", new bool[] { true, false, true, false } );
    +
    + myDb.AddRecord( record );
    +
    +

     

    +
    + + + + \ No newline at end of file diff --git a/Caching/FileDbCache/FileDbCache.csproj b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj similarity index 85% rename from Caching/FileDbCache/FileDbCache.csproj rename to Caching/FileDbCache.WPF/FileDbCache.WPF.csproj index 8f4d183d..ab6c3a81 100644 --- a/Caching/FileDbCache/FileDbCache.csproj +++ b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj @@ -8,8 +8,8 @@ {EF44F661-B98A-4676-927F-85D138F82300} Library Properties - Caching - FileDbCache + MapControl.Caching + FileDbCache.WPF v4.5 512 @@ -40,27 +40,27 @@ ..\..\MapControl.snk - + False - FileDb\FileDb.dll + ..\FileDb\FileDb.dll + + + - - - - MapControl.snk + + \ No newline at end of file diff --git a/Caching/FileDbCache.WinRT/FileDbCache.cs b/Caching/FileDbCache.WinRT/FileDbCache.cs new file mode 100644 index 00000000..ba288ca9 --- /dev/null +++ b/Caching/FileDbCache.WinRT/FileDbCache.cs @@ -0,0 +1,278 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI.Xaml; +using FileDbNs; + +namespace MapControl.Caching +{ + /// + /// IImageCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. + /// See http://www.eztools-software.com/tools/filedb/. + /// + public class FileDbCache : IImageCache, IDisposable + { + private const string keyField = "Key"; + private const string valueField = "Value"; + private const string expiresField = "Expires"; + + private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 }; + private readonly StorageFolder folder; + private readonly string name; + + public FileDbCache(string name = null, StorageFolder folder = null) + { + if (string.IsNullOrWhiteSpace(name)) + { + name = TileImageLoader.DefaultCacheName; + } + + if (string.IsNullOrEmpty(Path.GetExtension(name))) + { + name += ".fdb"; + } + + if (folder == null) + { + folder = TileImageLoader.DefaultCacheFolder; + } + + this.folder = folder; + this.name = name; + + Application.Current.Resuming += async (s, e) => await Open(); + Application.Current.Suspending += (s, e) => Close(); + + var task = Open(); + } + + public void Dispose() + { + Close(); + } + + public void Clean() + { + if (fileDb.IsOpen) + { + fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan)); + + if (fileDb.NumDeleted > 0) + { + Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted); + fileDb.Clean(); + } + } + } + + public async Task GetAsync(string key) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (!fileDb.IsOpen) + { + return null; + } + + return await Task.Run(() => Get(key)); + } + + public async Task SetAsync(string key, IBuffer buffer, DateTime expires) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (buffer == null) + { + throw new ArgumentNullException("The parameter buffer must not be null."); + } + + if (fileDb.IsOpen) + { + var bytes = buffer.ToArray(); + var ok = await Task.Run(() => AddOrUpdateRecord(key, bytes, expires)); + + if (!ok && (await RepairDatabase())) + { + await Task.Run(() => AddOrUpdateRecord(key, bytes, expires)); + } + } + } + + private async Task Open() + { + if (!fileDb.IsOpen) + { + try + { + var file = await folder.GetFileAsync(name); + var stream = await file.OpenAsync(FileAccessMode.ReadWrite); + + fileDb.Open(stream.AsStream()); + Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, file.Path); + + Clean(); + return; + } + catch + { + } + + await CreateDatabase(); + } + } + + private void Close() + { + if (fileDb.IsOpen) + { + fileDb.Close(); + } + } + + private async Task CreateDatabase() + { + Close(); + + var file = await folder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting); + var stream = await file.OpenAsync(FileAccessMode.ReadWrite); + + fileDb.Create(stream.AsStream(), new Field[] + { + new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, + new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, + new Field(expiresField, DataTypeEnum.DateTime) + }); + + Debug.WriteLine("FileDbCache: Created database {0}.", file.Path); + } + + private async Task RepairDatabase() + { + try + { + fileDb.Reindex(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message); + } + + try + { + await CreateDatabase(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", Path.Combine(folder.Path, name), ex.Message); + } + + return false; + } + + private ImageCacheItem Get(string key) + { + var fields = new string[] { valueField, expiresField }; + Record record = null; + + try + { + record = fileDb.GetRecordByKey(key, fields, false); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + } + + if (record != null) + { + try + { + return new ImageCacheItem + { + Buffer = ((byte[])record[0]).AsBuffer(), + Expires = (DateTime)record[1] + }; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: Decoding \"{0}\" failed: {1}", key, ex.Message); + } + + try + { + fileDb.DeleteRecordByKey(key); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + } + } + + return null; + } + + private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires) + { + var fieldValues = new FieldValues(3); + fieldValues.Add(valueField, value); + fieldValues.Add(expiresField, expires); + + bool recordExists; + + try + { + recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + + if (recordExists) + { + try + { + fileDb.UpdateRecordByKey(key, fieldValues); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + } + else + { + try + { + fieldValues.Add(keyField, key); + fileDb.AddRecord(fieldValues); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message); + return false; + } + } + + //Debug.WriteLine("Cached item {0}, expires {1}", key, expires); + return true; + } + } +} diff --git a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..02eedcb5 --- /dev/null +++ b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control FileDbCache (WinRT)")] +[assembly: AssemblyDescription("IImageCache implementation based on EzTools FileDb")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/Caching/FileDbCache/FileDb/FileDb.dll b/Caching/FileDbCache/FileDb/FileDb.dll deleted file mode 100644 index 2022a6199d64e05f46b57e9a9b67913475ab1ae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70144 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 diff --git a/Caching/FileDbCache/FileDb/FileDb.txt b/Caching/FileDbCache/FileDb/FileDb.txt deleted file mode 100644 index f3d49597..00000000 --- a/Caching/FileDbCache/FileDb/FileDb.txt +++ /dev/null @@ -1,5 +0,0 @@ -FileDbCache uses FileDb by Brett Goodman (aka EzTools), a simple No-SQL database for .NET. -FileDb is Open Source on Google Code Hosting at http://code.google.com/p/filedb-database/ -and licensed under the Apache License 2.0, http://www.apache.org/licenses/LICENSE-2.0. -See the product homepage at http://www.eztools-software.com/tools/filedb/. -Download FileDb from http://www.eztools-software.com/downloads/filedb.exe. diff --git a/Caching/ImageFileCache/ImageFileCache.csproj b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj similarity index 91% rename from Caching/ImageFileCache/ImageFileCache.csproj rename to Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj index 75ce1be8..b3514996 100644 --- a/Caching/ImageFileCache/ImageFileCache.csproj +++ b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj @@ -8,8 +8,8 @@ {86470440-FEE2-4120-AF5A-3762FB9C536F} Library Properties - Caching - ImageFileCache + MapControl.Caching + ImageFileCache.WPF v4.5 512 @@ -40,9 +40,12 @@ ..\..\MapControl.snk + + + diff --git a/Caching/ImageFileCache/ImageFileCache.cs b/Caching/ImageFileCache.WPF/ImageFileCache.cs similarity index 53% rename from Caching/ImageFileCache/ImageFileCache.cs rename to Caching/ImageFileCache.WPF/ImageFileCache.cs index d3f0fa5c..bf3bab36 100644 --- a/Caching/ImageFileCache/ImageFileCache.cs +++ b/Caching/ImageFileCache.WPF/ImageFileCache.cs @@ -6,61 +6,52 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.Caching; using System.Security.AccessControl; using System.Security.Principal; +using System.Windows.Media.Imaging; -namespace Caching +namespace MapControl.Caching { /// /// ObjectCache implementation based on local image files. - /// The only valid data type for cached values is a byte array containing an - /// 8-byte timestamp followed by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer. + /// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame. /// public class ImageFileCache : ObjectCache { - private static readonly Tuple[] imageFileTypes = new Tuple[] - { - new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), - new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), - new Tuple(".bmp", new byte[] { 0x42, 0x4D }), - new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), - new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), - new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), - new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), - }; - private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), FileSystemRights.FullControl, AccessControlType.Allow); + private readonly MemoryCache memoryCache = MemoryCache.Default; private readonly string name; - private readonly string directory; + private readonly string rootFolder; public ImageFileCache(string name, NameValueCollection config) - : this(name, config["directory"]) + : this(name, config["folder"]) { } - public ImageFileCache(string name, string directory) + public ImageFileCache(string name, string folder) { if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("The parameter name must not be null or empty or only white-space."); + throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters."); } - if (string.IsNullOrWhiteSpace(directory)) + if (string.IsNullOrWhiteSpace(folder)) { - throw new ArgumentException("The parameter directory must not be null or empty or only white-space."); + throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters."); } this.name = name; - this.directory = Path.Combine(directory, name.Trim()); - Directory.CreateDirectory(this.directory); + rootFolder = Path.Combine(folder, name); + Directory.CreateDirectory(rootFolder); - Trace.TraceInformation("Created ImageFileCache in {0}", this.directory); + Debug.WriteLine("Created ImageFileCache in {0}.", (object)rootFolder); } public override string Name @@ -81,46 +72,29 @@ namespace Caching protected override IEnumerator> GetEnumerator() { - throw new NotSupportedException("LocalFileCache does not support the ability to enumerate items."); + throw new NotSupportedException("ImageFileCache does not support the ability to enumerate items."); } public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) { - throw new NotSupportedException("LocalFileCache does not support the ability to create change monitors."); + throw new NotSupportedException("ImageFileCache does not support the ability to create change monitors."); } public override long GetCount(string regionName = null) { - throw new NotSupportedException("LocalFileCache does not support the ability to count items."); + throw new NotSupportedException("ImageFileCache does not support the ability to count items."); } public override bool Contains(string key, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - try - { - return MemoryCache.Default.Contains(key) || FindFile(GetPath(key)) != null; - } - catch - { - return false; - } + return memoryCache.Contains(key, regionName) || FindFile(GetPath(key)) != null; } public override object Get(string key, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } + var bitmap = memoryCache.Get(key, regionName) as BitmapFrame; - var value = MemoryCache.Default.Get(key); - - if (value == null) + if (bitmap == null) { try { @@ -128,20 +102,20 @@ namespace Caching if (path != null) { - var expirationTime = File.GetLastAccessTimeUtc(path); - var creationTime = File.GetLastWriteTimeUtc(path); - - if (expirationTime < creationTime) + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - expirationTime = creationTime; - } + bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - using (var fileStream = new FileStream(path, FileMode.Open)) - using (var memoryStream = new MemoryStream((int)(fileStream.Length + 8))) - { - memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, 8); - fileStream.CopyTo(memoryStream); - value = memoryStream.GetBuffer(); + var metadata = (BitmapMetadata)bitmap.Metadata; + DateTime expiration; + + // metadata.DateTaken must be parsed in CurrentCulture + if (metadata != null && + metadata.DateTaken != null && + DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration)) + { + memoryCache.Set(key, bitmap, expiration, regionName); + } } } } @@ -150,7 +124,7 @@ namespace Caching } } - return value; + return bitmap; } public override CacheItem GetCacheItem(string key, string regionName = null) @@ -162,58 +136,72 @@ namespace Caching public override IDictionary GetValues(IEnumerable keys, string regionName = null) { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - var values = new Dictionary(); - - foreach (string key in keys) - { - values[key] = Get(key); - } - - return values; + return keys.ToDictionary(key => key, key => Get(key, regionName)); } public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { - if (regionName != null) + var bitmap = value as BitmapFrame; + + if (bitmap == null) { - throw new NotSupportedException("The parameter regionName must be null."); + throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame."); } - var buffer = value as byte[]; + var metadata = (BitmapMetadata)bitmap.Metadata; + var format = metadata != null ? metadata.Format : "bmp"; + BitmapEncoder encoder = null; - if (buffer == null || buffer.Length <= 8) + switch (format) { - throw new NotSupportedException("The parameter value must be a byte[] containing at least 9 bytes."); + case "bmp": + encoder = new BmpBitmapEncoder(); + break; + case "gif": + encoder = new GifBitmapEncoder(); + break; + case "jpg": + encoder = new JpegBitmapEncoder(); + break; + case "png": + encoder = new PngBitmapEncoder(); + break; + case "tiff": + encoder = new TiffBitmapEncoder(); + break; + case "wmphoto": + encoder = new WmpBitmapEncoder(); + break; + default: + break; } - MemoryCache.Default.Set(key, buffer, policy); + if (encoder == null) + { + throw new NotSupportedException(string.Format("The bitmap format {0} is not supported.", format)); + } - var path = GetPath(key) + GetFileExtension(buffer); + memoryCache.Set(key, bitmap, policy, regionName); + + var path = string.Format("{0}.{1}", GetPath(key), format); try { Directory.CreateDirectory(Path.GetDirectoryName(path)); - using (var fileStream = new FileStream(path, FileMode.Create)) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) { - fileStream.Write(buffer, 8, buffer.Length - 8); + encoder.Frames.Add(bitmap); + encoder.Save(fileStream); } - var expirationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0)); - File.SetLastAccessTimeUtc(path, expirationTime); - var fileSecurity = File.GetAccessControl(path); fileSecurity.AddAccessRule(fullControlRule); File.SetAccessControl(path, fileSecurity); } catch (Exception ex) { - Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); + Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); } } @@ -253,7 +241,8 @@ namespace Caching public override object Remove(string key, string regionName = null) { var oldValue = Get(key, regionName); - MemoryCache.Default.Remove(key); + + memoryCache.Remove(key, regionName); try { @@ -273,7 +262,7 @@ namespace Caching private string GetPath(string key) { - return Path.Combine(directory, key); + return Path.Combine(rootFolder, key); } private static string FindFile(string path) @@ -283,34 +272,14 @@ namespace Caching return path; } - string directoryName = Path.GetDirectoryName(path); + string folderName = Path.GetDirectoryName(path); - if (Directory.Exists(directoryName)) + if (Directory.Exists(folderName)) { - return Directory.EnumerateFiles(directoryName, Path.GetFileName(path) + ".*").FirstOrDefault(); + return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault(); } return null; } - - private static string GetFileExtension(byte[] buffer) - { - var fileType = imageFileTypes.FirstOrDefault(t => - { - int i = 0; - - if (t.Item2.Length <= buffer.Length - 8) - { - while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8]) - { - i++; - } - } - - return i == t.Item2.Length; - }); - - return fileType != null ? fileType.Item1 : ".bin"; - } } } diff --git a/Caching/ImageFileCache/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs similarity index 75% rename from Caching/ImageFileCache/Properties/AssemblyInfo.cs rename to Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs index 313744d6..5d71f9eb 100644 --- a/Caching/ImageFileCache/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs @@ -1,14 +1,14 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("ImageFileCache")] +[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WPF)")] [assembly: AssemblyDescription("ObjectCache implementation based on local image files")] [assembly: AssemblyProduct("XAML Map Control")] [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.3.1")] -[assembly: AssemblyFileVersion("2.3.1")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj b/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj new file mode 100644 index 00000000..148d5184 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj @@ -0,0 +1,66 @@ + + + + + 12.0 + Debug + AnyCPU + {F789647E-96F7-43E3-A895-FA3FE8D01260} + Library + Properties + MapControl.Caching + ImageFileCache.WinRT + en-US + 512 + {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Windows + 8.1 + + + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_APP + prompt + 4 + + + none + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_APP + prompt + 4 + + + true + + + ..\..\MapControl.snk + + + + + MapControl.snk + + + + + + + + + {63cefdf7-5170-43b6-86f8-5c4a383a1615} + MapControl.WinRT + + + + + \ No newline at end of file diff --git a/Caching/ImageFileCache.WinRT/ImageFileCache.cs b/Caching/ImageFileCache.WinRT/ImageFileCache.cs new file mode 100644 index 00000000..113b0ba2 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/ImageFileCache.cs @@ -0,0 +1,94 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace MapControl.Caching +{ + public class ImageFileCache : IImageCache + { + private readonly string name; + private StorageFolder rootFolder; + + public ImageFileCache(string name = null, StorageFolder folder = null) + { + if (string.IsNullOrWhiteSpace(name)) + { + name = TileImageLoader.DefaultCacheName; + } + + if (folder == null) + { + folder = TileImageLoader.DefaultCacheFolder; + } + + this.name = name; + + folder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists).Completed = (o, s) => + { + rootFolder = o.GetResults(); + Debug.WriteLine("Created ImageFileCache in {0}.", rootFolder.Path); + }; + } + + public virtual async Task GetAsync(string key) + { + var item = await rootFolder.TryGetItemAsync(key); + + if (item == null || !item.IsOfType(StorageItemTypes.File)) + { + return null; + } + + var file = (StorageFile)item; + + var cacheItem = new ImageCacheItem + { + Buffer = await FileIO.ReadBufferAsync(file), + }; + + try + { + // Use ImageProperties.DateTaken to get expiration date + var imageProperties = await file.Properties.GetImagePropertiesAsync(); + cacheItem.Expires = imageProperties.DateTaken.UtcDateTime; + } + catch + { + } + + return cacheItem; + } + + public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires) + { + try + { + var names = key.Split('\\'); + var folder = rootFolder; + + for (int i = 0; i < names.Length - 1; i++) + { + folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists); + } + + var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting); + await FileIO.WriteBufferAsync(file, buffer); + + // Use ImageProperties.DateTaken to store expiration date + var imageProperties = await file.Properties.GetImagePropertiesAsync(); + imageProperties.DateTaken = expires; + await imageProperties.SavePropertiesAsync(); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + } +} diff --git a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..62f128f7 --- /dev/null +++ b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WinRT)")] +[assembly: AssemblyDescription("IImageCache implementation based on local image files")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/MapControl.sln b/MapControl.sln index 8f9d1105..622a4edd 100644 --- a/MapControl.sln +++ b/MapControl.sln @@ -1,44 +1,72 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache", "Caching\FileDbCache\FileDbCache.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WinRT", "Caching\FileDbCache.WinRT\FileDbCache.WinRT.csproj", "{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache", "Caching\ImageFileCache\ImageFileCache.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WPF", "Caching\FileDbCache.WPF\FileDbCache.WPF.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WinRT", "Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj", "{F789647E-96F7-43E3-A895-FA3FE8D01260}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WPF", "Caching\ImageFileCache.WPF\ImageFileCache.WPF.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication", "SampleApps\SilverlightApplication\SilverlightApplication.csproj", "{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication.Web", "SampleApps\SilverlightApplication.Web\SilverlightApplication.Web.csproj", "{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreApplication", "SampleApps\StoreApplication\StoreApplication.csproj", "{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApps\WpfApplication\WpfApplication.csproj", "{9949326E-9261-4F95-89B1-151F60498951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.PhoneSilverlight", "MapControl\MapControl.PhoneSilverlight.csproj", "{3499D618-2846-4FCE-A418-7D211FDBDCB3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.Build.0 = Release|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.Build.0 = Debug|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.Build.0 = Release|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.Build.0 = Release|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.Build.0 = Debug|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.ActiveCfg = Release|Any CPU {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.Build.0 = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU + {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU + {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -47,38 +75,16 @@ Global {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.Build.0 = Release|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU - {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Build.0 = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Deploy.0 = Release|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.Build.0 = Debug|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.ActiveCfg = Release|Any CPU {9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MapControl/BingMapsTileLayer.cs b/MapControl/BingMapsTileLayer.cs index 6c5b15b0..6d01b779 100644 --- a/MapControl/BingMapsTileLayer.cs +++ b/MapControl/BingMapsTileLayer.cs @@ -57,8 +57,7 @@ namespace MapControl var request = (HttpWebRequest)asyncResult.AsyncState; using (var response = request.EndGetResponse(asyncResult)) - using (var responseStream = response.GetResponseStream()) - using (var xmlReader = XmlReader.Create(responseStream)) + using (var xmlReader = XmlReader.Create(response.GetResponseStream())) { ReadImageryMetadataResponse(xmlReader); } diff --git a/MapControl/HyperlinkText.cs b/MapControl/HyperlinkText.cs index 767cf72a..29198ba7 100644 --- a/MapControl/HyperlinkText.cs +++ b/MapControl/HyperlinkText.cs @@ -21,7 +21,7 @@ namespace MapControl /// Converts text containing hyperlinks in markdown syntax [text](url) /// to a collection of Run and Hyperlink inlines. ///
    - public static ICollection ToInlines(this string text) + public static List ToInlines(this string text) { var inlines = new List(); diff --git a/MapControl/IMapElement.cs b/MapControl/IMapElement.cs index 9c8d5b55..33faaa3b 100644 --- a/MapControl/IMapElement.cs +++ b/MapControl/IMapElement.cs @@ -6,8 +6,6 @@ namespace MapControl { public interface IMapElement { - MapBase ParentMap { get; } - - void SetParentMap(MapBase parentMap); + MapBase ParentMap { get; set; } } } diff --git a/MapControl/ITileImageLoader.cs b/MapControl/ITileImageLoader.cs new file mode 100644 index 00000000..72b719ad --- /dev/null +++ b/MapControl/ITileImageLoader.cs @@ -0,0 +1,14 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System.Collections.Generic; + +namespace MapControl +{ + public interface ITileImageLoader + { + void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles); + void CancelLoadTiles(TileLayer tileLayer); + } +} diff --git a/MapControl/ImageCache.WinRT.cs b/MapControl/ImageCache.WinRT.cs new file mode 100644 index 00000000..6e0f9cea --- /dev/null +++ b/MapControl/ImageCache.WinRT.cs @@ -0,0 +1,22 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Threading.Tasks; +using Windows.Storage.Streams; + +namespace MapControl.Caching +{ + public class ImageCacheItem + { + public IBuffer Buffer { get; set; } + public DateTime Expires { get; set; } + } + + public interface IImageCache + { + Task GetAsync(string key); + Task SetAsync(string key, IBuffer buffer, DateTime expires); + } +} diff --git a/MapControl/ImageFileCache.WinRT.cs b/MapControl/ImageFileCache.WinRT.cs deleted file mode 100644 index 2639b0ae..00000000 --- a/MapControl/ImageFileCache.WinRT.cs +++ /dev/null @@ -1,67 +0,0 @@ -// XAML Map Control - http://xamlmapcontrol.codeplex.com/ -// Copyright © 2014 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Streams; - -namespace MapControl -{ - public class ImageFileCache : IObjectCache - { - private readonly IStorageFolder rootFolder; - - public ImageFileCache() - { - rootFolder = ApplicationData.Current.TemporaryFolder; - } - - public ImageFileCache(IStorageFolder folder) - { - if (folder == null) - { - throw new ArgumentNullException("The parameter folder must not be null."); - } - - rootFolder = folder; - } - - public async Task GetAsync(string key) - { - try - { - return await PathIO.ReadBufferAsync(Path.Combine(rootFolder.Path, key)); - } - catch - { - return null; - } - } - - public async Task SetAsync(string key, object value) - { - try - { - var buffer = (IBuffer)value; - var names = key.Split('\\'); - var folder = rootFolder; - - for (int i = 0; i < names.Length - 1; i++) - { - folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists); - } - - var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting); - await FileIO.WriteBufferAsync(file, buffer); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - } - } -} diff --git a/MapControl/ImageTileSource.WPF.cs b/MapControl/ImageTileSource.WPF.cs index 2af21f5b..4ff5eaa1 100644 --- a/MapControl/ImageTileSource.WPF.cs +++ b/MapControl/ImageTileSource.WPF.cs @@ -30,15 +30,16 @@ namespace MapControl { if (IsAsync) { - var buffer = new WebClient().DownloadData(uri); + var request = HttpWebRequest.CreateHttp(uri); + request.UserAgent = TileImageLoader.HttpUserAgent; - if (buffer != null) + using (var response = (HttpWebResponse)request.GetResponse()) + using (var responseStream = response.GetResponseStream()) + using (var memoryStream = new MemoryStream()) { - using (var stream = new MemoryStream(buffer)) - { - image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - image.Freeze(); - } + responseStream.CopyTo(memoryStream); + memoryStream.Position = 0; + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } } else diff --git a/MapControl/Int32Rect.cs b/MapControl/Int32Rect.cs index 2cbd9e8b..6dea4511 100644 --- a/MapControl/Int32Rect.cs +++ b/MapControl/Int32Rect.cs @@ -4,7 +4,7 @@ namespace MapControl { - internal struct Int32Rect + public struct Int32Rect { public Int32Rect(int x, int y, int width, int height) : this() diff --git a/MapControl/MapBase.Silverlight.WinRT.cs b/MapControl/MapBase.Silverlight.WinRT.cs index d1b207a0..fb41c2cf 100644 --- a/MapControl/MapBase.Silverlight.WinRT.cs +++ b/MapControl/MapBase.Silverlight.WinRT.cs @@ -2,14 +2,17 @@ // Copyright © 2014 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; #if WINDOWS_RUNTIME using Windows.Foundation; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Controls; #else using System.Windows; using System.Windows.Media; +using System.Windows.Controls; #endif namespace MapControl @@ -45,7 +48,11 @@ namespace MapControl partial void Initialize() { - Background = new SolidColorBrush(Colors.Transparent); + // set Background by Style to enable resetting by ClearValue in RemoveTileLayers + var style = new Style(typeof(MapBase)); + style.Setters.Add(new Setter(Panel.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); + Style = style; + Clip = new RectangleGeometry(); SizeChanged += OnRenderSizeChanged; @@ -58,11 +65,39 @@ namespace MapControl UpdateTransform(); } + private void SetViewportTransform(Point mapOrigin) + { + viewportTransform.Matrix = Matrix.Identity + .Translate(-mapOrigin.X, -mapOrigin.Y) + .Scale(ViewportScale, -ViewportScale) + .Rotate(Heading) + .Translate(viewportOrigin.X, viewportOrigin.Y); + } + + private void SetTileLayerTransform() + { + var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel); + + tileLayerTransform.Matrix = Matrix.Identity + .Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize) + .Scale(scale, scale) + .Translate(tileLayerOffset.X, tileLayerOffset.Y) + .RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); ; + } + private void SetTransformMatrixes() { - scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d); + scaleTransform.Matrix = Matrix.Identity.Scale(CenterScale, CenterScale); rotateTransform.Matrix = Matrix.Identity.Rotate(Heading); scaleRotateTransform.Matrix = scaleTransform.Matrix.Multiply(rotateTransform.Matrix); } + + private Matrix GetTileIndexMatrix(double scale) + { + return viewportTransform.Matrix + .Invert() // view to map coordinates + .Translate(180d, -180d) + .Scale(scale, -scale); // map coordinates to tile indices + } } } diff --git a/MapControl/MapBase.WPF.cs b/MapControl/MapBase.WPF.cs index 8f03fc95..0f710aab 100644 --- a/MapControl/MapBase.WPF.cs +++ b/MapControl/MapBase.WPF.cs @@ -2,6 +2,7 @@ // Copyright © 2014 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -11,7 +12,7 @@ namespace MapControl public partial class MapBase { public static readonly DependencyProperty ForegroundProperty = - System.Windows.Controls.Control.ForegroundProperty.AddOwner(typeof(MapBase)); + Control.ForegroundProperty.AddOwner(typeof(MapBase)); public static readonly DependencyProperty CenterProperty = DependencyProperty.Register( "Center", typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata( @@ -64,13 +65,50 @@ namespace MapControl UpdateTransform(); } + private void SetViewportTransform(Point mapOrigin) + { + var transform = Matrix.Identity; + transform.Translate(-mapOrigin.X, -mapOrigin.Y); + transform.Scale(ViewportScale, -ViewportScale); + transform.Rotate(Heading); + transform.Translate(viewportOrigin.X, viewportOrigin.Y); + + viewportTransform.Matrix = transform; + } + + private void SetTileLayerTransform() + { + var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel); + var transform = Matrix.Identity; + transform.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize); + transform.Scale(scale, scale); + transform.Translate(tileLayerOffset.X, tileLayerOffset.Y); + transform.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); + + tileLayerTransform.Matrix = transform; + } + private void SetTransformMatrixes() { - Matrix rotateMatrix = Matrix.Identity; + var rotateMatrix = Matrix.Identity; rotateMatrix.Rotate(Heading); rotateTransform.Matrix = rotateMatrix; - scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d); - scaleRotateTransform.Matrix = scaleTransform.Matrix * rotateMatrix; + + var scaleMatrix = Matrix.Identity; + scaleMatrix.Scale(CenterScale, CenterScale); + scaleTransform.Matrix = scaleMatrix; + + scaleRotateTransform.Matrix = scaleMatrix * rotateMatrix; + } + + private Matrix GetTileIndexMatrix(double scale) + { + var transform = viewportTransform.Matrix; + transform.Invert(); // view to map coordinates + transform.Translate(180d, -180d); + transform.Scale(scale, -scale); // map coordinates to tile indices + + return transform; } } } diff --git a/MapControl/MapBase.cs b/MapControl/MapBase.cs index e5f129ff..02ad37e3 100644 --- a/MapControl/MapBase.cs +++ b/MapControl/MapBase.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; #if WINDOWS_RUNTIME @@ -14,6 +15,7 @@ using Windows.UI.Xaml.Media.Animation; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; +using System.Windows.Threading; #endif namespace MapControl @@ -28,17 +30,17 @@ namespace MapControl { private const double MaximumZoomLevel = 22d; - public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( - "TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null, - (o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue))); + public static TimeSpan TileUpdateInterval = TimeSpan.FromSeconds(0.5); + public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.3); + public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; public static readonly DependencyProperty TileLayerProperty = DependencyProperty.Register( "TileLayer", typeof(TileLayer), typeof(MapBase), new PropertyMetadata(null, (o, e) => ((MapBase)o).TileLayerPropertyChanged((TileLayer)e.NewValue))); - public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register( - "TileOpacity", typeof(double), typeof(MapBase), new PropertyMetadata(1d, - (o, e) => ((MapBase)o).tileContainer.Opacity = (double)e.NewValue)); + public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( + "TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null, + (o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue))); public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register( "MinZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(1d, @@ -52,29 +54,32 @@ namespace MapControl "CenterPoint", typeof(Point), typeof(MapBase), new PropertyMetadata(new Point(), (o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue))); - private readonly TileContainer tileContainer = new TileContainer(); + private readonly PanelBase tileLayerPanel = new PanelBase(); + private readonly DispatcherTimer tileUpdateTimer = new DispatcherTimer { Interval = TileUpdateInterval }; private readonly MapTransform mapTransform = new MercatorTransform(); + private readonly MatrixTransform viewportTransform = new MatrixTransform(); + private readonly MatrixTransform tileLayerTransform = new MatrixTransform(); private readonly MatrixTransform scaleTransform = new MatrixTransform(); private readonly MatrixTransform rotateTransform = new MatrixTransform(); private readonly MatrixTransform scaleRotateTransform = new MatrixTransform(); + private Location transformOrigin; private Point viewportOrigin; + private Point tileLayerOffset; private PointAnimation centerAnimation; private DoubleAnimation zoomLevelAnimation; private DoubleAnimation headingAnimation; - private Brush storedBackground; - private Brush storedForeground; private bool internalPropertyChange; public MapBase() { - SetParentMap(); - + Children.Add(tileLayerPanel); TileLayers = new TileLayerCollection(); - Children.Add(tileContainer); - Initialize(); + tileUpdateTimer.Tick += UpdateTiles; Loaded += OnLoaded; + + Initialize(); } partial void Initialize(); @@ -85,6 +90,11 @@ namespace MapControl /// public event EventHandler ViewportChanged; + /// + /// Raised when the TileZoomLevel or TileGrid properties have changed. + /// + public event EventHandler TileGridChanged; + /// /// Gets or sets the map foreground Brush. /// @@ -95,16 +105,7 @@ namespace MapControl } /// - /// Gets or sets the TileLayers used by this Map. - /// - public TileLayerCollection TileLayers - { - get { return (TileLayerCollection)GetValue(TileLayersProperty); } - set { SetValue(TileLayersProperty, value); } - } - - /// - /// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0]. + /// Gets or sets the base TileLayer used by the Map control. /// public TileLayer TileLayer { @@ -113,12 +114,15 @@ namespace MapControl } /// - /// Gets or sets the opacity of the tile layers. + /// Gets or sets optional multiple TileLayers that are used simultaneously. + /// The first element in the collection is equal to the value of the TileLayer property. + /// The additional TileLayers usually have transparent backgrounds and their IsOverlay + /// property is set to true. /// - public double TileOpacity + public TileLayerCollection TileLayers { - get { return (double)GetValue(TileOpacityProperty); } - set { SetValue(TileOpacityProperty, value); } + get { return (TileLayerCollection)GetValue(TileLayersProperty); } + set { SetValue(TileLayersProperty, value); } } /// @@ -208,7 +212,15 @@ namespace MapControl /// public Transform ViewportTransform { - get { return tileContainer.ViewportTransform; } + get { return viewportTransform; } + } + + /// + /// Gets the RenderTransform to be used by TileLayers, with origin at TileGrid.X and TileGrid.Y. + /// + public Transform TileLayerTransform + { + get { return tileLayerTransform; } } /// @@ -245,6 +257,16 @@ namespace MapControl /// public double CenterScale { get; private set; } + /// + /// Gets the zoom level to be used by TileLayers. + /// + public int TileZoomLevel { get; private set; } + + /// + /// Gets the tile grid to be used by TileLayers. + /// + public Int32Rect TileGrid { get; private set; } + /// /// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter. /// @@ -258,7 +280,7 @@ namespace MapControl /// public Point LocationToViewportPoint(Location location) { - return ViewportTransform.Transform(mapTransform.Transform(location)); + return viewportTransform.Transform(mapTransform.Transform(location)); } /// @@ -266,7 +288,7 @@ namespace MapControl /// public Location ViewportPointToLocation(Point point) { - return mapTransform.Transform(ViewportTransform.Inverse.Transform(point)); + return mapTransform.Transform(viewportTransform.Inverse.Transform(point)); } /// @@ -365,105 +387,27 @@ namespace MapControl { if (southWest.Latitude < northEast.Latitude && southWest.Longitude < northEast.Longitude) { - var p1 = MapTransform.Transform(southWest); - var p2 = MapTransform.Transform(northEast); + var p1 = mapTransform.Transform(southWest); + var p2 = mapTransform.Transform(northEast); var lonScale = RenderSize.Width / (p2.X - p1.X) * 360d / TileSource.TileSize; var latScale = RenderSize.Height / (p2.Y - p1.Y) * 360d / TileSource.TileSize; var lonZoom = Math.Log(lonScale, 2d); var latZoom = Math.Log(latScale, 2d); TargetZoomLevel = Math.Min(lonZoom, latZoom); - TargetCenter = MapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d)); + TargetCenter = mapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d)); TargetHeading = 0d; } } - protected override void OnViewportChanged() - { - base.OnViewportChanged(); - - if (ViewportChanged != null) - { - ViewportChanged(this, EventArgs.Empty); - } - } - private void OnLoaded(object sender, RoutedEventArgs e) { Loaded -= OnLoaded; - if (TileLayer == null) + if (tileLayerPanel.Children.Count == 0 && !Children.OfType().Any()) { TileLayer = TileLayer.Default; } - - UpdateTransform(); - } - - private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Remove: - tileContainer.RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count); - break; -#if !SILVERLIGHT - case NotifyCollectionChangedAction.Move: -#endif - case NotifyCollectionChangedAction.Replace: - tileContainer.RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count); - tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Reset: - tileContainer.ClearTileLayers(); - if (e.NewItems != null) - { - tileContainer.AddTileLayers(0, e.NewItems.Cast()); - } - break; - - default: - break; - } - - var firstTileLayer = TileLayers.FirstOrDefault(); - - if (TileLayer != firstTileLayer) - { - TileLayer = firstTileLayer; - } - } - - private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) - { - tileContainer.ClearTileLayers(); - - if (oldTileLayers != null) - { - oldTileLayers.CollectionChanged -= TileLayerCollectionChanged; - } - - if (newTileLayers != null) - { - newTileLayers.CollectionChanged += TileLayerCollectionChanged; - tileContainer.AddTileLayers(0, newTileLayers); - - var firstTileLayer = TileLayers.FirstOrDefault(); - - if (TileLayer != firstTileLayer) - { - TileLayer = firstTileLayer; - } - } - else - { - TileLayer = null; - } } private void TileLayerPropertyChanged(TileLayer tileLayer) @@ -484,35 +428,94 @@ namespace MapControl TileLayers[0] = tileLayer; } } + } - if (tileLayer != null && tileLayer.Background != null) + private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) + { + if (oldTileLayers != null) { - if (storedBackground == null) + oldTileLayers.CollectionChanged -= TileLayerCollectionChanged; + RemoveTileLayers(0, oldTileLayers.Count); + } + + if (newTileLayers != null) + { + AddTileLayers(0, newTileLayers); + newTileLayers.CollectionChanged += TileLayerCollectionChanged; + } + + TileLayer = TileLayers.FirstOrDefault(); + } + + private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count); + break; +#if !SILVERLIGHT + case NotifyCollectionChangedAction.Move: +#endif + case NotifyCollectionChangedAction.Replace: + RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count); + AddTileLayers(e.NewStartingIndex, e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + if (e.OldItems != null) + { + RemoveTileLayers(0, e.OldItems.Count); + } + if (e.NewItems != null) + { + AddTileLayers(0, e.NewItems.Cast()); + } + break; + + default: + break; + } + + TileLayer = TileLayers.FirstOrDefault(); + } + + private void AddTileLayers(int index, IEnumerable tileLayers) + { + foreach (var tileLayer in tileLayers) + { + if (index == 0) { - storedBackground = Background; + if (tileLayer.Background != null) + { + Background = tileLayer.Background; + } + + if (tileLayer.Foreground != null) + { + Foreground = tileLayer.Foreground; + } } - Background = tileLayer.Background; + tileLayerPanel.Children.Insert(index++, tileLayer); } - else if (storedBackground != null) + } + + private void RemoveTileLayers(int index, int count) + { + while (count-- > 0) { - Background = storedBackground; - storedBackground = null; + tileLayerPanel.Children.RemoveAt(index + count); } - if (tileLayer != null && tileLayer.Foreground != null) + if (index == 0) { - if (storedForeground == null) - { - storedForeground = Foreground; - } - - Foreground = tileLayer.Foreground; - } - else if (storedForeground != null) - { - Foreground = storedForeground; - storedForeground = null; + ClearValue(BackgroundProperty); + ClearValue(ForegroundProperty); } } @@ -552,7 +555,7 @@ namespace MapControl if (centerAnimation == null) { InternalSetValue(TargetCenterProperty, center); - InternalSetValue(CenterPointProperty, MapTransform.Transform(center)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(center)); } } } @@ -573,10 +576,10 @@ namespace MapControl // animate private CenterPoint property by PointAnimation centerAnimation = new PointAnimation { - From = MapTransform.Transform(Center), - To = MapTransform.Transform(targetCenter, Center.Longitude), - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + From = mapTransform.Transform(Center), + To = mapTransform.Transform(targetCenter, Center.Longitude), + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -594,7 +597,7 @@ namespace MapControl centerAnimation = null; InternalSetValue(CenterProperty, TargetCenter); - InternalSetValue(CenterPointProperty, MapTransform.Transform(TargetCenter)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter)); RemoveAnimation(CenterPointProperty); // remove holding animation in WPF ResetTransformOrigin(); @@ -607,7 +610,7 @@ namespace MapControl if (!internalPropertyChange) { centerPoint.X = Location.NormalizeLongitude(centerPoint.X); - InternalSetValue(CenterProperty, MapTransform.Transform(centerPoint)); + InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint)); ResetTransformOrigin(); UpdateTransform(); } @@ -680,8 +683,8 @@ namespace MapControl zoomLevelAnimation = new DoubleAnimation { To = targetZoomLevel, - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -755,8 +758,8 @@ namespace MapControl headingAnimation = new DoubleAnimation { By = delta, - Duration = Settings.MapAnimationDuration, - EasingFunction = Settings.MapAnimationEasingFunction, + Duration = AnimationDuration, + EasingFunction = AnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -784,7 +787,12 @@ namespace MapControl { Location center; - if (transformOrigin != null) + if (transformOrigin == null) + { + center = Center; + SetViewportTransform(center); + } + else { SetViewportTransform(transformOrigin); @@ -802,7 +810,7 @@ namespace MapControl if (centerAnimation == null) { InternalSetValue(TargetCenterProperty, center); - InternalSetValue(CenterPointProperty, MapTransform.Transform(center)); + InternalSetValue(CenterPointProperty, mapTransform.Transform(center)); } if (resetTransformOrigin) @@ -811,11 +819,6 @@ namespace MapControl SetViewportTransform(center); } } - else - { - center = Center; - SetViewportTransform(center); - } CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude @@ -823,9 +826,77 @@ namespace MapControl OnViewportChanged(); } + protected override void OnViewportChanged() + { + base.OnViewportChanged(); + + var viewportChanged = ViewportChanged; + + if (viewportChanged != null) + { + viewportChanged(this, EventArgs.Empty); + } + } + private void SetViewportTransform(Location origin) { - ViewportScale = tileContainer.SetViewportTransform(ZoomLevel, Heading, mapTransform.Transform(origin), viewportOrigin); + var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / ViewportScale - 180d; + var mapOrigin = mapTransform.Transform(origin); + + ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d; + SetViewportTransform(mapOrigin); + + tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * ViewportScale; + tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * ViewportScale; + + if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d) + { + // immediately handle map origin leap when map center moves across 180° longitude + UpdateTiles(this, EventArgs.Empty); + } + else + { + SetTileLayerTransform(); + tileUpdateTimer.Start(); + } + } + + private void UpdateTiles(object sender, object e) + { + tileUpdateTimer.Stop(); + + // relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels) + var zoomLevelSwitchDelta = Math.Log(0.75, 2d); + var zoomLevel = (int)Math.Floor(ZoomLevel - zoomLevelSwitchDelta); + var transform = GetTileIndexMatrix((double)(1 << zoomLevel) / 360d); + + // tile indices of visible rectangle + var p1 = transform.Transform(new Point(0d, 0d)); + var p2 = transform.Transform(new Point(RenderSize.Width, 0d)); + var p3 = transform.Transform(new Point(0d, RenderSize.Height)); + var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height)); + + // index ranges of visible tiles + var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)))); + var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)))); + var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X)))); + var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y)))); + var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + + if (TileZoomLevel != zoomLevel || TileGrid != grid) + { + TileZoomLevel = zoomLevel; + TileGrid = grid; + + SetTileLayerTransform(); + + var tileGridChanged = TileGridChanged; + + if (tileGridChanged != null) + { + tileGridChanged(this, EventArgs.Empty); + } + } } } } diff --git a/MapControl/MapControl.PhoneSilverlight.csproj b/MapControl/MapControl.PhoneSilverlight.csproj index 958a2856..82a855b8 100644 --- a/MapControl/MapControl.PhoneSilverlight.csproj +++ b/MapControl/MapControl.PhoneSilverlight.csproj @@ -23,7 +23,7 @@ true full false - Bin\Debug + bin\Debug\ DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE true true @@ -33,7 +33,7 @@ none true - Bin\Release + bin\Release\ TRACE;SILVERLIGHT;WINDOWS_PHONE true true @@ -48,6 +48,7 @@ + @@ -78,13 +79,11 @@ - - - + diff --git a/MapControl/MapControl.Silverlight.csproj b/MapControl/MapControl.Silverlight.csproj index ed73399e..5b4043e2 100644 --- a/MapControl/MapControl.Silverlight.csproj +++ b/MapControl/MapControl.Silverlight.csproj @@ -75,6 +75,7 @@ + @@ -105,13 +106,11 @@ - - - + diff --git a/MapControl/MapControl.WPF.csproj b/MapControl/MapControl.WPF.csproj index 747fc7b7..28b705fa 100644 --- a/MapControl/MapControl.WPF.csproj +++ b/MapControl/MapControl.WPF.csproj @@ -58,6 +58,7 @@ + @@ -77,7 +78,6 @@ - @@ -86,15 +86,14 @@ + - - - + diff --git a/MapControl/MapImage.cs b/MapControl/MapImage.cs index ae0ed05a..b04a6d4e 100644 --- a/MapControl/MapImage.cs +++ b/MapControl/MapImage.cs @@ -19,7 +19,7 @@ namespace MapControl { public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( "Source", typeof(ImageSource), typeof(MapImage), - new PropertyMetadata(null, (o, e) => ((MapImage)o).SourceChanged((ImageSource)e.NewValue))); + new PropertyMetadata(null, (o, e) => ((MapImage)o).SourcePropertyChanged((ImageSource)e.NewValue))); public ImageSource Source { @@ -27,7 +27,7 @@ namespace MapControl set { SetValue(SourceProperty, value); } } - private void SourceChanged(ImageSource image) + private void SourcePropertyChanged(ImageSource image) { Fill = new ImageBrush { diff --git a/MapControl/MapImageLayer.cs b/MapControl/MapImageLayer.cs index 81ebd005..3205a4f1 100644 --- a/MapControl/MapImageLayer.cs +++ b/MapControl/MapImageLayer.cs @@ -40,7 +40,7 @@ namespace MapControl Children.Add(new MapImage { Opacity = 0d }); Children.Add(new MapImage { Opacity = 0d }); - updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval }; + updateTimer = new DispatcherTimer { Interval = MapBase.TileUpdateInterval }; updateTimer.Tick += (s, e) => UpdateImage(); } @@ -59,7 +59,7 @@ namespace MapControl /// /// Relative size of the map images in relation to the current viewport size. /// Setting a value greater than one will let MapImageLayer request images that - /// are larger than the viewport, in order to support smooth panning. + /// are larger than the viewport, in order to support smooth panning. /// public double RelativeImageSize { @@ -175,16 +175,16 @@ namespace MapControl if (topImage.Source != null) { topImage.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration }); + new DoubleAnimation { To = 1d, Duration = Tile.OpacityAnimationDuration }); } if (bottomImage.Source != null) { - var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Settings.TileAnimationDuration }; + var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Tile.OpacityAnimationDuration }; if (topImage.Source != null) { - fadeOutAnimation.BeginTime = Settings.TileAnimationDuration; + fadeOutAnimation.BeginTime = Tile.OpacityAnimationDuration; } bottomImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation); diff --git a/MapControl/MapItemsControl.WPF.cs b/MapControl/MapItemsControl.WPF.cs index af165538..e368d87c 100644 --- a/MapControl/MapItemsControl.WPF.cs +++ b/MapControl/MapItemsControl.WPF.cs @@ -3,12 +3,9 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; namespace MapControl { @@ -17,10 +14,6 @@ namespace MapControl /// public class MapItemsControl : ListBox { - public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.Register( - "SelectionGeometry", typeof(Geometry), typeof(MapItemsControl), - new PropertyMetadata((o, e) => ((MapItemsControl)o).SelectionGeometryPropertyChanged((Geometry)e.NewValue))); - static MapItemsControl() { DefaultStyleKeyProperty.OverrideMetadata( @@ -43,58 +36,6 @@ namespace MapControl return item is MapItem; } - /// - /// Gets or sets a Geometry that selects all items inside its fill area, i.e. - /// where Geometry.FillContains returns true for the item's viewport position. - /// - public Geometry SelectionGeometry - { - get { return (Geometry)GetValue(SelectionGeometryProperty); } - set { SetValue(SelectionGeometryProperty, value); } - } - - public object GetFirstItemInGeometry(Geometry geometry) - { - if (geometry == null || geometry.IsEmpty()) - { - return null; - } - - return Items.Cast().FirstOrDefault(i => IsItemInGeometry(i, geometry)); - } - - public IList GetItemsInGeometry(Geometry geometry) - { - if (geometry == null || geometry.IsEmpty()) - { - return null; - } - - return Items.Cast().Where(i => IsItemInGeometry(i, geometry)).ToList(); - } - - private bool IsItemInGeometry(object item, Geometry geometry) - { - var container = ItemContainerGenerator.ContainerFromItem(item) as UIElement; - Point? viewportPosition; - - return container != null && - (viewportPosition = MapPanel.GetViewportPosition(container)).HasValue && - geometry.FillContains(viewportPosition.Value); - } - - private void SelectionGeometryPropertyChanged(Geometry geometry) - { - if (SelectionMode == SelectionMode.Single) - { - SelectedItem = GetFirstItemInGeometry(geometry); - } - else - { - SetSelectedItems(GetItemsInGeometry(geometry)); - } - } - private void CurrentItemChanging(object sender, CurrentChangingEventArgs e) { var container = ItemContainerGenerator.ContainerFromItem(Items.CurrentItem) as UIElement; diff --git a/MapControl/MapPanel.Silverlight.WinRT.cs b/MapControl/MapPanel.Silverlight.WinRT.cs index 3f261587..dc6b0352 100644 --- a/MapControl/MapPanel.Silverlight.WinRT.cs +++ b/MapControl/MapPanel.Silverlight.WinRT.cs @@ -4,11 +4,9 @@ #if WINDOWS_RUNTIME using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; #else using System.Windows; -using System.Windows.Controls; using System.Windows.Media; #endif @@ -21,7 +19,11 @@ namespace MapControl public MapPanel() { - if (!(this is MapBase)) + if (this is MapBase) + { + SetValue(ParentMapProperty, this); + } + else { AddParentMapHandlers(this); } @@ -67,10 +69,5 @@ namespace MapControl return parentMap; } - - internal void SetParentMap() - { - SetValue(ParentMapProperty, this); - } } } diff --git a/MapControl/MapPanel.WPF.cs b/MapControl/MapPanel.WPF.cs index 7259136f..b49645f0 100644 --- a/MapControl/MapPanel.WPF.cs +++ b/MapControl/MapPanel.WPF.cs @@ -14,14 +14,17 @@ namespace MapControl public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty; + public MapPanel() + { + if (this is MapBase) + { + SetValue(ParentMapPropertyKey, this); + } + } + public static MapBase GetParentMap(UIElement element) { return (MapBase)element.GetValue(ParentMapProperty); } - - internal void SetParentMap() - { - SetValue(ParentMapPropertyKey, this); - } } } diff --git a/MapControl/MapPanel.cs b/MapControl/MapPanel.cs index 42a8b580..00699a1a 100644 --- a/MapControl/MapPanel.cs +++ b/MapControl/MapPanel.cs @@ -24,9 +24,6 @@ namespace MapControl public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached( "Location", typeof(Location), typeof(MapPanel), new PropertyMetadata(null, LocationPropertyChanged)); - public static readonly DependencyProperty ViewportPositionProperty = DependencyProperty.RegisterAttached( - "ViewportPosition", typeof(Point?), typeof(MapPanel), null); - public static Location GetLocation(UIElement element) { return (Location)element.GetValue(LocationProperty); @@ -37,21 +34,12 @@ namespace MapControl element.SetValue(LocationProperty, value); } - public static Point? GetViewportPosition(UIElement element) - { - return (Point?)element.GetValue(ViewportPositionProperty); - } - private MapBase parentMap; public MapBase ParentMap { get { return parentMap; } - } - - void IMapElement.SetParentMap(MapBase map) - { - SetParentMapOverride(map); + set { SetParentMapOverride(value); } } protected virtual void SetParentMapOverride(MapBase map) @@ -114,7 +102,7 @@ namespace MapControl if (mapElement != null) { - mapElement.SetParentMap(e.NewValue as MapBase); + mapElement.ParentMap = e.NewValue as MapBase; } } @@ -149,12 +137,10 @@ namespace MapControl { var mapPosition = parentMap.MapTransform.Transform(location, parentMap.Center.Longitude); // nearest to center longitude viewportPosition = parentMap.ViewportTransform.Transform(mapPosition); - element.SetValue(ViewportPositionProperty, viewportPosition); } else { viewportPosition = new Point(); - element.ClearValue(ViewportPositionProperty); } var translateTransform = element.RenderTransform as TranslateTransform; @@ -189,7 +175,7 @@ namespace MapControl private static void ArrangeElementWithLocation(UIElement element) { - var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height); + var rect = new Rect(new Point(), element.DesiredSize); var frameworkElement = element as FrameworkElement; if (frameworkElement != null) @@ -228,7 +214,7 @@ namespace MapControl private static void ArrangeElementWithoutLocation(UIElement element, Size parentSize) { - var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height); + var rect = new Rect(new Point(), element.DesiredSize); var frameworkElement = element as FrameworkElement; if (frameworkElement != null) diff --git a/MapControl/MapPath.cs b/MapControl/MapPath.cs index a25a5d28..ec59aea4 100644 --- a/MapControl/MapPath.cs +++ b/MapControl/MapPath.cs @@ -20,12 +20,11 @@ namespace MapControl public MapBase ParentMap { get { return parentMap; } - } - - void IMapElement.SetParentMap(MapBase map) - { - parentMap = map; - UpdateData(); + set + { + parentMap = value; + UpdateData(); + } } protected virtual void UpdateData() diff --git a/MapControl/MapPolyline.Silverlight.WinRT.cs b/MapControl/MapPolyline.Silverlight.WinRT.cs index d921ec58..65a85d32 100644 --- a/MapControl/MapPolyline.Silverlight.WinRT.cs +++ b/MapControl/MapPolyline.Silverlight.WinRT.cs @@ -7,7 +7,6 @@ using System.Linq; using Windows.UI.Xaml.Media; #else using System.Windows.Media; - #endif namespace MapControl diff --git a/MapControl/MapRectangle.cs b/MapControl/MapRectangle.cs index cf244559..39e21eff 100644 --- a/MapControl/MapRectangle.cs +++ b/MapControl/MapRectangle.cs @@ -7,7 +7,6 @@ using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; #else -using System; using System.Windows; using System.Windows.Media; #endif @@ -82,10 +81,9 @@ namespace MapControl !double.IsNaN(West) && !double.IsNaN(East) && South < North && West < East) { - var p1 = ParentMap.MapTransform.Transform(new Location(South, West)); - var p2 = ParentMap.MapTransform.Transform(new Location(North, East)); - - SetRect(new Rect(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y)); + SetRect(new Rect( + ParentMap.MapTransform.Transform(new Location(South, West)), + ParentMap.MapTransform.Transform(new Location(North, East)))); } else { diff --git a/MapControl/PanelBase.cs b/MapControl/PanelBase.cs index b895a4d6..e73efd0d 100644 --- a/MapControl/PanelBase.cs +++ b/MapControl/PanelBase.cs @@ -14,7 +14,7 @@ using System.Windows.Controls; namespace MapControl { /// - /// Common base class for MapPanel, TileLayer and TileContainer. + /// Common base class for MapPanel and TileLayer. /// public class PanelBase : Panel { @@ -32,9 +32,9 @@ namespace MapControl protected override Size ArrangeOverride(Size finalSize) { - foreach (UIElement child in Children) + foreach (UIElement element in Children) { - child.Arrange(new Rect(new Point(), finalSize)); + element.Arrange(new Rect(new Point(), finalSize)); } return finalSize; diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index a997374a..b1552621 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -17,8 +17,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.3.1")] -[assembly: AssemblyFileVersion("2.3.1")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Settings.cs b/MapControl/Settings.cs deleted file mode 100644 index 0938f846..00000000 --- a/MapControl/Settings.cs +++ /dev/null @@ -1,32 +0,0 @@ -// XAML Map Control - http://xamlmapcontrol.codeplex.com/ -// Copyright © 2014 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using System; -#if WINDOWS_RUNTIME -using Windows.UI.Xaml.Media.Animation; -#else -using System.Windows.Media.Animation; -#endif - -namespace MapControl -{ - /// - /// Stores global static properties that control the behaviour of the map control. - /// - public static class Settings - { - public static TimeSpan TileUpdateInterval { get; set; } - public static TimeSpan TileAnimationDuration { get; set; } - public static TimeSpan MapAnimationDuration { get; set; } - public static EasingFunctionBase MapAnimationEasingFunction { get; set; } - - static Settings() - { - TileUpdateInterval = TimeSpan.FromSeconds(0.5); - TileAnimationDuration = TimeSpan.FromSeconds(0.3); - MapAnimationDuration = TimeSpan.FromSeconds(0.3); - MapAnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; - } - } -} diff --git a/MapControl/Themes/Generic.xaml b/MapControl/Themes/Generic.xaml index 526596ba..9bb30526 100644 --- a/MapControl/Themes/Generic.xaml +++ b/MapControl/Themes/Generic.xaml @@ -1,4 +1,4 @@ -