From 4448d368c44cc39c3dc63aacdf29d5769ca7caf1 Mon Sep 17 00:00:00 2001 From: yysong <2306845914@qq.com> Date: Mon, 22 Jan 2024 20:51:48 +0800 Subject: [PATCH] ADD --- src/tool/BackupUtils.java | 347 ++++++++++++++++++++++++++++++++ src/tool/何家齐用例图.jpg | Bin 0 -> 15283 bytes 2 files changed, 347 insertions(+) create mode 100644 src/tool/BackupUtils.java create mode 100644 src/tool/何家齐用例图.jpg diff --git a/src/tool/BackupUtils.java b/src/tool/BackupUtils.java new file mode 100644 index 0000000..e383ec3 --- /dev/null +++ b/src/tool/BackupUtils.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + + +public class BackupUtils { + private static final String TAG = "BackupUtils"; + // Singleton stuff + private static BackupUtils sInstance; + + public static synchronized BackupUtils getInstance(Context context) { + //ynchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A) + //运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。 + //它包括两种用法:synchronized 方法和 synchronized 块。 + if (sInstance == null) { + //如果当前备份不存在,则新声明一个 + sInstance = new BackupUtils(context); + } + return sInstance; + } + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted SD卡没有被装入手机 + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // The backup file not exist 备份文件夹不存在 + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // The data is not well formated, may be changed by other programs 数据已被破坏,可能被修改 + public static final int STATE_DATA_DESTROIED = 2; + // Some run-time exception which causes restore or backup fails 超时异常 + public static final int STATE_SYSTEM_ERROR = 3; + // Backup or restore success 成功存储 + public static final int STATE_SUCCESS = 4; + + private TextExport mTextExport; + + private BackupUtils(Context context) { //初始化函数 + mTextExport = new TextExport(context); + } + + private static boolean externalStorageAvailable() { //外部存储功能是否可用 + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + public int exportToText() { + return mTextExport.exportToText(); + } + + public String getExportedTextFileName() { + return mTextExport.mFileName; + } + + public String getExportedTextFileDir() { + return mTextExport.mFileDirectory; + } + + private static class TextExport { + private static final String[] NOTE_PROJECTION = { + NoteColumns.ID, + NoteColumns.MODIFIED_DATE, + NoteColumns.SNIPPET, + NoteColumns.TYPE + }; + + private static final int NOTE_COLUMN_ID = 0; + + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + + private static final int NOTE_COLUMN_SNIPPET = 2; + + private static final String[] DATA_PROJECTION = { + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + private static final int DATA_COLUMN_CONTENT = 0; + + private static final int DATA_COLUMN_MIME_TYPE = 1; + + private static final int DATA_COLUMN_CALL_DATE = 2; + + private static final int DATA_COLUMN_PHONE_NUMBER = 4; + + private final String [] TEXT_FORMAT; + private static final int FORMAT_FOLDER_NAME = 0; + private static final int FORMAT_NOTE_DATE = 1; + private static final int FORMAT_NOTE_CONTENT = 2; + + private Context mContext; + private String mFileName; + private String mFileDirectory; + + public TextExport(Context context) { + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + mContext = context; + mFileName = ""; //为什么为空? + mFileDirectory = ""; + } + + private String getFormat(int id) { //获取文本的组成部分 + return TEXT_FORMAT[id]; + } + + /** + * Export the folder identified by folder id to text + */ + private void exportFolderToText(String folderId, PrintStream ps) { + // Query notes belong to this folder 通过查询parent id是文件夹id的note来选出制定ID文件夹下的Note + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { + folderId + }, null); + + if (notesCursor != null) { + if (notesCursor.moveToFirst()) { + do { + // Print note's last modified date ps里面保存有这份note的日期 + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = notesCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); //将文件导出到text + } while (notesCursor.moveToNext()); + } + notesCursor.close(); + } + } + + /** + * Export note identified by id to a print stream + */ + private void exportNoteToText(String noteId, PrintStream ps) { + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { + noteId + }, null); + + if (dataCursor != null) { //利用光标来扫描内容,区别为callnote和note两种,靠ps.printline输出 + if (dataCursor.moveToFirst()) { + do { + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + if (DataConstants.CALL_NOTE.equals(mimeType)) { + // Print phone number + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + String location = dataCursor.getString(DATA_COLUMN_CONTENT); + + if (!TextUtils.isEmpty(phoneNumber)) { //判断是否为空字符 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + phoneNumber)); + } + // Print call date + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + .format(mContext.getString(R.string.format_datetime_mdhm), + callDate))); + // Print call attachment location + if (!TextUtils.isEmpty(location)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + location)); + } + } else if (DataConstants.NOTE.equals(mimeType)) { + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + if (!TextUtils.isEmpty(content)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + content)); + } + } + } while (dataCursor.moveToNext()); + } + dataCursor.close(); + } + // print a line separator between note + try { + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { //总函数,调用上面的exportFolder和exportNote + if (!externalStorageAvailable()) { + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; + } + + PrintStream ps = getExportToTextPrintStream(); + if (ps == null) { + Log.e(TAG, "get print stream error"); + return STATE_SYSTEM_ERROR; + } + // First export folder and its notes 导出文件夹,就是导出里面包含的便签 + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + + if (folderCursor != null) { + if (folderCursor.moveToFirst()) { + do { + // Print folder's name + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } + if (!TextUtils.isEmpty(folderName)) { + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + exportFolderToText(folderId, ps); + } while (folderCursor.moveToNext()); + } + folderCursor.close(); + } + + // Export notes in root's folder 将根目录里的便签导出(由于不属于任何文件夹,因此无法通过文件夹导出来实现这一部分便签的导出) + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); + + if (noteCursor != null) { + if (noteCursor.moveToFirst()) { + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (noteCursor.moveToNext()); + } + noteCursor.close(); + } + ps.close(); + + return STATE_SUCCESS; + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); + if (file == null) { + Log.e(TAG, "create file to exported failed"); + return null; + } + mFileName = file.getName(); + mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; + try { + FileOutputStream fos = new FileOutputStream(file); + ps = new PrintStream(fos); //将ps输出流输出到特定的文件,目的就是导出到文件,而不是直接输出 + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + e.printStackTrace(); + return null; + } + return ps; + } + } + + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + StringBuilder sb = new StringBuilder(); + sb.append(Environment.getExternalStorageDirectory()); //外部(SD卡)的存储路径 + sb.append(context.getString(filePathResId)); //文件的存储路径 + File filedir = new File(sb.toString()); //filedir应该就是用来存储路径信息 + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString()); + + try { //如果这些文件不存在,则新建 + if (!filedir.exists()) { + filedir.mkdir(); + } + if (!file.exists()) { + file.createNewFile(); + } + return file; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } +// try catch 异常处理 + return null; + } +} + diff --git a/src/tool/何家齐用例图.jpg b/src/tool/何家齐用例图.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b628c58183d6026609f67c432478650e720fc9d8 GIT binary patch literal 15283 zcmeHu2T)Ymw)TMr8WG7kOBN*O43b3zBsWolB101- zDmfcTza?()HaQ^87-3AcjVvJ)lf-sl? zOkxZWF$TH=U_8H0Y>b}{;Fkjf6LfyPVB8CM7tc?qAp$TlKp;#k5H>az*7@0i=idWZ z#MmU41r%^db#8;1J;($@6JOx6T&s9Reye|rRmjRS>;fJIB^5Od8#@Q*6)s^BQ894| zNkt`P6;(C$>$-aS28Kq)5NjLTJ9hRCj(5GF-afv5{`Vh*M?^l1icU&?oRXUMqWyJ8XIFR6o8E!Jq2ZCyv2o<=-2B4g=PygkE89D}d;156 zM_-S>*@Xdsem3j;@6U$)$}Zw_yD+h^Kv>{!c41)poDUE&7WQQU91;Z`@NExLX2DQg zvTKPiDqdY+5xTWSZspmJN5LvQ%eMW^w4W^d*9;5$OP2l9u>Z0P3E+b;&Tk%w7?1;w z3!*u*G5<6EjXj8-o1?WqoP0;8ezr`-dFDje-oiOpb3_d*FPpZ$8vU|cFbx@RDE>Zx zRG$74c}n2iCEoUhiD`3HvJ#AY_iOq6U7c^=onY17m$`d$ke=jV(c1ggto+q$@n!A= zl#|=?GWv zLkA691izNsBX&E*$v^`S;kkWU8+?nZ|5^J#d-8wJPe60v2o0_{rNxIJ(Vft&lh23* z9=ZC~6wgm2{i+XKTl7!fOq{)tr#E5>QNUt&pmQU{3b#8aVNvTZ@O|)n}DK0E9%n@$5e1r)-e$r{`1TX4Ky>N9&mz+ zW6o{`VoPRF9_OI}FWn*_Cz|um4p8!z-06c;4K!f!>4Xzr*&qi(13g+Dt@~$apfY;( zPcQhp7Xa6t^Er(g4c=5m126DaRv#ZdM+3~zPbd9oVChG)VWDiA8QY%rVaJT9LV7=4_$8*o~qVX-ZnBJ(VW zKC<1A^4P38^&o$f^+bwWZ9+R6Y2oeZhYa`#uIks@3m_j9Ub;G;?w3SdWA1mcMGCV! z`Dj?v=X!=N^1eY!WnCA8)U~1yXaMx;HX6WS4iPqA!TqAf;G61mQjvy{u_QG#+;++_ z;MJd7Tgs3{=CkubT4ixOKUGQH7=w4AqQ?TVS%gukmdGiH$YuqM=2;KmX7}{HGWo0e z`Gg%@=9-Kl7n3Dx?^PtHfI3-#BDZlc=pB5#r;oP`Wl_W*`pze7Dg+W5lC*9b1r*2s$`-EJF^sWs21b0MuD>5k{ z=1$|2_qc`=u|#iiS&j_dZWz|;G6>rl+T7~6?k2?G!3{N$+w^eLVhbjHN4y%5+=?ta?}r;egHY&|{1b_6V7-=YHjV)Ug*= zv#OP3XszH{XS=31V`gAMqn#rf#TqWqJO!`gyt=0yRqqWQbNO;I6+Xy=M~9cPF)Q|s zYa*XaETj6tw1&3tjKV}?xa#9o{Ai`)mJ+DA$Z&W_Ij-d1`a|{;R?P-l%s%k~hfH)!__xf)yL0IABg6ad5!b|Mj5uzanVq zYZ~g*43yb6wLDhEvS!U%kkORVX{j4@&Tni^u?l0GeB0H)BiK9NS-0(Y+@VR4Mm%L} zg9bX$fF_;)F&?lYtZ^{3H|9ozG;tFqVM*J>$h>eI-7J4zowt!`V<`Wd!?dtt^D8MF z4NNDXfotT$qgS@2P)e(kd62rkh&RQ_AjRQkcnMOtH5*F~f3I1Q-x@Z^KK;0ZLJX^a7~z|_)(&aQ+s5<>P3ep+hB&= zVx?_+?{&n$aF^`IPrLoyh1u{ad2N6SGhDlbTpkYxpf9%n-Hi5U((Qk@Bk6Lai!=MN zL)AV~+_4V9z8YVVEA>)eCrL}el)D-w(Rzhe*qplVo)E#MRf5-N318%?WXWHmfd`Ey zBQ_4LbD5Nx4iDX3X+gBDQWr1_i?q$c3g$=TMN5B={T&_He_#LxS8Ko$1vO%1F2UEAL6B zDcn2*FplOzM2gC#N1+$2wm_Q2D`E%7DRE(@Q=K-mtX6%G#E4gLdzePVUh`3!%AgW+ zJM&d)ArDB*c)AZ7nxBws8}+E|GghFWOHj03p zCb7B57!hM>H@n#^f={oYQ1`dV3fSGLBirPCLKWDGycpo*H*<(3Y1dRaB95*7Av9oL zpDCqWQ)h?;SBfy?2~BK2^KJVaB100*9WormB)*M$Iva=AJE|M-*slJWIT^(({{<|j zXWn4B>%itxIm>ox_TjPfD3WI#_0-D^@?L6u-#cO$FEN$tUG>5OHlC-lKkPx2$YVmX zRnKr7b_0CI_y#(<6K$+L!waxe#eOeFQuFbZ<7g9QCw#>|q*x}KnFen@JqU^C7yBA5lsQQQtQQr*crBZ1qvuA9K8NY!C#k$ji7aA%$-A!! zl~%`&mGpjc)#R#O(&^W?_Ys{{qmv)O>dJl=(f6DV#`d@aI7Lz$S`ewjIhDaoIM>>e zS@44|h=au?O3?rg@4nQToM}UA10uEWL}9j4qqymdl011!P??w=<8@#U!YyY!4BLA` zqVaIUme)JadrP{kdk~L6wh*)7jIkqqx}m7jcq!3nOp*P~LQ8|LxHKJZZVwoX!KC#% zu*R8_TYr~MP`tZ0Z&#i~$DDiqV#l^t#QR*)L+DEuig6n6~bdP*xBAWyB z0Wuxse-85AdpFTQqU7e;5*paMY-Z+}McTS>vk?vW%bnqBgLQu?D2gk7j@*Czs27$v z5-*P=O{&ukJnXuzK#hiOtG1q>M<2?(Sst=IR$Wd`D~TBez*JaP-uXrfj*` z=*`t6d6J8s?2L^nZD5AZmYO}ybHhx{Yl^0PleLqTK0eQ_iCu4Lv6x}peMflMtmO!3 z8f?sWrtLV;NW&ZnklYSTIW&}{P4GfbfI2aoyZeJyGS10h;_291;HFyuS@@}v3CqhWf~)ls=&Sj&C; zfV#~>mh-L<8OD&KWL#zjMmlPR);DrnZ8rD}+SeLeIFu)FWTIX_1U@}|EByrAqShBg z?xwY6kme?iXdziKY`%9gx=RokIunG6(8rL+13;nCK*!cT9Fg327rsKI@tKzbIn7f{ z{6YI1(~ksqUJ{(w-FntO0*!*1@%9S38Y|g(w+o7k!)tR8p}esCu-i`-uf+<*hVk2j z+FL{}iJ1=yYwRUaxk0&&*5@?(?Cj0%Alo8m6^j>aP~_9T7u(Wg=||XyYGf=!ubl_k_xPQ|~Be-5k7( z`w`ZwWY;j}@jU-!=`(*3z~<=gjpiiU2V3Vpxt?DIRlhZJu~c1M+cWy+0v3^bnLzp; zH-@+Ku!v~rhDRiSv7Vf~I?Jxe2VaX-RND;sP<&NcjZRD!|1Zv#&kiL%G(*Ms zm7$>59yA&kV+AE63>Y5J09#t5Vh@BZ7)+23lB|&RqZBwViz0P2yCt)pbu2ywD?Vu! z%$ugA%GPaLRdaBk+o;U4GU0>COTi?HTZy@~IIojBY-Z-~n;7g@f-Iyeo{XKJnd5Rf`*)P&-jHt$ShvSYG*HDb(GNeaSjPODExd zlZDR6?fRpboM1bL)j}hO82|ULQdq5CKKm?isLSRj6P#6M4^e{fR6&rMQjq&ka#9W- zsvhcPdY_PZAd7rs+Gz|BbuQAf%2Pu)NR1-E_=ZiGp|?yCsHb47S2i?Pvs9!DW^F@I zcsE}<3Lm!;oM^V&N7P{_acua~msT$Wveg`S*7YRa;#o#+jj>WF3yMLWs%Jf(OGdF> z()C4=s)u)&5yi*xH;yYbS9F=;dj01ai(!pYYFA;mkndT;aJz=$Mes^wfOUBW(m9Xd z0^jzDo5N*RL(ch4sPUnVHKseMoH`+KtmgT5>k2J{Aba#zU^T%HqskmM>L6YSi6~x{ zueluWZyCxqd99kMq`eq-s54B{=5C%E$MIYA+?esAmI2nlbR*~8)G*<%yai1IynMnY z%QwGdXeC>E@7pltGM1zREAskO0LB`3r?kln$chEc-bn({Boy%R-`aY@kZ#N?x^d2q?KrMhk$Cef6%&0Fip}Y#p>#&=9a2ki13dXa5f%p zK$7##tm>f(HsG6;Ak9a5s?sk!;S^|XsF5)a&m8fKrEtF^loU_7i793Snr`lsAAosj zGLPPT`B2%-HA7#4V=_sLH90x`)zCf0yTy*8hiREC!%C?qO?e^8Bn2u2xSCTknojoV z`2KncOf5t3JSb9gcrvMcCU1r@JNuF`t6E~Fik>p{)ouW9HyHaU?RBl;G<&+so$hxH z!wlE3vLC%5R@FMx^pQ-BDs=ShX3upXe-pc?RNA1h=I;B>Bz^=p&Dp&xcO1yZ^=iJ= ztsh=62+wJVyE)Kb>+XOo>z`Pz6a2ueO4HT(bPN#R7qQhh+z63Pd4Cl1ngW>@=gk~3 zw=d71exMxLHu$R-kO+X=ZTav%9gO z=DRsg#}UQe#HSL)Mv&c;lkO<3nUc4`(t1eV1gP8We#Ni|PJN^0y}lSIkqbv9_7d2& za0KQXG=S>65&ucQh4PToC@{AV*Vxm00~8w?qSh722~zo<%=@9s#$Ea&atBtd(12dn zMl@8EZ(kj?RQSjBlX*btk!-i#sI-n>sWJ6{lUfp=a5AlnvBdbHI##?#`W_c9mgPrq zoMYv4%2~IFwW%PL=nrp@tvln_lbU>_QTl7uZ+6%`7gE$6f39SHp+OkT5YG>Xe!!7t} zC}wjIA%A5n7lvy}~ArTLtZTd;n8hf}Cuy-%!PmHp)>i;)_gI{E%- zsss&~>Y>vA4p>L`+ehd8R=lRmISN^@-il3IW8LO0yj#rB zay^R+0}TM?tx@pQuC9@GV)DntuZU1oqnKs=KefW>(n4FmYs=QW@umH)ZENzEdT_tV z>HYw04Gn+1qvt@Z6ICoXMYiH>v66h&fAhOy?RWV71~(E1u@>g$x{7+G`-&0z>7FKh zo_b+Lz*57C`kO8RGfgtUyDXT z6(WVl{W!8~#9i&>YHeD&bjM6!fuJ?VcsFA&!bGDqphu}Txl>v-w`(@?GbiZDme_af zL2UycDEK?_;O}Xjd~37(7=-^e@&KdPH$f3m*Mgeb+87#o6m$$~H{&=o`Sfw5H4@tB z;{AG^Vq8_X(%vF6>ZBBJ2(H!P&oc}|0|%Rt*WlT?XUzDsypOzDPNpM5BoUClt%DjT z_}ImaTlz!msg$#GK`ZSwoES;3>3R)}hKCMrr+)>*>w5ad!B70vNgUFUn`2262Jup5*DG67xStUyF!EhrvD}6|aJ@VN+t~9vw%QOR%Ce)@ z)%k(y&=(eX8-6E_Rd+l>84yLz(H0q)I9R8OF7><$Jyq5)?d%TQTTCn%uxS`@rA<1x z^%(10yExgG&AUOQ4wf>V0V`&y@ijhPb_bBlW{RTRMW zaJg<2XH9=$WkseJ<~oOQu9Ac8#YKTz#0qZBq0jUQ!zpz+BKP|2o%bDhQ13`GO1~&+ zrMO>XlA!xi5)JHeal%zQTiAz%OEntGylx)^*iP{*?#*%P2Rx#@TQ>+gzjP>h-0YK@ z@LGrcz7vz;JFl4`ZPxJuOM)7V#d6`!A2IaS_BAe!e~x&6fSj}UO=M0nPClCypbOG}e?;!$dg_0i^1L}5p^>ZFaMjk>3ac-Z zUjEV`oD(!y^e<{FxPRo*5Qy(vYS{z@KO+_0e;}8CHUhqH3jE%C&!Efw@Uj}h^-lyv zlqGTlE_cHbT8gOA7qD`|RZ+y}%sK;S6@qbQR|XDx{YYzwevj{cRm30^XS7K=cik5a zsMjo|smUc1bcIMBb4$sO@IaeoT-V0trOZXCe8->$^bcV)z(UhE236}H3@Qw+@dLw( z{7GUGJvUAJ@a^4>q{}Pt2Jlvizd=mlGNF`1?HgB)%g=SgS?#iyBr{WTl>yHz@_hbN zsnMr5Y1iIj9Ez7EQ24r{fm^7t+SCIHKT6 z2)Ej{O>GSHx&2~l3aR08sfol%EYYO3*Qkm-&LUproOIdXZDJ$Uuw}>!5KSwXs81|g z(_uDNTX3}$vzC#U9$t657NeK>e#X=kVg~7e9o}Ch?F-#-o#}r8+LD z6Y({t0EH1Ak#{n@YtJ4_3-Z5Kd0p~yP1$~w5xz|6uabTIB~sTb5+%D9dC@($e$Qi7 zp6~}MzP3Ig!l&QymeZ&^4soAfJq{JiA~jZ^Q{5lKlwa?_@6CU`Glst~^c>&R1x3FQ z@&67n%AjAMP`r#hd1JA`VVDR^R;rI$tmBl(*G6V2 zzQX_<8vGM7JXar#V=KcP4E-ryus6<*y^TJd9*a*V+$_&c7zvS*Uxr8GK7h2t#5#gh9XEqW=rc=E`EN^CbV&Xdw z|E>(^Y~xoE7!G?XQW0_7=`X&qBqg?+O)wB$nCEDKXvrd9e2`CT{UH&?alJ}ne(raJ% z#)d-mf1CqQ;=h9(P2A%94u&KCKacu}!px41Wq7P3Pj@rl7CI~S73xC6o}rOaXFgV_ zzn>(qx*|1dQt)ZQ(ObZ5thaKaSB#tQ>#Bho7LkeqT+)T>@U?&UgclVgP;y1#pp;yE ztQ!SU%>>tP%G1XgrXNg`^7|?H>PbHgsVd2gw12Y}pdY=P5M60%UzbX=M{3sTwAg3o z@1nzSExwhVyz;^{<+*Q$@zLA!6`PDnkG?cDRb!4>mkjK{-SFr1k^O0qwxpS$33w4Yv$f3>r3Q*gk{PYYt zb}hjLY8lV-7XK zihkfpnu*)Z5^JcrrE5~rG{BZQ=dEdCR3EUDCdHDf&jj>_Mj-)^v^F?@+Vr)Z6Hn(* z3>oniJMX1zACVNXXyFG{l>FvwYtHUDJ+u+lOPH8Wkw@b$KBdcdmm&5|seVj$_o~>+ zh(pwUxZ@V)F)pykR2ZX3>$kxNKKGsXO3kEHjJ?(;Kig0GIr_MmJ&`%DF!nH3A<#Z^ zh^o|tma_#%?=xV21%qY3l)U|vGxNltKYth+bCLm*pVV@ld1;hp6Rt8g3V6{y%kp2_ zOkIT8Q1IcI67LB27^@CpUN~LC^yhxZE9|$=Nc%eXC_#S+N|jd?@S5yaVEU9dqDYs< zvuy*#?wYc6u+{B`r-*sgqw9q}nI$JZK!(9)y?P9rN{Jn`KM-)#ZCCSs*At}+;^2ws zW*Dw8XDMEBu-(3Bw7U={e4=oIeEe|zBI-s^G~8iaF08nPlBOK0f8Zd@+Jk#j<4P6fZ6uNy4BDf`UOz)#Y?k{_no1Udf> zWdBDD@|49!1CO$zf>%$_fGzo`AGn z?Gg;3;6kE|htI{-rnAOH<;;|zlm1orj9t%YymcJw5%`P25-55bKTQ-R5=Or^A{p^q zT1C)zSKAdUxXs5f<5aToP@jnLRW z2yZ~SCQrxojdPrIXpkQxU8;`U~R#BJTQn87#Kg$-_(av?>I%dzU%Cwk~3XJYrfTn(aNg3h?I5nR>A%;NH$i zsx@a_I64dPJUdR zZU$4PbhP-A93C;eh{&$IEDaE(9?A*HdALV@gz?4Awzmh zY?oipLyEb!eYi33@i*m2rse;T7-^m18tVTr)duVQX}|YYr{uA_l4;X(yeB{GFO#e0jnDr9?3_=6v;+k30`@YwY>8~Gu(Hz}y z9@wxDnKRM}5eF3?>$c9)kDkkjoe}3PX?rx#9wLe4PCA!2`BJqd3G7usq s{&O*UEtZoZ?0@Hvu+Zc6*AL~s{R%W7q0RMh=U~Ed*Y9qQ!v5L+1Mv