pax_global_header00006660000000000000000000000064141436700360014516gustar00rootroot0000000000000052 comment=77f10379cb42e3a41805efed8a15c7c2db46fb68 mu-1.6.10/000077500000000000000000000000001414367003600122245ustar00rootroot00000000000000mu-1.6.10/.dir-locals.el000066400000000000000000000000571414367003600146570ustar00rootroot00000000000000((emacs-lisp-mode (indent-tabs-mode . nil))) mu-1.6.10/.editorconfig000066400000000000000000000017601414367003600147050ustar00rootroot00000000000000#-*-mode:conf-*- # editorconfig file (see EditorConfig.org), with some # lowest-denominator settings that should work for many editors. root = true # this is the top-level [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true # The "best" answer is "tabs-for-indentation; spaces for alignment", but in # practice that's hard to accomplish in many editors. # # So we use spaces instead, at least that looks consistent for all [*.{cc,cpp,hh,hpp}] indent_style = space indent_size = 8 max_line_length = 100 [*.{c,h}] indent_style = space indent_size = 8 max_line_length = 80 [configure.ac] indent_style = space indent_size = 4 max_line_length = 100 [Makefile.am] indent_style = tab indent_size = 8 max_line_length = 100 mu-1.6.10/.github/000077500000000000000000000000001414367003600135645ustar00rootroot00000000000000mu-1.6.10/.github/ISSUE_TEMPLATE/000077500000000000000000000000001414367003600157475ustar00rootroot00000000000000mu-1.6.10/.github/ISSUE_TEMPLATE/feature-request.md000066400000000000000000000011561414367003600214150ustar00rootroot00000000000000--- name: Mu4e Feature request about: Suggest an idea for this project title: "[mu4e rfe]" labels: rfe, mu4e, new assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. mu-1.6.10/.github/ISSUE_TEMPLATE/guile.md000066400000000000000000000010041414367003600173710ustar00rootroot00000000000000--- name: Guile about: mu-guile related item title: "[guile]" labels: new, guile assignees: '' --- **Describe the item** A clear and concise description of what you expected or wished to happen and what actually happened while using mu-guile. **To Reproduce** Steps to reproduce the behavior. **Environment** Please describe the versions of OS, Emacs, mu/mu4e etc. you are using. **Checklist** - [ ] you are running either the latest 1.4.x release, or a 1.5.11+ development release (otherwise, please upgrade). mu-1.6.10/.github/ISSUE_TEMPLATE/misc.md000066400000000000000000000006511414367003600172260ustar00rootroot00000000000000--- name: Misc about: Miscellaneous items you want to share title: "[misc]" labels: new assignees: '' --- **Note**: for questions, please use the mailing-list: https://groups.google.com/g/mu-discuss **Describe the issue** A clear and concise description, i.e. what you expected/desired to happen and what actually happened. **Environment** If applicable, please describe the versions of OS, Emacs, mu etc. you are using. mu-1.6.10/.github/ISSUE_TEMPLATE/mu-bug-report.md000066400000000000000000000011711414367003600207760ustar00rootroot00000000000000--- name: Mu Bug Report about: Create a report to help us improve title: "[mu bug]" labels: bug, mu, new assignees: '' --- **Describe the bug** A clear and concise description of what the bug is, what you expected to happen and what actually happened. **To Reproduce** Detailed steps to reproduce the behavior. If this is about a specific (kind of) message, **always** attach an (anonymized as need) example message. **Environment** Please describe the versions of OS, Emacs, mu etc. you are using. **Checklist** - [ ] you are running either the latest 1.6.x release, or a 1.7.x development release (otherwise, please upgrade). mu-1.6.10/.github/ISSUE_TEMPLATE/mu4e-bug-report.md000066400000000000000000000015651414367003600212360ustar00rootroot00000000000000--- name: Mu4e Bug Report about: Create a report to help us improve title: "[mu4e bug]" labels: bug, mu4e, new assignees: '' --- **Describe the bug** A clear and concise description of what the bug is, what you expected to happen and what actually happened. **To Reproduce** Detailed steps to reproduce the behavior. If this is about a specific (kind of) message, **always** attach an (anonymized as need) example message. **Environment** Please describe the versions of OS, Emacs, mu/mu4e etc. you are using. **Checklist** - [ ] you are running vanilla emacs (i.e. without Doom, Evil, Spacemacs etc.) (otherwise, please try to reproduce without those - [ ] you are running mu4e without any third-party extensions (otherwise, please try to reproduce without those) - [ ] you are running either the latest 1.6.x release, or a 1.7.x development release (otherwise, please upgrade). mu-1.6.10/.github/issue_template.md000066400000000000000000000022751414367003600171370ustar00rootroot00000000000000# Important! Before filing an issue, please consider the following: * Ensure your mu/mu4e setup is no older than the latest stable release (1.6.x). * Disable any third-party mu4e extensions; this includes customizations like the ones in "Doom" / "Evil" etc. * If a problem occurs with a certain (type of) message, attach an (anonymized) example of such a message * Please provide some minimal steps to reproduce * Please follow the below template Thanks! ## Expected or desired behavior Please describe the behavior you expect or want ## Actual behavior Please describe the behavior you are actually seeing. For bug-reports, if applicable, include error messages, emacs stack traces, example messages etc. Try to be as specific as possible - when do you see this happening? Does it happen always? Sometimes? How often? ## Steps to reproduce For bug-reports, please describe in as much detail as possible how one can reproduce the problem. If there's a problem with a specific (type of) message, please attach such a message to the report. ## Versions of mu, mu4e/emacs, operating system etc. ## Any other detail E.g. are you using the gnus-based message view? mu-1.6.10/.github/workflows/000077500000000000000000000000001414367003600156215ustar00rootroot00000000000000mu-1.6.10/.github/workflows/build-and-test.yml000066400000000000000000000015451414367003600211650ustar00rootroot00000000000000name: Build & run tests on: - push - pull_request jobs: build: runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: os: - ubuntu-latest - macos-latest steps: - uses: actions/checkout@v2 - if: contains(matrix.os, 'ubuntu') name: ubuntu-deps run: | sudo apt update sudo apt-get install automake autoconf-archive autotools-dev libglib2.0-dev libxapian-dev libgmime-3.0-dev m4 make libtool pkg-config - if: contains(matrix.os, 'macos') name: macos-deps run: | brew install autoconf automake libgpg-error libtool pkg-config gettext glib gmime xapian - name: configure run: ./autogen.sh --disable-guile - name: build run: make - name: test run: make test mu-1.6.10/.gitignore000066400000000000000000000031761414367003600142230ustar00rootroot00000000000000www/mu mug /mu/mu /mu/mu-help-strings.h mug2 .desktop *html .deps .libs autom4te* Makefile Makefile.in INSTALL aclocal.m4 config.* configure install-sh depcomp libtool ltmain.sh # Added automatically by `autoreconf` m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 missing nohup.out vgdump stamp-h1 GPATH GRTAGS GSYMS GTAGS *.lo *.o *.la *.x *.go *.gz *.bz2 \#* *.aux *.cp *.fn *.info *.ky *.log *.pg *.toc *.tp *.vr *.elc *.gcda *.gcno *.trs *.exe *.lib aminclude_static.am elisp-comp elc-stamp dummy.cc msg2pdf gmime-test test-mu-cmd test-mu-cmd-cfind test-mu-contacts test-mu-container test-mu-date test-mu-flags test-mu-maildir test-mu-msg test-mu-msg-fields test-mu-query test-mu-runtime test-mu-store test-mu-str test-mu-threads test-mu-util test-parser test-tokenizer test-utils tokenize test-command-parser test-mu-utils test-sexp-parser test-scanner /guile/tests/test-mu-guile mu4e-meta.el mu4e.pdf texinfo.tex texi.texi *.tex *.pdf /www/auto/* configure.lineno /test.xml /mu4e/mdate-sh /mu4e/mu4e-about.el /mu4e/stamp-vti /mu4e/version.texi /lib/doxyfile /version.texi /compile build-aux/ /TAGS parse *_flymake.* *_flymake_* /perf.data perf.data perf.data.old *vgdump /lib/asan.log* /man/mu-mfind.1 /mu/mu-memcheck mu-*-coverage mu*tar.xz compile_commands.json /lib/utils/test-sexp /lib/utils/test-option /lib/test-mu-threader /lib/test-mu-tokenizer /lib/test-mu-parser /lib/test-mu-query-threader /lib/test-contacts /lib/test-flags /lib/test-maildir /lib/test-msg /lib/test-msg-fields /lib/test-query /lib/test-store /lib/test-threader /mu/test-cmd /mu/test-cmd-cfind /mu/test-query /mu/test-threads /lib/test-threads mu-1.6.10/.mailmap000066400000000000000000000000531414367003600136430ustar00rootroot00000000000000Dirk-Jan C. Binnema mu-1.6.10/.travis.yml000066400000000000000000000022641414367003600143410ustar00rootroot00000000000000language: c sudo: required compiler: - gcc env: global: - BUILD_PKGS="libtool autoconf autoconf-archive automake texinfo" - BUILD_LIBS="libgmime-2.6-dev libxapian-dev guile-2.0-dev libwebkitgtk-dev" - TEST_PKGS="pmccabe" matrix: - EVM_EMACS=emacs-24.1-bin - EVM_EMACS=emacs-24.2-bin - EVM_EMACS=emacs-24.3-bin # - EVM_EMACS=emacs-24.5-travis # - EVM_EMACS=emacs-25.1-travis before_install: - git submodule update --init --recursive # The Ubuntu version on travis is way too old, need Autoconf 2.69 - sudo add-apt-repository ppa:dns/gnu -y - sudo apt-get -qq update - sudo apt-get install -qq ${BUILD_PKGS} ${BUILD_LIBS} ${TEST_PKGS} install: - sudo mkdir /usr/local/evm - sudo chown $(id -u):$(id -g) /usr/local/evm - curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash - export PATH="$HOME/.evm/bin:$PATH" - evm install $EVM_EMACS --use script: # Need recent version of autoconf-archive - curl http://nl.mirror.babylon.network/gnu/autoconf-archive/autoconf-archive-2016.09.16.tar.xz -o /tmp/aa.tar.xz && tar xf /tmp/aa.tar.xz - cp autoconf-archive-2016.09.16/m4/*.m4 m4/ - ./autogen.sh - ./configure - make - make check mu-1.6.10/AUTHORS000066400000000000000000000000531414367003600132720ustar00rootroot00000000000000Dirk-Jan C. Binnema mu-1.6.10/COPYING000066400000000000000000001045131414367003600132630ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . mu-1.6.10/ChangeLog000066400000000000000000000000151414367003600137720ustar00rootroot00000000000000See NEWS.org mu-1.6.10/HACKING000066400000000000000000000100571414367003600132160ustar00rootroot00000000000000* HACKING Here are some guidelines for hacking on the 'mu' source code. This is a fairly long list -- this is not meant to discourage anyone from working on mu; I think most of the rules are common sense anyway, and some of the more stylistic-aesthetic rules are clearly visible in current source code, so as long as any new code 'fits in', it should go a long way in satisfying them. I should add some notes for the Lisp/Scheme code as well... ** Coding style For consistency and, more important, to keep things understandable, mu attempts to follow the following rules: 1. Basic code layout is like in the Linux kernel coding style. Keep the '{' on the same line as the statement, except for functions. We're slowly moving to use SPC for indentation: all new code should use that. While TABs are techically better, it seems that using SPCs is harder to get wrong. 2. Lines should not exceed 100 characters 3. Functions should be kept short. 4. Source files should not exceed 1000 lines, with few exceptions. 5. Non-static C-functions have the prefix based on their module, e.g., ~mu-foo.h~ declares a function of 'mu_foo_bar (int a);', mu-foo.c implements this. C++ functions use the Mu namespace 6. Non-global functions *don't* have the module prefix, and are declared static. 7. Functions have their return type on a separate line before the function name, so: #+BEGIN_EXAMPLE static int foo (const char *bar) { .... } #+END_EXAMPLE 8. In C code, variable-declarations are at the beginning of a block. In C code, the declaration does *not* initialize the variable. This will give the compiler a chance to warn us if the variable is not initialized in a certain code path. Exception: autoptr & friends. 9. Returned strings of type ~char*~ must be freed by the caller; if they are not to be freed, ~const char*~ should be used instead 10. Functions calls have a space between function name and arguments, unless there are none, so: ~foo (12, 3)~; and ~bar();~ after a comma, a space should follow. 11. C-functions that do not take arguments are explicitly declared as f(void) and not f(). Reason: f() means that the arguments are /unspecified/ (in C) 12. C-code should not use ~//~ comments. ** Logging For logging, mu uses the GLib logging functions/macros as listed below, except when logging may not have been initialized. The logging system redirects most logging to the log file (typically, =~/.cache/mu/mu.log, or to the systemd journal=). ~g_critical~ messages are written to stderr. - ~g_message~ is for non-error messages the user will see (unless running with ~--quiet~) - ~g_warning~ is for problems the user may be able to do something about (and they are written on ~stderr~) - ~g_critical~ is for mu bugs, serious, internal problems (~g_return_if_fail~ and friends use this). (and they are written on ~stderr~) - don't use ~g_error~ ** Compiling from git For hacking, you're strongly advised to use the latest git version. Compilation from git should be straightforward, if you have the right tools installed. *** dependencies You need to install a few dependencies; e.g. on Debian/Ubuntu: #+BEGIN_EXAMPLE sudo apt-get install \ automake \ autoconf-archive \ autotools-dev \ libglib2.0-dev \ libxapian-dev \ libgmime-3.0-dev \ m4 \ make \ libtool \ pkg-config #+END_EXAMPLE Then, to compile straight from ~git~: #+BEGIN_EXAMPLE $ git clone https://github.com/djcb/mu $ cd mu $ ./autogen.sh $ make #+END_EXAMPLE You only need to run ~./autogen.sh~ the first time and after changes in the build system; otherwise you can use ~./configure~. # Local Variables: # mode: org; org-startup-folded: nofold # fill-column: 80 # End: mu-1.6.10/Makefile.am000066400000000000000000000026551414367003600142700ustar00rootroot00000000000000## Copyright (C) 2008-2020 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk if BUILD_GUILE guile=guile else guile= endif if BUILD_MU4E mu4e=mu4e else mu4e= endif SUBDIRS=m4 man lib $(guile) mu $(mu4e) contrib toys ACLOCAL_AMFLAGS=-I m4 # so we can say 'make test' check: test cleanupnote cleanupnote: @echo -e "\nNote: you can remove the mu-test- dir in your tempdir" @echo "after 'make check' has finished." tags: gtags EXTRA_DIST= \ TODO \ HACKING \ README.org \ gtest.mk \ NEWS \ NEWS.org \ autogen.sh doc_DATA = \ NEWS.org include $(top_srcdir)/aminclude_static.am CODE_COVERAGE_IGNORE_PATTERN= \ '/usr/*' \ '*test-*' mu-1.6.10/NEWS000066400000000000000000000000161414367003600127200ustar00rootroot00000000000000See NEWS.org mu-1.6.10/NEWS.org000066400000000000000000001167411414367003600135230ustar00rootroot00000000000000#+STARTUP:showall * NEWS (user visible changes & bigger non-visible ones) * 1.6 (released, as of July 27 2021) NOTE: After upgrading, you need to call ~mu init~, with your prefered parameters before you can use ~mu~ / ~mu4e~. This is because the underlying database-schema has changed. *** mu - Where available (and with suitably equiped ~libglib~), log to the ~systemd~ journal instead of ~~/.cache/mu.log~. Passing the ~--debug~ option to ~mu~ increases the amount that is logged. - Follow symlinks in maildirs, and support moving messsages across filesystems. Obviously, that is typically quite a bit slower than the single-filesystem case, but can be still be useful. - Optionally provide readline support for the ~mu~ server (when in tty-mode) - Reworked the way mu generates s-expressions for mu4e; they are created programmatically now instead of through string building. - The indexer (the part of mu that scans maildirs and updates the message store) has been rewritten so it can work asynchronously and take advantage of multiple cores. Note that for now, indexing in ~mu4e~ is still a blocking operation. - Portability updates for dealing with non-POSIX systems, and in particular VFAT filesystem, and building using Clang/libc++. - The personal addresses (as per ~--my-address=~ for ~mu init~) can now also include regular expressions (basic POSIX); wrap the expression in ~/~, e.g., ~--my-address='/.*@example.*/~'. - Modernized the querying/threading machinery; this makes some old code a lot easier to understand and maintain, and even while not an explicit goal, is also faster. - Experimental support for the Meson build system. *** mu4e - Use the gnus-based message viewer as the default; the new viewer has quite a few extra features compared to the old, mu4e-specific one, such as faster crypto, support for S/MIME, syntax-highlighting, calendar invitations and more. The new view is superior in most ways, but if you still depend on something from the old one, you can use: #+begin_example ;; set *before* loading mu4e; and restart emacs if you want to change it ;; users of use-packag~ should can use the :init section for this. (setq mu4e-view-use-old t) #+end_example (The older variable ~mu4e-view-use-gnus~ with the opposite meaning is obsolete now, and no longer in use). - Include maildir-shortcuts in the main-view with overall/unread counts, similar to bookmarks, and with the same ~:hide~ and ~:hide-unread~ properties. Note that for the latter, you need to update your maildir-shortcuts to the new format, as explained in the ~mu4e-maildir-shortcuts~ docstring. You can set ~mu4e-main-hide-fully-read~ to hide any bookmarks/maildirs that have no unread messages. - Add some more properties for use in capturing org-mode links to messages / queries. See [[info:mu4e#Org-mode links][the mu4e manual]] for details. - Honor ~truncate-string-ellipsis~ so you can now use 'fancy' ellipses for truncated strings with ~(setq truncate-string-ellipsis "…")~ - Add a variable ~mu4e-mu-debug~ which, when set to non-~nil,~ makes the ~mu~ server log more verbosely (to ~mu.log~ or the journal) - Better alignment in headers-buffers; this looks nicer, but is also a bit slower, hence you need to enable ~mu4e-headers-precise-alignment~ for this. - Support ~mu~'s new regexp-based personal addresses, and add ~mu4e-personal-address-p~ to check whether a given string matches a personal address. - TAB-Completion for writing ~mu~ queries - Switch the context for existing draft messages using ~mu4e-compose-context-switch~ or ~C-c C-;~ in ~mu4e-compose-mode~. * 1.4 (released, as of April 18 2020) *** mu - mu now defaults to the [[https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html][XDG Base Directory Specification]] for the default locations for various files. E.g. on Unix the mu database now lives under ~~/.cache/mu/~ rather than ~~/.mu~. You can still use the old location by passing ~--muhome=~/.mu~ to various ~mu~ commands, or setting ~(setq mu4e-mu-home "~/.mu")~ for ~mu4e~. If your ~~/.cache~ is volatile (e.g., is cleared on reboot), you may want use ~--muhome~. Some mailing-list dicussion suggest that's fairly rare though. After upgrading, you may wish to delete the files in the old location to recover some diskspace. - There's a new subcommand ~mu init~ to initialize the mu database, which takes the ~--maildir~ and ~--my-address~ parameters that ~index~ used to take. These parameters are persistent so ~index~ does not need (or accept) them anymore. ~mu4e~ now depends on those parameters. ~init~ only needs to be run once or when changing these parameters. That implies that you need to re-index after changing these parameters. The ~.noupdate~ files are ignored when indexing the first time after ~mu init~ (or in general, when the database is empty). - There is another new subcommand ~mu info~ to get information about the mu database, the personal addresses etc. - The contacts cache (which is used by ~mu cfind~ and ~mu4e~'s contact-completion) is now stored as part of the Xapian database rather than as a separate file. - The ~--xbatchsize~ and ~--autoupgrade~ options for indexing are gone; both are determined implicitly now. *** mu4e - ~mu4e~ no longer uses the ~mu4e-maildir~ and ~mu4e-user-mail-address-list~ variables; instead it uses the information it gets from ~mu~ (see the ~mu~ section above). If you have a non-default ~mu4e-mu-home~, make sure to set it before ~mu4e~ starts. It is strongly recommended that you run ~mu init~ with the appropriate parameters to (re)initialize the Xapian database, as mentioned in the mu-section above. The main screen shows your address(es), and issues a warning if ~user-email-address~ is not part of that (and refer you to ~mu init~). You can avoid the addresses in the main screen and the warning by setting ~mu4e-main-view-hide-addresses~ to non-nil. - In many cases, ~mu4e~ used to receive /all/ contacts after each indexing operation; this was slow for some users, so we have updated this to /only/ get the contacts that have changed since the last round. We also moved sorting the contacts to the mu-side, which speeds things up further. However, as a side-effect of this, ~mu4e-contact-rewrite-function~ and ~mu4e-compose-complete-ignore-address-regexp~ have been obsoleted; users of those should migrate to ~mu4e-contact-process-function~; see its docstring for details. - Christophe Troestler contributed support for Gnus' calender-invitation handling in mu4e (i.e., you should be able to accept/reject invitations etc.). It's very fresh code, and likely it'll be tweaked in the future. But it's available now for testing. Note that this requires the gnus-based viewer, as per ~(setq mu4e-view-use-gnus t)~ - In addition, he added support for custom headers, so the ones for for the non-gnus-view should work just as well. - ~org-mode~ support is enabled by default now. ~speedbar~ support is disabled by default. The support org functionality has been moved to ~mu4e-org.el~, with ~org-mu4e.el~ remaining for older things. - ~mu4e~ now adds message-ids to messages when saving drafts, so we can find them even with ~mu4e-headers-skip-duplicates~. - Bookmarks (as in ~mu4e-bookmarks~) are now simple plists (instead of cl structs). ~make-mu4e-bookmark~ has been updated to produce such plists (for backward compatibility). A bookmark now looks like a list of e.g. ~(:name "My bookmark" :query "banana OR pear" :key ?f)~ this format is a bit easier extensible. - ~mu4e~ recognizes an attribute ~:hide t~, which will hide the bookmark item from the main-screen (and speedbar), but keep it available through the completion UI. - ~mu4e-maildir-shortcuts~ have also become plists. The older format is still recognized for backward compatibility, but you are encouraged to upgrade. - Replying to mailing-lists has been improved, allowing for choosing for replying to all, sender, list-only. - A very visible change, ~mu4e~ now shows unread/all counts for bookmarks in the main screen that are strings. This is on by default, but can be disabled by setting ~:hide-unread~ in the bookmark ~plist~ to ~t~. For speed-reasons, these counts do _not_ filter out duplicates nor messages that have been removed from the filesystem. - ~mu4e-attachment-dir~ now also applies to composing messages; it determines the default directory for inclusion. - The mu4e <-> mu interaction has been rewritten to communicate using s-expressions, with a repl for testing. *** guile - guile 3.0 is now supported; guile 2.2 still works. *** toys - Updated the ~mug~ toy UI to use Webkit2/GTK+. Note that this is just a toy which is not meant for distribution. ~msg2pdf~ is disabled for now. *** How to upgrade mu4e - upgrade ~mu~ to the latest stable version (1.4.x) - shut down emacs - Run ~mu init~ in a terminal - Make sure ~mu init~ points to the right Maildir folder and add your email address(es) the following way: ~mu init --maildir=~/Maildir --my-address=jim@example.com --my-address=bob@example.com~ - once this is done, run ~mu index~ - Don't forget to delete your old mail cache location if necessary (see release notes for more detail). ** 1.2 After a bit over a year since version 1.0, here is version 1.2. This is mostly a bugfix release, but there are also a number of new features. *** mu - Substantial (algorithmic) speed-up of message-threading; this also (or especially) affects mu4e, since threading is the default. See commit eb9bfbb1ca3c for all the details, and thanks to Nicolas Avrutin. - The query-parser now generates better queries for wildcard searches, by using the Xapian machinery for that (when available) rather than transforming into regexp queries. - The perl backend is hardly used and will be removed; for now we just disable it in the build. - Allow outputting messages in json format, closely following the sexp output. This adds an (optional) dependency on the Json-Glib library. *** mu4e - Bump the minimal required emacs version to 24.4. This was already de-facto true, now it is enforced. - In mu4e-bookmarks, allow the `:query` element to take a function (or lambda) to dynamically generate the query string. - There is a new message-view for mu4e, based on the Gnus' article-view. This bring a lot of (but not all) of the very rich Gnus article-mode feature-set to mu4e, such as S/MIME-support, syntax-highlighting, For now this is experimental ("tech preview"), but might replace the current message-view in a future release. Enable it with: (setq mu4e-view-use-gnus t) Thanks to Christophe Troestler for his work on fixing various encoding issues. - Many bug fixes *** guile - Now requires guile 2.2. *** Contributors for this release: Ævar Arnfjörð Bjarmason, Albert Krewinkel, Alberto Luaces, Alex Bennée, Alex Branham, Alex Murray, Cheong Yiu Fung, Chris Nixon, Christian Egli, Christophe Troestler, Dirk-Jan C. Binnema, Eric Danan, Evan Klitzke, Ian Kelling, ibizaman, James P. Ascher, John Whitbeck, Junyeong Jeong, Kevin Foley, Marcelo Henrique Cerri, Nicolas Avrutin, Oleh Krehel, Peter W. V. Tran-Jørgensen, Piotr Oleskiewicz, Sebastian Miele, Ulrich Ölmann, ** 1.0 After a decade of development, *mu 1.0*! Note: the new release requires a C++14 capable compiler. *** mu - New, custom query parser which replaces Xapian's 'QueryParser' both in mu and mu4e. Existing queries should still work, but the new engine handles non-alphanumeric queries much better. - Support regular expressions in queries (with the new query engine), e.g. "subject:/foo.*bar/". See the new `mu-query` and updated `mu-easy` manpages for examples. - cfind: ensure nicks are unique - auxiliary programs invoked from mu/mu4e survive terminating the shell / emacs *** mu4e - Allow for rewriting message bodies - Toggle-menus for header settings - electric-quote-(local-)mode work when composing emails - Respect format=flowed and delsp=yes for viewing plain-text messages - Added new mu4e-split-view mode: single-window - Add menu item for `untrash'. - Unbreak abbrevs in mu4e-compose-mode - Allow forwarding messages as attachments (`mu4e-compose-forward-as-attachment') - New defaults: default to 'skip duplicates' and 'include related' in headers-view, which should be good defaults for most users. Can be customized using `mu4e-headers-skip-duplicates' and `mu4e-headers-include-related', respectively. - Many bug fixed (see github for all the details). - Updated documentation *** Contributors for this release: Ævar Arnfjörð Bjarmason, Alex Bennée, Arne Köhn, Christophe Troestler, Damien Garaud, Dirk-Jan C. Binnema, galaunay, Hong Xu, Ian Kelling, John Whitbeck, Josiah Schwab, Jun Hao, Krzysztof Jurewicz, maxime, Mekeor Melire, Nathaniel Nicandro, Ronald Evers, Sean 'Shaleh' Perry, Sébastien Le Callonnec, Stig Brautaset, Thierry Volpiatto, Titus von der Malsburg, Vladimir Sedach, Wataru Ashihara, Yuri D'Elia. And all the people on the mailing-list and in github, with bug reports, questions and suggestions. ** 0.9.18 New development series which will lead to 0.9.18. *** mu - Increase the default maximum size for messages to index to 500 Mb; you can customize this using the --max-msg-size parameter to mu index. - implement "lazy-checking", which makes mu not descend into subdirectories when the directory-timestamp is up to date; greatly speeds up indexing (see --lazy-check) - prefer gpg2 for crypto - fix a crash when running on OpenBSD - fix --clear-links (broken filenames) - You can now set the MU_HOME environment variable as an alternative way of setting the mu homedir via the --muhome command-line parameter. *** mu4e **** reading messages - Add `mu4e-action-view-with-xwidget`, and action for viewing e-mails inside a Webkit-widget inside emacs (requires emacs 25.x with xwidget/webkit/gtk3 support) - Explicitly specify utf8 for external html viewing, so browsers can handle it correctly. - Make `shr' the default renderer for rich-text emails (when available) - Add a :user-agent field to the message-sexp (in mu4e-view), which is either the User-Agent or X-Mailer field, when present. **** composing messages - Cleanly handle early exits from message composition as well as while composing. - Allow for resending existing messages, possibly editing them. M-x mu4e-compose-resend, or use the menu; no shortcut. - Better handle the closing of separate compose frames - Improved font-locking for the compose buffers, and more extensive checks for cited parts. - automatically sign/encrypt replies to signed/encrypted messages (subject to `mu4e-compose-crypto-reply-policy') **** searching & marking - Add a hook `mu4e-mark-execute-pre-hook`, which is run just before executing marks. - Just before executing any search, a hook-function `mu4e-headers-search-hook` is invoked, which receives the search expression as its parameter. - In addition, there's a `mu4e-headers-search-bookmark-hook` which gets called when searches get invoked as a bookmark (note that `mu4e-headers-search-hook` will also be called just afterwards). This hook also receives the search expression as its parameter. - Remove the 'z' keybinding for leaving the headers view. Keybindings are precious! - Fix parentheses/precedence in narrowing search terms **** indexing - Allow for indexing in the background; see `mu4e-index-update-in-background`. - Better handle mbsync output in the update buffer - Add variables mu4e-index-cleanup and mu4e-index-lazy to enable lazy checking from mu4e; you can sit from mu4e using something like: #+BEGIN_SRC elisp (setq mu4e-index-cleanup nil ;; don't do a full cleanup check mu4e-index-lazy-check t) ;; don't consider up-to-date dirs #+END_SRC **** misc - don't overwrite global-mode-string, append to it. - Make org-links (and more general, all users of mu4e-view-message-with-message-id) use a headers buffer, then view the message. This way, those linked message are just like any other, and can be deleted, moved etc. - Support org-mode 9.x - Improve file-name escaping, and make it support non-ascii filenames - Attempt to jump to the same messages after a re-search update operation - Add action for spam-filter options - Let `mu4e~read-char-choice' become case-insensitive if there is no exact match; small convenience that affects most the single-char option-reading in mu4e. *** Perl - an experimental Perl binding ("mup") is available now. See perl/README.md for details. ** Contributors: Aaron LI, Abdo Roig-Maranges, Ævar Arnfjörð Bjarmason, Alex Bennée, Allen, Anders Johansson, Antoine Levitt, Arthur Lee, attila, Charles-H. Schulz, Christophe Troestler, Chunyang Xu, Dirk-Jan C. Binnema, Jakub Sitnicki, Josiah Schwab, jsrjenkins, Jun Hao, Klaus Holst, Lukas Fürmetz, Magnus Therning, Maximilian Matthe, Nicolas Richard, Piotr Trojanek, Prashant Sachdeva, Remco van 't Veer, Stephen Eglen, Stig Brautaset, Thierry Volpiatto, Thomas Moulia, Titus von der Malsburg, Yuri D'Elia, Vladimir Sedach * Old news :PROPERTIES: :VISIBILITY: folded :END: ** 0.9.16 *** Release 2016-01-20: Release from the 0.9.15 series *** Contributors: Adam Sampson, Ævar Arnfjörð Bjarmason, Bar Shirtcliff, Charles-H. Schulz, Clément Pit--Claudel, Damien Cassou, Declan Qian, Dima Kogan, Dirk-Jan C. Binnema, Foivos S. Zakkak, Hinrik Örn Sigurðsson, Jeroen Tiebout, JJ Asghar, Jonas Bernoulli, Jun Hao, Martin Yrjölä, Maximilian Matthé, Piotr Trojanek, prsarv, Thierry Volpiatto, Titus von der Malsburg (and of course all people who reported issues, provided suggestions etc.) ** 0.9.15 - bump version to 0.9.15. From now on, odd minor version numbers are for development versions; thus, 0.9.16 is to be the next stable release. - special case text/calendar attachments to get .vcs extensions. This makes it easier to process those with external tools. - change the message file names to better conform to the maildir spec; this was confusing some tools. - fix navigation when not running in split-view mode - add `mu4e-view-body-face', so the body-face for message in the view can be customized; e.g. (set-face-attribute 'mu4e-view-body-face nil :font "Liberation Serif-10") - add `mu4e-action-show-thread`, an action for the headers and view buffers to search for messages in the same thread as the current one. - allow for transforming mailing-list names for display, using `mu4e-mailing-list-patterns'. - some optimizations in indexing (~30% faster in some cases) - new variable mu4e-user-agent-string, to customize the User-Agent: header. - when removing the "In-reply-to" header from replies, mu4e will also remove the (hidden) References header, effectively creating a new message-thread. - implement 'mu4e-context', for defining and switching between various contexts, which are groups of settings. This can be used for instance for switch between e-mail accounts. See the section in the manual for details. - correctly decode mailing-list headers - allow for "fancy" mark-characters; and improve the default set - by default, the maildirs are no longer cached; please see the variable ~mu4e-cache-maildir-list~ if you have a lot of maildirs and it gets slow. - change the default value for ~org-mu4e-link-query-in-headers-mode~ to ~nil~, ie. by default link to the message, not the query, as this is usually more useful behavior. - overwrite target message files that already exist, rather than erroring out. - set mu4e-view-html-plaintext-ratio-heuristic to 5, as 10 was too high to detect some effectively html-only messages - add mu4e-view-toggle-html (keybinding: 'h') to toggle between text and html display. The existing 'mu4e-view-toggle-hide-cited' gets the new binding '#'. - add a customization variable `mu4e-view-auto-mark-as-read' (defaults to t); if set to nil, mu4e won't mark messages as read when you open them. This can be useful on read-only file-systems, since marking-as-read implies a file-move operation. - use smaller chunks for mu server on Cygwin, allowing for better mu4e support there. ** 0.9.13 *** contributors Attila, Daniele Pizzolli, Charles-H.Schulz, David C Sterrat, Dirk-Jan C. Binnema, Eike Kettner, Florian Lindner, Foivos S. Zakkak, Gour, KOMURA Takaaki, Pan Jie, Phil Hagelberg, thdox, Tiago Saboga, Titus von der Malsburg (and of course all people who reported issues, provided suggestions etc.) *** mu/mu4e/guile - NEWS (this file) is now visible from within mu4e – "N" in the main-menu. - make `mu4e-headers-sort-field', `mu4e-headers-sort-direction' public (that, is change the prefix from mu4e~ to mu4e-), so users can manipulate them - make it possible the 'fancy' (unicode) characters separately for headers and marks (see the variable `mu4e-use-fancy-chars'.) - allow for composing in a separate frame (see `mu4e-compose-in-new-frame') - add the `:thread-subject' header field, for showing the subject for a thread only once. So, instead of (from the manual): #+BEGIN_EXAMPLE 06:32 Nu To Edmund Dantès GstDev + Re: Gstreamer-V4L... 15:08 Nu Abbé Busoni GstDev + Re: Gstreamer-V... 18:20 Nu Pierre Morrel GstDev \ Re: Gstreamer... 2013-03-18 S Jacopo EmacsUsr + emacs server on win... 2013-03-18 S Mercédès EmacsUsr \ RE: emacs server ... 2013-03-18 S Beachamp EmacsUsr + Re: Copying a whole... 22:07 Nu Albert de Moncerf EmacsUsr \ Re: Copying a who... 2013-03-18 S Gaspard Caderousse GstDev | Issue with GESSimpl... 2013-03-18 Ss Baron Danglars GuileUsr | Guile-SDL 0.4.2 ava... End of search results #+END_EXAMPLE the headers list would now look something like: #+BEGIN_EXAMPLE 10:26 ⭑☐ Nicolas Goaziou Orgmode /bulk ◼ Re: [O] 2 issue with Include function 11:00 ⭑☐ Leonard Randall Orgmode /bulk ┗▶ 10:55 ⭑☐ Guillermo Rodrigu... GstDev /bulk ◼ Re: stop pipeline into a callback function. 12:04 ⭑☐ Enrique Ocaña Gon... GstDev /bulk ┗▶ 11:27 ⭑☐ Tim Müller GstDev /bulk ◼ 09:34 ⭑☐ Robert Klein Orgmode /bulk ◼ Re: [O] Agenda Tag filtering - has the behaviour changed? #+END_EXAMPLE This is a feature known from e.g. `mutt' and `gnus` and many other clients, and can be enabled by customizing `mu4e-headers-fields' (replacing `:subject' with `:thread-subject') It's not the default yet, but may become so in the future. - add some spam-handling actions to mu4e-contrib.el - mu4e now targets org 8.x, which support for previous versions relegated to `org-old-mu4e.el`. Some of the new org-features are improved capture templates. - updates to the documentation, in particular about using BBDB. - improved URL-handling (use emacs built-in functionality) - many bug fixes, including some crash fixes on BSD *** guile – add --delete option to the find-dups scripts, to automatically delete them. Use with care! ** Release 0.9.12 *** mu - truncate /all/ terms the go beyond xapian's max term length - lowercase the domain-part of email addresses in mu cfind (and mu4e), if the domain is in ascii - give messages without msgids fake-message-ids; this fixes the problem where such messages were not found in --include-related queries - cleanup of the query parser - provide fake message-ids for messages without it; fixes #183 - allow showing tags in 'mu find' output - fix CSV quoting *** mu4e - update the emacs <-> backend protocol; documented in the mu-server man page - show 'None' as date for messages without it (Headers View) - add `mu4e-headers-found-hook', `mu4e-update-pre-hook'. - split org support in org-old-mu4e.el (org <= 7.x) and org-mu4e.el - org: improve template keywords - rework URL handling ** Release 0.9.5 *** mu - allow 'contact:' as a shortcut in queries for 'from:foo OR to:foo OR cc:foo OR bcc:foo', and 'recip:' as a shortcut for 'to:foo OR cc:foo OR bcc:foo' - support getting related messages (--include-related), which includes messages that may not match the query, but that are in the same threads as messages that were - support "list:"/"v:" for matching mailing list names, and the "v" format-field to show them. E.g 'mu find list:emacs-orgmode.gnu.org' *** mu4e - scroll down in message view takes you to next message (but see `mu4e-view-scroll-to-next') - support 'human dates', that is, show the time for today's messages, and the date for older messages in the headers view - replace `mu4e-user-mail-address-regexp' and `mu4e-my-mail-addresses' with `mu4e-user-mail-address-list' - support tags (i.e.., X-Keywords and friends) in the headers-view, and the message view. Thanks to Abdó Roig-Maranges. New field ":tags". - automatically update the headers buffer when new messages are found during indexing; set `mu4e-headers-auto-update' to nil to disable this. - update mail/index with M-x mu4e-update-mail-and-index; which everywhere in mu4e is available with key C-S-u. Use prefix argument to run in background. - add function `mu4e-update-index' to only update the index - add 'friendly-names' for mailing lists, so they should up nicely in the headers view *** guile - add 'mu script' command to run mu script, for example to do statistics on your message corpus. See the mu-script man-page. *** mug - ported to gtk+ 3; remove gtk+ 2.x code ** Release 0.9.9 <2012-10-14> *** mu4e - view: address can be toggled long/short, compose message - sanitize opening urls (mouse-1, and not too eager) - tooltips for header labels, flags - add sort buttons to header-labels - support signing / decryption of messages - improve address-autocompletion (e.g., ensure it's case-insensitive) - much faster when there are many maildirs - improved line wrapping - better handle attached messages - improved URL-matching - improved messages to user (mu4e-(warn|error|message)) - add refiling functionality - support fancy non-ascii in the UI - dynamic folders (i.e.., allow mu4e-(sent|draft|trash|refile)-folder) to be a function - dynamic attachment download folder (can be a function now) - much improved manual *** mu - remove --summary (use --summary-len instead) - add --after for mu find, to limit to messages after T - add new command `mu verify', to verify signatures - fix iso-2022-jp decoding (and other 7-bit clean non-ascii) - add support for X-keywords - performance improvements for threaded display (~ 25% for 23K msgs) - mu improved user-help (and the 'mu help' command) - toys/mug2 replaces toys/mug *** mu-guile - automated tests - add mu:timestamp, mu:count - handle db reopenings in the background ** Release 0.9.8.5 <2012-07-01> *** mu4e - auto-completion of e-mail addresses - inline display of images (see `mu4e-view-show-images'), uses imagemagick if available - interactively change number of headers / columns for showing headers with C-+ and C-- in headers, view mode - support flagging message - navigate to previous/next queries like a web browser (with , ) - narrow search results with '/' - next/previous take a prefix arg now, to move to the nth previous/next message - allow for writing rich-text messages with org-mode - enable marking messages as Flagged - custom marker functions (see manual) - better "dwim" handling of buffer switching / killing - deferred marking of message (i.e.., mark now, decide what to mark for later) - enable changing of sort order, display of threads - clearer marks for marked messages - fix sorting by subject (disregarding Re:, Fwd: etc.) - much faster handling when there are many maildirs (speedbar) - handle mailto: links - improved, extended documentation *** mu - support .noupdate files (parallel to .noindex, dir is ignored unless we're doing a --rebuild). - append all inline text parts, when getting the text body - respect custom maildir flags - correctly handle the case where g_utf8_strdown (str) > len (str) - make gtk, guile, webkit dependency optional, even if they are installed ** Release 0.9.8.4 <2012-05-08> *** mu4e - much faster header buffers - split view mode (headers, view); see `mu4e-split-view'. - add search history for queries - ability to open attachments with arbitrary programs, pipe through shell commands or open in the current emacs - quote names in recipient addresses - mu4e-get-maildirs works now for recursive maildirs as well - define arbitrary operations for headers/messages/attachments using the actions system -- see the chapter 'Actions' in the manual - allow mu4e to be uses as the default emacs mailer (`mu4e-user-agent') - mark headers based on a regexp, `mu4e-mark-matches', or '%' - mark threads, sub-threads (mu4e-hdrs-mark-thread, mu4e-hdrs-mark-subthread, or 'T', 't') - add msg2pdf toy - easy logging (using `mu4e-toggle-logging') - improve mu4e-speedbar for use in headers/view - use the message-mode FCC system for saving messages to the sent-messages folder - fix: off-by-one in number of matches shown *** general - fix for opening files with non-ascii names - much improved support for searching non-Latin (Cyrillic etc.) languages we can now match 'Тесла' or 'Аркона' without problems - smarter escaping (fixes issues with finding message ids) - fixes for queries with brackets - allow --summary-len for the length of message summaries - numerous other small fixes ** Release 0.9.8.3 <2012-04-06> *NOTE*: existing mu/mu4e are recommended to run `mu index --rebuild' after installation. *** mu4e - allow for searching by editing bookmarks (`mu4e-search-bookmark-edit-first') (keybinding 'B') - make it configurable what to do with sent messages (see `mu4e-sent-messages-behavior') - speedbar support (initial patch by Antono V) - better handling of drafts: - don't save too early - more descriptive buffer names (based on Subject, if any) - don't put "--text-follows-this-line--" markers in files - automatically include signatures, if set - add user-settable variables mu4e-view-wrap-lines and mu4e-view-hide-cited, which determine the initial way a message is displayed - improved documentation *** general - much improved searching for GMail folders (i.e. maildir:/ matching); this requires a 'mu index --rebuild' - correctly handle utf-8 messages, even if they don't specify this explicitly - fix compiler warnings for newer/older gcc and clang/clang++ - fix unit tests (and some code) for Ubuntu 10.04 and FreeBSD9 - fix warnings for compilation with GTK+ 3.2 and recent glib (g_set_error) - fix mu_msg_move_to_maildir for top-level messages - fix in maildir scanning - plug some memleaks ** Release 0.9.8.2 <2012-03-11> *** mu4e: - make mail updating non-blocking - allow for automatic periodic update ('mu4e-update-interval') - allow for external triggering of update - make behavior when leaving the headers buffer customizable, ie. ask/apply/ignore ('mu4e-headers-leave-behaviour') *** general - fix output for some non-UTF8 locales - open ('play') file names with spaces - don't show unnecessary errors for --format=links - make build warning-free for clang/clang++ - allow for slightly older autotools - fix unit tests for some hidden assumptions (locale, dir structure etc.) - some documentation updates / clarifications ** Release 0.9.8.1 <2012-02-18 Sat> *** mu - show only leaf/rfc822 MIME-parts *** mu4e - allow for shell commands with arguments in `mu4e-get-mail-command'. - support marking messages as 'read' and 'unread' - show the current query in the the mode-line (`global-mode-string'). - don't repeat 'Re:' / 'Fwd:' - colorize cited message parts - better handling of text-based, embedded message attachments - for text-bodies, concatenate all text/plain parts - make filladapt dep optional - documentation improvements ** Release 0.9.8 <2012-01-31> - '--descending' has been renamed into '--reverse' - search for attachment MIME-type using 'mime:' or 'y:' - search for text in text-attachments using 'embed:' or 'e:' - searching for attachment file names now uses 'file:' (was: 'attach:') - experimental emacs-based mail client -- "mu4e" - added more unit tests - improved guile binding - no special binary is needed anymore, it's installable are works with the normal guile system; code has been substantially improved. still 'experimental' ** Release 0.9.7 <2011-09-03 Sat> - don't enforce UTF-8 output, use locale (fixes issue #11) - add mail threading to mu-find (using -t/--threads) (sorta fixes issue #13) - add header line to --format=mutt-ab (mu cfind), (fixes issue #42) - terminate mu view results with a form-feed marker (use --terminate) (fixes issue #41) - search X-Label: tags (fixes issue #40) - added toys/muile, the mu guile shells, which allows for message stats etc. - fix date handling (timezones) ** Release 0.9.6 <2011-05-28 Sat> - FreeBSD build fix - fix matching for mu cfind to be as expected - fix mu-contacts for broken names/emails - clear the contacts-cache too when doing a --rebuild - wildcard searches ('*') for fields (except for path/maildir) - search for attachment file names (with 'a:'/'attach:') -- also works with wildcards - remove --xquery completely; use --output=xquery instead - fix progress info in 'mu index' - display the references for a message using the 'r' character (xmu find) - remove --summary-len/-k, instead use --summary for mu view and mu find, and - support colorized output for some sub-commands (view, cfind and extract). Disabled by default, use --color to enable, or set env MU_COLORS to non-empty - update documentation, added more examples ** Release 0.9.5 <2011-04-25 Mon> - bug fix for infinite loop in Maildir detection - minor fixes in tests, small optimizations ** Release 0.9.4 <2011-04-12 Tue> - add the 'cfind' command, to search/export contact information - add 'flag:unread' as a synonym for 'flag:new OR NOT flag:unseen' - updated documentation ** Release 0.9.3 <2011-02-13 Sun> - don't warn about missing files with --quiet ** Release 0.9.2 <2011-02-02 Wed> - stricter checking of options; and options must now *follow* the sub-command (if any); so, something like: 'mu index --maildir=/foo/bar' - output searches as plain text (default), XML, JSON or s-expressions using --format=plain|xml|json|sexp. For example: 'mu find foobar --output=json'. These format options are experimental (except for 'plain') - the --xquery option should now be used as --format=xquery, for output symlinks, use --format=links. This is a change in the options. - search output can include the message size using the 'z' shortcut - match message size ranges (i.e.. size:500k..2M) - fix: honor the --overwrite (or lack thereof) parameter - support folder names with special characters (@, ' ', '.' and so on) - better check for already-running mu index - when --maildir= is not provided for mu index, default to the last one - add --max-msg-size, to specify a new maximum message size - move the 'mug' UI to toys/mug; no longer installable - better support for Solaris builds, Gentoo. ** Release 0.9.1 <2010-12-05 Sun> - Add missing icon for mug - Fix unit tests (Issue #30) - Fix Fedora 14 build (broken GTK+ 3) (Issue #31) ** Release 0.9 <2010-12-04 Sat> - you can now search for the message priority ('prio:high', 'prio:low', 'prio:normal') - you can now search for message flags, e.g. 'flag:attach' for messages with attachment, or 'flag:encrypted' for encrypted messages - you can search for time-intervals, e.g. 'date:2010-11-26..2010-11-29' for messages in that range. See the mu-find(1) and mu-easy(1) man-pages for details and examples. - you can store bookmarked queries in ~/.mu/bookmarks - the 'flags' parameter has been renamed in 'flag' - add a simple graphical UI for searching, called 'mug' - fix --clearlinks for file systems without entry->d_type (fixes issue #28) - make matching case-insensitive and accent-insensitive (accent-insensitive for characters in Unicode Blocks 'Latin-1 Supplement' and 'Latin Extended-A') - more extensive pre-processing is done to make searching for email-addresses and message-ids less likely to not work (issue #21) - updated the man-pages - experimental support for Fedora 14, which uses GMime 2.5.x (fixes issue #29) ** Release 0.8 <2010-10-30 Sat> - There's now 'mu extract' for getting information about MIME-parts (attachments) and extracting them - Queries are now internally converted to lowercase; this solves some of the false-negative issues - All mu sub-commands now have their own man-page - 'mu find' now takes a --summary-len= argument to print a summary of up-to-n lines of the message - Same for 'mu view'; the summary replaces the full body - Setting the mu home dir now goes with -m, --muhome - --log-stderr, --reindex, --rebuild, --autoupgrade, --nocleanup, --mode, --linksdir, --clearlinks lost their single char version ** Release 0.7 <2010-02-27 Sat> - Database format changed - Automatic database scheme version check, notifies users when an upgrade is needed - 'mu view', to view mail message files - Support for >10K matches - Support for unattended upgrades - that is, the database can automatically by upgraded (--autoupgrade). Also, the log file is automatically cleaned when it gets too big (unless you use --nocleanup) - Search for a certain Maildir using the maildir:,m: search prefixes. For example, you can find all messages located in ~/Maildir/foo/bar/cur/msg ~/Maildir/foo/bar/new/msg and with m:/foo/bar this replace the search for path/p in 0.6 - Fixes for reported issues () - A test suite with a growing number of unit tests ** Release 0.6 <2010-01-23 Sat> - First new release of mu since 2008 - No longer depends on sqlite # Local Variables: # mode: org; org-startup-folded: nil # fill-column:80 # End: mu-1.6.10/README.org000066400000000000000000000065531414367003600137030ustar00rootroot00000000000000#+TITLE:MU [[https://github.com/djcb/mu/blob/master/COPYING][https://img.shields.io/github/license/djcb/mu?logo=gnu&.svg]] [[https://en.cppreference.com][https://img.shields.io/badge/Made%20with-C/CPP-1f425f?logo=c&.svg]] [[https://img.shields.io/github/v/release/djcb/mu][https://img.shields.io/github/v/release/djcb/mu.svg]] [[https://github.com/djcb/mu/graphs/contributors][https://img.shields.io/github/contributors/djcb/mu.svg]] [[https://github.com/djcb/mu/issues][https://img.shields.io/github/issues/djcb/mu.svg]] [[https://github.com/djcb/mu/issues?q=is%3Aissue+is%3Aopen+label%3Arfe][https://img.shields.io/github/issues/djcb/mu/rfe?color=008b8b.svg]] [[https://github.com/djcb/mu/pull/new][https://img.shields.io/badge/PRs-welcome-brightgreen.svg]]\\ [[https://melpa.org/#/?q=mu4e&sort=version&asc=false][https://img.shields.io/badge/Emacs-25.3-922793?logo=gnu-emacs&logoColor=b39ddb&.svg]] [[https://www.djcbsoftware.nl/code/mu/mu4e/Installation.html#Dependencies-for-Debian_002fUbuntu][https://img.shields.io/badge/Platform-Linux-2e8b57?logo=linux&.svg]] [[https://www.djcbsoftware.nl/code/mu/mu4e/Installation.html#Building-from-a-release-tarball-1][https://img.shields.io/badge/Platform-FreeBSD-8b3a3a?logo=freebsd&logoColor=c32136&.svg]] [[https://formulae.brew.sh/formula/mu#default][https://img.shields.io/badge/Platform-macOS-101010?logo=apple&logoColor=ffffff&.svg]] [[https://github.com/msys2-unofficial/MSYS2-packages/blob/master/mu/README.org][https://img.shields.io/badge/Platform-Windows-00bfff?logo=windows&logoColor=00bfff&.svg]] Welcome to ~mu~! *Note*: you are looking at the *development* branch, which is where new code is being developed and tested, and which may occasionally break. Distribution and non-adventurous users are instead recommended to use the [[https://github.com/djcb/mu/tree/release/1.6.x][1.6.x Release Branch]] or to pick up one of the [[https://github.com/djcb/mu/releases][1.6.x Releases]]. Given the enormous amounts of e-mail many people gather and the importance of e-mail message in our work-flows, it's essential to quickly deal with all that mail - in particular, to instantly find that one important e-mail you need right now, and quickly file away message for later use. ~mu~ is a tool for dealing with e-mail messages stored in the Maildir-format. ~mu~'s purpose in life is to help you to quickly find the messages you need; in addition, it allows you to view messages, extract attachments, create new maildirs, and so on. See the [[www/cheatsheet.md][mu cheatsheet]] for some examples. =mu= is fully documented. After indexing your messages into a [[http://www.xapian.org][Xapian]]-database, you can search them using a custom query language. You can use various message fields or words in the body text to find the right messages. Built on top of ~mu~ are some extensions (included in this package): - mu4e: a full-featured e-mail client that runs inside emacs - mu-guile: bindings for the Guile/Scheme programming language (version 2.2 and later) ~mu~ is written in C and C++; ~mu4e~ is written in elisp, and ~mu-guile~ in a mix of C and Scheme. Note, ~mu~ is available in Linux distributions (e.g. Debian/Ubuntu and Fedora) under the name ~maildir-utils~; apparently because they don't like short names. All of the code is distributed under the terms of the [[https://www.gnu.org/licenses/gpl-3.0.en.html][GNU General Public License version 3]] (or higher). mu-1.6.10/TODO000066400000000000000000000140501414367003600127140ustar00rootroot00000000000000#+STARTUP: showall * TODO (fixes, ideas, etc.) ** Future stuff *** mu - put threading information in the database, and enable getting the complete threads when searching - refactor fill_database function in test cases - don't show duplicate e-mails (i.e.. for Gmail); check the message-id *** mu-guile - move contact export to separate scm - fix logging *** mu4e - special-case replying to messages sent by self - identities (see Jacek's 'mu4e: From field in replies' mail) ==> [ workaround available, using mu4e-pre-compose-hook, dynamic folders ] - new-mail warning ==> [ workaround available, using mu4e-index-updated-hook ] - custom header fields in headers-view, message-view - show maildirs as a tree, not a list in speed bar - review emacs menus - re-factor / separate window/buffer management - enable keeping message view buffers around - better naming for draft/view buffers - header updating interferes with marks (when updating for 'mark as read', when reading a marked message) - set/unset flag editing command - handling of database upgrades - restore point after rerunning a search - make the mu4e-bookmarks format similar to the other ones - refresh current query after update? - fix mu4e-mark-set to work from the view buffer as well - open links to mails through headers-mode somehow (i.e.., mu4e-view-message-with-msgid) - improve mouse interaction (i.e., cursor vs point) - show counts of messages in searches (in main view) - show flush only if there's something to flush (and # of flushables) - fix unsafe temp-file handling - make copy paste name/address in mu4e-view possible * Done (0.9.9.x) - mu4e: scroll down –> go to next message - mu: add contact: as a shortcut for matching from/to/cc/bcc: - guile integration - statistics - 'human' dates in the headers view - :tags in headers, message view * Done :PROPERTIES: :VISIBILITY: folded :END: ** Done (0.9.9) - make contacts in the view clickable (toggle long/short display, compose message) - opening urls is too eager (now use M-RET for opening url at point, not just RET, which conflicted with using RET for scrolling) - document quoting of queries - use mu-error - tooltips in header labels - tooltip for flags field - remove --summary option (for mu find, mu view); use --summary-len instead - add sort buttons to header labels (and do the sorting) - cleanup mu-cmd-find - implement --after for mu find, to only show message files changed after a certain time (mtime) - add mu:timestamp for guile (referring to the message file's mtime) - guile automated tests - add 'mu verify' - automated tests - handle verbose/quiet/normal output 'mu verify' - check gmime 2.4 does not break - hook up mu4e with 'mu verify' - add 'help' command - refactor mu-msg-part - move widgets/ into toys/mug2, remove toys/mug/, rename toys/mug2 -> toys/mug - add guile mu:count - don't show GPG/PKCS7 sigs as attachments - fix address completion (quote names) - add support for X-Keywords (in addition to X-Label) - guile: add stats test cases - fixed iso-2022-jp (japanese) decoding - make address completion case-insensitive - recognize '*' in urls - handle exception 'The revision being read has been discarded - you should call Xapian::Database::reopen() and retry the operation' - handle passwords from get-mail shell command - support fancy (non-ascii) chars for header flags, thread prefix strings - improve performance of getting the list of maildirs - fix setting wrapped/hide state in viewer - fix ' realpath() failed for...' stuff - allow for fancy chars (> ascii), make it configurable (mu4e-use-fancy-chars) - don't user `error' for user-errors - better echo-area reporting - improve help feedback for user (command line) - handling of encrypted messages - improved checked for gmime-2.6 crypto funcs - handling of command line options / help - fix / add support for :size - mu4e~view-wrap-lines (use visual-line-mode? see Jacek's mu4e~view-wrap-lines mail) - better help - threading optimizations - actions for /all/ headers, actions for /all/ attachment - handle attached messages with attachments ** Done (0.8.9.5) - make next/prev header respect prefix argument (Jacek's patch) - make search results a stack (well, multiple stacks) - optionally keep cc with user's email - enable setting/unsetting 'Flagged' on messages - allow narrowing of search results - interactive split-view control (Jacek) - view images inline - *FIX* slow maildirs when there are many - *FIX* ignore unrecognized maildir flag letters - *FIX*: reply-to does not make it to the frontend - *FIX* wrong buffer deleted after sending (see '(non mu) buffer is killed') - rich text composing (with org-mode) - let message-mode deal with burying/killing compose buffers - *FIX* add runtime check for imagemagick - *FIX* no error note if target message already exists (when moving) - sorting + show / hide threads - *FIX* having multiple header views visible - *FIX* fix for strings where len (g_utf8_strdown (str)) > len (str) - make sure marks correspond to the *current* message in message view (see https://github.com/djcb/mu/issues/26) - *FIX* don't remove unknown message flags when moving - make guile/gtk/webkit dependency optional - improve fringe marks (see https://github.com/djcb/mu/issues/21) - mark message, decide what to do with them later (i.e.. 'deferred marking') - custom predicate functions for marking - make mu4e buffer killing less aggressive (i.e.., DWIM) - about mu4e - hide some headers when composing - fix sorting subjects with ':' (but not 'Re:' or 'Fwd:') - strip signature from original when replying - make refresh after changing sort, threads the default - contact completion (see Jacek's 'mu4e: using' mail) - *FIX* emacs23 mailto: handling - *FIX* message interference - *FIX* emacs23.2+ auto-completion # Local Variables: # mode: org # End: mu-1.6.10/autogen.sh000077500000000000000000000013621414367003600142270ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. test -f mu/mu.cc || { echo "*** Run this script from the top-level mu source directory" exit 1 } # opportunistically; usually not needed, but occasionally it'll # avoid build errors that would otherwise confuse users. test -f Makefile && { echo "*** clear out old things" make distclean 2> /dev/null } command -V autoreconf > /dev/null if [ $? != 0 ]; then echo "*** No autoreconf found, please install it ***" exit 1 fi rm -f config.cache rm -rf autom4te.cache autoreconf --force --install --verbose || exit $? if test -z "$*"; then echo "# Configuring without parameters" else echo "# Configure with parameters $*" fi ./configure --config-cache $@ mu-1.6.10/build-aux/000077500000000000000000000000001414367003600141165ustar00rootroot00000000000000mu-1.6.10/build-aux/config.rpath000066400000000000000000000000001414367003600164110ustar00rootroot00000000000000mu-1.6.10/configure.ac000066400000000000000000000325201414367003600145140ustar00rootroot00000000000000## Copyright (C) 2008-2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. AC_PREREQ([2.68]) AC_INIT([mu],[1.6.10],[https://github.com/djcb/mu/issues],[mu]) AC_COPYRIGHT([Copyright (C) 2008-2021 Dirk-Jan C. Binnema]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([mu/mu.cc]) # libtoolize wants to put some stuff in here; if you have an old # autotools/libtool setup. you can try to comment this out AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([1.14 foreign no-dist-gzip tar-ustar dist-xz]) # silent build if we have a new enough automake m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AS_IF([test x$prefix = xNONE],[prefix=/usr/local]) AC_SUBST(prefix) # AC_PROG_CXX *before* AC_PROG_CC, otherwise configure won't error out # when a c++ compiler is not found. Weird, huh? AC_PROG_CXX AC_PROG_CC AC_PROG_CPP AC_PROG_CC_STDC AC_PROG_CC_C99 AC_PROG_INSTALL AC_HEADER_STDC extra_flags="-Wformat-security \ -Wstack-protector \ -Wstack-protector-all \ -Wno-cast-function-type \ -Wno-bad-function-cast" AX_CXX_COMPILE_STDCXX_14 AX_COMPILER_FLAGS_CXXFLAGS([],[],[${extra_cflags}]) AX_APPEND_COMPILE_FLAGS([-Wno-inline ],[CXXFLAGS]) AX_VALGRIND_CHECK LT_INIT AX_CODE_COVERAGE AC_PROG_AWK AC_CHECK_PROG(SORT,sort,sort) AC_CHECK_HEADERS([wordexp.h]) # use the 64-bit versions AC_SYS_LARGEFILE # asan is somewhat similar to valgrind, but has low enough overhead so it # can be used during normal operation. AC_ARG_ENABLE([asan],[AS_HELP_STRING([--enable-asan], [Enable Address Sanitizer])], [use_asan=$enableval], [use_asan=no]) AS_IF([test "x$use_asan" = "xyes"],[ AC_SUBST(ASAN_CFLAGS, "-fsanitize=address -static-libasan -fno-omit-frame-pointer") AC_SUBST(ASAN_CXXFLAGS,"-fsanitize=address -static-libasan -fno-omit-frame-pointer") AC_SUBST(ASAN_LDFLAGS, "-fsanitize=address -static-libasan -fno-omit-frame-pointer") ]) # check for makeinfo AC_CHECK_PROG(have_makeinfo,makeinfo,yes,no) AM_CONDITIONAL(HAVE_MAKEINFO,test "x$have_makeinfo" = "xyes") AM_COND_IF(HAVE_MAKEINFO,[],[ # seems build *insists* on trying to makeinfo, erroring out # if it does not exist. Let's work around that. AC_SUBST(MAKEINFO,[true]) ]) # we need emacs for byte-compiling mu4e build_mu4e=no AC_ARG_ENABLE([mu4e], AS_HELP_STRING([--disable-mu4e],[Disable building mu4e])) AS_IF([test "x$enable_mu4e" != "xno"], [ AM_PATH_LISPDIR AS_IF([test "x$lispdir" != "xno"], [ emacs_version="$($EMACS --version | head -1)" lispdir="${lispdir}/mu4e/" ]) AS_CASE([$emacs_version], [*25.3*],[build_mu4e=yes], [*26*|*27*|*28*|*29*],[build_mu4e=yes], [AC_WARN([emacs is too old to build mu4e (need emacs >= 25.3)])]) ]) AM_CONDITIONAL(BUILD_MU4E, test "x$build_mu4e" = "xyes") # we need some special tricks for filesystems that don't have d_type; # e.g. Solaris. See mu-maildir.c. Explicitly disabling it is for # testing purposes only AC_ARG_ENABLE([dirent-d-type], AC_HELP_STRING([--disable-dirent-d-type], [Don't use dirent->d_type, even if you have it]), [], [AC_STRUCT_DIRENT_D_TYPE] ) AS_IF([test "x$ac_cv_member_struct_dirent_d_type" != "xyes"], [use_dirent_d_type="no"], [use_dirent_d_type="yes"]) # support for d_ino (inode) in struct dirent is optional; if it's # available we can sort direntries by inode and access them in that # order; this is much faster on some file systems (such as extfs3). # Explicitly disabling it is for testing purposes only. AC_ARG_ENABLE([dirent-d-ino], AC_HELP_STRING([--disable-dirent-d-ino], [Don't use dirent->d_ino, even if you have it]), [], [AC_STRUCT_DIRENT_D_INO] ) AS_IF([test "x$ac_cv_member_struct_dirent_d_ino" != "xyes"], [use_dirent_d_ino="no"], [use_dirent_d_ino="yes"]) AC_CHECK_FUNCS([memset memcpy realpath setlocale strerror getpass setsid]) AC_CHECK_FUNCS([vasprintf strptime]) # timegm is no longer used in the source # AC_CHECK_FUNC(timegm,[],AC_MSG_ERROR([missing required function timegm])) # require pkg-config >= 0.28 (release in 2013; should be old enough...) # with that version, we don't need the AC_SUBST stuff after PKG_CHECK. m4_ifndef([PKG_PROG_PKG_CONFIG], [m4_fatal([please install pkg-config >= 0.28 before running autoconf/autogen])]) PKG_PROG_PKG_CONFIG(0.28) # latest version in buildroot AS_IF([test -z "$PKG_CONFIG"], AC_MSG_ERROR([ *** pkg-config with version >= 0.28 could not be found. *** *** Make sure it is in your path, or set the PKG_CONFIG environment variable *** to the full path to pkg-config.]) ) # glib2? PKG_CHECK_MODULES(GLIB,glib-2.0 >= 2.50 gobject-2.0 gio-2.0) glib_version="$($PKG_CONFIG --modversion glib-2.0)" # gmime, version 3.0 or higher PKG_CHECK_MODULES(GMIME,gmime-3.0) gmime_version="$($PKG_CONFIG --modversion gmime-3.0)" # xapian checking - we need 1.4 at least PKG_CHECK_MODULES(XAPIAN,xapian-core >= 1.4,[ have_xapian=yes xapian_version=$($PKG_CONFIG xapian-core --modversion) AC_SUBST(XAPIAN_CXXFLAGS,${XAPIAN_CFLAGS}) ],[ # fall back to the xapian-config script. Not sure if there are cases where the # pkgconfig does not work, but xapian-config does, so keep this for now. AC_MSG_NOTICE([falling back to xapian-config]) AC_CHECK_PROG(XAPIAN_CONFIG,xapian-config,xapian-config,no) AS_IF([test "x$XAPIAN_CONFIG" = "xno"],[ AC_MSG_ERROR([ *** xapian could not be found; please install it *** e.g., in debian/ubuntu the package would be 'libxapian-dev' *** If you compiled it yourself, you should ensure that xapian-config *** is in your PATH.])], [xapian_version=$($XAPIAN_CONFIG --version | sed -e 's/.* //')]) AS_CASE([$xapian_version], [1.[[4-9]].[[0-9]]*], [AC_MSG_NOTICE([xapian $xapian_version found.])], [AC_MSG_ERROR([*** xapian version >= 1.4 needed, but version $xapian_version found.])]) XAPIAN_CXXFLAGS="$($XAPIAN_CONFIG --cxxflags)" XAPIAN_LIBS="$($XAPIAN_CONFIG --libs)" have_xapian="yes" AC_SUBST(XAPIAN_CXXFLAGS) AC_SUBST(XAPIAN_LIBS) ]) ############################################################################### # we set the set the version of the MuStore (Xapian database) layout # here; it will become part of the db name, so we can automatically # recreate the database when we have incompatible changes. # # note that MU_STORE_SCHEMA_VERSION does not follow mu versioning, as we # hopefully don't have updates for each version; also, this has nothing to do # with Xapian's software version AC_DEFINE(MU_STORE_SCHEMA_VERSION,["452"],['Schema' version of the database]) ############################################################################### ############################################################################### # we need GTK+3 for some of the graphical tools # use --without-gtk to disable it AC_ARG_ENABLE([gtk],AS_HELP_STRING([--disable-gtk],[Disable GTK+])) AS_IF([test "x$enable_gtk" != "xno"],[ PKG_CHECK_MODULES(GTK,gtk+-3.0,[have_gtk=yes],[have_gtk=no]) gtk_version="$($PKG_CONFIG --modversion gtk+-3.0)" ]) AM_CONDITIONAL(HAVE_GTK,[test "x$have_gtk" = "xyes"]) # webkit? needed for the fancy web widget # use --disable-webkit to disable it, even if you have it # # and note this is just a toy, not for distribution. AC_ARG_ENABLE([webkit],AS_HELP_STRING([--disable-webkit],[Disable webkit])) AS_IF([test "x$enable_webkit" != "xno"],[ PKG_CHECK_MODULES(WEBKIT,webkit2gtk-4.0 >= 2.0, [have_webkit=yes],[have_webkit=no]) AS_IF([test "x$have_webkit" = "xyes"],[ webkit_version="$($PKG_CONFIG --modversion webkit2gtk-4.0)"]) ]) AM_CONDITIONAL(HAVE_WEBKIT, [test "x$have_webkit" = "xyes"]) AM_CONDITIONAL(BUILD_GUI,[test "x$have_webkit" = "xyes" -a "x$have_gtk" = "xyes"]) ############################################################################### ################################################################################ # should we try to build an emacs dynamic module? #AC_CHECK_HEADER([emacs-module.h],[ # AC_DEFINE([HAVE_EMACS_MODULE_H],[1], [Whether we have the emacs-module header])], # AC_MSG_NOTICE([emacs-module.h not found; not building module]) #) #AM_CONDITIONAL([BUILD_EMACS_MODULE],[test "x$ac_cv_header_emacs_module_h" != "x"]) ################################################################################ ############################################################################### # build with guile 3.0/2.2 when available and not disabled. AC_ARG_ENABLE([guile], AS_HELP_STRING([--disable-guile],[Disable guile])) AS_IF([test "x$enable_guile" != "xno"],[ PKG_CHECK_MODULES(GUILE, [guile-3.0], [have_guile=yes],[ PKG_CHECK_MODULES(GUILE, [guile-2.2], [have_guile=yes], [have_guile=no])]) AS_IF([test "x$have_guile" = "xyes"],[ GUILE_PKG([3.0 2.2]) GUILE_PROGS GUILE_FLAGS AC_DEFINE_UNQUOTED([GUILE_BINARY],"$GUILE",[guile binary]) vsnarf=guile-snarf${GUILE_EFFECTIVE_VERSION} AC_CHECK_PROGS(GUILE_SNARF,[${vsnarf} guile-snarf], [no]) guile_version=$($PKG_CONFIG guile-$GUILE_EFFECTIVE_VERSION --modversion) ]) ]) AM_CONDITIONAL(BUILD_GUILE,[test "x$have_guile" = "xyes" -a \ "x$ac_cv_prog_GUILE_SNARF" != "xno"]) AM_COND_IF([BUILD_GUILE],[AC_DEFINE(BUILD_GUILE,[1], [Do we support Guile?])]) ############################################################################### ############################################################################### # optional readline AC_ARG_ENABLE([readline], AS_HELP_STRING([--disable-readline],[Disable readline])) AS_IF([test "x$enable_readline" != "xno"], [ saved_libs=$LIBS AX_LIB_READLINE AC_SUBST(READLINE_LIBS,${LIBS}) LIBS=$saved_libs ]) ############################################################################### ############################################################################### # check for makeinfo AC_CHECK_PROG(have_makeinfo,makeinfo,yes,no) AM_CONDITIONAL(HAVE_MAKEINFO, [test "x$have_makeinfo" = "xyes"]) ############################################################################### ############################################################################### # docdir, so we can use it in mu4e-meta.el.in AC_SUBST(MU_DOC_DIR, "${prefix}/share/doc/mu") ############################################################################### AC_CONFIG_FILES([ Makefile mu/Makefile lib/Makefile lib/doxyfile lib/utils/Makefile lib/index/Makefile mu4e/Makefile mu4e/mu4e-meta.el guile/Makefile guile/mu/Makefile guile/examples/Makefile guile/tests/Makefile guile/scripts/Makefile toys/Makefile toys/mug/Makefile man/Makefile m4/Makefile contrib/Makefile ]) AC_CONFIG_FILES([mu/mu-memcheck], [chmod +x mu/mu-memcheck]) AC_OUTPUT dnl toys/msg2pdf/Makefile echo echo "mu configuration is complete." echo "------------------------------------------------" echo "mu version : $VERSION" echo echo "Xapian version : $xapian_version" echo "GLib version : $glib_version" echo "GMime version : $gmime_version" AM_COND_IF([BUILD_GUI],[ echo "GTK+ version : $gtk_version" echo "Webkit2/GTK+ version : $webkit_version" ]) AM_COND_IF([BUILD_GUILE],[ echo "Guile version : $guile_version" ]) AS_IF([test "x$build_mu4e" = "xyes"],[ echo "Build mu4e : yes" echo "Emacs version : $emacs_version" #AM_COND_IF([BUILD_EMACS_MODULE],[ #echo "Build emacs module : yes"],[ #echo "Build emacs module : no" #])]) AM_COND_IF([BUILD_GUI],[ echo "Build 'mug' toy-ui (gtk+/webkit) : yes"],[ echo "Build 'mug' toy-ui (gtk+/webkit) : no" ]) echo echo "Have direntry->d_ino : $use_dirent_d_ino" echo "Have direntry->d_type : $use_dirent_d_type" echo "------------------------------------------------" echo # # Warnings / notes # # makeinfo if test "x$have_makeinfo" != "xyes"; then echo "* You do not seem to have the makeinfo program; if you are building from git" echo " you need that to create documentation for guile and emacs. It is in the" echo " texinfo package in debian/ubuntu/fedora/... " echo fi # gui AS_IF([test "x$buildgui" = "xyes"],[ echo "* The demo UI will be built in toys/mug" echo ]) # wordexp AS_IF([test "x$ac_cv_header_wordexp_h" != "xyes"],[ echo "* Your system does not seem to have the 'wordexp' function." echo " This means that you cannot use shell-like expansion in options and " echo " some other places. So, for example, instead of" echo " --maildir=~/Maildir" echo " you should use the complete path, something like:" echo " --maildir=/home/user/Maildir" ]) echo echo "Now, type 'make' (or 'gmake') to build mu" echo mu-1.6.10/contrib/000077500000000000000000000000001414367003600136645ustar00rootroot00000000000000mu-1.6.10/contrib/Makefile.am000066400000000000000000000021721414367003600157220ustar00rootroot00000000000000## Copyright (C) 2008-2013 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk AM_CPPFLAGS=$(GMIME_CFLAGS) $(GLIB_CFLAGS) -I${prefix}/include AM_CFLAGS=-Wall -Wextra -Wno-unused-parameter -Wdeclaration-after-statement -pedantic noinst_PROGRAMS=gmime-test gmime_test_SOURCES=gmime-test.c gmime_test_LDADD=$(GMIME_LIBS) $(GLIB_LIBS) EXTRA_DIST= \ mu-completion.zsh \ mu-sexp-convert \ mu.spec mu-1.6.10/contrib/gmime-test.c000066400000000000000000000123101414367003600161000ustar00rootroot00000000000000/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2011-2017 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ /* gmime-test; compile with: gcc -o gmime-test gmime-test.c -Wall -O0 -ggdb \ `pkg-config --cflags --libs gmime-2.6` */ #include #include #include #include #include static gchar* get_recip (GMimeMessage *msg, GMimeAddressType atype) { char *recep; InternetAddressList *receps; receps = g_mime_message_get_addresses (msg, atype); recep = (char*)internet_address_list_to_string (receps, NULL, FALSE); if (!recep || !*recep) { g_free (recep); return NULL; } return recep; } static gchar* get_refs_str (GMimeMessage *msg) { const gchar *str; GMimeReferences *mime_refs; int i, refs_len; gchar *rv; str = g_mime_object_get_header (GMIME_OBJECT(msg), "References"); if (!str) return NULL; mime_refs = g_mime_references_parse (NULL, str); refs_len = g_mime_references_length (mime_refs); for (rv = NULL, i = 0; i < refs_len; ++i) { const char* msgid; msgid = g_mime_references_get_message_id (mime_refs, i); rv = g_strdup_printf ("%s%s%s", rv ? rv : "", rv ? "," : "", msgid); } g_mime_references_free (mime_refs); return rv; } static void print_date (GMimeMessage *msg) { GDateTime *dt; gchar *buf; dt = g_mime_message_get_date (msg); if (!dt) return; dt = g_date_time_to_local (dt); buf = g_date_time_format (dt, "%c"); g_date_time_unref (dt); if (buf) { g_print ("Date : %s\n", buf); g_free (buf); } } static void print_body (GMimeMessage *msg) { GMimeObject *body; GMimeDataWrapper *wrapper; GMimeStream *stream; body = g_mime_message_get_body (msg); if (GMIME_IS_MULTIPART(body)) body = g_mime_multipart_get_part (GMIME_MULTIPART(body), 0); if (!GMIME_IS_PART(body)) return; wrapper = g_mime_part_get_content (GMIME_PART(body)); if (!GMIME_IS_DATA_WRAPPER(wrapper)) return; stream = g_mime_data_wrapper_get_stream (wrapper); if (!GMIME_IS_STREAM(stream)) return; do { char buf[512]; ssize_t len; len = g_mime_stream_read (stream, buf, sizeof(buf)); if (len == -1) break; if (write (fileno(stdout), buf, len) == -1) break; if (len < (int)sizeof(buf)) break; } while (1); } static gboolean test_message (GMimeMessage *msg) { gchar *val; const gchar *str; val = get_recip (msg, GMIME_ADDRESS_TYPE_FROM); g_print ("From : %s\n", val ? val : "" ); g_free (val); val = get_recip (msg, GMIME_ADDRESS_TYPE_TO); g_print ("To : %s\n", val ? val : "" ); g_free (val); val = get_recip (msg, GMIME_ADDRESS_TYPE_CC); g_print ("Cc : %s\n", val ? val : "" ); g_free (val); val = get_recip (msg, GMIME_ADDRESS_TYPE_BCC); g_print ("Bcc : %s\n", val ? val : "" ); g_free (val); str = g_mime_message_get_subject (msg); g_print ("Subject: %s\n", str ? str : ""); print_date (msg); str = g_mime_message_get_message_id (msg); g_print ("Msg-id : %s\n", str ? str : ""); { gchar *refsstr; refsstr = get_refs_str (msg); g_print ("Refs : %s\n", refsstr ? refsstr : ""); g_free (refsstr); } print_body (msg); return TRUE; } static gboolean test_stream (GMimeStream *stream) { GMimeParser *parser; GMimeMessage *msg; gboolean rv; parser = NULL; msg = NULL; parser = g_mime_parser_new_with_stream (stream); if (!parser) { g_warning ("failed to create parser"); rv = FALSE; goto leave; } msg = g_mime_parser_construct_message (parser, NULL); if (!msg) { g_warning ("failed to construct message"); rv = FALSE; goto leave; } rv = test_message (msg); leave: if (parser) g_object_unref (parser); if (msg) g_object_unref (msg); return rv; } static gboolean test_file (const char *path) { FILE *file; GMimeStream *stream; gboolean rv; stream = NULL; file = NULL; file = fopen (path, "r"); if (!file) { g_warning ("cannot open file '%s': %s", path, g_strerror(errno)); rv = FALSE; goto leave; } stream = g_mime_stream_file_new (file); if (!stream) { g_warning ("cannot open stream for '%s'", path); rv = FALSE; goto leave; } rv = test_stream (stream); g_object_unref (stream); return rv; leave: if (file) fclose (file); return rv; } int main (int argc, char *argv[]) { gboolean rv; if (argc != 2) { g_printerr ("usage: %s \n", argv[0]); return 1; } setlocale (LC_ALL, ""); g_mime_init(); rv = test_file (argv[1]); g_mime_shutdown (); return rv ? 0 : 1; } mu-1.6.10/contrib/mu-completion.zsh000066400000000000000000000063641414367003600172130ustar00rootroot00000000000000#compdef mu ## Copyright (C) 2011-2012 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # zsh completion for mu. Install this by copying/linking to this file somewhere in # your $fpath; the link/copy must have a name starting with an underscore "_" # main dispatcher function _mu() { if (( CURRENT > 2 )) ; then local cmd=${words[2]} curcontext="${curcontext%:*:*}:mu-$cmd" (( CURRENT-- )) shift words _call_function ret _mu_$cmd return ret else _mu_commands fi } _mu_commands() { local -a mu_commands mu_commands=( 'index:scan your maildirs and import their metadata in the database' 'find:search for messages in the database' 'view:display specific messages' 'cfind:search for contacts (name + email) in the database' 'extract:extract message-parts (attachments) and save or open them' 'mkdir:create maildirs' # below are not generally very useful, so let's not auto-complete them # 'add: add a message to the database.' # 'remove:remove a message from the database.' # 'server:sart the mu server' ) _describe -t command 'command' mu_commands } _mu_common_options=( '--debug[output information useful for debugging mu]' '--quiet[do not give any non-critical information]' '--nocolor[do not use colors in some of the output]' '--version[display mu version and copyright information]' '--log-stderr[log to standard error]' ) _mu_db_options=( '--muhome[use some non-default location for the mu database]:directory:_files' ) _mu_find_options=( '--fields[fields to display in the output]' '--sortfield[field to sort the output by]' '--descending[sort in descending order]' '--summary[include a summary of the message]' '--summary-len[number of lines to use for the summary]' '--bookmark[use a named bookmark]' '--output[set the kind of output for the query]' ) _mu_view_options=( '--summary[only show a summary of the message]' '--summary-len[number of lines to use for the summary]' ) _mu_view() { _arguments -s : \ $_mu_common_options \ $_mu_view_options } _mu_extract() { _files } _mu_find() { _arguments -s : \ $_mu_common_options \ $_mu_db_options \ $_mu_find_options } _mu_index() { _arguments -s : \ $_mu_db_options \ $_mu_common_options }mu _mu_cleanup() { _arguments -s : \ $_mu_db_options \ $_mu_common_options } _mu_mkdir() { _arguments -s : \ '--mode=[file mode for the new Maildir]:file mode: ' \ $_mu_common_options } _mu "$@" # Local variables: # mode: sh # End: mu-1.6.10/contrib/mu-sexp-convert000077500000000000000000000144221414367003600166710ustar00rootroot00000000000000#!/bin/sh exec guile -e main -s $0 $@ !# ;; Copyright (C) 2012 Dirk-Jan C. Binnema ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; ;; a little hack to convert the output of ;; mu find --format=sexp ;; and ;; mu view --format=sexp ;; into XML or JSON (use-modules (ice-9 getopt-long) (ice-9 format) (ice-9 regex)) (use-modules (sxml simple)) (define (mapconcat func lst sepa) "Apply FUNC to elements of LST, concat the result as strings separated by SEPA." (if (null? lst) "" (string-append (func (car lst)) (if (null? (cdr lst)) "" (string-append sepa (mapconcat func (cdr lst) sepa)))))) (define (property-list? obj) "Is OBJ a elisp-style property list (ie. a list of the form (:symbol1 something :symbol2 somethingelse), as in an elisp proplilst." (and (list? obj) (not (null? obj)) (symbol? (car obj)) (string= ":" (substring (symbol->string (car obj)) 0 1)))) (define (plist->pairs plist) "Convert an elisp-style property list; e.g: (:prop1 foo :prop2: bar ...) into a list of pairs ((prop1 . foo) (prop2 . bar) ...)." (if (null? plist) '() (cons (cons (substring (symbol->string (car plist)) 1) (cadr plist)) (plist->pairs (cddr plist))))) (define (string->xml str) "XML-encode STR." ;; sneakily re-using sxml->xml (call-with-output-string (lambda (port) (sxml->xml str port)))) (define (string->json str) "Convert string into a JSON-encoded string." (letrec ((convert (lambda (lst) (if (null? lst) "" (string-append (cond ((equal? (car lst) #\") "\\\"") ((equal? (car lst) #\\) "\\\\") ((equal? (car lst) #\/) "\\/") ((equal? (car lst) #\bs) "\\b") ((equal? (car lst) #\ff) "\\f") ((equal? (car lst) #\lf) "\\n") ((equal? (car lst) #\cr) "\\r") ((equal? (car lst) #\ht) "\\t") (#t (string (car lst)))) (convert (cdr lst))))))) (convert (string->list str)))) (define (etime->time_t t) "Convert elisp time object T into a time_t value." (logior (ash (car t) 16) (car (cdr t)))) (define (sexp->xml) "Convert string INPUT to XML, return the XML (string)." (letrec ((convert-xml (lambda* (expr #:optional parent) (cond ((property-list? expr) (mapconcat (lambda (pair) (format #f "\t<~a>~a\n" (car pair) (convert-xml (cdr pair) (car pair)) (car pair))) (plist->pairs expr) " ")) ((list? expr) (cond ((member parent '("from" "to" "cc" "bcc")) (mapconcat (lambda (addr) (format #f "
~a~a
" (if (string? (car addr)) (format #f "~a" (string->xml (car addr))) "") (if (string? (cdr addr)) (format #f "~a" (string->xml (cdr addr))) ""))) expr " ")) ((string= parent "parts") "") ;; for now, ignore ;; convert the crazy emacs time thingy to time_t... ((string= parent "date") (format #f "~a" (etime->time_t expr))) (#t (mapconcat (lambda (elm) (format #f "~a" (convert-xml elm))) expr "")))) ((string? expr) (string->xml expr)) ((symbol? expr) (format #f "~a" expr)) ((number? expr) (number->string expr)) (#t ".")))) (msg->xml (lambda () (let ((expr (read))) (if (not (eof-object? expr)) (string-append (format #f "\n~a\n" (convert-xml expr)) (msg->xml)) ""))))) (format #f "\n\n~a" (msg->xml)))) (define (sexp->json) "Convert string INPUT to JSON, return the JSON (string)." (letrec ((convert-json (lambda* (expr #:optional parent) (cond ((property-list? expr) (mapconcat (lambda (pair) (format #f "\n\t\"~a\": ~a" (car pair) (convert-json (cdr pair) (car pair)))) (plist->pairs expr) ", ")) ((list? expr) (cond ((member parent '("from" "to" "cc" "bcc")) (string-append "[" (mapconcat (lambda (addr) (format #f "{~a~a}" (if (string? (car addr)) (format #f "\"name\": \"~a\"," (string->json (car addr))) "") (if (string? (cdr addr)) (format #f "\"email\": \"~a\"" (string->json (cdr addr))) ""))) expr ", ") "]")) ((string= parent "parts") "[]") ;; todo ;; convert the crazy emacs time thingy to time_t... ((string= parent "date") (format #f "~a" (format #f "~a" (etime->time_t expr)))) (#t (string-append "[" (mapconcat (lambda (elm) (format #f "~a" (convert-json elm))) expr ",") "]")))) ((string? expr) (format #f "\"~a\"" (string->json expr))) ((symbol? expr) (format #f "\"~a\"" expr)) ((number? expr) (number->string expr)) (#t ".")))) (msg->json (lambda (first) (let ((expr (read))) (if (not (eof-object? expr)) (string-append (format #f "~a{~a\n}" (if first "" ",\n") (convert-json expr)) (msg->json #f)) ""))))) (format #f "[\n~a\n]" (msg->json #t)))) (define (main args) (let* ((optionspec '((format (value #t)))) (options (getopt-long args optionspec)) (msg (string-append "usage: mu-sexp-convert " "--format=\n" "reads from standard-input and prints to standard output\n")) (outformat (or (option-ref options 'format #f) (begin (display msg) (exit 1))))) (cond ((string= outformat "xml") (format #t "~a\n" (sexp->xml))) ((string= outformat "json") (format #t "~a\n" (sexp->json))) (#t (begin (display msg) (exit 1)))))) ;; Local Variables: ;; mode: scheme ;; End: mu-1.6.10/contrib/mu.spec000066400000000000000000000066251414367003600151720ustar00rootroot00000000000000 # These refer to the release version # When 0.9.9.6 gets out, remove the global pre line %global pre pre2 %global rel 1 Summary: A lightweight email search engine for Maildirs Name: mu Version: 0.9.9.6 URL: https://github.com/djcb/mu # From Packaging:NamingGuidelines for pre-relase versions: # Release: 0.%{X}.%{alphatag} where %{X} is the release number %if %{pre} Release: 0.%{rel}.%{prerelease}%{?dist} %else Release: %{rel}%{?dist} %endif License: GPLv3 Group: Applications/Internet BuildRoot: %{_tmppath}/%{name}-%{version}-build # Source is at ssaavedra repo because djcb has not yet this version tag created Source0: http://github.com/ssaavedra/%{name}/archive/v%{version}%{?pre}.tar.gz BuildRequires: emacs-el BuildRequires: emacs BuildRequires: gmime-devel BuildRequires: guile-devel BuildRequires: xapian-core-devel BuildRequires: libuuid-devel BuildRequires: texinfo Requires: gmime Requires: guile Requires: xapian-core-libs Requires: emacs-filesystem >= %{_emacs_version} %description E-mail is the 'flow' in the work flow of many people. Consequently, one spends a lot of time searching for old e-mails, to dig up some important piece of information. With people having tens of thousands of e-mails (or more), this is becoming harder and harder. How to find that one e-mail in an ever-growing haystack? Enter mu. 'mu' is a set of command-line tools for Linux/Unix that enable you to quickly find the e-mails you are looking for, assuming that you store your e-mails in Maildirs (if you don't know what 'Maildirs' are, you are probably not using them). %package gtk Group: Applications/Internet Summary: GUI for using mu (called mug) BuildRequires: gtk3-devel BuildRequires: webkitgtk3-devel Requires: gtk3 Requires: gmime Requires: webkitgtk3 Requires: mu = %{version}-%{release} %description gtk Mug is a simple GUI for mu from version 0.9. %package guile Group: Applications/Internet Summary: Guile scripting capabilities for mu Requires: guile Requires: mu = %{version}-%{release} Requires(post): info Requires(preun): info %description guile Bindings for Guile to interact with mu. %prep %setup -n %{name}-%{version}%{?pre} -q %build autoreconf -i %configure make %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} install -p -c -m 755 %{_builddir}/%{buildsubdir}/toys/mug/mug %{buildroot}%{_bindir}/mug cp -p %{_builddir}/%{buildsubdir}/mu4e/*.el %{buildroot}%{_emacs_sitelispdir}/mu4e/ rm -f %{buildroot}%{_infodir}/dir %clean rm -rf %{buildroot} %post /sbin/install-info \ --info-dir=%{_infodir} %{_infodir}/mu4e.info.gz || : %preun if [ $1 = 0 -a -f %{_infodir}/mu4e.info.gz ]; then /sbin/install-info --delete \ --info-dir=%{_infodir} %{_infodir}/mu4e.info.gz || : fi %post guile /sbin/install-info \ --info-dir=%{_infodir} %{_infodir}/mu-guile.info.gz || : %preun guile if [ $1 = 0 -a -f %{_infodir}/mu-guile.info.gz ]; then /sbin/install-info --delete \ --info-dir=%{_infodir} %{_infodir}/mu-guile.info.gz || : fi %files %defattr(-,root,root) %{_bindir}/mu %{_mandir}/man1/* %{_mandir}/man5/* %{_datadir}/mu/* %{_emacs_sitelispdir}/mu4e %{_emacs_sitelispdir}/mu4e/*.elc %{_emacs_sitelispdir}/mu4e/*.el %{_infodir}/mu4e.info.gz %files gtk %{_bindir}/mug %files guile %{_libdir}/libguile-mu.* %{_datadir}/guile/site/2.0/mu/* %{_datadir}/guile/site/2.0/mu.scm %{_infodir}/mu-guile.info.gz %changelog * Wed Feb 12 2014 Santiago Saavedra - 0.9.9.5-1 - Create first SPEC. mu-1.6.10/gtest.mk000066400000000000000000000025071414367003600137070ustar00rootroot00000000000000## Copyright (C) 2011 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 3, or (at your option) any ## later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. TEST_PROGS= # NOTE: we set the locale/tz to some well-know values, so the tests # (at least when running under 'make check') run in a predictable # environment. There are specific tests different timezone, though. # test: all $(TEST_PROGS) @export LC_ALL="en_US.utf8" @export TZ="Europe/Helsinki" @test -z "$(TEST_PROGS)" || gtester --verbose $(TEST_PROGS) || exit $$?; \ test -z "$(SUBDIRS)" || \ for subdir in $(SUBDIRS); do \ test "$$subdir" = "." || \ (cd ./$$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $$? ; \ done .PHONY: test gprof mu-1.6.10/guile/000077500000000000000000000000001414367003600133315ustar00rootroot00000000000000mu-1.6.10/guile/Makefile.am000066400000000000000000000050651414367003600153730ustar00rootroot00000000000000## Copyright (C) 2011-2013 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk # note, we need top_builddir for snarfing with 'make distcheck' (ie., # with separate builddir) SUBDIRS= . mu scripts examples tests AM_CPPFLAGS= \ -I. -I${top_builddir} -I${top_srcdir}/lib \ ${GUILE_CFLAGS} \ ${GLIB_CFLAGS} # don't use -Werror, as it might break on other compilers # use -Wno-unused-parameters, because some callbacks may not # really need all the params they get AM_CFLAGS= \ $(ASAN_CFLAGS) \ ${WARN_CFLAGS} \ -Wno-suggest-attribute=noreturn \ -Wno-missing-prototypes \ -Wno-missing-declarations AM_CXXFLAGS= \ $(ASAN_CXXFLAGS) \ ${WARN_CXXFLAGS} \ -Wno-redundant-decls \ -Wno-missing-declarations \ -Wno-suggest-attribute=noreturn lib_LTLIBRARIES= \ libguile-mu.la libguile_mu_la_SOURCES= \ mu-guile.cc \ mu-guile.hh \ mu-guile-message.cc \ mu-guile-message.hh libguile_mu_la_CFLAGS=$(AM_CFLAGS) libguile_mu_la_CXXFLAGS=$(AM_CXXFLAGS) libguile_mu_la_LIBADD= \ ${top_builddir}/lib/libmu.la \ ${top_builddir}/lib/utils/libmu-utils.la \ $(READLINE_LIBS) \ ${GUILE_LIBS} libguile_mu_la_LDFLAGS= \ $(ASAN_LDFLAGS) \ -shared \ -export-dynamic XFILES= \ mu-guile.x \ mu-guile-message.x info_TEXINFOS= \ mu-guile.texi mu_guile_TEXINFOS= \ fdl.texi BUILT_SOURCES=$(XFILES) export CPP snarfcxxopts= $(DEFS) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) SUFFIXES = .x .doc .cc.x: $(AM_V_GEN) $(GUILE_SNARF) -o $@ $< $(snarfcxxopts) # FIXME: GUILE_SITEDIR would be better, but that # breaks 'make distcheck' scmdir=${prefix}/share/guile/site/${GUILE_EFFECTIVE_VERSION} scm_DATA=mu.scm EXTRA_DIST=$(scm_DATA) ## Add -MG to make the .x magic work with auto-dep code. MKDEP = $(CC) -M -MG $(snarfcppopts) CLEANFILES=$(XFILES) mu-1.6.10/guile/compile-scm.in000066400000000000000000000015611414367003600160740ustar00rootroot00000000000000#!/bin/sh ## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @abs_builddir@/build-env @guild@ compile "$@" # Local-Variables: # mode: sh # End: mu-1.6.10/guile/examples/000077500000000000000000000000001414367003600151475ustar00rootroot00000000000000mu-1.6.10/guile/examples/Makefile.am000066400000000000000000000016011414367003600172010ustar00rootroot00000000000000## Copyright (C) 2012-2013 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk EXTRA_DIST= \ msg-graphs \ contacts-export \ org2mu4e \ mu-biff mu-1.6.10/guile/examples/contacts-export000077500000000000000000000052231414367003600202340ustar00rootroot00000000000000#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012 Dirk-Jan C. Binnema ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (use-modules (ice-9 getopt-long) (ice-9 format)) (use-modules (srfi srfi-1)) (use-modules (mu)) (define (sort-by-freq c1 c2) (< (mu:frequency c1) (mu:frequency c2))) (define (sort-by-newness c1 c2) (< (mu:last-seen c1) (mu:last-seen c2))) (define (main args) (let* ((optionspec '( (muhome (value #t)) (sort-by (value #t)) (revert (value #f)) (format (value #t)) (limit (value #t)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (msg (string-append "usage: contacts-export [--help] [--muhome=] " "--format= " "--sort-by= [--revert] [--limit=]\n")) (help (option-ref options 'help #f)) (muhome (option-ref options 'muhome #f)) (sort-by (or (option-ref options 'sort-by #f) "frequency")) (revert (option-ref options 'revert #f)) (form (or (option-ref options 'format #f) "plain")) (limit (string->number (option-ref options 'limit "1000000")))) (if help (begin (display msg) (exit 0)) (begin (setlocale LC_ALL "") (mu:initialize muhome) (let* ((sort-func (cond ((string= sort-by "frequency") sort-by-freq) ((string= sort-by "newness") sort-by-newness) (else (begin (display msg) (exit 1))))) (contacts '())) ;; make a list of all contacts (mu:for-each-contact (lambda (c) (set! contacts (cons c contacts)))) ;; should we sort it? (if sort-by (set! contacts (sort! contacts (if revert (negate sort-func) sort-func)))) ;; should we limit the number? (if (and limit (< limit (length contacts))) (set! contacts (take! contacts limit))) ;; export! (for-each (lambda (c) (format #t "~a\n" (mu:contact->string c form))) contacts)))))) ;; Local Variables: ;; mode: scheme ;; End: mu-1.6.10/guile/examples/msg-graphs000077500000000000000000000110601414367003600171430ustar00rootroot00000000000000#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (setlocale LC_ALL "") (use-modules (ice-9 getopt-long) (ice-9 optargs) (ice-9 popen) (ice-9 format)) (use-modules (mu) (mu stats) (mu plot)) ;;(use-modules (mu) (mu message) (mu stats) (mu plot)) (define (per-hour expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot (sort (mu:tabulate (lambda (msg) (tm:hour (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per hour matching ~a" expr) "Hour" "Messages" output)) (define (per-day expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot (mu:weekday-numbers->names (sort (mu:tabulate (lambda (msg) (tm:wday (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y))))) (format #f "Messages per weekday matching ~a" expr) "Day" "Messages" output)) (define (per-month expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot (mu:month-numbers->names (sort (mu:tabulate (lambda (msg) (tm:mon (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y))))) (format #f "Messages per month matching ~a" expr) "Month" "Messages" output)) (define (per-year-month expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot (sort (mu:tabulate (lambda (msg) (string->number (format #f "~d~2'0d" (+ 1900 (tm:year (localtime (mu:date msg)))) (tm:mon (localtime (mu:date msg)))))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per year/month matching ~a" expr) "Year/Month" "Messages" output)) (define (per-year expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot (sort (mu:tabulate (lambda (msg) (+ 1900 (tm:year (localtime (mu:date msg))))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per year matching ~a" expr) "Year" "Messages" output)) (define (main args) (let* ((optionspec '( (muhome (value #t)) (what (value #t)) (text (value #f)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (msg (string-append "usage: mu-msg-stats [--help] [--text] " "[--muhome=] " "--what= [searchexpr]\n")) (help (option-ref options 'help #f)) (what (option-ref options 'what #f)) (text (option-ref options 'text #f)) ;; if `text' is `#f', use a graphical window by setting output to "wxt", ;; else use text-mode plotting ("dumb") (output (if text "dumb" "wxt")) (muhome (option-ref options 'muhome #f)) (restargs (option-ref options '() #f)) (expr (if restargs (string-join restargs) ""))) (if (or help (not what)) (begin (display msg) (exit (if help 0 1)))) (mu:initialize muhome) (cond ((string= what "per-hour") (per-hour expr output)) ((string= what "per-day") (per-day expr output)) ((string= what "per-month") (per-month expr output)) ((string= what "per-year-month") (per-year-month expr output)) ((string= what "per-year") (per-year expr output)) (else (begin (display msg) (exit 1)))))) ;; Local Variables: ;; mode: scheme ;; End: mu-1.6.10/guile/examples/mu-biff000077500000000000000000000035531414367003600164300ustar00rootroot00000000000000#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012 Dirk-Jan C. Binnema ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; script to list the message matching which are newer than ;; minutes ;; use it, eg. like: ;; $ mu-biff --newer-than=`date +%s --date='5 minutes ago'` "maildir:/inbox" (use-modules (ice-9 getopt-long) (ice-9 format)) (use-modules (mu)) (define (main args) (let* ((optionspec '((muhome (value #t)) (newer-than (value #t)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (msg (string-append "usage: mu-biff [--help] [--muhome=]" " [--newer-than=] ")) (help (option-ref options 'help #f)) (newer-than (string->number (option-ref options 'newer-than "0"))) (muhome (option-ref options 'muhome #f)) (query (string-concatenate (option-ref options '() '())))) (if help (begin (display msg) (newline) (exit 0)) (begin (mu:initialize muhome) (mu:for-each-message (lambda (msg) (if (> (mu:timestamp msg) newer-than) (format #t "~a ~a\n" (mu:from msg) (mu:subject msg)))) query))))) ;; Local Variables: ;; mode: scheme ;; End: mu-1.6.10/guile/examples/org2mu4e000077500000000000000000000050041414367003600165400ustar00rootroot00000000000000#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (use-modules (ice-9 getopt-long) (ice-9 format)) (use-modules (mu)) (define (display-org-header query) "Print the header for the org-file for QUERY." (format #t "* Messages matching '~a'\n\n" query)) (define (org-mu4e-link msg) "Create a link for this message understandable by org-mu4e." (let* ((subject ;; cleanup subject (string-map (lambda (kar) (if (member kar '(#\] #\[)) #\space kar)) (or (mu:subject msg) "No subject")))) (format #f "[[mu4e:msgid:~a][~s]]" (mu:message-id msg) subject))) (define (display-org-entry msg tag) "Write an org entry for MSG." (format #t "** ~a ~a\n\t~s\n\t~s\n" (org-mu4e-link msg) (if tag (string-concatenate `(":" ,tag "::")) "") (or (mu:from msg) "?") (let ((body (mu:body-txt msg))) (if (not body) ;; get a 'summary' of the body text "" (string-map (lambda (c) (if (or (char=? c #\newline) (char=? c #\return)) #\space c)) (substring body 0 (min (string-length body) 100))))))) (define (main args) (let* ((optionspec '( (muhome (value #t)) (tag (value #t)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (msg (string-append "usage: mu4e-org [--help] [--muhome=] [--tag=] ")) (help (option-ref options 'help #f)) (tag (option-ref options 'tag #f)) (muhome (option-ref options 'muhome #f)) (query (string-concatenate (option-ref options '() '())))) (if help (begin (display msg) (exit 0)) (begin (mu:initialize muhome) (display-org-header query) (mu:for-each-message (lambda (msg) (display-org-entry msg tag)) query))))) ;; Local Variables: ;; mode: scheme ;; End: mu-1.6.10/guile/fdl.texi000066400000000000000000000510301414367003600147700ustar00rootroot00000000000000@c The GNU Free Documentation License. @center Version 1.2, November 2002 @c This file is intended to be included within another document, @c hence no sectioning command or @node. @display Copyright @copyright{} 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @end display @enumerate 0 @item PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document @dfn{free} in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of ``copyleft'', which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. @item APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The ``Document'', below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as ``you''. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A ``Modified Version'' of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A ``Secondary Section'' is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The ``Invariant Sections'' are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The ``Cover Texts'' are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A ``Transparent'' copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not ``Transparent'' is called ``Opaque''. Examples of suitable formats for Transparent copies include plain @sc{ascii} without markup, Texinfo input format, La@TeX{} input format, @acronym{SGML} or @acronym{XML} using a publicly available @acronym{DTD}, and standard-conforming simple @acronym{HTML}, PostScript or @acronym{PDF} designed for human modification. Examples of transparent image formats include @acronym{PNG}, @acronym{XCF} and @acronym{JPG}. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, @acronym{SGML} or @acronym{XML} for which the @acronym{DTD} and/or processing tools are not generally available, and the machine-generated @acronym{HTML}, PostScript or @acronym{PDF} produced by some word processors for output purposes only. The ``Title Page'' means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, ``Title Page'' means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. A section ``Entitled XYZ'' means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as ``Acknowledgements'', ``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' of such a section when you modify the Document means that it remains a section ``Entitled XYZ'' according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. @item VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. @item COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. @item MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: @enumerate A @item Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. @item List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. @item State on the Title page the name of the publisher of the Modified Version, as the publisher. @item Preserve all the copyright notices of the Document. @item Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. @item Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. @item Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. @item Include an unaltered copy of this License. @item Preserve the section Entitled ``History'', Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled ``History'' in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. @item Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the ``History'' section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. @item For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. @item Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. @item Delete any section Entitled ``Endorsements''. Such a section may not be included in the Modified Version. @item Do not retitle any existing section to be Entitled ``Endorsements'' or to conflict in title with any Invariant Section. @item Preserve any Warranty Disclaimers. @end enumerate If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled ``Endorsements'', provided it contains nothing but endorsements of your Modified Version by various parties---for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. @item COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled ``History'' in the various original documents, forming one section Entitled ``History''; likewise combine any sections Entitled ``Acknowledgements'', and any sections Entitled ``Dedications''. You must delete all sections Entitled ``Endorsements.'' @item COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. @item AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an ``aggregate'' if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. @item TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled ``Acknowledgements'', ``Dedications'', or ``History'', the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. @item TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. @item FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See @uref{http://www.gnu.org/copyleft/}. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License ``or any later version'' applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. @end enumerate @page @heading ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: @smallexample @group Copyright (C) @var{year} @var{your name}. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''. @end group @end smallexample If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the ``with@dots{}Texts.'' line with this: @smallexample @group with the Invariant Sections being @var{list their titles}, with the Front-Cover Texts being @var{list}, and with the Back-Cover Texts being @var{list}. @end group @end smallexample If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. @c Local Variables: @c ispell-local-pdict: "ispell-dict" @c End: mu-1.6.10/guile/meson.build000066400000000000000000000050161414367003600154750ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # create a shell script for compiling from the source dirs compile_scm_conf = configuration_data() compile_scm_conf.set('abs_builddir', meson.current_build_dir()) compile_scm_conf.set('guild', 'guild') compile_scm=configure_file( input: 'compile-scm.in', output: 'compile-scm', configuration: compile_scm_conf, install: false ) run_command('chmod', '+x', compile_scm) scm_compiler=join_paths(meson.current_build_dir(), 'compile-scm') snarf = find_program('guile-snarf') snarf_args=['-o', '@OUTPUT@', '@INPUT@', '-I' + meson.current_source_dir() + '/..', '-I' + meson.current_source_dir() + '/../lib', '-I' + meson.current_build_dir() + '/..'] pkg_config=find_program('pkg-config') snarf_args+=run_command(pkg_config, '--cflags', 'glib-2.0', 'guile-3.0').stdout().strip() message(snarf_args) snarf_gen=generator(snarf, output: '@BASENAME@.x', arguments: snarf_args) snarf_srcs=['mu-guile.cc', 'mu-guile-message.cc'] snarf_x=snarf_gen.process(snarf_srcs) lib_guile_mu = shared_module( 'guile-mu', [ 'mu-guile.cc', 'mu-guile.hh', 'mu-guile-message.cc', 'mu-guile-message.hh', snarf_x ], dependencies: [guile_dep, glib_dep, lib_mu_dep, config_h_dep, thread_dep ], install: true) if makeinfo.found() custom_target('mu_guile_info', input: 'mu-guile.texi', output: 'mu-guile.info', install: true, install_dir: infodir, command: [makeinfo, '-o', join_paths(meson.current_build_dir(), 'mu-guile.info'), join_paths(meson.current_source_dir(), 'mu-guile.texi'), '-I', join_paths(meson.current_build_dir(), '..')]) endif guile_scm_dir=join_paths(datadir, 'guile', 'site', '3.0', 'mu') install_data(['mu.scm','mu/script.scm', 'mu/message.scm', 'mu/stats.scm', 'mu/plot.scm'], install_dir: guile_scm_dir) mu-1.6.10/guile/mu-guile-message.cc000066400000000000000000000374451414367003600170230ustar00rootroot00000000000000/* ** Copyright (C) 2011-2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-guile-message.hh" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include #pragma GCC diagnostic pop #include "mu-guile.hh" #include #include #include #include #include using namespace Mu; /* pseudo field, not in Xapian */ #define MU_GUILE_MSG_FIELD_ID_TIMESTAMP (MU_MSG_FIELD_ID_NUM + 1) /* some symbols */ static SCM SYMB_PRIO_LOW, SYMB_PRIO_NORMAL, SYMB_PRIO_HIGH; static SCM SYMB_FLAG_NEW, SYMB_FLAG_PASSED, SYMB_FLAG_REPLIED, SYMB_FLAG_SEEN, SYMB_FLAG_TRASHED, SYMB_FLAG_DRAFT, SYMB_FLAG_FLAGGED, SYMB_FLAG_SIGNED, SYMB_FLAG_ENCRYPTED, SYMB_FLAG_HAS_ATTACH, SYMB_FLAG_UNREAD, SYMB_FLAG_LIST; static SCM SYMB_CONTACT_TO, SYMB_CONTACT_CC, SYMB_CONTACT_BCC, SYMB_CONTACT_FROM; struct _MuMsgWrapper { MuMsg *_msg; gboolean _unrefme; }; typedef struct _MuMsgWrapper MuMsgWrapper; static long MSG_TAG; static gboolean mu_guile_scm_is_msg (SCM scm) { return SCM_NIMP(scm) && (long)SCM_CAR(scm) == MSG_TAG; } SCM mu_guile_msg_to_scm (MuMsg *msg) { MuMsgWrapper *msgwrap; g_return_val_if_fail (msg, SCM_UNDEFINED); msgwrap = (MuMsgWrapper*)scm_gc_malloc (sizeof (MuMsgWrapper), "msg"); msgwrap->_msg = msg; msgwrap->_unrefme = FALSE; SCM_RETURN_NEWSMOB (MSG_TAG, msgwrap); } struct _FlagData { MuFlags flags; SCM lst; }; typedef struct _FlagData FlagData; #define MU_GUILE_INITIALIZED_OR_ERROR \ do { \ if (!(mu_guile_initialized())) { \ mu_guile_error (FUNC_NAME, 0, \ "mu not initialized; call mu:initialize", \ SCM_UNDEFINED); \ return SCM_UNSPECIFIED; \ } \ } while (0) static void check_flag (MuFlags flag, FlagData *fdata) { SCM flag_scm; if (!(fdata->flags & flag)) return; switch (flag) { case MU_FLAG_NONE: break; case MU_FLAG_NEW: flag_scm = SYMB_FLAG_NEW; break; case MU_FLAG_PASSED: flag_scm = SYMB_FLAG_PASSED; break; case MU_FLAG_REPLIED: flag_scm = SYMB_FLAG_REPLIED; break; case MU_FLAG_SEEN: flag_scm = SYMB_FLAG_SEEN; break; case MU_FLAG_TRASHED: flag_scm = SYMB_FLAG_TRASHED; break; case MU_FLAG_SIGNED: flag_scm = SYMB_FLAG_SIGNED; break; case MU_FLAG_DRAFT: flag_scm = SYMB_FLAG_DRAFT; break; case MU_FLAG_FLAGGED: flag_scm = SYMB_FLAG_FLAGGED; break; case MU_FLAG_ENCRYPTED: flag_scm = SYMB_FLAG_ENCRYPTED; break; case MU_FLAG_HAS_ATTACH: flag_scm = SYMB_FLAG_HAS_ATTACH; break; case MU_FLAG_UNREAD: flag_scm = SYMB_FLAG_UNREAD; break; case MU_FLAG_LIST: flag_scm = SYMB_FLAG_LIST; break; default: flag_scm = SCM_UNDEFINED; } fdata->lst = scm_append_x (scm_list_2(fdata->lst, scm_list_1 (flag_scm))); } static SCM get_flags_scm (MuMsg *msg) { FlagData fdata; fdata.flags = mu_msg_get_flags (msg); fdata.lst = SCM_EOL; mu_flags_foreach ((MuFlagsForeachFunc)check_flag, &fdata); return fdata.lst; } static SCM get_prio_scm (MuMsg *msg) { switch (mu_msg_get_prio (msg)) { case MU_MSG_PRIO_LOW: return SYMB_PRIO_LOW; case MU_MSG_PRIO_NORMAL: return SYMB_PRIO_NORMAL; case MU_MSG_PRIO_HIGH: return SYMB_PRIO_HIGH; default: g_return_val_if_reached (SCM_UNDEFINED); } } static SCM msg_string_list_field (MuMsg *msg, MuMsgFieldId mfid) { SCM scmlst; const GSList *lst; lst = mu_msg_get_field_string_list (msg, mfid); for (scmlst = SCM_EOL; lst; lst = g_slist_next(lst)) { SCM item; item = scm_list_1 (mu_guile_scm_from_str((const char*)lst->data)); scmlst = scm_append_x (scm_list_2(scmlst, item)); } return scmlst; } static SCM get_body (MuMsg *msg, gboolean html) { SCM data; const char* body; MuMsgOptions opts; opts = MU_MSG_OPTION_NONE; if (html) body = mu_msg_get_body_html (msg, opts); else body = mu_msg_get_body_text (msg, opts); if (body) data = mu_guile_scm_from_str (body); else data = SCM_BOOL_F; /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file (msg); return data; } SCM_DEFINE (get_field, "mu:c:get-field", 2, 0, 0, (SCM MSG, SCM FIELD), "Get the field FIELD from message MSG.\n") #define FUNC_NAME s_get_field { MuMsgWrapper *msgwrap; MuMsgFieldId mfid; msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_integer_p(FIELD), FIELD, SCM_ARG2, FUNC_NAME); mfid = scm_to_int (FIELD); SCM_ASSERT (mfid < MU_MSG_FIELD_ID_NUM || mfid == MU_GUILE_MSG_FIELD_ID_TIMESTAMP, FIELD, SCM_ARG2, FUNC_NAME); switch (mfid) { case MU_MSG_FIELD_ID_PRIO: return get_prio_scm (msgwrap->_msg); case MU_MSG_FIELD_ID_FLAGS: return get_flags_scm (msgwrap->_msg); case MU_MSG_FIELD_ID_BODY_HTML: return get_body (msgwrap->_msg, TRUE); case MU_MSG_FIELD_ID_BODY_TEXT: return get_body (msgwrap->_msg, FALSE); /* our pseudo-field; we get it from the message file */ case MU_GUILE_MSG_FIELD_ID_TIMESTAMP: return scm_from_uint ( (unsigned)mu_msg_get_timestamp(msgwrap->_msg)); default: break; } switch (mu_msg_field_type (mfid)) { case MU_MSG_FIELD_TYPE_STRING: return mu_guile_scm_from_str (mu_msg_get_field_string(msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_BYTESIZE: case MU_MSG_FIELD_TYPE_TIME_T: return scm_from_uint ( mu_msg_get_field_numeric (msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_INT: return scm_from_int ( mu_msg_get_field_numeric (msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_STRING_LIST: return msg_string_list_field (msgwrap->_msg, mfid); default: SCM_ASSERT (0, FIELD, SCM_ARG2, FUNC_NAME); } } #undef FUNC_NAME struct _EachContactData { SCM lst; MuMsgContactType ctype; }; typedef struct _EachContactData EachContactData; static void contacts_to_list (MuMsgContact *contact, EachContactData *ecdata) { SCM item; if (ecdata->ctype != MU_MSG_CONTACT_TYPE_ALL && mu_msg_contact_type (contact) != ecdata->ctype) return; item = scm_list_1 (scm_cons (mu_guile_scm_from_str(mu_msg_contact_name (contact)), mu_guile_scm_from_str(mu_msg_contact_email (contact)))); ecdata->lst = scm_append_x (scm_list_2(ecdata->lst, item)); } SCM_DEFINE (get_contacts, "mu:c:get-contacts", 2, 0, 0, (SCM MSG, SCM CONTACT_TYPE), "Get a list of contact information pairs.\n") #define FUNC_NAME s_get_contacts { MuMsgWrapper *msgwrap; EachContactData ecdata; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_symbol_p (CONTACT_TYPE) || scm_is_bool(CONTACT_TYPE), CONTACT_TYPE, SCM_ARG2, FUNC_NAME); if (CONTACT_TYPE == SCM_BOOL_F) return SCM_UNSPECIFIED; /* nothing to do */ else if (CONTACT_TYPE == SCM_BOOL_T) ecdata.ctype = MU_MSG_CONTACT_TYPE_ALL; else { if (scm_is_eq (CONTACT_TYPE, SYMB_CONTACT_TO)) ecdata.ctype = MU_MSG_CONTACT_TYPE_TO; else if (scm_is_eq (CONTACT_TYPE, SYMB_CONTACT_CC)) ecdata.ctype = MU_MSG_CONTACT_TYPE_CC; else if (scm_is_eq (CONTACT_TYPE, SYMB_CONTACT_BCC)) ecdata.ctype = MU_MSG_CONTACT_TYPE_BCC; else if (scm_is_eq (CONTACT_TYPE, SYMB_CONTACT_FROM)) ecdata.ctype = MU_MSG_CONTACT_TYPE_FROM; else { mu_guile_error (FUNC_NAME, 0, "invalid contact type", SCM_UNDEFINED); return SCM_UNSPECIFIED; } } ecdata.lst = SCM_EOL; msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" mu_msg_contact_foreach (msgwrap->_msg, (MuMsgContactForeachFunc)contacts_to_list, &ecdata); #pragma GCC diagnostic pop /* explicitly close the file backend, so we won't run out of fds */ mu_msg_unload_msg_file (msgwrap->_msg); return ecdata.lst; } #undef FUNC_NAME struct _AttInfo { SCM attlist; gboolean attachments_only; }; typedef struct _AttInfo AttInfo; static void each_part (MuMsg *msg, MuMsgPart *part, AttInfo *attinfo) { char *mime_type, *filename; SCM elm; if (!part->type) return; if (attinfo->attachments_only && !mu_msg_part_maybe_attachment (part)) return; mime_type = g_strdup_printf ("%s/%s", part->type, part->subtype); filename = mu_msg_part_get_filename (part, FALSE); elm = scm_list_5 ( /* msg */ mu_guile_scm_from_str (mu_msg_get_path(msg)), /* index */ scm_from_uint(part->index), /* filename or #f */ filename ? mu_guile_scm_from_str (filename) : SCM_BOOL_F, /* mime-type */ mime_type ? mu_guile_scm_from_str (mime_type): SCM_BOOL_F, /* size */ part->size > 0 ? scm_from_uint (part->size) : SCM_BOOL_F); g_free (mime_type); g_free (filename); attinfo->attlist = scm_cons (elm, attinfo->attlist); } SCM_DEFINE (get_parts, "mu:c:get-parts", 1, 1, 0, (SCM MSG, SCM ATTS_ONLY), "Get the list of mime-parts for MSG. If ATTS_ONLY is #t, only" "get parts that are (look like) attachments. The resulting list has " "elements which are list of the form (index name mime-type size).\n") #define FUNC_NAME s_get_parts { MuMsgWrapper *msgwrap; AttInfo attinfo; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_is_bool(ATTS_ONLY), ATTS_ONLY, SCM_ARG2, FUNC_NAME); attinfo.attlist = SCM_EOL; /* empty list */ attinfo.attachments_only = ATTS_ONLY == SCM_BOOL_T ? TRUE : FALSE; msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); mu_msg_part_foreach (msgwrap->_msg, MU_MSG_OPTION_NONE, (MuMsgPartForeachFunc)each_part, &attinfo); /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file (msgwrap->_msg); return attinfo.attlist; } #undef FUNC_NAME SCM_DEFINE (get_header, "mu:c:get-header", 2, 0, 0, (SCM MSG, SCM HEADER), "Get an arbitrary HEADER from MSG.\n") #define FUNC_NAME s_get_header { MuMsgWrapper *msgwrap; char *header; SCM val; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT (mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_is_string (HEADER)||HEADER==SCM_UNDEFINED, HEADER, SCM_ARG2, FUNC_NAME); msgwrap = (MuMsgWrapper*) SCM_CDR(MSG); header = scm_to_utf8_string (HEADER); val = mu_guile_scm_from_str (mu_msg_get_header(msgwrap->_msg, header)); free (header); /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file (msgwrap->_msg); return val; } #undef FUNC_NAME static Mu::Option get_query_results (Mu::Store& store, const char* expr, int maxnum) { Mu::Query query(store); return query.run(expr, MU_MSG_FIELD_ID_NONE, Mu::QueryFlags::None, maxnum); } SCM_DEFINE (for_each_message, "mu:c:for-each-message", 3, 0, 0, (SCM FUNC, SCM EXPR, SCM MAXNUM), "Call FUNC for each msg in the message store matching EXPR. EXPR is" "either a string containing a mu search expression or a boolean; in the former " "case, limit the messages to only those matching the expression, in the " "latter case, match /all/ messages if the EXPR equals #t, and match " "none if EXPR equals #f.") #define FUNC_NAME s_for_each_message { char* expr{}; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT (scm_procedure_p (FUNC), FUNC, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_is_bool(EXPR) || scm_is_string (EXPR), EXPR, SCM_ARG2, FUNC_NAME); SCM_ASSERT (scm_is_integer (MAXNUM), MAXNUM, SCM_ARG3, FUNC_NAME); if (EXPR == SCM_BOOL_F) return SCM_UNSPECIFIED; /* nothing to do */ if (EXPR == SCM_BOOL_T) expr = strdup (""); /* note, "" matches *all* messages */ else expr = scm_to_utf8_string(EXPR); const auto res{get_query_results(mu_guile_store(), expr, scm_to_int(MAXNUM))}; free (expr); if (!res) return SCM_UNSPECIFIED; for (auto&& mi: *res) { auto msg{mi.floating_msg()}; if (msg) { auto msgsmob{mu_guile_msg_to_scm (mu_msg_ref(msg))}; scm_call_1 (FUNC, msgsmob); } } return SCM_UNSPECIFIED; } #undef FUNC_NAME static SCM register_symbol (const char *name) { SCM scm; scm = scm_from_utf8_symbol (name); scm_c_define (name, scm); scm_c_export (name, NULL); return scm; } static void define_symbols (void) { SYMB_CONTACT_TO = register_symbol ("mu:contact:to"); SYMB_CONTACT_CC = register_symbol ("mu:contact:cc"); SYMB_CONTACT_FROM = register_symbol ("mu:contact:from"); SYMB_CONTACT_BCC = register_symbol ("mu:contact:bcc"); SYMB_PRIO_LOW = register_symbol ("mu:prio:low"); SYMB_PRIO_NORMAL = register_symbol ("mu:prio:normal"); SYMB_PRIO_HIGH = register_symbol ("mu:prio:high"); SYMB_FLAG_NEW = register_symbol ("mu:flag:new"); SYMB_FLAG_PASSED = register_symbol ("mu:flag:passed"); SYMB_FLAG_REPLIED = register_symbol ("mu:flag:replied"); SYMB_FLAG_SEEN = register_symbol ("mu:flag:seen"); SYMB_FLAG_TRASHED = register_symbol ("mu:flag:trashed"); SYMB_FLAG_DRAFT = register_symbol ("mu:flag:draft"); SYMB_FLAG_FLAGGED = register_symbol ("mu:flag:flagged"); SYMB_FLAG_SIGNED = register_symbol ("mu:flag:signed"); SYMB_FLAG_ENCRYPTED = register_symbol ("mu:flag:encrypted"); SYMB_FLAG_HAS_ATTACH = register_symbol ("mu:flag:has-attach"); SYMB_FLAG_UNREAD = register_symbol ("mu:flag:unread"); SYMB_FLAG_LIST = register_symbol ("mu:flag:list"); } static struct { const char* name; unsigned val; } VAR_PAIRS[] = { { "mu:field:bcc", MU_MSG_FIELD_ID_BCC }, { "mu:field:body-html", MU_MSG_FIELD_ID_BODY_HTML }, { "mu:field:body-txt", MU_MSG_FIELD_ID_BODY_TEXT }, { "mu:field:cc", MU_MSG_FIELD_ID_CC }, { "mu:field:date", MU_MSG_FIELD_ID_DATE }, { "mu:field:flags", MU_MSG_FIELD_ID_FLAGS }, { "mu:field:from", MU_MSG_FIELD_ID_FROM }, { "mu:field:maildir", MU_MSG_FIELD_ID_MAILDIR }, { "mu:field:message-id",MU_MSG_FIELD_ID_MSGID }, { "mu:field:path", MU_MSG_FIELD_ID_PATH }, { "mu:field:prio", MU_MSG_FIELD_ID_PRIO }, { "mu:field:refs", MU_MSG_FIELD_ID_REFS }, { "mu:field:size", MU_MSG_FIELD_ID_SIZE }, { "mu:field:subject", MU_MSG_FIELD_ID_SUBJECT }, { "mu:field:tags", MU_MSG_FIELD_ID_TAGS }, { "mu:field:to", MU_MSG_FIELD_ID_TO }, /* non-Xapian field: timestamp */ { "mu:field:timestamp", MU_GUILE_MSG_FIELD_ID_TIMESTAMP } }; static void define_vars (void) { unsigned u; for (u = 0; u != G_N_ELEMENTS(VAR_PAIRS); ++u) { scm_c_define (VAR_PAIRS[u].name, scm_from_uint (VAR_PAIRS[u].val)); scm_c_export (VAR_PAIRS[u].name, NULL); } } static SCM msg_mark (SCM msg_smob) { MuMsgWrapper *msgwrap; msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); msgwrap->_unrefme = TRUE; return SCM_UNSPECIFIED; } static size_t msg_free (SCM msg_smob) { MuMsgWrapper *msgwrap; msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); if (msgwrap->_unrefme) mu_msg_unref (msgwrap->_msg); return sizeof (MuMsgWrapper); } static int msg_print (SCM msg_smob, SCM port, scm_print_state * pstate) { MuMsgWrapper *msgwrap; msgwrap = (MuMsgWrapper*) SCM_CDR(msg_smob); scm_puts ("#_msg), port); scm_puts (">", port); return 1; } void* mu_guile_message_init (void *data) { MSG_TAG = scm_make_smob_type ("msg", sizeof(MuMsgWrapper)); scm_set_smob_mark (MSG_TAG, msg_mark); scm_set_smob_free (MSG_TAG, msg_free); scm_set_smob_print (MSG_TAG, msg_print); define_vars (); define_symbols (); #ifndef SCM_MAGIC_SNARFER #include "mu-guile-message.x" #endif /*SCM_MAGIC_SNARFER*/ return NULL; } mu-1.6.10/guile/mu-guile-message.hh000066400000000000000000000017631414367003600170270ustar00rootroot00000000000000/* ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_GUILE_MESSAGE_H__ #define MU_GUILE_MESSAGE_H__ /** * Initialize this mu guile module. * * @param data * * @return */ extern "C" { void* mu_guile_message_init (void *data);} #endif /*MU_GUILE_MESSAGE_HH__*/ mu-1.6.10/guile/mu-guile.cc000066400000000000000000000131071414367003600153660ustar00rootroot00000000000000/* ** Copyright (C) 2011-2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-guile.hh" #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include #pragma GCC diagnostic pop #include #include #include #include using namespace Mu; SCM mu_guile_scm_from_str (const char *str) { if (!str) return SCM_BOOL_F; else return scm_from_stringn (str, strlen(str), "UTF-8", SCM_FAILED_CONVERSION_QUESTION_MARK); } SCM mu_guile_error (const char *func_name, int status, const char *fmt, SCM args) { scm_error_scm (scm_from_locale_symbol ("MuError"), scm_from_utf8_string (func_name ? func_name : ""), scm_from_utf8_string (fmt), args, scm_list_1 (scm_from_int (status))); return SCM_UNSPECIFIED; } SCM mu_guile_g_error (const char *func_name, GError *err) { scm_error_scm (scm_from_locale_symbol ("MuError"), scm_from_utf8_string (func_name), scm_from_utf8_string (err ? err->message : "error"), SCM_UNDEFINED, SCM_UNDEFINED); return SCM_UNSPECIFIED; } /* there can be only one */ static std::unique_ptr StoreSingleton; static gboolean mu_guile_init_instance (const char *muhome) try { setlocale (LC_ALL, ""); if (!mu_runtime_init (muhome, "guile", true) || StoreSingleton) return FALSE; StoreSingleton = std::make_unique( mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB)); g_debug ("mu-guile: opened store @ %s (n=%zu); maildir: %s", StoreSingleton->metadata().database_path.c_str(), StoreSingleton->size(), StoreSingleton->metadata().root_maildir.c_str()); return TRUE; } catch (...) { return FALSE; } static void mu_guile_uninit_instance () { StoreSingleton.reset(); mu_runtime_uninit (); } Mu::Store& mu_guile_store () { if (!StoreSingleton) g_error("mu guile not initialized"); return *StoreSingleton.get(); } gboolean mu_guile_initialized () { g_debug ("initialized ? %u", !!StoreSingleton); return !!StoreSingleton; } SCM_DEFINE_PUBLIC (mu_initialize, "mu:initialize", 0, 1, 0, (SCM MUHOME), "Initialize mu - needed before you call any of the other " "functions. Optionally, you can provide MUHOME which should be an " "absolute path to your mu home directory " "-- typically, the default, ~/.cache/mu, should be just fine.") #define FUNC_NAME s_mu_initialize { char *muhome; gboolean rv; SCM_ASSERT (scm_is_string (MUHOME) || MUHOME == SCM_BOOL_F || SCM_UNBNDP(MUHOME), MUHOME, SCM_ARG1, FUNC_NAME); if (mu_guile_initialized()) return mu_guile_error (FUNC_NAME, 0, "Already initialized", SCM_UNSPECIFIED); if (SCM_UNBNDP(MUHOME) || MUHOME == SCM_BOOL_F) muhome = NULL; else muhome = scm_to_utf8_string (MUHOME); rv = mu_guile_init_instance (muhome); free (muhome); if (!rv) return mu_guile_error (FUNC_NAME, 0, "Failed to initialize mu", SCM_UNSPECIFIED); g_debug ("mu-guile: initialized @ %s (%p)", muhome ? muhome : "", StoreSingleton.get()); /* cleanup when we're exiting */ atexit (mu_guile_uninit_instance); return SCM_UNSPECIFIED; } #undef FUNC_NAME SCM_DEFINE_PUBLIC (mu_initialized_p, "mu:initialized?", 0, 0, 0, (void), "Whether mu is initialized or not.\n") #define FUNC_NAME s_mu_initialized_p { return mu_guile_initialized() ? SCM_BOOL_T : SCM_BOOL_F; } #undef FUNC_NAME SCM_DEFINE (log_func, "mu:c:log", 1, 0, 1, (SCM LEVEL, SCM FRM, SCM ARGS), "log some message at LEVEL using a list of ARGS applied to FRM" "(in 'simple-format' notation).\n") #define FUNC_NAME s_log_func { gchar *output; SCM str; int level; SCM_ASSERT (scm_integer_p(LEVEL), LEVEL, SCM_ARG1, FUNC_NAME); SCM_ASSERT (scm_is_string(FRM), FRM, SCM_ARG2, ""); SCM_VALIDATE_REST_ARGUMENT(ARGS); level = scm_to_int (LEVEL); if (level != G_LOG_LEVEL_MESSAGE && level != G_LOG_LEVEL_WARNING && level != G_LOG_LEVEL_CRITICAL) return mu_guile_error (FUNC_NAME, 0, "invalid log level", SCM_UNSPECIFIED); str = scm_simple_format (SCM_BOOL_F, FRM, ARGS); if (!scm_is_string (str)) return SCM_UNSPECIFIED; output = scm_to_utf8_string (str); g_log (G_LOG_DOMAIN, (GLogLevelFlags)level, "%s", output); free (output); return SCM_UNSPECIFIED; } #undef FUNC_NAME static struct { const char* name; unsigned val; } VAR_PAIRS[] = { { "mu:message", G_LOG_LEVEL_MESSAGE }, { "mu:warning", G_LOG_LEVEL_WARNING }, { "mu:critical", G_LOG_LEVEL_CRITICAL } }; static void define_vars (void) { unsigned u; for (u = 0; u != G_N_ELEMENTS(VAR_PAIRS); ++u) { scm_c_define (VAR_PAIRS[u].name, scm_from_uint (VAR_PAIRS[u].val)); scm_c_export (VAR_PAIRS[u].name, NULL); } } void* mu_guile_init (void *data) { define_vars (); #ifndef SCM_MAGIC_SNARFER #include "mu-guile.x" #endif /*SCM_MAGIC_SNARFER*/ return NULL; } mu-1.6.10/guile/mu-guile.hh000066400000000000000000000040051414367003600153750ustar00rootroot00000000000000/* ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_GUILE_H__ #define __MU_GUILE_H__ #include #include #include /** * get the singleton Store instance */ Mu::Store& mu_guile_store (); /** * whether mu-guile is initialized * * @return TRUE if MuGuile is Initialized, FALSE otherwise */ gboolean mu_guile_initialized (); /** * raise a guile error (based on a GError) * * @param func_name function name * @param err the error * * @return SCM_UNSPECIFIED */ SCM mu_guile_g_error (const char *func_name, GError *err); /** * raise a guile error * * @param func_name function * @param status err code * @param fmt format string for error msg * @param args params for format string * * @return SCM_UNSPECIFIED */ SCM mu_guile_error (const char *func_name, int status, const char *fmt, SCM args); /** * convert a const char* into an SCM -- either a string or, if str == * NULL, #f. It assumes str is in UTF8 encoding, and replace * characters with '?' if needed. * * @param str a string or NULL * * @return a guile string or #f */ SCM mu_guile_scm_from_str (const char *str); /** * Initialize this mu guile module. * * @param data * * @return */ extern "C" { void* mu_guile_init (void *data); } #endif /*__MU_GUILE_H__*/ mu-1.6.10/guile/mu-guile.texi000066400000000000000000001007261414367003600157560ustar00rootroot00000000000000\input texinfo.tex @c -*-texinfo-*- @c %**start of header @setfilename mu-guile.info @settitle mu-guile user manual @c Use proper quote and backtick for code sections in PDF output @c Cf. Texinfo manual 14.2 @set txicodequoteundirected @set txicodequotebacktick @documentencoding UTF-8 @c %**end of header @include version.texi @copying Copyright @copyright{} 2012 Dirk-Jan C. Binnema @quotation Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License.'' @end quotation @end copying @titlepage @title @t{mu-guile} - extending @t{mu} with Guile Scheme @subtitle version @value{VERSION} @author Dirk-Jan C. Binnema @c The following two commands start the copyright page. @page @vskip 0pt plus 1filll @insertcopying @end titlepage @dircategory The Algorithmic Language Scheme @direntry * Mu-guile: (mu-guile). Guile-bindings for the mu e-mail indexer/searcher @end direntry @contents @ifnottex @node Top @top mu-guile manual @end ifnottex @iftex @node Welcome to mu-guile @unnumbered Welcome to mu-guile @end iftex Welcome to @t{mu-guile}! @t{mu} is a program for indexing and searching your e-mails. It can search your messages in many different ways, but sometimes that may not be enough. If you have very specific queries, or want do generate some statistics, you need some more power. @t{mu-guile} is made for those cases. @t{mu-guile} exposes the internals of @t{mu} and its database to the @t{guile} programming language. Guile is the @emph{GNU Ubiquitous Intelligent Language for Extensions} - a version of the @emph{Scheme} programming language and the official GNU extension language. Guile/Scheme is a member of the @emph{Lisp} family of programming languages -- like emacs-lisp, @emph{Racket}, Common Lisp. If you're not familiar with Scheme, @t{mu-guile} is an excellent opportunity to learn a bit about! Trust me, it's not very hard -- and it's @emph{fun}! @menu * Getting started:: * Initializing mu-guile:: * Messages:: * Contacts:: * Attachments and other parts:: * Statistics:: * Plotting data:: * Writing scripts:: Appendices * Recipes:: Snippets do specific things * GNU Free Documentation License:: The license of this manual. @end menu @node Getting started @chapter Getting started @menu * Installation:: * Making sure it works:: @end menu This chapter walks you through the installation and the basic setup. @node Installation @section Installation @t{mu-guile} is part of @t{mu} - by installing the latter, the former is necessarily installed as well. At the time of writing, there are no distribution-provided packaged versions of @t{mu-guile}; so for now, you need to follow the steps below. @subsection Guile 2.x @t{mu-guile} is built automatically when @t{mu} is built, if you have @t{guile} version 2 or higher. (@t{mu} checks for this during @t{configure}). Thus, the first step is to ensure you have @t{guile} installed. On Debian/Ubuntu you can install @t{guile} 2.x using the @t{guile-2.0-dev} package (and its dependencies): @example $ sudo apt-get install guile-2.0-dev @end example At the time of writing, there are no official packages for Fedora@footnote{@url{https://bugzilla.redhat.com/show_bug.cgi?id=678238}}. If you are using Fedora or any other system that does not have packages, you need to compile @t{guile} from source@footnote{@url{http://www.gnu.org/software/guile/manual/html_node/Obtaining-and-Installing-Guile.html#Obtaining-and-Installing-Guile}}. @subsection gnuplot For creating graphs with @t{mu-guile}, you need the @t{gnuplot} program -- most likely, there is a package available for your system; for example: @example $ sudo apt-get install gnuplot @end example and in Fedora: @example $ sudo yum install gnuplot @end example @subsection mu Assuming @t{guile} 2.x is installed correctly, @t{mu} finds it during its @t{configure}-stage, and creates @t{mu-guile}. Building @t{mu} follows the normal steps -- please see the @t{mu} documentation for the details. The output of @t{./configure} should end with a little text describing the detected versions of various libraries @t{mu} depends on. In particular, it should mention the @t{guile} version, e.g. @example Guile version : 2.0.3.82-a2c66 @end example If you don't see any line referring to @t{guile}, please install it, and run @t{configure} again. After a successful @t{./configure}, we can make and install the package: @example $ make && sudo make install @end example @subsection mu-guile After this, @t{mu} and @t{mu-guile} are installed -- usually somewhere under @t{/usr/local}.You may need to update @t{guile}'s @code{%load-path} to find it there. You can check the current @code{%load-path} with the following: @example guile -c '(display %load-path)(newline)' @end example If necessary, you can add the @t{%load-path} by adding to your @file{~/.guile}: @lisp (set! %load-path (cons "/usr/local/share/guile/site/2.0" %load-path)) @end lisp Or, alternatively, you can set @t{GUILE_LOAD_PATH}: @example export GUILE_LOAD_PATH=/usr/local/share/guile/site/2.0 @end example In both cases the directory should be the directory that contains the installed @t{mu.scm}; if you installed @t{mu} under a different prefix, you must change the @code{%load-path} accordingly. After this, you should be ready to go! Furthermore, you need to ensure that @t{guile} can find the mu-guile library; for this we can use @code{LTDL_LIBRARY_PATH}, e.g. @example export LTDL_LIBRARY_PATH=/usr/local/lib @end example @node Making sure it works @section Making sure it works Assuming @t{mu-guile} has been installed correctly (@ref{Installation}), and also assuming that you have already indexed your e-mail messages (if necessary, see the @t{mu-index} man-page), we are ready to start @t{mu-guile}; a session may look something like this: @cartouche @verbatim GNU Guile 2.0.5.123-4bd53 Copyright (C) 1995-2012 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> @end verbatim @end cartouche @noindent Now, copy-paste the following after the prompt: @cartouche @lisp (use-modules (mu)) (mu:initialize) (for-each (lambda(msg) (format #t "Subject: ~a\n" (mu:subject msg))) (mu:message-list "hello")) @end lisp @end cartouche @noindent After pressing @key{Enter}, you should get a list of all subjects of messages that match @t{hello}: @verbatim ... Subject: RE: The Bird Serpent War Cataclysm Subject: Hello! Subject: Re: post-run tomorrow Subject: When all is lost ... @end verbatim @noindent If all this works, congratulations! @t{mu-guile} is installed now, ready to serve your every searching need! @node Initializing mu-guile @chapter Initializing mu-guile We now have installed @t{mu-guile}, and in @ref{Making sure it works} confirmed that things work by trying some simple script. In this and the following chapters, we take a closer look at programming with @t{mu-guile}. It is possible to write separate programs with @t{mu-guile}, but for now we'll do things @emph{interactively}, that is, from the Guile-prompt (``@abbr{REPL}''). As we have seen, we start our @t{mu-guile} session by starting @t{guile}: @verbatim $ guile @end verbatim @cartouche @verbatim GNU Guile 2.0.5.123-4bd53 Copyright (C) 1995-2012 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> @end verbatim @end cartouche The first thing we need to do is loading the modules. All the basics are in the @t{(mu)} module, with some statistical extras in @t{(mu stats)}, and some graph plotting functionality in @t{(mu plot)}@footnote{@code{(mu plot)} requires the @t{gnuplot} program}. Let's load all of them: @verbatim scheme@(guile-user)> (use-modules (mu) (mu stats) (mu plot)) @end verbatim The first time you do this, @t{guile} will probably respond by showing some messages about compiling the modules, and then return to you with another prompt. Before we can do anything with @t{mu guile}, we need to initialize the system: @verbatim scheme@(guile-user)> (mu:initialize) @end verbatim This opens the database for reading, using the default location of @file{~/.cache/mu}@footnote{If you keep your @t{mu} database in a non-standard place, use @code{(mu:initialize "/path/to/my/mu/")}} Now, @t{mu-guile} is ready to go. In the next chapter, we go through the modules and show what you can do with them. @node Messages @chapter Messages In this chapter, we discuss searching messages and doing things with them. @menu * Finding messages:: query for messages in the database * Message methods:: what methods are available for messages? * Example - the longest subject:: find the messages with the longest subject @end menu @node Finding messages @section Finding messages Now we are ready to retrieve some messages from the system. There are two main procedures to do this: @itemize @item @code{(mu:message-list [])} @item @code{(mu:for-each-message [])} @end itemize @noindent The first procedure, @code{mu:message-list} returns a list of all messages matching @t{}; if you leave @t{} out, it returns @emph{all} messages. For example, to get all messages with @t{coffee} in the subject line: @verbatim scheme@(guile-user)> (mu:message-list "subject:coffee") $1 = (#< 9040640> #< 9040630> #< 9040570>) @end verbatim @noindent Apparently, we have three messages matching @t{subject:coffee}, so we get a list of three @code{} objects. Let's just use the @code{mu:subject} procedure ('method') provided by @code{} objects to retrieve the subject-field (more about methods in the next section). For your convenience, @t{guile} has saved the result of our last query in a variable called @t{$1}, so to get the subject of the first message in the list, we can do: @verbatim scheme@(guile-user)> (mu:subject (car $1)) $2 = "Re: best coffee ever!" @end verbatim @noindent The second procedure we mentioned, @code{mu:for-each-message}, executes some procedure for each message matched by the search expression (or @emph{all} messages if the search expression is omitted): @verbatim scheme@(guile-user)> (mu:for-each-message (lambda(msg) (display (mu:subject msg)) (newline)) "subject:coffee") Re: best coffee ever! best coffee ever! Coffee beans scheme@(guile-user)> @end verbatim @noindent Using @code{mu:message-list} and/or @code{mu:for-each-message}@footnote{Implementation node: @code{mu:message-list} is implemented in terms of @code{mu:for-each-message}, not the other way around. Due to the way @t{mu} works, @code{mu:for-each-message} is rather more efficient than a combination of @code{for-each} and @code{mu:message-list}} and a couple of @t{} methods, together with what Guile/Scheme provides, should allow for many interesting programs. @node Message methods @section Message methods Now that we've seen how to retrieve lists of message objects (@code{}), let's see what we can do with such an object. @code{} defines the following methods that all take a single @code{} object as a parameter. We won't go into the exact meanings for all of these procedures here - for the details about various flags / properties, please refer to the @t{mu-find} man-page. @itemize @item @code{(mu:bcc msg)}: the @t{Bcc} field of the message, or @t{#f} if there is none @item @code{(mu:body-html msg)}: : the html body of the message, or @t{#f} if there is none @item @code{(mu:body-txt msg)}: the plain-text body of the message, or @t{#f} if there is none @item @code{(mu:cc msg)}: the @t{Bcc} field of the message, or @t{#f} if there is none @item @code{(mu:date msg)}: the @t{Date} field of the message, or 0 if there is none @item @code{(mu:flags msg)}: list of message-flags for this message @item @code{(mu:from msg)}: the @t{From} field of the message, or @t{#f} if there is none @item @code{(mu:maildir msg)}: the maildir this message lives in, or @t{#f} if there is none @item @code{(mu:message-id msg)}: the @t{Message-Id} field of the message, or @t{#f} if there is none @item @code{(mu:path msg)}: the file system path for this message @item @code{(mu:priority msg)}: the priority of this message (either @t{mu:prio:low}, @t{mu:prio:normal} or @t{mu:prio:high} @item @code{(mu:references msg)}: the list of messages (message-ids) this message refers to in(mu: the @t{References:} header @item @code{(mu:size msg)}: size of the message in bytes @item @code{(mu:subject msg)}: the @t{Subject} field of the message, or @t{#f} if there is none. @item @code{(mu:tags msg)}: list of tags for this message @item @code{(mu:timestamp msg)}: the timestamp (mtime) of the message file, or #f if there is none. message file @item @code{(mu:to msg)}: the sender of the message, or @t{#f} if there is none @end itemize With these methods, we can query messages for their properties; for example: @verbatim scheme@(guile-user)> (define msg (car (mu:message-list "snow"))) scheme@(guile-user)> (mu:subject msg) $1 = "Re: Running in the snow is beautiful" scheme@(guile-user)> (mu:flags msg) $2 = (mu:flag:replied mu:flag:seen) scheme@(guile-user)> (strftime "%F" (localtime (mu:date msg))) $3 = "2011-01-15" @end verbatim There are a couple more methods: @itemize @item @code{(mu:header msg "")} returns an arbitrary message header (or @t{#f} if not found) -- e.g. @code{(header msg "User-Agent")} @item If you include the @t{mu contact} module, the @code{(mu:contacts msg [contact-type])} method (to get a list of contacts) is added. @xref{Contacts}. @item If you include the @t{mu part} module, the @code{((mu:parts msg)} and @code{(mu:attachments msg)} methods are added. @xref{Attachments and other parts}. @end itemize @node Example - the longest subject @section Example - the longest subject Now, let's write a little example -- let's find out what is the @emph{longest subject} of any e-mail messages we received in the year 2011. You can try this if you put the following in a separate file, make it executable, and run it like any program. @lisp #!/bin/sh exec guile -s $0 $@ !# (use-modules (mu)) (use-modules (srfi srfi-1)) (mu:initialize) ;; note: (subject msg) => #f if there is no subject (define list-of-subjects (map (lambda (msg) (or (mu:subject msg) "")) (mu:message-list "date:2011..2011"))) ;; see the mu-find manpage for the date syntax (define longest-subject (fold (lambda (subj1 subj2) (if (> (string-length subj1) (string-length subj2)) subj1 subj2)) "" list-of-subjects)) (format #t "Longest subject: ~s\n" longest-subject) @end lisp There are many other ways to solve the same problem, for example by using an iterative approach with @code{mu:for-each-message}, but it should show how one can easily write little programs to answer specific questions about your e-mail corpus. @node Contacts @chapter Contacts We can retrieve the sender and recipients of an e-mail message using methods like @code{mu:from}, @code{mu:to} etc.; @xref{Message methods}. These procedures return the list of recipients as a single string; however, often it is more useful to deal with recipients as separate objects. @menu * Contact procedures and objects:: * All contacts:: * Utility procedures:: * Example - mutt export:: @end menu @node Contact procedures and objects @section Contact procedures and objects Message objects (@pxref{Messages}) have a method @t{mu:contacts}: @code{(mu:contacts [])} The @t{} is a symbol, one of @code{mu:to}, @code{mu:from}, @code{mu:cc} or @code{mu:bcc}. This will then get the contact objects for the contacts of the corresponding type. If you leave out the contact-type (or specify @t{#t} for it, you will get a list of @emph{all} contact objects for the message. A contact object (@code{}) has two methods: @itemize @item @code{mu:name} returns the name of the contact, or #f if there is none @item @code{mu:email} returns the e-mail address of the contact, or #f if there is none @end itemize Let's get a list of all names and e-mail addresses in the 'To:' field, of messages matching 'book': @lisp (use-modules (mu)) (mu:initialize) (mu:for-each-message (lambda (msg) (for-each (lambda (contact) (format #t "~a => ~a\n" (or (mu:email contact) "") (or (mu:name contact) "no-name"))) (mu:contacts msg mu:contact:to))) "book") @end lisp This shows what the methods do, but for many uses, it would be more useful to have each of the contacts only show up @emph{once} - for that, please refer to @xref{All contacts}. @node All contacts @section All contacts Sometimes you may want to inspect @emph{all} the different contacts in the @t{mu} database. This is useful, for instance, when exporting contacts to some external format that can then be important in an e-mail program. To enable this, there is the procedure @code{mu:for-each-contact}, defined as @code{(mu:for-each-contact procedure [search-expression])}. This will aggregate the unique contacts from @emph{all} messages matching @t{} (when it is left empty, it will match all messages in the database), and execute @t{procedure} for each of them. The @t{procedure} receives an object of the type @t{}, which is a @emph{subclass} of the @t{} class discussed in @xref{Contact procedures and objects}. @t{} objects expose the following additional methods: @itemize @item @code{(mu:frequency )}: returns the @emph{number of times} this contact occurred in one of the address fields @item @code{(mu:last-seen )}: returns the @emph{most recent time} the contact was seen in one of the address fields, as a @t{time_t} value @end itemize The method assumes an e-mail address is unique for a certain contact; if a certain e-mail address occurs with different names, it uses the most recent non-empty name. @node Utility procedures @section Utility procedures To make dealing with contacts even easier, there are a number of utility procedures that can save you a bit of typing. For converting contacts to some textual form, there is @code{(mu:contact->string format)}, which takes a contact and returns a text string with the given format. Currently supported formats are @t{"org-contact}, @t{"mutt-alias"}, @t{"mutt-ab"}, @t{"wanderlust"} and @t{"plain"}. @node Example - mutt export @section Example - mutt export Let's see how we could export the addresses in the @t{mu} database to the addressbook format of the venerable @t{mutt}@footnote{@url{http://www.mutt.org/}} e-mail client. The addressbook format that @t{mutt} uses is a sequence of lines that look something like: @verbatim alias [] "<" ">" @end verbatim @t{mu guile} provides the procedure @code{(mu:contact->string format)} that we can use to do the conversion. We may want to focus on people with whom we have frequent correspondence; so we may want to limit ourselves to people we have seen at least 10 times in the last year. It is a bit hard to @emph{guess} the nick name for e-mail contacts, but @code{mu:contact->string} tries something based on the name. You can always adjust them later by hand, obviously. @lisp #!/bin/sh exec guile -s $0 $@ !# (use-modules (mu)) (mu:initialize) ;; Get a list of contacts that were seen at least 20 times since 2010 (define (selected-contacts) (let ((addrs '()) (start (car (mktime (car (strptime "%F" "2010-01-01"))))) (minfreq 20)) (mu:for-each-contact (lambda (contact) (if (and (mu:email contact) (>= (mu:frequency contact) minfreq) (>= (mu:last-seen contact) start)) (set! addrs (cons contact addrs))))) addrs)) (for-each (lambda (contact) (format #t "~a\n" (mu:contact->string contact "mutt-alias"))) (selected-contacts)) @end lisp This simple program could be improved in many ways; this is left as an exercise to the reader. @node Attachments and other parts @chapter Attachments and other parts To deal with @emph{attachments}, or, more in general @emph{MIME-parts}, there is the @t{mu part} module. @menu * Parts and their methods:: * Attachment example:: @end menu @node Parts and their methods @section Parts and their methods The module defines the @code{} class, and adds two methods to @code{} objects: @itemize @item @code{(mu:parts msg)} - returns a list @code{} objects, one for each MIME-parts in the message. @item @code{(mu:attachments msg)} - like @code{parts}, but only list those MIME-parts that look like proper attachments. @end itemize A @code{} object exposes a few methods to get information about the part: @itemize @item @code{(mu:name )} - returns the file name of the mime-part, or @code{#f} if there is none. @item @code{(mu:mime-type )} - returns the mime-type of the mime-part, or @code{#f} if there is none. @item @code{(mu:size )} - returns the size in bytes of the mime-part @end itemize @c Then, we may want to save the part to a file; this can be done using either: @c @itemize @c @item @code{(mu:save part )} - save a part to a temporary file, return the file @c name@footnote{the temporary filename is a predictable procedure of (user-id, @c msg-path, part-index)} @c @item @code{(mu:save-as )} - save part to file at path @c @end itemize @node Attachment example @section Attachment example Let's look at some small example. Let's get a list of the biggest attachments in messages about Luxemburg: @lisp #!/bin/sh exec guile -s $0 $@ !# (use-modules (mu)) (mu:initialize) (define (all-attachments expr) "Return a list of (name . size) for all attachments in messages matching EXPR." (let ((pairs '())) (mu:for-each-message (lambda (msg) (for-each (lambda (att) ;; add (filename . size) to the list (set! pairs (cons (cons (mu:name att) (or (mu:size att) 0)) pairs))) (mu:attachments msg))) expr) pairs)) (for-each (lambda (att) (format #t "~a: ~,1fKb\n" (car att) (exact->inexact (/ (cdr att) 1024)))) (sort (all-attachments "Luxemburg") (lambda (att1 att2) (< (cdr att1) (cdr att2))))) @end lisp As an exercise for the reader, you might want to re-rewrite the @code{all-attachments} in terms of @code{mu:message-list}, which would probably be a bit more elegant. @node Statistics @chapter Statistics @t{mu-guile} offers some convenience procedures to determine various statistics about the messages in the database. @menu * Basics:: @code{mu:count}, @code{mu:average}, ... * Tabulating values:: @code{mu:tabulate} * Most frequent values:: @code{mu:top-n-most-frequent} @end menu @node Basics @section Basics Let's look at some of the basic statistical operations available, in an interactive session: @example GNU Guile 2.0.5.123-4bd53 Copyright (C) 1995-2012 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@@(guile-user)> ;; load modules, initialize mu scheme@@(guile-user)> (use-modules (mu) (mu stats)) scheme@@(guile-user)> (mu:initialize) scheme@@(guile-user)> scheme@@(guile-user)> ;; count the number of messages with 'hello' in their subject scheme@@(guile-user)> (mu:count "subject:hello") $1 = 162 scheme@@(guile-user)> ;; average the size of messages with hello in their subject scheme@@(guile-user)> (mu:average mu:size "subject:hello") $2 = 34597733/81 scheme@@(guile-user)> (exact->inexact $2) $3 = 427132.506172839 scheme@@(guile-user)> ;; calculate the correlation between message size and scheme@@(guile-user)> ;; subject length scheme@@(guile-user)> (mu:correl mu:size (lambda (msg) (string-length (mu:subject msg))) "subject:hello") $5 = -0.10804368622292 scheme@@(guile-user)> @end example @node Tabulating values @section Tabulating values @code{(mu:tabulate [])} applies @t{} to each message matching @t{} (leave empty to match @emph{all} messages), and returns a associative list (a list of pairs) with each of the different results of @t{} and their frequencies. For fields that contain lists of values (such as address-fields), each of the values in the list is added separately. @subsection Example: messages per weekday We demonstrate @code{mu:tabulate} with an example. Suppose we want to know how many messages we receive per weekday: @lisp #!/bin/sh exec guile -s $0 $@ !# (use-modules (mu) (mu stats) (mu plot)) (mu:initialize) ;; create a list like (("Sun" . 13) ("Mon" . 23) ...) (define weekday-table (mu:weekday-numbers->names (sort (mu:tabulate (lambda (msg) (tm:wday (localtime (mu:date msg))))) (lambda (a b) (< (car a) (car b)))))) (for-each (lambda (elm) (format #t "~a: ~a\n" (car elm) (cdr elm))) weekday-table) @end lisp The procedure @code{weekday-table} uses @code{mu:tabulate-message} to get the frequencies per hour -- this returns a list of pairs: @verbatim ((5 . 2339) (0 . 2278) (4 . 2800) (2 . 3184) (6 . 1856) (3 . 2833) (1 . 2993)) @end verbatim We sort these pairs by the day number, and then apply @code{mu:weekday-numbers->names}, which takes the list, and returns a list where the day numbers are replace by there abbreviated name (in the current locale). Note, there is also @code{mu:month-numbers->names}. The script then outputs these numbers in the following form: @verbatim Sun: 2278 Mon: 2993 Tue: 3184 Wed: 2833 Thu: 2800 Fri: 2339 Sat: 1856 @end verbatim Clearly, Saturday is a slow day for e-mail... @node Most frequent values @section Most frequent values In the above example, the number of values is small (the seven weekdays); however, in many cases there can be many different values (for example, all different message subjects), many of which may not be very interesting -- all we need to know is the top-10 of most frequently seen values. This is fairly easy to achieve using @code{mu:tabulate} -- to get the top-10 subjects@footnote{this requires the @code{(srfi srfi-1)}-module}, we can use something like this: @lisp (take (sort (mu:tabulate mu:subject) (lambda (a b) (> (cdr a) (cdr b)))) 10) @end lisp If this is not short enough, @t{mu-guile} offers a convenience procedure to do this: @code{mu:top-n-most-frequent}. For example, to get the top-10 people we sent mail to most often: @lisp (mu:top-n-most-frequent mu:to 10 "maildir:/sent") @end lisp Can't make it much easier than that! @node Plotting data @chapter Plotting data You can plot the results in the format produced by @code{mu:tabulate} with the @t{(mu plot)} module, an experimental module that requires the @t{gnuplot}@footnote{@url{http://www.gnuplot.info/}} program to be installed on your system. The @code{mu:plot-histogram} procedure takes the following arguments: @code{(mu:plot-histogram <x-label> <y-label> [<want-ascii>])} Here, @code{<data>} is a table of data in the format that @code{mu:tabulate} produces. @code{<title>}, @code{<x-label>} and @code{<y-lablel>} are, respectively, the title of the graph, and the labels for X- and Y-axis. Finally, if you pass @t{#t} for the final @code{<want-ascii>} parameter, a plain-text rendering of the graph will be produced; otherwise, a graphical window will be shown. An example should clarify how this works in practice; let's plot the number of message per hour: @lisp #!/bin/sh exec guile -s $0 $@ !# (use-modules (mu) (mu stats) (mu plot)) (mu:initialize) (define (mail-per-hour-table) (sort (mu:tabulate (lambda (msg) (tm:hour (localtime (mu:date msg))))) (lambda (x y) (< (car x) (car y))))) (mu:plot-histogram (mail-per-hour-table) "Mail per hour" "Hour" "Frequency") @end lisp @cartouche @verbatim Mail per hour Frequency 1200 ++--+--+--+--+-+--+--+--+--+-+--+--+--+-+--+--+--+--+-+--+--+--+--++ |+ + + + + + + "/tmp/fileHz7D2u" using 2:xticlabels(1) ******** 1100 ++ *** +* **** * * * 1000 *+ * **** * +* * * ****** **** * ** * * 900 *+ * * ** **** * **** ** * +* * * * ** * * ********* * ** ** * * 800 *+ * **** ** * * * * ** * * ** ** * +* 700 *+ *** **** * ** * * * * ** **** * ** ** * +* * * * **** * * ** * * * * ** * **** ** ** * * 600 *+ * **** * * * * ** * * * * ** * * * ** ** * +* * * ** * * * * * ** * * * * ** * * * ** ** * * 500 *+ * ** * * * * * ** * * * * ** * * * ** ** * +* * * ** **** *** * * * ** * * * * ** * * * ** ** * * 400 *+ * ** ** **** * * * * * ** * * * * ** * * * ** ** * +* *+ *+**+**+* +*******+* +* +*+ *+**+* +*+ *+ *+**+* +*+ *+**+**+* +* 300 ******************************************************************** 0 1 2 3 4 5 6 7 8 910 11 12 1314 15 16 17 1819 20 21 22 23 Hour @end verbatim @end cartouche @node Writing scripts @chapter Writing scripts The @t{mu} program has built-in support for running guile-scripts, and comes with a number of examples. You can get a list of all scripts with the @t{mu script} command: @verbatim $ mu script Available scripts (use --verbose for details): * find-dups: find duplicate messages * msgs-count: count the number of messages matching some query * msgs-per-day: graph the number of messages per day * msgs-per-hour: graph the number of messages per hour * msgs-per-month: graph the number of messages per month * msgs-per-year: graph the number of messages per year * msgs-per-year-month: graph the number of messages per year-month @end verbatim You can then execute such a script by its name: @verbatim $ mu msgs-per-month --textonly --query=hello Messages per month matching hello 240 ++-+-----+----+-----+-----+-----+----+-----+-----+-----+----+-----+-++ | + + + + "/tmp/filewi9H0N" using 2:xticlabels(1) ****** | 220 ++ * * ****** | * * * * 200 ++ * * * +* | * * * * 180 ++ ****** * * * +* | * * * * * * 160 ****** * * * * * +* * * * * * * * * * ******* * * * * ****** * * 140 *+ ** * * * * * * ******** +* * ** ******* * * * * * ** ** * 120 *+ ** ** ******* * * * * ** ** +* * ** ** ** * * * ******* ** ** * 100 *+ ** ** ** * * * * ** ** ** +* * + ** + ** + ** + * + * + + * + * + ** + ** + ** + * 80 ********************************************************************** Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Month @end verbatim Please refer to the @t{mu-script} man-page for some details on writing your own scripts. @node Recipes @appendix Recipes @itemize @item Calculating the average length of subject-lines @lisp ;; the average length of all our (let ((len 0) (n 0)) (mu:for-each-message (lambda (msg) (set! len (+ len (string-length (or (mu:subject msg) "")))) (set! n (+ n 1)))) (if (= n 0) 0 (/ len n))) ;; this gives a rational, exact result; ;; use exact->inexact to get decimals ;; we we can make this short with the mu:average (with (mu stats)) (mu:average (lambda (msg) (string-length (or (mu:subject msg) "")))) @end lisp @end itemize @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @bye ������������������������������������������mu-1.6.10/guile/mu.scm������������������������������������������������������������������������������0000664�0000000�0000000�00000024527�14143670036�0014470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;; Copyright (C) 2011-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (define-module (mu) :use-module (oop goops) :use-module (ice-9 optargs) :use-module (texinfo string-utils) :export ( ;; classes <mu:message> <mu:contact> <mu:part> ;; general ;; mu:initialize ;; mu:initialized? mu:log-warning mu:log-message mu:log-critical ;; search funcs mu:for-each-message mu:for-each-msg mu:message-list ;; message funcs mu:header ;; message accessors mu:field:bcc mu:field:body-html mu:field:body-txt mu:field:cc mu:field:date mu:field:flags mu:field:from mu:field:maildir mu:field:message-id mu:field:path mu:field:prio mu:field:refs mu:field:size mu:field:subject mu:field:tags mu:field:timestamp mu:field:to ;; contact funcs mu:name mu:email mu:contact->string ;; mu:for-each-contact ;; mu:contacts ;; ;; <mu:contact-with-stats> mu:frequency mu:last-seen ;; parts <mu:part> ;; message function mu:attachments mu:parts ;; <mu:part> methods mu:name mu:mime-type ;; size ;; mu:save ;; mu:save-as )) ;; this is needed for guile < 2.0.4 (setlocale LC_ALL "") ;; load the binary (load-extension "libguile-mu" "mu_guile_init") (load-extension "libguile-mu" "mu_guile_message_init") ;; define some dummies so we don't get errors during byte compilation (eval-when (compile) (define mu:c:get-field) (define mu:c:get-contacts) (define mu:c:for-each-message) (define mu:c:get-header) (define mu:critical) (define mu:c:log) (define mu:message) (define mu:c:log) (define mu:warning) (define mu:c:log) (define mu:c:get-parts)) (define (mu:log-warning frm . args) "Log FRM with ARGS at warning." (mu:c:log mu:warning frm args)) (define (mu:log-message frm . args) "Log FRM with ARGS at warning." (mu:c:log mu:message frm args)) (define (mu:log-critical frm . args) "Log FRM with ARGS at warning." (mu:c:log mu:critical frm args)) (define-class <mu:message> () (msg #:init-keyword #:msg)) ;; the MuMsg-smob we're wrapping (define-syntax define-getter (syntax-rules () ((define-getter method-name field) (begin (define-method (method-name (msg <mu:message>)) (mu:c:get-field (slot-ref msg 'msg) field)) (export method-name))))) (define-getter mu:bcc mu:field:bcc) (define-getter mu:body-html mu:field:body-html) (define-getter mu:body-txt mu:field:body-txt) (define-getter mu:cc mu:field:cc) (define-getter mu:date mu:field:date) (define-getter mu:flags mu:field:flags) (define-getter mu:from mu:field:from) (define-getter mu:maildir mu:field:maildir) (define-getter mu:message-id mu:field:message-id) (define-getter mu:path mu:field:path) (define-getter mu:priority mu:field:prio) (define-getter mu:references mu:field:refs) (define-getter mu:size mu:field:size) (define-getter mu:subject mu:field:subject) (define-getter mu:tags mu:field:tags) (define-getter mu:timestamp mu:field:timestamp) (define-getter mu:to mu:field:to) (define-method (mu:header (msg <mu:message>) (hdr <string>)) "Get an arbitrary header HDR from message MSG; return #f if it does not exist." (mu:c:get-header (slot-ref msg 'msg) hdr)) (define* (mu:for-each-message func #:optional (expr #t) (maxresults -1)) "Execute function FUNC for each message that matches mu search expression EXPR. If EXPR is not provided, match /all/ messages in the store. MAXRESULTS specifies the maximum of messages to return, or -1 (the default) for no limit." (mu:c:for-each-message (lambda (msg) (func (make <mu:message> #:msg msg))) expr maxresults)) ;; backward-compatibility alias (define mu:for-each-msg mu:for-each-message) (define* (mu:message-list #:optional (expr #t) (maxresults -1)) "Return a list of all messages matching mu search expression EXPR. If EXPR is not provided, return a list of /all/ messages in the store. MAXRESULTS specifies the maximum of messages to return, or -1 (the default) for no limit." (let ((lst '())) (mu:for-each-message (lambda (m) (set! lst (append! lst (list m)))) expr maxresults) lst)) ;; contacts (define-class <mu:contact> () (name #:init-value #f #:accessor mu:name #:init-keyword #:name) (email #:init-value #f #:accessor mu:email #:init-keyword #:email)) (define-method (mu:contacts (msg <mu:message>) contact-type) "Get all contacts for MSG of the given CONTACT-TYPE. MSG is of type <mu-message>, while contact type is either `mu:contact:to', `mu:contact:cc', `mu:contact:from' or `mu:contact:bcc' to get the corresponding type of contacts, or #t to get all. Returns a list of <mu-contact> objects." (map (lambda (pair) ;; a pair (na . addr) (make <mu:contact> #:name (car pair) #:email (cdr pair))) (mu:c:get-contacts (slot-ref msg 'msg) contact-type))) (define-method (mu:contacts (msg <mu:message>)) "Get contacts of all types for message MSG as a list of <mu-contact> objects." (mu:contacts msg #t)) (define-class <mu:contact-with-stats> (<mu:contact>) (tstamp #:init-value 0 #:accessor mu:timestamp #:init-keyword #:timestamp) (last-seen #:init-value 0 #:accessor mu:last-seen) (freq #:init-value 1 #:accessor mu:frequency)) (define* (mu:for-each-contact proc #:optional (expr #t)) "Execute PROC for each contact. PROC receives a <mu-contact> instance as parameter. If EXPR is specified, only consider contacts in messages matching EXPR." (let ((c-hash (make-hash-table 4096))) (mu:for-each-message (lambda (msg) (for-each (lambda (ct) (let ((ct-ws (make <mu:contact-with-stats> #:name (mu:name ct) #:email (mu:email ct) #:timestamp (mu:date msg)))) (update-contacts-hash c-hash ct-ws))) (mu:contacts msg #t))) expr) (hash-for-each ;; c-hash now contains a map of email->contact (lambda (email ct-ws) (proc ct-ws)) c-hash))) (define-method (update-contacts-hash c-hash (nc <mu:contact-with-stats>)) "Update the contacts hash with a new and/or existing contact." ;; xc: existing-contact, nc: new contact (let ((xc (hash-ref c-hash (mu:email nc)))) (if (not xc) ;; no existing contact with this email address? (hash-set! c-hash (mu:email nc) nc) ;; store the new contact. ;; otherwise: (begin ;; 1) update the frequency for the existing contact (set! (mu:frequency xc) (1+ (mu:frequency xc))) ;; 2) update the name if the new one is not empty and its timestamp is newer ;; in that case, also update the timestamp (if (and (mu:name nc) (> (string-length (mu:name nc))) (> (mu:timestamp nc) (mu:timestamp xc))) (set! (mu:name xc) (mu:name nc)) (set! (mu:timestamp xc) (mu:timestamp nc))) ;; 3) update last-seen with timestamp, if x's timestamp is newer (if (> (mu:timestamp nc) (mu:last-seen xc)) (set! (mu:last-seen xc) (mu:timestamp nc))) ;; okay --> now xc has been updated; but it back in the hash (hash-set! c-hash (mu:email xc) xc))))) (define-method (mu:contact->string (contact <mu:contact>) (form <string>)) "Convert a contact to a string in format FORM, which is a string, either \"org-contact\", \"mutt-alias\", \"mutt-ab\", \"wanderlust\", \"quoted\" \"plain\"." (let* ((name (mu:name contact)) (email (mu:email contact)) (nick ;; simplistic nick guessing... (string-map (lambda(kar) (if (char-alphabetic? kar) kar #\_)) (string-downcase (or name email))))) (cond ((string= form "plain") (format #f "~a~a~a" (or name "") (if name " " "") email)) ((string= form "org-contact") (format #f "* ~s\n:PROPERTIES:\n:EMAIL:~a\n:NICK:~a\n:END:" (or name email) email nick)) ((string= form "wanderlust") (format #f "~a ~s ~s" nick (or name email) email)) ((string= form "mutt-alias") (format #f "alias ~a ~a <~a>" nick (or name email) email)) ((string= form "mutt-ab") (format #f "~a\t~a\t" email (or name ""))) ((string= form "quoted") (string-append "\"" (escape-special-chars (string-append (if name (format #f "\"~a\" " name) "") (format #f "<~a>" email)) "\"" #\\) "\"")) (else (error "Unsupported format"))))) ;; message parts (define-class <mu:part> () (msgpath #:init-value #f #:init-keyword #:msgpath) (index #:init-value #f #:init-keyword #:index) (name #:init-value #f #:getter mu:name #:init-keyword #:name) (mime-type #:init-value #f #:getter mu:mime-type #:init-keyword #:mime-type) (size #:init-value 0 #:getter mu:size #:init-keyword #:size)) (define-method (get-parts (msg <mu:message>) (files-only <boolean>)) "Get the part for MSG as a list of <mu:part> objects; if FILES-ONLY is #t, only get the part with file names." (map (lambda (part) (make <mu:part> #:msgpath (list-ref part 0) #:index (list-ref part 1) #:name (list-ref part 2) #:mime-type (list-ref part 3) #:size (list-ref part 4))) (mu:c:get-parts (slot-ref msg 'msg) files-only))) (define-method (mu:attachments (msg <mu:message>)) "Get the attachments for MSG as a list of <mu:part> objects." (get-parts msg #t)) (define-method (mu:parts (msg <mu:message>)) "Get the MIME-parts for MSG as a list of <mu-part> objects." (get-parts msg #f)) ;; (define-method (mu:save (part <mu:part>)) ;; "Save PART to a temporary file, and return the file name. If the ;; part had a filename, the temporary file's file name will be just that; ;; otherwise a name is made up." ;; (mu:save-part (slot-ref part 'msgpath) (slot-ref part 'index))) ;; (define-method (mu:save-as (part <mu:part>) (filepath <string>)) ;; "Save message-part PART to file system path PATH." ;; (copy-file (save part) filepath)) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/���������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0013752�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/Makefile.am����������������������������������������������������������������������0000664�0000000�0000000�00000001704�14143670036�0016010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk scmdir=${prefix}/share/guile/site/${GUILE_EFFECTIVE_VERSION}/mu/ scm_DATA= \ stats.scm \ plot.scm \ script.scm EXTRA_DIST=$(scm_DATA) ������������������������������������������������������������mu-1.6.10/guile/mu/README���������������������������������������������������������������������������0000664�0000000�0000000�00000015023�14143670036�0014633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������* OUTDATED * * README ** What is muile? `muile' is a little experiment/toy using the equally experimental mu guile bindings, to be found in libmuguile/ in the top-level source directory. `guile'[1] is an interpreter/library for the Scheme programming language[2], specifically meant for extending other programs. It is, in fact, the official GNU language for doing so. 'muile' requires guile 2.x to get the full support. Older versions may not support e.g. the 'mu-stats.scm' things discussed below. The combination of mu + guile is called `muile', and allows you to write little Scheme-programs to query the mu-database, or inspect individual messages. It is still in an experimental stage, but useful already. ** How do I get it? The git-version and the future 0.9.7 version of mu will automatically build muile if you have guile. I've been using guile 2.x from git, but installing the 'guile-1.8-dev' package (Ubuntu/Debian) should do the trick. (I only did very minimal testing with guile 1.8 though). Then, configure mu. The configure output should tell you about whether guile was found (and where). If it's found, build mu, and toys/muile should be created, as well. ** What can I do with it? Go to toys/muile and start muile. You'll end up with a guile-shell where you can type scheme [1], it looks something like this (for guile 2.x): ,---- | scheme@(guile-user)> `---- Now, let's load a message (of course, replace with a message on your system): ,---- | scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S")) `---- This defines a variable 'msg', which holds some message on your file system. It's now easy to inspect this message: ,---- | scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S")) `---- Now, we can inspect this message a bit: ,---- | scheme@(guile-user)> (mu:msg:subject msg) | $1 = "See me in bikini :-)" | scheme@(guile-user)> (mu:msg:flags msg) | $2 = (mu:attach mu:unread) `---- and so on. Note, it's probably easiest to explore the various mu: methods using autocompletion; to enable that make sure you have ,---- | (use-modules (ice-9 readline)) | (activate-readline) `---- in your ~/.guile configuration. ** does this tool have some parameters? Yes, there is --muhome to set a non-default place for the message database (see the documentation on --muhome in the mu-find manpage). And there is --msg=<path> where you specify some particular message file; it will be available as 'mu:current-msg' in the guile (muile) environment. For example: ,---- | ./muile --msg=~/Maildir/inbox/cur/1311310172_1234:2,S | [...] | scheme@(guile-user)> mu:current-msg | $1 = #<msg /home/djcb/Maildir/inbox/cur/1311310172_1234:2,S> | scheme@(guile-user)> (mu:msg:size mu:current-msg) | $2 = 7206 `---- ** What about searching messages in the database? That's easy, too - it does require a little more scheme knowledge. For searching messages there is the mu:store:for-each function, which takes two arguments; the first argument is a function that will be called for each message found. The optional second argument is the search expression (following 'mu find' syntax); if don't provide the argument, all messages match. So how does this work in practice? Let's see I want to see the subject and sender for messages about milk: ,---- | (mu:store:for-each (lambda(msg) (format #t "~s ~s\n" (mu:msg:from msg) (mu:msg:subject msg))) "milk") `---- or slightly more readable: ,---- | (mu:store:for-each | (lambda(msg) | (format #t "~s ~s\n" (mu:msg:from msg) (mu:msg:subject msg))) | "milk") `---- As you can see, I provide an anonymous ('lambda') function which will be called for each message matching 'milk'. Admittedly, this requires a bit of Scheme-knowledge... but this time is good as any to learn this nice language. ** Can I do some statistics on my messages? Yes you can. In fact, it's pretty easy. If you load (in the muile/ directory) the file 'mu-stats.scm': ,---- | (load "mu-stats.scm") `---- you'll get a bunch of functions (with names starting with 'mu:stats') to make this very easy. Let's see, suppose I want to see how many messages I get per weekday: ,---- | scheme@(guile-user)> (mu:stats:per-weekday) | $1 = ((0 . 2255) (1 . 2788) (2 . 2868) (3 . 2599) (4 . 2629) (5 . 2287) (6 . 1851)) `---- Note, Sunday=0, Monday=1 and so on. Apparently, I get/send most of e-mail on Tuesdays, and least on Saturday. And note that mu:stats:per-weekdays takes an optional search expression argument, to limit the results to messages matching that, e.g., to only consider messages related to emacs during this year: ,---- | scheme@(guile-user)> (mu:stats:per-weekday "emacs date:2011..now") | $8 = ((0 . 54) (1 . 22) (2 . 46) (3 . 47) (4 . 39) (5 . 54) (6 . 50)) `---- There's also 'mu:stats:per-month', 'mu:stats:per-year', 'mu:stats:per-hour'. I learnt that during 3-4am I sent/receive only about a third of what I sent during 11-12pm. ** What about getting the top-10 people in the To:-field? Easy. ,---- | scheme@(guile-user)> (mu:stats:top-n-to) | $1 = ((("Abc" "myself@example.com") . 4465) (("Def" "somebodyelse@example.com") . 2114) | (and so on) `---- I've changed the names a bit to protect the innocent, but what the function does is return a list of pairs of (<name> <email>) . <frequency> descending in order of frequency. Note, 'mu:stats:top-n-to' takes two optional arguments - first the 'n' in top-n (default is 10), and seconds as search expression to limit the messages considered. There are also the functions 'mu:stats:top-n-subject' and 'mu:stats:top-n-from' which do the same, mutatis mutandis, and it's quite easy to add your own (see the mu-stats.scm for examples) ** What about showing the results in a table? Even easier. Try: ,---- | (mu:stats:table (mu:stats:top-n-to)) `---- or ,---- | (mu:stats:table (mu:stats:per-weekday)) `---- You can also export the table: ,---- | (mu:stats:export (mu:stats:per-weekday)) `---- which will create a temporary file with the results, for further processing in e.g. 'R' or 'gnuplot'. [1] http://www.gnu.org/s/guile/ [2] http://en.wikipedia.org/wiki/Scheme_(programming_language) # Local Variables: # mode: org; org-startup-folded: nil # End: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/contact.scm����������������������������������������������������������������������0000664�0000000�0000000�00000000205�14143670036�0016106�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������(define-module (mu contact) :use-module(mu)) (display "(mu contact) is deprecated, please remove from (use-modules ...)") (newline) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/message.scm����������������������������������������������������������������������0000664�0000000�0000000�00000000206�14143670036�0016100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������(define-module (mu message) :use-module (mu)) (display "(mu message) is deprecated, please remove from (use-modules ...)") (newline) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/part.scm�������������������������������������������������������������������������0000664�0000000�0000000�00000000200�14143670036�0015414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������(define-module (mu part) :use-module (mu)) (display "(mu part) is deprecated, please remove from (use-modules ...)") (newline) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/plot.scm�������������������������������������������������������������������������0000664�0000000�0000000�00000005446�14143670036�0015445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;; ;; Copyright (C) 2011-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (define-module (mu plot) :use-module (mu) :use-module (ice-9 popen) :export ( mu:plot ;; alias for mu:plot-histogram mu:plot-histogram )) (define (export-pairs pairs) "Write a temporary file with the list of PAIRS in table format, and return the file name." (let* ((datafile (tmpnam)) (output (open datafile (logior O_CREAT O_WRONLY) #O0600))) (for-each (lambda(pair) (display (format #f "~a ~a\n" (car pair) (cdr pair)) output)) pairs) (close output) datafile)) (define (find-program-in-path prog) "Find exutable program PROG in PATH; return the full path, or #f if not found." (let* ((path (parse-path (getenv "PATH"))) (progpath (search-path path prog))) (if (not progpath) #f (if (access? progpath X_OK) ;; is progpath #f)))) (define* (mu:plot-histogram data title x-label y-label #:optional (output "dumb") (extra-gnuplot-opts '())) "Plot DATA with TITLE, X-LABEL and X-LABEL using the gnuplot program. DATA is a list of cons-pairs (X . Y). OUTPUT is a string that determines the type of output that gnuplot produces, depending on the system. Which options are available depends on the particulars for the gnuplot installation, but typical examples would be \"dumb\" for text-only display, \"wxterm\" to write to a graphical window, or \"png\" to write a PNG-image to stdout. EXTRA-GNUPLOT-OPTS is a list of any additional options for gnuplot." (if (not (find-program-in-path "gnuplot")) (error "cannot find 'gnuplot' in path")) (let ((datafile (export-pairs data)) (gnuplot (open-pipe "gnuplot -p" OPEN_WRITE))) (display (string-append "reset\n" "set term " (or output "dumb") "\n" "set title \"" title "\"\n" "set xlabel \"" x-label "\"\n" "set ylabel \"" y-label "\"\n" "set boxwidth 0.9\n" (string-join extra-gnuplot-opts "\n") "plot \"" datafile "\" using 2:xticlabels(1) with boxes fs solid\n") gnuplot) (close-pipe gnuplot))) ;; backward compatibility (define mu:plot mu:plot-histogram) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/script.scm�����������������������������������������������������������������������0000664�0000000�0000000�00000004052�14143670036�0015763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (define-module (mu script) :export (mu:run-stats)) (use-modules (ice-9 getopt-long) (ice-9 optargs) (ice-9 popen) (ice-9 format)) (use-modules (mu) (mu stats) (mu plot)) (define (help-and-exit) "Show some help." (display (string-append "usage: script [--help] [--textonly] " "[--muhome=<muhome>] [--query=<query>") (newline)) (exit 0)) (define (mu:run-stats args func) "Run some statistics function. Interpret argument-list ARGS (like command-line arguments). Possible arguments are: --help (show some help and exit) --muhome (path to alternative mu home directory) --output (a string describing the output, e.g. \"dumb\", \"png\" \"wxt\") searchexpr (a search query) then call FUNC with args SEARCHEXPR and OUTPUT." (setlocale LC_ALL "") (let* ((optionspec '((muhome (value #t)) (query (value #t)) (output (value #f)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (query (option-ref options 'query #f)) (help (option-ref options 'help #f)) (output (option-ref options 'output #f)) (muhome (option-ref options 'muhome #f)) (restargs (option-ref options '() #f))) (if help (help-and-exit)) (mu:initialize muhome) (func (or query "") output))) ;; Local Variables: ;; mode: scheme ;; End: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/mu/stats.scm������������������������������������������������������������������������0000664�0000000�0000000�00000013405�14143670036�0015617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;; ;; Copyright (C) 2011-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (define-module (mu stats) :use-module (oop goops) :use-module (mu) :use-module (srfi srfi-1) :use-module (ice-9 i18n) :use-module (ice-9 r5rs) :export ( mu:tabulate mu:top-n-most-frequent mu:count mu:average mu:stddev mu:correl mu:max mu:min mu:weekday-numbers->names mu:month-numbers->names)) (define* (mu:tabulate func #:optional (expr #t)) "Execute FUNC for each message matching EXPR, and return an alist with maps each result of FUNC to its frequency. If the result of FUNC is a list, add each of its values separately. FUNC is a function takes a <mu-message> instance as its argument. For example, to tabulate messages by weekday, one could use: (mu:tabulate (lambda(msg) (tm:wday (localtime (date msg))))), and get back a list like ((1 . 2) (2 . 5)(3 . 4)(4 . 4)(5 . 12)(6 . 7)(7. 2))." (let* ((table '()) ;; func to add a value to our table (update-table (lambda (val) (let ((old-freq (or (assoc-ref table val) 0))) (set! table (assoc-set! table val (1+ old-freq))))))) (mu:for-each-message (lambda(msg) (let ((val (func msg))) (if (list? val) (for-each update-table val) (update-table val)))) expr) table)) (define* (top-n func less n #:optional (expr #t)) "Take the results of (mu:tabulate FUNC EXPR), sort using LESS (a function taking two arguments A and B (cons cells, (VAL . FREQ)), and returns #t if A < B, #f otherwise), and then take the first N." (take (sort (mu:tabulate func expr) less) n)) (define* (mu:top-n-most-frequent func n #:optional (expr #t)) "Take the results of (mu:tabulate FUNC EXPR), and return the N items with the highest frequency." (top-n func (lambda (a b) (> (cdr a) (cdr b))) n expr)) (define* (mu:count #:optional (expr #t)) "Count the number of messages matching EXPR. If EXPR is not provided, match /all/ messages." (let ((num 0)) (mu:for-each-message (lambda (msg) (set! num (1+ num))) expr) num)) (define (average lst) "Calculate the average of a list LST of numbers, or #f if undefined." (if (null? lst) #f (/ (apply + lst) (length lst)))) (define (stddev lst) "Calculate the standard deviation of a list LST of numbers or #f if undefined." (let* ((avg (average lst)) (sosq (if avg (apply + (map (lambda (x)(* (- x avg) (- x avg))) lst))))) (if sosq (sqrt (/ sosq (length lst)))))) (define* (mu:average func #:optional (expr #t)) "Get the average value of FUNC applied to all messages matching EXPR (or #t for all). Returns #f if undefined." (average (map func (mu:message-list expr)))) (define* (mu:stddev func #:optional (expr #t)) "Get the standard deviation the the values of FUNC applied to all messages matching EXPR (or #t for all). This is the 'population' stddev, not the 'sample' stddev. Returns #f if undefined." (stddev (map func (mu:message-list expr)))) (define* (mu:max func #:optional (expr #t)) "Get the maximum value of FUNC applied to all messages matching EXPR (or #t for all). Returns #f if undefined." (apply max (map func (mu:message-list expr)))) (define* (mu:min func #:optional (expr #t)) "Get the minimum value of FUNC applied to all messages matching EXPR (or #t for all). Returns #f if undefined." (apply min (map func (mu:message-list expr)))) (define (correl lst) "Calculate Pearson's correlation coefficient for a list LST of cons pair, where the car and cdr of the pairs are values from data set 1 and 2, respectively." (let ((n (length lst)) (sx (apply + (map car lst))) (sy (apply + (map cdr lst))) (sxy (apply + (map (lambda (cell) (* (car cell) (cdr cell))) lst))) (sxx (apply + (map (lambda (cell) (* (car cell) (car cell))) lst))) (syy (apply + (map (lambda (cell) (* (cdr cell) (cdr cell))) lst)))) (/ (- (* n sxy) (* sx sy)) (sqrt (* (- (* n sxx) (* sx sx)) (- (* n syy) (* sy sy))))))) (define* (mu:correl func1 func2 #:optional (expr #t)) "Determine Pearson's correlation coefficient between the value for functions FUNC1 and FUNC2 to all messages matching EXPR (or #t for all). Returns #f if undefined." (let ((data (map (lambda (msg) (cons (func1 msg) (func2 msg))) (mu:message-list expr)))) (if data (correl data) #f))) ;; a list of abbreviated, localized day names (define day-names (map locale-day-short (iota 7 1))) (define (mu:weekday-numbers->names table) "Convert a list of pairs with the car denoting a day number (0-6) into a list of pairs with the car replaced by the corresponding day name (abbreviated) for the current locale." (map (lambda (pair) (cons (list-ref day-names (car pair)) (cdr pair))) table)) ;; a list of abbreviated, localized month names (define month-names (map locale-month-short (iota 12 1))) (define (mu:month-numbers->names table) "Convert a list of pairs with the car denoting a month number (0-11) into a list of pairs with the car replaced by the corresponding day name (abbreviated)." (map (lambda (pair) (cons (list-ref month-names (car pair)) (cdr pair))) table)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0015020�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/Makefile.am�����������������������������������������������������������������0000664�0000000�0000000�00000002067�14143670036�0017061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk EXTRA_DIST= \ msgs-count.scm \ msgs-per-year.scm \ msgs-per-hour.scm \ msgs-per-month.scm \ msgs-per-day.scm \ msgs-per-year-month.scm \ find-dups.scm muguiledistscriptdir = $(pkgdatadir)/scripts/ muguiledistscript_SCRIPTS = $(EXTRA_DIST) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/find-dups.scm���������������������������������������������������������������0000775�0000000�0000000�00000007251�14143670036�0017425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2013-2015 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: find duplicate messages ;; INFO: options: ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --delete: delete all but the first one (experimental, be careful!) (use-modules (mu) (mu script) (mu stats)) (use-modules (ice-9 getopt-long) (ice-9 optargs) (ice-9 popen) (ice-9 format) (ice-9 rdelim)) (define (md5sum path) (let* ((port (open-pipe* OPEN_READ "md5sum" path)) (md5 (read-delimited " " port))) (close-pipe port) md5)) (define (find-dups delete expr) (let ((id-table (make-hash-table 20000))) ;; fill the hash with <msgid-size> => <list of paths> (mu:for-each-message (lambda (msg) (let* ((id (format #f "~a-~d" (mu:message-id msg) (mu:size msg))) (lst (hash-ref id-table id))) (if lst (set! lst (cons (mu:path msg) lst)) (set! lst (list (mu:path msg)))) (hash-set! id-table id lst))) expr) ;; list all the paths with multiple elements; check the md5sum to ;; make 100%-minus-ε sure they are really the same file. (hash-for-each (lambda (id paths) (if (> (length paths) 1) (let ((hash (make-hash-table 10))) (for-each (lambda (path) (when (file-exists? path) (let* ((md5 (md5sum path)) (lst (hash-ref hash md5))) (if lst (set! lst (cons path lst)) (set! lst (list path))) (hash-set! hash md5 lst)))) paths) ;; hash now maps the md5sum to the messages... (hash-for-each (lambda (md5 mpaths) (if (> (length mpaths) 1) (begin ;;(format #t "md5sum: ~a:\n" md5) (let ((num 1)) (for-each (lambda (path) (if (equal? num 1) (format #t "~a\n" path) (begin (format #t "~a: ~a\n" (if delete "deleting" "dup") path) (if delete (delete-file path)))) (set! num (+ 1 num))) mpaths))))) hash)))) id-table))) (define (main args) "Find duplicate messages and, potentially, delete the dups. Be careful with that! Interpret argument-list ARGS (like command-line arguments). Possible arguments are: --muhome (path to alternative mu home directory). --delete (delete all but the first one). Run mu index afterwards. --expr (expression to constrain search)." (setlocale LC_ALL "") (let* ((optionspec '( (muhome (value #t)) (delete (value #f)) (expr (value #t)) (help (single-char #\h) (value #f)))) (options (getopt-long args optionspec)) (help (option-ref options 'help #f)) (delete (option-ref options 'delete #f)) (expr (option-ref options 'expr #t)) (muhome (option-ref options 'muhome #f))) (mu:initialize muhome) (find-dups delete expr))) ;; Local Variables: ;; mode: scheme ;; End: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-count.scm��������������������������������������������������������������0000775�0000000�0000000�00000002425�14143670036�0017631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir (use-modules (mu) (mu script) (mu stats)) (define (count expr output) "Print the total number of messages matching the query EXPR. OUTPUT is ignored." (display (mu:count expr)) (newline)) (define (main args) (mu:run-stats args count)) ;; Local Variables: ;; mode: scheme ;; End: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-per-day.scm������������������������������������������������������������0000775�0000000�0000000�00000003340�14143670036�0020037�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --output: the output format, such as "png", "wxt" ;; INFO: (depending on the environment) (use-modules (mu) (mu script) (mu stats) (mu plot)) (define (per-day expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot-histogram (mu:weekday-numbers->names (sort (mu:tabulate (lambda (msg) (tm:wday (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y))))) (format #f "Messages per weekday matching ~a" expr) "Day" "Messages" output)) (define (main args) (mu:run-stats args per-day)) ;; Local Variables: ;; mode: scheme ;; End: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-per-hour.scm�����������������������������������������������������������0000775�0000000�0000000�00000003304�14143670036�0020237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --output: the output format, such as "png", "wxt" ;; INFO: (depending on the environment) (use-modules (mu) (mu script) (mu stats) (mu plot)) (define (per-hour expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot-histogram (sort (mu:tabulate (lambda (msg) (tm:hour (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per hour matching ~a" expr) "Hour" "Messages" output)) (define (main args) (mu:run-stats args per-hour)) ;; Local Variables: ;; mode: scheme ;; End: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-per-month.scm����������������������������������������������������������0000775�0000000�0000000�00000003341�14143670036�0020410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --output: the output format, such as "png", "wxt" ;; INFO: (depending on the environment) (use-modules (mu) (mu script) (mu stats) (mu plot)) (define (per-month expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot-histogram (mu:month-numbers->names (sort (mu:tabulate (lambda (msg) (tm:mon (localtime (mu:date msg)))) expr) (lambda (x y) (< (car x) (car y))))) (format #f "Messages per month matching ~a" expr) "Month" "Messages" output)) (define (main args) (mu:run-stats args per-month)) ;; Local Variables: ;; mode: scheme ;; End: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-per-year-month.scm�����������������������������������������������������0000775�0000000�0000000�00000003500�14143670036�0021343�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --output: the output format, such as "png", "wxt" ;; INFO: (depending on the environment) (use-modules (mu) (mu script) (mu stats) (mu plot)) (define (per-year-month expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot-histogram (sort (mu:tabulate (lambda (msg) (string->number (format #f "~d~2'0d" (+ 1900 (tm:year (localtime (mu:date msg)))) (tm:mon (localtime (mu:date msg)))))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per year/month matching ~a" expr) "Year/Month" "Messages" output)) (define (main args) (mu:run-stats args per-year-month)) ;; Local Variables: ;; mode: scheme ;; End: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/scripts/msgs-per-year.scm�����������������������������������������������������������0000775�0000000�0000000�00000003320�14143670036�0020220�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;; INFO: graph the number of messages per day (using gnuplot) ;; INFO: options: ;; INFO: --query=<query>: limit to messages matching query ;; INFO: --muhome=<muhome>: path to mu home dir ;; INFO: --output: the output format, such as "png", "wxt" ;; INFO: (depending on the environment) (use-modules (mu) (mu script) (mu stats) (mu plot)) (define (per-year expr output) "Count the total number of messages for each weekday (0-6 for Sun..Sat) that match EXPR. OUTPUT corresponds to the output format, as per gnuplot's 'set terminal'." (mu:plot-histogram (sort (mu:tabulate (lambda (msg) (+ 1900 (tm:year (localtime (mu:date msg))))) expr) (lambda (x y) (< (car x) (car y)))) (format #f "Messages per year matching ~a" expr) "Year" "Messages" output)) (define (main args) (mu:run-stats args per-year)) ;; Local Variables: ;; mode: scheme ;; End: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/tests/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0014473�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/tests/Makefile.am�������������������������������������������������������������������0000664�0000000�0000000�00000003452�14143670036�0016533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 3, or (at your option) any ## later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk AM_CPPFLAGS=$(XAPIAN_CXXFLAGS) \ $(GMIME_CFLAGS) \ $(GLIB_CFLAGS) \ -I${top_srcdir} \ -I${top_srcdir}/lib \ -DMU_TESTMAILDIR=\"${abs_top_srcdir}/lib/testdir\" \ -DMU_TESTMAILDIR2=\"${abs_top_srcdir}/lib/testdir2\" \ -DMU_TESTMAILDIR3=\"${abs_top_srcdir}/lib/testdir3\" \ -DMU_PROGRAM=\"${abs_top_builddir}/mu/mu\" \ -DMU_GUILE_MODULE_PATH=\"${abs_top_srcdir}/guile/\" \ -DMU_GUILE_LIBRARY_PATH=\"${abs_top_builddir}/guile/.libs\" \ -DABS_CURDIR=\"${abs_builddir}\" \ -DABS_SRCDIR=\"${abs_srcdir}\" # don't use -Werror, as it might break on other compilers # use -Wno-unused-parameters, because some callbacks may not # really need all the params they get AM_CFLAGS=$(ASAN_CFLAGS) ${WARN_CFLAGS} AM_CXXFLAGS=$(ASAN_CXXFLAGS) ${WARN_CXXFLAGS} AM_LDFLAGS=$(ASAN_LDFLAGS) noinst_PROGRAMS= $(TEST_PROGS) TEST_PROGS += test-mu-guile test_mu_guile_SOURCES= test-mu-guile.cc test_mu_guile_LDADD=${top_builddir}/lib/libtestmucommon.la EXTRA_DIST=test-mu-guile.scm test-mu-guile.cc ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/guile/tests/test-mu-guile.cc��������������������������������������������������������������0000664�0000000�0000000�00000005663�14143670036�0017515�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <glib/gstdio.h> #include <lib/mu-query.hh> #include <stdlib.h> #include <unistd.h> #include <string.h> #include "test-mu-common.hh" #include <lib/mu-store.hh> /* Tests For The command line interface, uses testdir2 */ static gchar* fill_database (void) { gchar *cmdline, *tmpdir; GError *err; tmpdir = test_mu_common_get_random_tmpdir(); cmdline = g_strdup_printf ( "/bin/sh -c '" "%s init --muhome=%s --maildir=%s --quiet; " "%s index --muhome=%s --quiet'", MU_PROGRAM, tmpdir, MU_TESTMAILDIR2, MU_PROGRAM, tmpdir); if (g_test_verbose()) g_print ("%s\n", cmdline); err = NULL; if (!g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, &err)) { g_printerr ("Error: %s\n", err ? err->message : "?"); g_assert (0); } g_free (cmdline); return tmpdir; } static void test_something (const char *what) { char *dir, *cmdline; gint result; dir = fill_database (); cmdline = g_strdup_printf ( "GUILE_AUTO_COMPILE=0 " "LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH " "%s -q -L %s -e main %s/test-mu-guile.scm " "--muhome=%s --test=%s", MU_GUILE_LIBRARY_PATH, GUILE_BINARY, MU_GUILE_MODULE_PATH, ABS_SRCDIR, dir, what); if (g_test_verbose ()) g_print ("cmdline: %s\n", cmdline); result = system (cmdline); g_assert (result == 0); g_free (dir); g_free (cmdline); } static void test_mu_guile_queries (void) { test_something ("queries"); } static void test_mu_guile_messages (void) { test_something ("message"); } static void test_mu_guile_stats (void) { test_something ("stats"); } int main (int argc, char *argv[]) { int rv; g_test_init (&argc, &argv, NULL); if (!set_en_us_utf8_locale()) return 0; /* don't error out... */ g_test_add_func ("/guile/queries", test_mu_guile_queries); g_test_add_func ("/guile/message", test_mu_guile_messages); g_test_add_func ("/guile/stats", test_mu_guile_stats); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_LEVEL_WARNING| G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); rv = g_test_run (); return rv; } �����������������������������������������������������������������������������mu-1.6.10/guile/tests/test-mu-guile.scm�������������������������������������������������������������0000775�0000000�0000000�00000010420�14143670036�0017700�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh exec guile -e main -s $0 $@ !# ;; Copyright (C) 2012-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ;; ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 3, or (at your option) any ;; later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software Foundation, ;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. (setlocale LC_ALL "") (use-modules (srfi srfi-1)) (use-modules (ice-9 getopt-long) (ice-9 optargs) (ice-9 popen) (ice-9 format)) (use-modules (mu) (mu stats)) (define (n-results-or-exit query n) "Run QUERY, and exit 1 if the number of results != N." (let ((lst (mu:message-list query))) (if (not (= (length lst) n)) (begin (simple-format (current-error-port) "Query: \"~A\"; expected ~A, got ~A\n" query n (length lst)) (exit 1))))) (define (test-queries) "Test a bunch of queries (or die trying)." (n-results-or-exit "hello" 1) (n-results-or-exit "f:john fruit" 1) (n-results-or-exit "f:soc@example.com" 1) (n-results-or-exit "t:alki@example.com" 1) (n-results-or-exit "t:alcibiades" 1) (n-results-or-exit "f:soc@example.com OR f:john" 2) (n-results-or-exit "f:soc@example.com OR f:john OR t:edmond" 3) (n-results-or-exit "t:julius" 1) (n-results-or-exit "s:dude" 1) (n-results-or-exit "t:dantès" 1) (n-results-or-exit "file:sittingbull.jpg" 1) (n-results-or-exit "file:custer.jpg" 1) (n-results-or-exit "file:custer.*" 1) (n-results-or-exit "j:sit*" 1) (n-results-or-exit "mime:image/jpeg" 1) (n-results-or-exit "mime:text/plain" 13) (n-results-or-exit "y:text*" 13) (n-results-or-exit "y:image*" 1) (n-results-or-exit "mime:message/rfc822" 2)) (define (error-exit msg . args) "Print error and exit." (let ((msg (apply format #f msg args))) (simple-format (current-error-port) "*ERROR*: ~A\n" msg) (exit 1))) (define (str-equal-or-exit got exp) "S1 == S2 or exit 1." ;; (format #t "'~A' <=> '~A'\n" s1 s2) (if (not (string= exp got)) (error-exit "Expected \"~A\", got \"~A\"\n" exp got))) (define (test-message) "Test functions for a particular message." (let ((msg (car (mu:message-list "hello")))) (str-equal-or-exit (mu:subject msg) "Fwd: rfc822") (str-equal-or-exit (mu:to msg) "martin") (str-equal-or-exit (mu:from msg) "foobar <foo@example.com>") (str-equal-or-exit (mu:header msg "X-Mailer") "Ximian Evolution 1.4.5") (if (not (equal? (mu:priority msg) mu:prio:normal)) (error-exit "Expected ~A, got ~A" (mu:priority msg) mu:prio:normal))) (let ((msg (car (mu:message-list "atoms")))) (str-equal-or-exit (mu:subject msg) "atoms") (str-equal-or-exit (mu:to msg) "Democritus <demo@example.com>") (str-equal-or-exit (mu:from msg) "\"Richard P. Feynman\" <rpf@example.com>") ;;(str-equal-or-exit (mu:header msg "Content-transfer-encoding") "7BIT") (if (not (equal? (mu:priority msg) mu:prio:high)) (error-exit "Expected ~a, got ~a" (mu:priority msg) mu:prio:high)))) (define (num-equal-or-exit got exp) "S1 == S2 or exit 1." ;; (format #t "'~A' <=> '~A'\n" s1 s2) (if (not (= exp got)) (error-exit "Expected \"~S\", got \"~S\"\n" exp got))) (define (test-stats) "Test statistical functions." ;; average (num-equal-or-exit (mu:average mu:size) 82152/13) (num-equal-or-exit (floor (mu:stddev mu:size)) 13020.0) (num-equal-or-exit (mu:max mu:size) 46308) (num-equal-or-exit (mu:min mu:size) 111)) (define (main args) (let* ((optionspec '((muhome (value #t)) (test (value #t)))) (options (getopt-long args optionspec)) (muhome (option-ref options 'muhome #f)) (test (option-ref options 'test #f))) (mu:initialize muhome) (if test (cond ((string= test "queries") (test-queries)) ((string= test "message") (test-message)) ((string= test "stats") (test-stats)) (#t (exit 1)))))) ;; Local Variables: ;; mode: scheme ;; End: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/��������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0012772�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/Makefile.am���������������������������������������������������������������������������0000664�0000000�0000000�00000030005�14143670036�0015024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2010-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # enforce compiling guile (optionally) first,then this dir first # before descending into tests/ include $(top_srcdir)/gtest.mk SUBDIRS= utils index TESTDEFS= \ -DMU_TESTMAILDIR=\"${abs_srcdir}/testdir\" \ -DMU_TESTMAILDIR2=\"${abs_srcdir}/testdir2\" \ -DMU_TESTMAILDIR4=\"${abs_srcdir}/testdir4\" \ -DABS_CURDIR=\"${abs_builddir}\" \ -DABS_SRCDIR=\"${abs_srcdir}\" AM_CFLAGS= \ $(WARN_CFLAGS) \ $(GMIME_CFLAGS) \ $(XAPIAN_CFLAGS) \ $(GLIB_CFLAGS) \ $(GUILE_CFLAGS) \ $(ASAN_CFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ $(TESTDEFS) \ -Wno-format-nonliteral \ -Wno-switch-enum \ -Wno-deprecated-declarations \ -Wno-inline AM_CXXFLAGS= \ $(GMIME_CFLAGS) \ $(GLIB_CFLAGS) \ $(GUILE_CFLAGS) \ $(WARN_CXXFLAGS) \ $(XAPIAN_CXXFLAGS) \ $(ASAN_CXXFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ $(TESTDEFS) AM_CPPFLAGS= \ $(CODE_COVERAGE_CPPFLAGS) noinst_LTLIBRARIES= \ libmu.la libmu_la_SOURCES= \ mu-bookmarks.cc \ mu-bookmarks.hh \ mu-contacts.cc \ mu-contacts.hh \ mu-data.hh \ mu-parser.cc \ mu-parser.hh \ mu-query.cc \ mu-query.hh \ mu-query-results.hh \ mu-query-match-deciders.cc \ mu-query-match-deciders.hh \ mu-query-threads.cc \ mu-query-threads.hh \ mu-runtime.cc \ mu-runtime.hh \ mu-script.cc \ mu-script.hh \ mu-server.cc \ mu-server.hh \ mu-store.cc \ mu-store.hh \ mu-tokenizer.cc \ mu-tokenizer.hh \ mu-tree.hh \ mu-xapian.cc \ mu-xapian.hh \ mu-maildir.cc \ mu-maildir.hh \ mu-flags.cc \ mu-flags.hh \ mu-msg-crypto.cc \ mu-msg-doc.cc \ mu-msg-doc.hh \ mu-msg-fields.c \ mu-msg-fields.h \ mu-msg-file.cc \ mu-msg-file.hh \ mu-msg-part.cc \ mu-msg-part.hh \ mu-msg-prio.c \ mu-msg-prio.h \ mu-msg-priv.hh \ mu-msg-sexp.cc \ mu-msg.cc \ mu-msg.hh libmu_la_LIBADD= \ $(XAPIAN_LIBS) \ $(GMIME_LIBS) \ $(GLIB_LIBS) \ $(GUILE_LIBS) \ ${builddir}/index/libmu-index.la \ $(CODE_COVERAGE_LIBS) libmu_la_LDFLAGS= \ $(ASAN_LDFLAGS) noinst_PROGRAMS= \ tokenize tokenize_SOURCES= \ tokenize.cc tokenize_LDADD= \ $(WARN_LDFLAGS) \ libmu.la \ utils/libmu-utils.la EXTRA_DIST= \ doxyfile.in noinst_PROGRAMS+=$(TEST_PROGS) noinst_LTLIBRARIES+= \ libtestmucommon.la TEST_PROGS += test-maildir test_maildir_SOURCES= test-mu-maildir.cc test_maildir_LDADD= libtestmucommon.la TEST_PROGS += test-msg-fields test_msg_fields_SOURCES= test-mu-msg-fields.cc test_msg_fields_LDADD= libtestmucommon.la TEST_PROGS += test-msg test_msg_SOURCES= test-mu-msg.cc test_msg_LDADD= libtestmucommon.la TEST_PROGS += test-store test_store_SOURCES= test-mu-store.cc test_store_LDADD= libtestmucommon.la TEST_PROGS += test-query test_query_SOURCES= test-query.cc test_query_LDADD= libtestmucommon.la TEST_PROGS += test-flags test_flags_SOURCES= test-mu-flags.cc test_flags_LDADD= libtestmucommon.la TEST_PROGS+=test-tokenizer test_tokenizer_SOURCES=test-tokenizer.cc test_tokenizer_LDADD=libtestmucommon.la TEST_PROGS+=test-threads test_threads_SOURCES=mu-query-threads.cc test_threads_LDADD=libtestmucommon.la test_threads_CXXFLAGS=$(AM_CXXFLAGS) -DBUILD_TESTS TEST_PROGS += test-contacts test_contacts_SOURCES= mu-contacts.cc test_contacts_CXXFLAGS=$(AM_CXXFLAGS) -DBUILD_TESTS test_contacts_LDADD= libtestmucommon.la TEST_PROGS+=test-parser test_parser_SOURCES=test-parser.cc test_parser_LDADD=libtestmucommon.la libtestmucommon_la_SOURCES= \ test-mu-common.cc \ test-mu-common.hh libtestmucommon_la_LIBADD= \ libmu.la \ utils/libmu-utils.la # note the question marks; make does not like files with ':', so we # use the (also supported) version with '!' instead. We could escape # the : with \: but automake does not recognize that.... # test messages, the '.ignore' message should be ignored # when indexing EXTRA_DIST+= \ testdir/tmp/1220863087.12663.ignore \ testdir/new/1220863087.12663_9.mindcrime \ testdir/new/1220863087.12663_25.mindcrime \ testdir/new/1220863087.12663_21.mindcrime \ testdir/new/1220863087.12663_23.mindcrime \ testdir/cur/1220863087.12663_5.mindcrime!2,S \ testdir/cur/1220863087.12663_7.mindcrime!2,RS \ testdir/cur/1220863087.12663_15.mindcrime!2,PS \ testdir/cur/1220863087.12663_19.mindcrime!2,S \ testdir/cur/1220863042.12663_1.mindcrime!2,S \ testdir/cur/1220863060.12663_3.mindcrime!2,S \ testdir/cur/1283599333.1840_11.cthulhu!2, \ testdir/cur/1305664394.2171_402.cthulhu!2, \ testdir/cur/1252168370_3.14675.cthulhu!2,S \ testdir/cur/encrypted!2,S \ testdir/cur/multimime!2,FS \ testdir/cur/signed!2,S \ testdir/cur/signed-encrypted!2,S \ testdir/cur/special!2,Sabc \ testdir/cur/multirecip!2,S \ testdir2/bar/cur/mail1 \ testdir2/bar/cur/mail2 \ testdir2/bar/cur/mail3 \ testdir2/bar/cur/mail4 \ testdir2/bar/cur/mail5 \ testdir2/bar/cur/181736.eml \ testdir2/bar/cur/mail6 \ testdir2/bar/tmp/.noindex \ testdir2/bar/new/.noindex \ testdir2/Foo/cur/mail5 \ testdir2/Foo/cur/arto.eml \ testdir2/Foo/cur/fraiche.eml \ testdir2/Foo/tmp/.noindex \ testdir2/Foo/new/.noindex \ testdir2/wom_bat/cur/atomic \ testdir2/wom_bat/cur/rfc822.1 \ testdir2/wom_bat/cur/rfc822.2 \ testdir4/1220863087.12663_19.mindcrime!2,S \ testdir4/1220863042.12663_1.mindcrime!2,S \ testdir4/1283599333.1840_11.cthulhu!2, \ testdir4/1305664394.2171_402.cthulhu!2, \ testdir4/1252168370_3.14675.cthulhu!2,S \ testdir4/mail1 \ testdir4/mail5 \ testdir4/181736.eml \ testdir4/encrypted!2,S \ testdir4/multimime!2,FS \ testdir4/signed!2,S \ testdir4/signed-bad!2,S \ testdir4/signed-encrypted!2,S \ testdir4/special!2,Sabc TESTS=$(TEST_PROGS) CLEANFILES=*.log *.trs *core* *vgdump* *.gcda *.gcno include $(top_srcdir)/aminclude_static.am ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/doxyfile.in���������������������������������������������������������������������������0000664�0000000�0000000�00000015576�14143670036�0015163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Doxyfile 0.1 #--------------------------------------------------------------------------- # General configuration options #--------------------------------------------------------------------------- PROJECT_NAME = mu PROJECT_NUMBER = @VERSION@ OUTPUT_DIRECTORY = apidocs OUTPUT_LANGUAGE = English EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ALWAYS_DETAILED_SEC = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = INTERNAL_DOCS = NO STRIP_CODE_COMMENTS = YES CASE_SENSE_NAMES = YES SHORT_NAMES = NO HIDE_SCOPE_NAMES = NO VERBATIM_HEADERS = YES SHOW_INCLUDE_FILES = YES JAVADOC_AUTOBRIEF = YES INHERIT_DOCS = YES INLINE_INFO = YES SORT_MEMBER_DOCS = YES DISTRIBUTE_GROUP_DOC = NO TAB_SIZE = 8 GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES ALIASES = ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 OPTIMIZE_OUTPUT_FOR_C = YES SHOW_USED_FILES = YES #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_FORMAT = WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = @top_srcdir@/lib/ FILE_PATTERNS = *.c *.h RECURSIVE = YES EXCLUDE = tests # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. EXCLUDE_PATTERNS = Makefile.* ChangeLog CHANGES CHANGES.* README \ README.* *.png AUTHORS DESIGN DESIGN.* *.desktop \ DESKTOP* COMMENTS HOWTO magic NOTES TODO THANKS # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = NO COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = YES MAN_OUTPUT = man MAN_EXTENSION = .3mu MAN_LINKS = YES #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = YES #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = "G_BEGIN_DECLS=" \ "G_END_DECLS=" # "DOXYGEN_SHOULD_SKIP_THIS" \ # "DBUS_GNUC_DEPRECATED=" \ # "_DBUS_DEFINE_GLOBAL_LOCK(name)=" \ # "_DBUS_GNUC_PRINTF(from,to)=" SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::addtions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO PERL_PATH = #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES TEMPLATE_RELATIONS = YES HIDE_UNDOC_RELATIONS = YES INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES GRAPHICAL_HIERARCHY = YES DOT_PATH = DOTFILE_DIRS = MAX_DOT_GRAPH_WIDTH = 640 MAX_DOT_GRAPH_HEIGHT = 1024 GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::addtions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = NO ����������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0014101�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/Makefile.am���������������������������������������������������������������������0000664�0000000�0000000�00000002505�14143670036�0016137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk AM_CPPFLAGS= \ $(CODE_COVERAGE_CPPFLAGS) AM_CXXFLAGS= \ $(WARN_CXXFLAGS) \ $(GLIB_CFLAGS) \ $(XAPIAN_CFLAGS) \ $(ASAN_CXXFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ -I${top_srcdir}/lib AM_LDFLAGS= \ $(ASAN_LDFLAGS) noinst_LTLIBRARIES= \ libmu-index.la libmu_index_la_SOURCES= \ mu-indexer.cc \ mu-indexer.hh \ mu-scanner.cc \ mu-scanner.hh libmu_index_la_LIBADD= \ $(GLIB_LIBS) \ $(CODE_COVERAGE_LIBS) include $(top_srcdir)/aminclude_static.am �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/meson.build���������������������������������������������������������������������0000664�0000000�0000000�00000002310�14143670036�0016237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. index_srcs=[ 'mu-indexer.hh', 'mu-indexer.cc', 'mu-scanner.hh', 'mu-scanner.cc' ] lib_mu_index_inc_dep = declare_dependency( include_directories: include_directories(['.', '..'])) lib_mu_index=static_library('mu-index', [index_srcs], dependencies: [ config_h_dep, glib_dep, lib_mu_index_inc_dep ], install: false) lib_mu_index_dep = declare_dependency( link_with: lib_mu_index ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/mu-indexer.cc�������������������������������������������������������������������0000664�0000000�0000000�00000026057�14143670036�0016477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-indexer.hh" #include <config.h> #include <atomic> #include <mutex> #include <vector> #include <thread> #include <condition_variable> #include <iostream> #include <atomic> #include <chrono> using namespace std::chrono_literals; #include <xapian.h> #include "mu-scanner.hh" #include "utils/mu-async-queue.hh" #include "utils/mu-error.hh" #include "../mu-store.hh" using namespace Mu; struct IndexState { enum State { Idle, Scanning, Cleaning }; static const char* name(State s) { switch(s) { case Idle: return "idle"; case Scanning: return "scanning"; case Cleaning: return "cleaning"; default: return "<error>"; } } bool operator==(State rhs) const { return state_ == rhs; } void change_to(State new_state) { g_debug ("changing indexer state %s->%s", name((State)state_), name((State)new_state)); state_ = new_state; } private: State state_{Idle}; }; struct Indexer::Private { Private (Mu::Store& store): store_{store}, scanner_{store_.metadata().root_maildir, [this](auto&& path, auto&& statbuf, auto&& info){ return handler(path, statbuf, info); }}, max_message_size_{store_.metadata().max_message_size} { g_message ("created indexer for %s -> %s", store.metadata().root_maildir.c_str(), store.metadata().database_path.c_str()); } ~Private() { stop(); } bool dir_predicate (const std::string& path, const struct dirent* dirent) const; bool handler (const std::string& fullpath, struct stat *statbuf, Scanner::HandleType htype); void maybe_start_worker(); void worker(); bool cleanup(); bool start(const Indexer::Config& conf); bool stop(); Indexer::Config conf_; Store& store_; Scanner scanner_; const size_t max_message_size_; time_t dirstamp_{}; std::size_t max_workers_; std::vector<std::thread> workers_; std::thread scanner_worker_; AsyncQueue<std::string> fq_; Progress progress_; IndexState state_; std::mutex lock_, wlock_; }; bool Indexer::Private::handler (const std::string& fullpath, struct stat *statbuf, Scanner::HandleType htype) { switch (htype) { case Scanner::HandleType::EnterDir: case Scanner::HandleType::EnterNewCur: { // in lazy-mode, we ignore this dir if its dirstamp suggest it // is up-to-date (this is _not_ always true; hence we call it // lazy-mode); only for actual message dirs, since the dir // tstamps may not bubble up. dirstamp_ = store_.dirstamp(fullpath); if (conf_.lazy_check && dirstamp_ == statbuf->st_mtime && htype == Scanner::HandleType::EnterNewCur) { g_debug("skip %s (seems up-to-date)", fullpath.c_str()); return false; } // don't index dirs with '.noindex' auto noindex = ::access((fullpath + "/.noindex").c_str(), F_OK) == 0; if (noindex) { g_debug ("skip %s (has .noindex)", fullpath.c_str()); return false; // don't descend into this dir. } // don't index dirs with '.noupdate', unless we do a full // (re)index. if (!conf_.ignore_noupdate) { auto noupdate = ::access((fullpath + "/.noupdate").c_str(), F_OK) == 0; if (noupdate) { g_debug ("skip %s (has .noupdate)", fullpath.c_str()); return false; } } g_debug ("process %s", fullpath.c_str()); return true; } case Scanner::HandleType::LeaveDir: { store_.set_dirstamp(fullpath, statbuf->st_mtime); return true; } case Scanner::HandleType::File: { if ((size_t)statbuf->st_size > max_message_size_) { g_debug ("skip %s (too big: %" G_GINT64_FORMAT " bytes)", fullpath.c_str(), (gint64)statbuf->st_size); return false; } // if the message is not in the db yet, or not up-to-date, queue // it for updating/inserting. if (statbuf->st_mtime <= dirstamp_ && store_.contains_message (fullpath)) { //g_debug ("skip %s: already up-to-date"); return false; } fq_.push(std::string{fullpath}); return true; } default: g_return_val_if_reached (false); return false; } } void Indexer::Private::maybe_start_worker() { std::lock_guard<std::mutex> wlock{wlock_}; if (fq_.size() > workers_.size() && workers_.size() < max_workers_) workers_.emplace_back(std::thread([this]{worker();})); } void Indexer::Private::worker() { std::string item; g_debug ("started worker"); while (state_ == IndexState::Scanning || !fq_.empty()) { if (!fq_.pop (item, 250ms)) continue; //g_debug ("popped (n=%zu) path %s", fq_.size(), item.c_str()); ++progress_.processed; try { store_.add_message(item); ++progress_.updated; } catch (const Mu::Error& er) { g_warning ("error adding message @ %s: %s", item.c_str(), er.what()); } maybe_start_worker(); } } bool Indexer::Private::cleanup() { g_debug ("starting cleanup"); size_t n{}; std::vector<Store::Id> orphans; // store messages without files. store_.for_each_message_path([&](Store::Id id, const std::string &path) { ++n; if (::access(path.c_str(), R_OK) != 0) { g_debug ("cannot read %s (id=%u); queueing for removal from store", path.c_str(), id); orphans.emplace_back(id); } return state_ == IndexState::Cleaning; }); g_debug("remove %zu message(s) from store", orphans.size()); store_.remove_messages (orphans); progress_.removed += orphans.size(); return true; } bool Indexer::Private::start(const Indexer::Config& conf) { stop(); conf_ = conf; if (conf_.max_threads == 0) max_workers_ = std::thread::hardware_concurrency(); else max_workers_ = conf.max_threads; g_debug ("starting indexer with <= %zu worker thread(s)", max_workers_); g_debug ("indexing: %s; clean-up: %s", conf_.scan ? "yes" : "no", conf_.cleanup ? "yes" : "no"); workers_.emplace_back(std::thread([this]{worker();})); state_.change_to(IndexState::Scanning); scanner_worker_ = std::thread([this]{ progress_ = {}; if (conf_.scan) { g_debug("starting scanner"); if (!scanner_.start()) { // blocks. g_warning ("failed to start scanner"); goto leave; } g_debug ("scanner finished with %zu file(s) in queue", fq_.size()); } // now there may still be messages in the work queue... // finish those; this is a bit ugly; perhaps we should // handle SIGTERM etc. while (!fq_.empty()) std::this_thread::sleep_for(100ms); if (conf_.cleanup) { g_debug ("starting cleanup"); state_.change_to(IndexState::Cleaning); cleanup(); g_debug ("cleanup finished"); } store_.commit(); leave: state_.change_to(IndexState::Idle); }); g_debug ("started indexer"); return true; } bool Indexer::Private::stop() { scanner_.stop(); state_.change_to(IndexState::Idle); const auto w_n = workers_.size(); fq_.clear(); if (scanner_worker_.joinable()) scanner_worker_.join(); for (auto&& w: workers_) if (w.joinable()) w.join(); workers_.clear(); if (w_n > 0) g_debug ("stopped indexer (joined %zu worker(s))", w_n); return true; } Indexer::Indexer (Store& store): priv_{std::make_unique<Private>(store)} {} Indexer::~Indexer() = default; bool Indexer::start(const Indexer::Config& conf) { const auto mdir{priv_->store_.metadata().root_maildir}; if (G_UNLIKELY(access (mdir.c_str(), R_OK) != 0)) { g_critical("'%s' is not readable: %s", mdir.c_str(), g_strerror (errno)); return false; } std::lock_guard<std::mutex> l(priv_->lock_); if (is_running()) return true; return priv_->start(conf); } bool Indexer::stop() { std::lock_guard<std::mutex> l(priv_->lock_); if (!is_running()) return true; g_debug ("stopping indexer"); return priv_->stop(); } bool Indexer::is_running() const { return !(priv_->state_ == IndexState::Idle) || !priv_->fq_.empty(); } Indexer::Progress Indexer::progress() const { priv_->progress_.running = priv_->state_ == IndexState::Idle ? false : true; return priv_->progress_; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/mu-indexer.hh�������������������������������������������������������������������0000664�0000000�0000000�00000006346�14143670036�0016510�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_INDEXER_HH__ #define MU_INDEXER_HH__ #include <memory> #include <chrono> namespace Mu { class Store; /// An object abstracting the index process. class Indexer { public: /** * Construct an indexer object * * @param store the message store to use */ Indexer (Store& store); /** * DTOR */ ~Indexer(); /// A configuration object for the indexer struct Config { bool scan{true}; /**< scan for new messages */ bool cleanup{true}; /**< clean messages no longer in the file system */ size_t max_threads{}; /**< maximum # of threads to use */ bool ignore_noupdate{}; /**< ignore .noupdate files */ bool lazy_check{}; /**< whether to skip directories that don't have a changed * mtime */ }; /** * Start indexing. If already underway, do nothing. * * @param conf a configuration object * * @return true if starting worked or an indexing process was already * underway; false otherwise. * */ bool start(const Config& conf); /** * Stop indexing. If not indexing, do nothing. * * * @return true if we stopped indexing, or indexing was not underway. * False otherwise. */ bool stop(); /** * Is an indexing process running? * * @return true or false. */ bool is_running() const; // Object describing current progress struct Progress { bool running{}; /**< Is an index operation in progress? */ size_t processed{}; /**< Number of messages processed */ size_t updated{}; /**< Number of messages added/updated to store */ size_t removed{}; /**< Number of message removed from store */ }; /** * Get an object describing the current progress. The progress object * describes the most recent indexing job, and is reset up a fresh * start(). * * @return a progress object. */ Progress progress() const; private: struct Private; std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /* MU_INDEXER_HH__ */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/mu-scanner.cc�������������������������������������������������������������������0000664�0000000�0000000�00000015533�14143670036�0016467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-scanner.hh" #include "config.h" #include <chrono> #include <mutex> #include <atomic> #include <thread> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <glib.h> #include "utils/mu-utils.hh" #include "utils/mu-error.hh" using namespace Mu; struct Scanner::Private { Private (const std::string& root_dir, Scanner::Handler handler): root_dir_{root_dir}, handler_{handler} { if (!handler_) throw Mu::Error{Error::Code::Internal, "missing handler"}; } ~Private() { stop(); } bool start(); bool stop(); bool process_dentry (const std::string& path, struct dirent *dentry, bool is_maildir); bool process_dir (const std::string& path, bool is_maildir); const std::string root_dir_; const Scanner::Handler handler_; std::atomic<bool> running_{}; std::mutex lock_; }; static bool is_special_dir (const struct dirent *dentry) { const auto d_name{dentry->d_name}; return d_name[0] == '\0' || (d_name[1] == '\0' && d_name[0] == '.') || (d_name[2] == '\0' && d_name[0] == '.' && d_name[1] == '.'); } static bool is_new_cur (const char *dirname) { if (dirname[0] == 'c' && dirname[1] == 'u' && dirname[2] == 'r' && dirname[3] == '\0') return true; if (dirname[0] == 'n' && dirname[1] == 'e' && dirname[2] == 'w' && dirname[3] == '\0') return true; return false; } bool Scanner::Private::process_dentry (const std::string& path, struct dirent *dentry, bool is_maildir) { if (is_special_dir (dentry)) return true; // ignore. const auto fullpath{path + "/" + dentry->d_name}; struct stat statbuf; if (::stat(fullpath.c_str(), &statbuf) != 0) { g_warning ("failed to stat %s: %s", fullpath.c_str(), g_strerror(errno)); return false; } if (S_ISDIR(statbuf.st_mode)) { const auto new_cur = is_new_cur(dentry->d_name); const auto htype = new_cur ? Scanner::HandleType::EnterNewCur : Scanner::HandleType::EnterDir; const auto res = handler_(fullpath, &statbuf, htype); if (!res) return true; // skip process_dir (fullpath, new_cur); return handler_(fullpath, &statbuf, Scanner::HandleType::LeaveDir); } else if (S_ISREG(statbuf.st_mode) && is_maildir) return handler_(fullpath, &statbuf, Scanner::HandleType::File); g_debug ("skip %s (neither maildir-file nor directory)", fullpath.c_str()); return true; } bool Scanner::Private::process_dir (const std::string& path, bool is_maildir) { const auto dir = opendir (path.c_str()); if (G_UNLIKELY(!dir)) { g_warning("failed to scan dir %s: %s", path.c_str(), g_strerror(errno)); return false; } // TODO: sort dentries by inode order, which makes things faster for extfs. // see mu-maildir.c while (running_) { errno = 0; const auto dentry{readdir(dir)}; if (G_LIKELY(dentry)) { process_dentry (path, dentry, is_maildir); continue; } if (errno != 0) { g_warning("failed to read %s: %s", path.c_str(), g_strerror(errno)); continue; } break; } closedir (dir); return true; } bool Scanner::Private::start() { const auto& path{root_dir_}; if (G_UNLIKELY(path.length() > PATH_MAX)) { g_warning("path too long"); return false; } const auto mode{F_OK | R_OK}; if (G_UNLIKELY(access (path.c_str(), mode) != 0)) { g_warning("'%s' is not readable: %s", path.c_str(), g_strerror (errno)); return false; } struct stat statbuf{}; if (G_UNLIKELY(stat (path.c_str(), &statbuf) != 0)) { g_warning("'%s' is not stat'able: %s", path.c_str(), g_strerror (errno)); return false; } if (G_UNLIKELY(!S_ISDIR (statbuf.st_mode))) { g_warning("'%s' is not a directory", path.c_str()); return false; } running_ = true; g_debug ("starting scan @ %s", root_dir_.c_str()); auto basename{g_path_get_basename(root_dir_.c_str())}; const auto is_maildir = (g_strcmp0(basename, "cur") == 0 || g_strcmp0(basename,"new") == 0); g_free(basename); const auto start{std::chrono::steady_clock::now()}; process_dir(root_dir_, is_maildir); const auto elapsed = std::chrono::steady_clock::now() - start; g_debug ("finished scan of %s in %" G_GINT64_FORMAT " ms", root_dir_.c_str(), to_ms(elapsed)); running_ = false; return true; } bool Scanner::Private::stop() { if (!running_) return true; // nothing to do g_debug ("stopping scan"); running_ = false; return true; } Scanner::Scanner (const std::string& root_dir, Scanner::Handler handler): priv_{std::make_unique<Private>(root_dir, handler)} {} Scanner::~Scanner() = default; bool Scanner::start() { { std::lock_guard<std::mutex> l(priv_->lock_); if (priv_->running_) return true; //nothing to do priv_->running_ = true; } const auto res = priv_->start(); priv_->running_ = false; return res; } bool Scanner::stop() { std::lock_guard<std::mutex> l(priv_->lock_); return priv_->stop(); } bool Scanner::is_running() const { return priv_->running_; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/mu-scanner.hh�������������������������������������������������������������������0000664�0000000�0000000�00000005457�14143670036�0016505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_SCANNER_HH__ #define MU_SCANNER_HH__ #include <functional> #include <memory> #include <dirent.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> namespace Mu { /// @brief Maildir scanner /// /// Scans maildir (trees) recursively, and calls the Handler callback for /// directories & files. /// /// It filters out (i.e., does call the handler for): /// - files starting with '.' /// - files that do not live in a cur / new leaf maildir /// - directories '.' and '..' /// class Scanner { public: enum struct HandleType { File, EnterNewCur, /* cur/ or new/ */ EnterDir, /* some other directory */ LeaveDir }; /// Prototype for a handler function using Handler = std::function<bool(const std::string& fullpath, struct stat* statbuf, HandleType htype)>; /** * Construct a scanner object for scanning a directory, recursively. * * If handler is a directory * * * @param root_dir root dir to start scanning * @param handler handler function for some direntry */ Scanner (const std::string& root_dir, Handler handler); /** * DTOR */ ~Scanner(); /** * Start the scan; this is a blocking call than runs until * finished or (from another thread) stop() is called. * * @return true if starting worked; false otherwise */ bool start(); /** * Stop the scan * * @return true if stopping worked; false otherwi%sse */ bool stop(); /** * Is a scan currently running? * * @return true or false */ bool is_running() const; private: struct Private; std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /* MU_SCANNER_HH__ */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/index/test-scanner.cc�����������������������������������������������������������������0000664�0000000�0000000�00000003536�14143670036�0017025�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include <vector> #include <glib.h> #include <iostream> #include <sstream> #include "mu-scanner.hh" #include "mu-utils.hh" using namespace Mu; static void test_scan_maildir () { allow_warnings(); Scanner scanner{"/home/djcb/Maildir", [](const dirent* dentry)->bool { g_print ("%02x %s\n", dentry->d_type, dentry->d_name); return true; }, [](const std::string& fullpath, const struct stat* statbuf, auto&& info)->bool { g_print ("%s %zu\n", fullpath.c_str(), statbuf->st_size); return true; } }; g_assert_true (scanner.start()); while (scanner.is_running()) { sleep(1); } } int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); g_test_add_func ("/utils/scanner/scan-maildir", test_scan_maildir); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/meson.build���������������������������������������������������������������������������0000664�0000000�0000000�00000011147�14143670036�0015140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. subdir('utils') subdir('index') lib_mu=static_library( 'mu', [ 'mu-bookmarks.cc', 'mu-bookmarks.hh', 'mu-contacts.cc', 'mu-contacts.hh', 'mu-data.hh', 'mu-parser.cc', 'mu-parser.hh', 'mu-query.cc', 'mu-query.hh', 'mu-query-results.hh', 'mu-query-match-deciders.cc', 'mu-query-match-deciders.hh', 'mu-query-threads.cc', 'mu-query-threads.hh', 'mu-runtime.cc', 'mu-runtime.hh', 'mu-script.cc', 'mu-script.hh', 'mu-server.cc', 'mu-server.hh', 'mu-store.cc', 'mu-store.hh', 'mu-tokenizer.cc', 'mu-tokenizer.hh', 'mu-tree.hh', 'mu-xapian.cc', 'mu-xapian.hh', 'mu-maildir.cc', 'mu-maildir.hh', 'mu-flags.cc', 'mu-flags.hh', 'mu-msg-crypto.cc', 'mu-msg-doc.cc', 'mu-msg-doc.hh', 'mu-msg-fields.c', 'mu-msg-fields.h', 'mu-msg-file.cc', 'mu-msg-file.hh', 'mu-msg-part.cc', 'mu-msg-part.hh', 'mu-msg-prio.c', 'mu-msg-prio.h', 'mu-msg-priv.hh', 'mu-msg-sexp.cc', 'mu-msg.cc', 'mu-msg.hh' ], dependencies: [ glib_dep, gio_dep, gmime_dep, xapian_dep, config_h_dep, lib_mu_utils_dep, lib_mu_index_dep ], install: false) lib_mu_dep = declare_dependency( link_with: lib_mu, include_directories: include_directories(['.', '..']) ) # # tests # lib_test_mu_common_inc_dep = declare_dependency( include_directories: include_directories(['.', '..'])) lib_test_mu_common=static_library('mu-test-common', [ 'test-mu-common.cc', 'test-mu-common.hh'], dependencies: [ glib_dep, thread_dep, lib_test_mu_common_inc_dep]) lib_test_mu_common_dep=declare_dependency( link_with: lib_test_mu_common, include_directories: include_directories(['.'])) testmaildir=join_paths(meson.current_source_dir(),'.') test('test_maildir', executable('test-maildir', 'test-mu-maildir.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_msg_fields', executable('test-msg-fields', 'test-mu-msg-fields.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_msg', executable('test-msg', 'test-mu-msg.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep], cpp_args:['-DMU_TESTMAILDIR2="'+ join_paths(testmaildir, 'testdir2') + '"', '-DMU_TESTMAILDIR4="'+ join_paths(testmaildir, 'testdir4') + '"' ])) test('test_store', executable('test-store', 'test-mu-store.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep], cpp_args:['-DMU_TESTMAILDIR="'+ join_paths(testmaildir, 'testdir') + '"', '-DMU_TESTMAILDIR2="'+ join_paths(testmaildir, 'testdir2') + '"', '-DMU_TESTMAILDIR4="'+ join_paths(testmaildir, 'testdir4') + '"'])) test('test_query', executable('test-query', 'test-query.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep], cpp_args:['-DMU_TESTMAILDIR="'+ join_paths(testmaildir, 'testdir') + '"'])) test('test_flags', executable('test-flags', 'test-mu-flags.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_tokenizer', executable('test-tokenizer', 'test-tokenizer.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_threads', executable('test-threads', 'mu-query-threads.cc', install: false, cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_contacts', executable('test-contacts', 'mu-contacts.cc', install: false, cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) test('test_parser', executable('test-parser', 'test-parser.cc', install: false, dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-bookmarks.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000005733�14143670036�0015720�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2010-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <glib.h> #include "mu-bookmarks.hh" #define MU_BOOKMARK_GROUP "mu" struct MuBookmarks { char *_bmpath; GHashTable *_hash; }; static void fill_hash (GHashTable *hash, GKeyFile *kfile) { gchar **keys, **cur; keys = g_key_file_get_keys (kfile, MU_BOOKMARK_GROUP, NULL, NULL); if (!keys) return; for (cur = keys; *cur; ++cur) { gchar *val; val = g_key_file_get_string (kfile, MU_BOOKMARK_GROUP, *cur, NULL); if (val) g_hash_table_insert (hash, *cur, val); } /* don't use g_strfreev, because we put them in the hash table; * only free the gchar** itself */ g_free (keys); } static GHashTable* create_hash_from_key_file (const gchar *bmpath) { GKeyFile *kfile; GHashTable *hash; kfile = g_key_file_new (); if (!g_key_file_load_from_file (kfile, bmpath, G_KEY_FILE_NONE, NULL)) { g_key_file_free (kfile); return NULL; } hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); fill_hash (hash, kfile); g_key_file_free (kfile); return hash; } MuBookmarks* mu_bookmarks_new (const gchar *bmpath) { MuBookmarks *bookmarks; GHashTable *hash; g_return_val_if_fail (bmpath, NULL); hash = create_hash_from_key_file (bmpath); if (!hash) return NULL; bookmarks = g_new (MuBookmarks, 1); bookmarks->_bmpath = g_strdup (bmpath); bookmarks->_hash = hash; return bookmarks; } void mu_bookmarks_destroy (MuBookmarks *bm) { if (!bm) return; g_free (bm->_bmpath); g_hash_table_destroy (bm->_hash); g_free (bm); } const gchar* mu_bookmarks_lookup (MuBookmarks *bm, const gchar *name) { g_return_val_if_fail (bm, NULL); g_return_val_if_fail (name, NULL); return (const char*)g_hash_table_lookup (bm->_hash, name); } struct _BMData { MuBookmarksForeachFunc _func; gpointer _user_data; }; typedef struct _BMData BMData; static void each_bookmark (const gchar* key, const gchar *val, BMData *bmdata) { bmdata->_func (key, val, bmdata->_user_data); } void mu_bookmarks_foreach (MuBookmarks *bm, MuBookmarksForeachFunc func, gpointer user_data) { BMData bmdata; g_return_if_fail (bm); g_return_if_fail (func); bmdata._func = func; bmdata._user_data = user_data; g_hash_table_foreach (bm->_hash, (GHFunc)each_bookmark, &bmdata); } �������������������������������������mu-1.6.10/lib/mu-bookmarks.hh�����������������������������������������������������������������������0000664�0000000�0000000�00000004335�14143670036�0015727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_BOOKMARKS_HH__ #define MU_BOOKMARKS_HH__ #include <glib.h> /** * @addtogroup MuBookmarks * Functions for dealing with bookmarks * @{ */ /*! \struct MuBookmarks * \brief Opaque structure representing a sequence of bookmarks */ struct MuBookmarks; /** * create a new bookmarks object. when it's no longer needed, use * mu_bookmarks_destroy * * @param bmpath path to the bookmarks file * * @return a new BookMarks object, or NULL in case of error */ MuBookmarks *mu_bookmarks_new (const gchar *bmpath) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * destroy a bookmarks object * * @param bm a bookmarks object, or NULL */ void mu_bookmarks_destroy (MuBookmarks *bm); /** * get the value for some bookmark * * @param bm a valid bookmarks object * @param name name of the bookmark to retrieve * * @return the value of the bookmark or NULL in case in error, e.g. if * the bookmark was not found */ const gchar* mu_bookmarks_lookup (MuBookmarks *bm, const gchar *name); typedef void (*MuBookmarksForeachFunc) (const gchar *key, const gchar *val, gpointer user_data); /** * call a function for each bookmark * * @param bm a valid bookmarks object * @param func a callback function to be called for each bookmarks * @param user_data a user pointer passed to the callback */ void mu_bookmarks_foreach (MuBookmarks *bm, MuBookmarksForeachFunc func, gpointer user_data); /** @} */ #endif /*__MU_BOOKMARKS_H__*/ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-contacts.cc������������������������������������������������������������������������0000664�0000000�0000000�00000031174�14143670036�0015544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2019 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-contacts.hh" #include <mutex> #include <unordered_map> #include <set> #include <sstream> #include <functional> #include <algorithm> #include <regex> #include <ctime> #include <utils/mu-utils.hh> #include <glib.h> using namespace Mu; ContactInfo::ContactInfo (const std::string& _full_address, const std::string& _email, const std::string& _name, bool _personal, time_t _last_seen, size_t _freq): full_address{_full_address}, email{_email}, name{_name}, personal{_personal}, last_seen{_last_seen}, freq{_freq}, tstamp{g_get_monotonic_time()} {} struct EmailHash { std::size_t operator()(const std::string& email) const { std::size_t djb = 5381; // djb hash for (const auto c : email) djb = ((djb << 5) + djb) + g_ascii_tolower(c); return djb; } }; struct EmailEqual { bool operator()(const std::string& email1, const std::string& email2) const { return g_ascii_strcasecmp(email1.c_str(), email2.c_str()) == 0; } }; struct ContactInfoHash { std::size_t operator()(const ContactInfo& ci) const { std::size_t djb = 5381; // djb hash for (const auto c : ci.email) djb = ((djb << 5) + djb) + g_ascii_tolower(c); return djb; } }; struct ContactInfoEqual { bool operator()(const Mu::ContactInfo& ci1, const Mu::ContactInfo& ci2) const { return g_ascii_strcasecmp(ci1.email.c_str(), ci2.email.c_str()) == 0; } }; constexpr auto RecentOffset{15 * 24 * 3600}; struct ContactInfoLessThan { ContactInfoLessThan(): recently_{::time({}) - RecentOffset} {} bool operator()(const Mu::ContactInfo& ci1, const Mu::ContactInfo& ci2) const { if (ci1.personal != ci2.personal) return ci1.personal; // personal comes first if ((ci1.last_seen > recently_) != (ci2.last_seen > recently_)) return ci1.last_seen > ci2.last_seen; if (ci1.freq != ci2.freq) // more frequent comes first return ci1.freq > ci2.freq; return g_ascii_strcasecmp(ci1.email.c_str(), ci2.email.c_str()) < 0; } // only sort recently seen contacts by recency; approx 15 days. // this changes during the lifetime, but that's all fine. const time_t recently_; }; using ContactUMap = std::unordered_map<const std::string, ContactInfo, EmailHash, EmailEqual>; //using ContactUSet = std::unordered_set<ContactInfo, ContactInfoHash, ContactInfoEqual>; using ContactSet = std::set<std::reference_wrapper<const ContactInfo>, ContactInfoLessThan>; struct Contacts::Private { Private(const std::string& serialized, const StringVec& personal): contacts_{deserialize(serialized)} { make_personal(personal); } void make_personal(const StringVec& personal); ContactUMap deserialize(const std::string&) const; std::string serialize() const; ContactUMap contacts_; std::mutex mtx_; StringVec personal_plain_; std::vector<std::regex> personal_rx_; }; constexpr auto Separator = "\xff"; // Invalid in UTF-8 void Contacts::Private::make_personal (const StringVec& personal) { for (auto&& p: personal) { if (p.empty()) continue; // invalid if (p.size() < 2 || p.at(0) != '/' || p.at(p.length() - 1) != '/') personal_plain_.emplace_back(p); // normal address else { // a regex pattern. try { const auto rxstr{p.substr(1, p.length()-2)}; personal_rx_.emplace_back( std::regex(rxstr, std::regex::basic | std::regex::optimize | std::regex::icase)); } catch (const std::regex_error& rex) { g_warning ("invalid personal address regexp '%s': %s", p.c_str(), rex.what()); } } } } ContactUMap Contacts::Private::deserialize(const std::string& serialized) const { ContactUMap contacts; std::stringstream ss{serialized, std::ios_base::in}; std::string line; while (getline (ss, line)) { const auto parts = Mu::split (line, Separator); if (G_UNLIKELY(parts.size() != 6)) { g_warning ("error: '%s'", line.c_str()); continue; } ContactInfo ci(std::move(parts[0]), // full address parts[1], // email std::move(parts[2]), // name parts[3][0] == '1' ? true : false, // personal (time_t)g_ascii_strtoll(parts[4].c_str(), NULL, 10), // last_seen (std::size_t)g_ascii_strtoll(parts[5].c_str(), NULL, 10)); // freq contacts.emplace(std::move(parts[1]), std::move(ci)); } return contacts; } Contacts::Contacts (const std::string& serialized, const StringVec& personal) : priv_{std::make_unique<Private>(serialized, personal)} {} Contacts::~Contacts() = default; std::string Contacts::serialize() const { std::lock_guard<std::mutex> l_{priv_->mtx_}; std::string s; for (auto& item: priv_->contacts_) { const auto& ci{item.second}; s += Mu::format("%s%s" "%s%s" "%s%s" "%d%s" "%" G_GINT64_FORMAT "%s" "%" G_GINT64_FORMAT "\n", ci.full_address.c_str(), Separator, ci.email.c_str(), Separator, ci.name.c_str(), Separator, ci.personal ? 1 : 0, Separator, (gint64)ci.last_seen, Separator, (gint64)ci.freq); } return s; } const ContactInfo Contacts::add (ContactInfo&& ci) { std::lock_guard<std::mutex> l_{priv_->mtx_}; auto it = priv_->contacts_.find(ci.email); if (it == priv_->contacts_.end()) { // completely new contact ci.name = Mu::remove_ctrl(ci.name); ci.full_address = remove_ctrl(ci.full_address); auto email{ci.email}; return priv_->contacts_.emplace(ContactUMap::value_type(email, std::move(ci))).first->second; } else { // existing contact. auto& ci_existing{it->second}; ++ci_existing.freq; if (ci.last_seen > ci_existing.last_seen) { // update. ci_existing.email = std::move(ci.email); ci_existing.name = Mu::remove_ctrl(ci.name); ci_existing.full_address = Mu::remove_ctrl(ci.full_address); ci_existing.tstamp = g_get_monotonic_time(); ci_existing.last_seen = ci.last_seen; } return ci; } } const ContactInfo* Contacts::_find (const std::string& email) const { std::lock_guard<std::mutex> l_{priv_->mtx_}; const auto it = priv_->contacts_.find(email); if (it == priv_->contacts_.end()) return {}; else return &it->second; } void Contacts::clear() { std::lock_guard<std::mutex> l_{priv_->mtx_}; priv_->contacts_.clear(); } std::size_t Contacts::size() const { std::lock_guard<std::mutex> l_{priv_->mtx_}; return priv_->contacts_.size(); } void Contacts::for_each(const EachContactFunc& each_contact) const { std::lock_guard<std::mutex> l_{priv_->mtx_}; if (!each_contact) return; // nothing to do // first sort them for 'rank' ContactSet sorted; for (const auto& item: priv_->contacts_) sorted.emplace(item.second); for (const auto& ci: sorted) each_contact (ci); } bool Contacts::is_personal(const std::string& addr) const { for (auto&& p: priv_->personal_plain_) if (g_ascii_strcasecmp(addr.c_str(), p.c_str()) == 0) return true; for (auto&& rx: priv_->personal_rx_) { std::smatch m; // perhaps cache addr in personal_plain_? if (std::regex_match(addr, m, rx)) return true; } return false; } #ifdef BUILD_TESTS /* * Tests. * */ #include "test-mu-common.hh" static void test_mu_contacts_01() { Mu::Contacts contacts (""); g_assert_true (contacts.empty()); g_assert_cmpuint (contacts.size(), ==, 0); contacts.add(Mu::ContactInfo ("Foo <foo.bar@example.com>", "foo.bar@example.com", "Foo", false, 12345)); g_assert_false (contacts.empty()); g_assert_cmpuint (contacts.size(), ==, 1); contacts.add(Mu::ContactInfo ("Cuux <cuux.fnorb@example.com>", "cuux@example.com", "Cuux", false, 54321)); g_assert_cmpuint (contacts.size(), ==, 2); contacts.add(Mu::ContactInfo ("foo.bar@example.com", "foo.bar@example.com", "Foo", false, 77777)); g_assert_cmpuint (contacts.size(), ==, 2); contacts.add(Mu::ContactInfo ("Foo.Bar@Example.Com", "Foo.Bar@Example.Com", "Foo", false, 88888)); g_assert_cmpuint (contacts.size(), ==, 2); // note: replaces first. { const auto info = contacts._find("bla@example.com"); g_assert_false (info); } { const auto info = contacts._find("foo.BAR@example.com"); g_assert_true (info); g_assert_cmpstr(info->email.c_str(), ==, "Foo.Bar@Example.Com"); } contacts.clear(); g_assert_true (contacts.empty()); g_assert_cmpuint (contacts.size(), ==, 0); } static void test_mu_contacts_02() { Mu::StringVec personal = { "foo@example.com", "bar@cuux.org", "/bar-.*@fnorb.f./" }; Mu::Contacts contacts{"", personal}; g_assert_true (contacts.is_personal("foo@example.com")); g_assert_true (contacts.is_personal("Bar@CuuX.orG")); g_assert_true (contacts.is_personal("bar-123abc@fnorb.fi")); g_assert_true (contacts.is_personal("bar-zzz@fnorb.fr")); g_assert_false (contacts.is_personal("foo@bar.com")); g_assert_false (contacts.is_personal("BÂr@CuuX.orG")); g_assert_false (contacts.is_personal("bar@fnorb.fi")); g_assert_false (contacts.is_personal("bar-zzz@fnorb.xr")); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/mu-contacts/01", test_mu_contacts_01); g_test_add_func ("/mu-contacts/02", test_mu_contacts_02); g_log_set_handler (NULL, (GLogLevelFlags) (G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); return g_test_run (); } #endif /*BUILD_TESTS*/ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-contacts.hh������������������������������������������������������������������������0000664�0000000�0000000�00000011062�14143670036�0015550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_CONTACTS_HH__ #define __MU_CONTACTS_HH__ #include <glib.h> #include <time.h> #include <memory> #include <functional> #include <chrono> #include <string> #include <time.h> #include <inttypes.h> #include <utils/mu-utils.hh> namespace Mu { /// Data-structure representing information about some contact. struct ContactInfo { /** * Construct a new ContactInfo * * @param _full_address the full email address + name. * @param _email email address * @param _name name or empty * @param _personal is this a personal contact? * @param _last_seen when was this contact last seen? * @param _freq how often was this contact seen? */ ContactInfo (const std::string& _full_address, const std::string& _email, const std::string& _name, bool personal, time_t _last_seen, size_t freq=1); std::string full_address; /**< Full name <email> */ std::string email; /**< email address */ std::string name; /**< name (or empty) */ bool personal{}; /**< is this a personal contact? */ time_t last_seen{}; /**< when was this contact last seen? */ std::size_t freq{}; /**< how often was this contact seen? */ int64_t tstamp{}; /**< Time-stamp, as per g_get_monotonic_time */ }; /// All contacts class Contacts { public: /** * Construct a new contacts objects * * @param serialized serialized contacts * @param personal personal addresses */ Contacts (const std::string& serialized = "", const StringVec& personal={}); /** * DTOR * */ ~Contacts (); /** * Add a contact * * @param ci A contact-info object * * @return the inserted / updated / washed contact info. Note that * this is return _as copy_ to make it thread-safe. */ const ContactInfo add(ContactInfo&& ci); /** * Clear all contacts * */ void clear(); /** * Get the number of contacts * * @return number of contacts */ std::size_t size() const; /** * Are there no contacts? * * @return true or false */ bool empty() const { return size() == 0; } /** * Get the contacts, serialized. * * @return serialized contacts */ std::string serialize() const; /** * Does this look like a 'personal' address? * * @param addr some e-mail address * * @return true or false */ bool is_personal(const std::string& addr) const; /** * Find a contact based on the email address. This is not safe, since * the returned ptr can be invalidated at any time; only for unit-tests. * * @param email email address * * @return contact info, or {} if not found */ const ContactInfo* _find (const std::string& email) const; /** * Prototype for a callable that receives a contact * * @param contact some contact */ using EachContactFunc = std::function<void (const ContactInfo& contact_info)>; /** * Invoke some callable for each contact, in order of rank. * * @param each_contact */ void for_each (const EachContactFunc& each_contact) const; private: struct Private; std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /* __MU_CONTACTS_HH__ */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-data.hh����������������������������������������������������������������������������0000664�0000000�0000000�00000007161�14143670036�0014650�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef __DATA_HH__ #define __DATA_HH__ #include <string> #include <iostream> #include <regex> #include <utils//mu-utils.hh> namespace Mu { // class representing some data item; either a Value or a Range a Value can still be a Regex (but // that's not a separate type here) struct Data { enum class Type { Value, Range }; virtual ~Data() = default; Type type; /**< type of data */ std::string field; /**< full name of the field */ std::string prefix; /**< Xapian prefix for thef field */ unsigned id; /**< Xapian value no for the field */ protected: Data (Type _type, const std::string& _field, const std::string& _prefix, unsigned _id): type(_type), field(_field), prefix(_prefix), id(_id) {} }; /** * operator<< * * @param os an output stream * @param t a data type * * @return the updated output stream */ inline std::ostream& operator<< (std::ostream& os, Data::Type t) { switch (t) { case Data::Type::Value: os << "value"; break; case Data::Type::Range: os << "range"; break; default: os << "bug"; break; } return os; } /** * Range type -- [a..b] */ struct Range: public Data { /** * Construct a range * * @param _field the field * @param _prefix the xapian prefix * @param _id xapian value number * @param _lower lower bound * @param _upper upper bound */ Range (const std::string& _field, const std::string& _prefix, unsigned _id, const std::string& _lower,const std::string& _upper): Data(Data::Type::Range, _field, _prefix, _id), lower(_lower), upper(_upper) {} std::string lower; /**< lower bound */ std::string upper; /**< upper bound */ }; /** * Basic value * */ struct Value: public Data { /** * Construct a Value * * @param _field the field * @param _prefix the xapian prefix * @param _id xapian value number * @param _value the value */ Value (const std::string& _field, const std::string& _prefix, unsigned _id, const std::string& _value, bool _phrase = false): Data(Value::Type::Value, _field, _prefix, _id), value(_value), phrase(_phrase) {} std::string value; /**< the value */ bool phrase; }; /** * operator<< * * @param os an output stream * @param v a data ptr * * @return the updated output stream */ inline std::ostream& operator<< (std::ostream& os, const std::unique_ptr<Data>& v) { switch (v->type) { case Data::Type::Value: { const auto bval = dynamic_cast<Value*> (v.get()); os << ' ' << quote(v->field) << ' ' << quote(utf8_flatten(bval->value)); if (bval->phrase) os << " (ph)"; break; } case Data::Type::Range: { const auto rval = dynamic_cast<Range*> (v.get()); os << ' ' << quote(v->field) << ' ' << quote(rval->lower) << ' ' << quote(rval->upper); break; } default: os << "unexpected type"; break; } return os; } } // namespace Mu #endif /* __DATA_HH__ */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-flags.cc���������������������������������������������������������������������������0000664�0000000�0000000�00000011545�14143670036�0015022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2011-2020 <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <string.h> #include "mu-flags.hh" using namespace Mu; struct FlagInfo { MuFlags flag; char kar; const char *name; MuFlagType flag_type; }; static const FlagInfo FLAG_INFO[] = { /* NOTE: order of this is significant, due to optimizations * below */ { MU_FLAG_DRAFT, 'D', "draft", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_FLAGGED, 'F', "flagged", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_PASSED, 'P', "passed", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_REPLIED, 'R', "replied", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_SEEN, 'S', "seen", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_TRASHED, 'T', "trashed", MU_FLAG_TYPE_MAILFILE }, { MU_FLAG_NEW, 'N', "new", MU_FLAG_TYPE_MAILDIR }, { MU_FLAG_SIGNED, 'z', "signed", MU_FLAG_TYPE_CONTENT }, { MU_FLAG_ENCRYPTED, 'x', "encrypted", MU_FLAG_TYPE_CONTENT }, { MU_FLAG_HAS_ATTACH, 'a', "attach", MU_FLAG_TYPE_CONTENT }, { MU_FLAG_LIST, 'l', "list", MU_FLAG_TYPE_CONTENT }, { MU_FLAG_UNREAD, 'u', "unread", MU_FLAG_TYPE_PSEUDO } }; MuFlagType Mu::mu_flag_type (MuFlags flag) { unsigned u; for (u = 0; u != G_N_ELEMENTS (FLAG_INFO); ++u) if (FLAG_INFO[u].flag == flag) return FLAG_INFO[u].flag_type; return MU_FLAG_TYPE_INVALID; } char Mu::mu_flag_char (MuFlags flag) { unsigned u; for (u = 0; u != G_N_ELEMENTS (FLAG_INFO); ++u) if (FLAG_INFO[u].flag == flag) return FLAG_INFO[u].kar; return 0; } MuFlags Mu::mu_flag_char_from_name (const char *str) { unsigned u; g_return_val_if_fail (str, MU_FLAG_INVALID); for (u = 0; u != G_N_ELEMENTS (FLAG_INFO); ++u) if (g_strcmp0(FLAG_INFO[u].name, str) == 0) return (MuFlags)FLAG_INFO[u].kar; return (MuFlags)0; } static MuFlags mu_flag_from_char (char kar) { unsigned u; for (u = 0; u != G_N_ELEMENTS (FLAG_INFO); ++u) if (FLAG_INFO[u].kar == kar) return FLAG_INFO[u].flag; return MU_FLAG_INVALID; } const char* Mu::mu_flag_name (MuFlags flag) { unsigned u; for (u = 0; u != G_N_ELEMENTS (FLAG_INFO); ++u) if (FLAG_INFO[u].flag == flag) return FLAG_INFO[u].name; return NULL; } const char* Mu::mu_flags_to_str_s (MuFlags flags, MuFlagType types) { unsigned u,v; static char str[sizeof(FLAG_INFO) + 1]; for (u = 0, v = 0; u != G_N_ELEMENTS(FLAG_INFO); ++u) if (flags & FLAG_INFO[u].flag && types & FLAG_INFO[u].flag_type) str[v++] = FLAG_INFO[u].kar; str[v] = '\0'; return str; } MuFlags Mu::mu_flags_from_str (const char *str, MuFlagType types, gboolean ignore_invalid) { const char *cur; MuFlags flag; g_return_val_if_fail (str, MU_FLAG_INVALID); for (cur = str, flag = MU_FLAG_NONE; *cur; ++cur) { MuFlags f; f = mu_flag_from_char (*cur); if (f == MU_FLAG_INVALID) { if (ignore_invalid) continue; return MU_FLAG_INVALID; } if (mu_flag_type (f) & types) flag |= f; } return flag; } char* Mu::mu_flags_custom_from_str (const char *str) { char *custom; const char* cur; unsigned u; g_return_val_if_fail (str, NULL); for (cur = str, u = 0, custom = NULL; *cur; ++cur) { MuFlags flag; flag = mu_flag_from_char (*cur); /* if it's a valid file flag, ignore it */ if (flag != MU_FLAG_INVALID && mu_flag_type (flag) == MU_FLAG_TYPE_MAILFILE) continue; /* otherwise, add it to our custom string */ if (!custom) custom = g_new0 (char, strlen(str) + 1); custom[u++] = *cur; } return custom; } void Mu::mu_flags_foreach (MuFlagsForeachFunc func, gpointer user_data) { unsigned u; g_return_if_fail (func); for (u = 0; u != G_N_ELEMENTS(FLAG_INFO); ++u) func (FLAG_INFO[u].flag, user_data); } MuFlags Mu::mu_flags_from_str_delta (const char *str, MuFlags oldflags, MuFlagType types) { const char *cur; MuFlags newflags; g_return_val_if_fail (str, MU_FLAG_INVALID); for (cur = str, newflags = oldflags; *cur; ++cur) { MuFlags f; if (*cur == '+' || *cur == '-') { f = mu_flag_from_char (cur[1]); if (f == 0) goto error; if (*cur == '+') newflags |= f; else newflags &= ~f; ++cur; continue; } goto error; } return newflags; error: g_warning ("invalid flag string"); return MU_FLAG_INVALID; } �����������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-flags.hh���������������������������������������������������������������������������0000664�0000000�0000000�00000011553�14143670036�0015033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_FLAGS_HH__ #define MU_FLAGS_HH__ #include <glib.h> #include <utils/mu-utils.hh> namespace Mu { enum MuFlags { MU_FLAG_NONE = 0, /* next 6 are seen in the file-info part of maildir message * file names, ie., in a name like "1234345346:2,<fileinfo>", * <fileinfo> consists of zero or more of the following * characters (in ascii order) */ MU_FLAG_DRAFT = 1 << 0, MU_FLAG_FLAGGED = 1 << 1, MU_FLAG_PASSED = 1 << 2, MU_FLAG_REPLIED = 1 << 3, MU_FLAG_SEEN = 1 << 4, MU_FLAG_TRASHED = 1 << 5, /* decides on cur/ or new/ in the maildir */ MU_FLAG_NEW = 1 << 6, /* content flags -- not visible in the filename, but used for * searching */ MU_FLAG_SIGNED = 1 << 7, MU_FLAG_ENCRYPTED = 1 << 8, MU_FLAG_HAS_ATTACH = 1 << 9, /* pseudo-flag, only for queries, so we can search for * flag:unread, which is equivalent to 'flag:new OR NOT * flag:seen' */ MU_FLAG_UNREAD = 1 << 10, /* other content flags */ MU_FLAG_LIST = 1 << 11 }; MU_ENABLE_BITOPS(MuFlags); #define MU_FLAG_INVALID ((MuFlags)-1) enum MuFlagType { MU_FLAG_TYPE_MAILFILE = 1 << 0, MU_FLAG_TYPE_MAILDIR = 1 << 1, MU_FLAG_TYPE_CONTENT = 1 << 2, MU_FLAG_TYPE_PSEUDO = 1 << 3 }; MU_ENABLE_BITOPS(MuFlagType); #define MU_FLAG_TYPE_ANY ((MuFlagType)-1) #define MU_FLAG_TYPE_INVALID ((MuFlagType)-1) /** * Get the type of flag (mailfile, maildir, pseudo or content) * * @param flag a MuFlag * * @return the flag type or MU_FLAG_TYPE_INVALID in case of error */ MuFlagType mu_flag_type (MuFlags flag) G_GNUC_CONST; /** * Get the flag character * * @param flag a MuFlag (single) * * @return the character, or 0 if it's not a valid flag */ char mu_flag_char (MuFlags flag) G_GNUC_CONST; /** * Get the flag name * * @param flag a single MuFlag * * @return the name (don't free) as string or NULL in case of error */ const char* mu_flag_name (MuFlags flag) G_GNUC_CONST; /** * Get the string representation of an OR'ed set of flags * * @param flags MuFlag (OR'ed) * @param types allowable types (OR'ed) for the result; the rest is ignored * * @return The string representation (static, don't free), or NULL in * case of error */ const char* mu_flags_to_str_s (MuFlags flags, MuFlagType types); /** * Get the (OR'ed) flags corresponding to a string representation * * @param str the file info string * @param types the flag types to accept (other will be ignored) * @param ignore invalid if TRUE, ignore invalid flags, otherwise return * MU_FLAG_INVALID if an invalid flag is encountered * * @return the (OR'ed) flags */ MuFlags mu_flags_from_str (const char *str, MuFlagType types, gboolean ignore_invalid); /** * Get the MuFlag char for some flag name * * @param str a flag name * * @return a flag character, or 0 */ MuFlags mu_flag_char_from_name (const char *str); /** * return the concatenation of all non-standard file flags in str * (ie., characters other than DFPRST) as a newly allocated string. * * @param str the file info string * * @return concatenation of all non-standard flags, as a string; free * with g_free when done. If there are no such flags, return NULL. */ char* mu_flags_custom_from_str (const char *str) G_GNUC_WARN_UNUSED_RESULT; /** * Update #oldflags with the flags in #str, where #str consists of the * the normal flag characters, but prefixed with either '+' or '-', * which means resp. "add this flag" or "remove this flag" from * oldflags. So, e.g. "-N+S" would unset the NEW flag and set the * SEEN flag, without affecting other flags. * * @param str the string representation * @param old flags to update * @param types the flag types to accept (other will be ignored) * * @return */ MuFlags mu_flags_from_str_delta (const char *str, MuFlags oldflags, MuFlagType types); typedef void (*MuFlagsForeachFunc) (MuFlags flag, gpointer user_data); /** * call a function for each available flag * * @param func a function to call * @param user_data a user pointer to pass to the function */ void mu_flags_foreach (MuFlagsForeachFunc func, gpointer user_data); } // namespace Mu #endif /*__MU_FLAGS_H__*/ �����������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-maildir.cc�������������������������������������������������������������������������0000664�0000000�0000000�00000037704�14143670036�0015354�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to 59the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <glib/gprintf.h> #include <gio/gio.h> #include "mu-maildir.hh" #include "utils/mu-str.h" using namespace Mu; #define MU_MAILDIR_NOINDEX_FILE ".noindex" #define MU_MAILDIR_NOUPDATE_FILE ".noupdate" /* On Linux (and some BSD), we have entry->d_type, but some file * systems (XFS, ReiserFS) do not support it, and set it DT_UNKNOWN. * On other OSs, notably Solaris, entry->d_type is not present at all. * For these cases, we use lstat (in get_dtype) as a slower fallback, * and return it in the d_type parameter */ static unsigned char get_dtype (struct dirent* dentry, const char *path, gboolean use_lstat) { #ifdef HAVE_STRUCT_DIRENT_D_TYPE if (dentry->d_type == DT_UNKNOWN) goto slowpath; if (dentry->d_type == DT_LNK && !use_lstat) goto slowpath; return dentry->d_type; /* fastpath */ slowpath: #endif /*HAVE_STRUCT_DIRENT_D_TYPE*/ return mu_util_get_dtype (path, use_lstat); } static gboolean create_maildir (const char *path, mode_t mode, GError **err) { int i; const char* subdirs[] = {"new", "cur", "tmp"}; for (i = 0; i != G_N_ELEMENTS(subdirs); ++i) { const char *fullpath; int rv; /* static buffer */ fullpath = mu_str_fullpath_s (path, subdirs[i]); /* if subdir already exists, don't try to re-create * it */ if (mu_util_check_dir (fullpath, TRUE, TRUE)) continue; rv = g_mkdir_with_parents (fullpath, (int)mode); /* note, g_mkdir_with_parents won't detect an error if * there's already such a dir, but with the wrong * permissions; so we need to check */ if (rv != 0 || !mu_util_check_dir(fullpath, TRUE, TRUE)) return mu_util_g_set_error (err,MU_ERROR_FILE_CANNOT_MKDIR, "creating dir failed for %s: %s", fullpath, g_strerror (errno)); } return TRUE; } static gboolean create_noindex (const char *path, GError **err) { /* create a noindex file if requested */ int fd; const char *noindexpath; /* static buffer */ noindexpath = mu_str_fullpath_s (path, MU_MAILDIR_NOINDEX_FILE); fd = creat (noindexpath, 0644); /* note, if the 'close' failed, creation may still have * succeeded...*/ if (fd < 0 || close (fd) != 0) return mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_CREATE, "error in create_noindex: %s", g_strerror (errno)); return TRUE; } gboolean Mu::mu_maildir_mkdir (const char* path, mode_t mode, gboolean noindex, GError **err) { g_return_val_if_fail (path, FALSE); if (!create_maildir (path, mode, err)) return FALSE; if (noindex && !create_noindex (path, err)) return FALSE; return TRUE; } /* determine whether the source message is in 'new' or in 'cur'; * we ignore messages in 'tmp' for obvious reasons */ static gboolean check_subdir (const char *src, gboolean *in_cur, GError **err) { gboolean rv; char *srcpath; srcpath = g_path_get_dirname (src); *in_cur = FALSE; rv = TRUE; if (g_str_has_suffix (srcpath, "cur")) *in_cur = TRUE; else if (!g_str_has_suffix (srcpath, "new")) rv = mu_util_g_set_error (err, MU_ERROR_FILE_INVALID_SOURCE, "invalid source message '%s'", src); g_free (srcpath); return rv; } static char* get_target_fullpath (const char* src, const char *targetpath, GError **err) { char *targetfullpath, *srcfile; gboolean in_cur; if (!check_subdir (src, &in_cur, err)) return NULL; srcfile = g_path_get_basename (src); /* create targetpath; note: make the filename *cough* unique * by including a hash of the srcname in the targetname. This * helps if there are copies of a message (which all have the * same basename) */ targetfullpath = g_strdup_printf ("%s%c%s%c%u_%s", targetpath, G_DIR_SEPARATOR, in_cur ? "cur" : "new", G_DIR_SEPARATOR, g_str_hash(src), srcfile); g_free (srcfile); return targetfullpath; } gboolean Mu::mu_maildir_link (const char* src, const char *targetpath, GError **err) { char *targetfullpath; int rv; g_return_val_if_fail (src, FALSE); g_return_val_if_fail (targetpath, FALSE); targetfullpath = get_target_fullpath (src, targetpath, err); if (!targetfullpath) return FALSE; rv = symlink (src, targetfullpath); if (rv != 0) mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_LINK, "error creating link %s => %s: %s", targetfullpath, src, g_strerror (errno)); g_free (targetfullpath); return rv == 0 ? TRUE: FALSE; } /* * determine if path is a maildir leaf-dir; ie. if it's 'cur' or 'new' * (we're skipping 'tmp' for obvious reasons) */ gboolean Mu::mu_maildir_is_leaf_dir (const char *path) { size_t len; /* path is the full path; it cannot possibly be shorter * than 4 for a maildir (/cur or /new) */ len = path ? strlen (path) : 0; if (G_UNLIKELY(len < 4)) return FALSE; /* optimization; one further idea would be cast the 4 bytes to an * integer and compare that -- need to think about alignment, * endianness */ if (path[len - 4] == G_DIR_SEPARATOR && path[len - 3] == 'c' && path[len - 2] == 'u' && path[len - 1] == 'r') return TRUE; if (path[len - 4] == G_DIR_SEPARATOR && path[len - 3] == 'n' && path[len - 2] == 'e' && path[len - 1] == 'w') return TRUE; return FALSE; } static gboolean clear_links (const char *path, DIR *dir) { gboolean rv; struct dirent *dentry; rv = TRUE; errno = 0; while ((dentry = readdir (dir))) { guint8 d_type; char *fullpath; if (dentry->d_name[0] == '.') continue; /* ignore .,.. other dotdirs */ fullpath = g_build_path ("/", path, dentry->d_name, NULL); d_type = get_dtype (dentry, fullpath, TRUE/*lstat*/); if (d_type == DT_LNK) { if (unlink (fullpath) != 0 ) { g_warning ("error unlinking %s: %s", fullpath, g_strerror(errno)); rv = FALSE; } } else if (d_type == DT_DIR) { DIR *subdir; subdir = opendir (fullpath); if (!subdir) { g_warning ("failed to open dir %s: %s", fullpath, g_strerror(errno)); rv = FALSE; goto next; } if (!clear_links (fullpath, subdir)) rv = FALSE; closedir (subdir); } next: g_free (fullpath); } return rv; } gboolean Mu::mu_maildir_clear_links (const char *path, GError **err) { DIR *dir; gboolean rv; g_return_val_if_fail (path, FALSE); dir = opendir (path); if (!dir) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_OPEN, "failed to open %s: %s", path, g_strerror(errno)); return FALSE; } rv = clear_links (path, dir); closedir (dir); return rv; } MuFlags Mu::mu_maildir_get_flags_from_path (const char *path) { g_return_val_if_fail (path, MU_FLAG_INVALID); /* try to find the info part */ /* note that we can use either the ':', ';', or '!' as separator; * the former is the official, but as it does not work on e.g. VFAT * file systems, some Maildir implementations use the latter instead * (or both). For example, Tinymail/modest does this. The python * documentation at http://docs.python.org/lib/mailbox-maildir.html * mentions the '!' as well as a 'popular choice'. Isync uses ';' by * default on Windows. */ /* we check the dir -- */ if (strstr (path, G_DIR_SEPARATOR_S "new" G_DIR_SEPARATOR_S)) { char *dir, *dir2; MuFlags flags; dir = g_path_get_dirname (path); dir2 = g_path_get_basename (dir); flags = MU_FLAG_NONE; if (g_strcmp0 (dir2, "new") == 0) flags = MU_FLAG_NEW; g_free (dir); g_free (dir2); /* NOTE: new/ message should not have :2,-stuff, as * per http://cr.yp.to/proto/maildir.html. If they, do * we ignore it */ if (flags == MU_FLAG_NEW) return flags; } /* get the file flags */ { const char *info; info = strrchr (path, '2'); if (!info || info == path || (info[-1] != ':' && info[-1] != '!' && info[-1] != ';') || (info[1] != ',')) return MU_FLAG_NONE; else return mu_flags_from_str (&info[2], MU_FLAG_TYPE_MAILFILE, TRUE /*ignore invalid */); } } /* * take an existing message path, and return a new path, based on * whether it should be in 'new' or 'cur'; ie. * * /home/user/Maildir/foo/bar/cur/abc:2,F and flags == MU_FLAG_NEW * => /home/user/Maildir/foo/bar/new * and * /home/user/Maildir/foo/bar/new/abc and flags == MU_FLAG_REPLIED * => /home/user/Maildir/foo/bar/cur * * so the difference is whether MU_FLAG_NEW is set or not; and in the * latter case, no other flags are allowed. * */ static char* get_new_path (const char *mdir, const char *mfile, MuFlags flags, const char* custom_flags, char flags_sep) { if (flags & MU_FLAG_NEW) return g_strdup_printf ("%s%cnew%c%s", mdir, G_DIR_SEPARATOR, G_DIR_SEPARATOR, mfile); else { const char *flagstr; flagstr = mu_flags_to_str_s (flags, MU_FLAG_TYPE_MAILFILE); return g_strdup_printf ("%s%ccur%c%s%c2,%s%s", mdir, G_DIR_SEPARATOR, G_DIR_SEPARATOR, mfile, flags_sep, flagstr, custom_flags ? custom_flags : ""); } } char* Mu::mu_maildir_get_maildir_from_path (const char* path) { char *mdir; /* determine the maildir */ mdir = g_path_get_dirname (path); if (!g_str_has_suffix (mdir, "cur") && !g_str_has_suffix (mdir, "new")) { g_warning ("%s: not a valid maildir path: %s", __func__, path); g_free (mdir); return NULL; } /* remove the 'cur' or 'new' */ mdir[strlen(mdir) - 4] = '\0'; return mdir; } static char* get_new_basename (void) { return g_strdup_printf ("%u.%08x%08x.%s", (guint)time(NULL), g_random_int(), (gint32)g_get_monotonic_time (), g_get_host_name ()); } static char* find_path_separator(const char *path) { const char *cur; for (cur = &path[strlen(path)-1]; cur > path; --cur) { if ((*cur == ':' || *cur == '!' || *cur == ';') && (cur[1] == '2' && cur[2] == ',')) { return (char*)cur; } } return NULL; } char* Mu::mu_maildir_get_new_path (const char *oldpath, const char *new_mdir, MuFlags newflags, gboolean new_name) { char *mfile, *mdir, *custom_flags, *cur, *newpath, flags_sep = ':'; g_return_val_if_fail (oldpath, NULL); mfile = newpath = custom_flags = NULL; /* determine the maildir */ mdir = mu_maildir_get_maildir_from_path (oldpath); if (!mdir) return NULL; /* determine the name of the location of the flag separator */ if (new_name) { mfile = get_new_basename (); cur = find_path_separator (oldpath); if (cur) { /* preserve the existing flags separator * in the new file name */ flags_sep = *cur; } } else { mfile = g_path_get_basename (oldpath); cur = find_path_separator (mfile); if (cur) { /* get the custom flags (if any) */ custom_flags = mu_flags_custom_from_str (cur + 3); /* preserve the existing flags separator * in the new file name */ flags_sep = *cur; cur[0] = '\0'; /* strip the flags */ } } newpath = get_new_path (new_mdir ? new_mdir : mdir, mfile, newflags, custom_flags, flags_sep); g_free (mfile); g_free (mdir); g_free (custom_flags); return newpath; } static gint64 get_file_size (const char* path) { int rv; struct stat statbuf; rv = stat (path, &statbuf); if (rv != 0) { /* g_warning ("error: %s", g_strerror (errno)); */ return -1; } return (gint64)statbuf.st_size; } static gboolean msg_move_check_pre (const char *src, const char *dst, GError **err) { gint size1, size2; if (!g_path_is_absolute(src)) return mu_util_g_set_error (err, MU_ERROR_FILE, "source is not an absolute path: '%s'", src); if (!g_path_is_absolute(dst)) return mu_util_g_set_error (err, MU_ERROR_FILE, "target is not an absolute path: '%s'", dst); if (access (src, R_OK) != 0) return mu_util_g_set_error (err, MU_ERROR_FILE, "cannot read %s", src); if (access (dst, F_OK) != 0) return TRUE; /* target exist; we simply overwrite it, unless target has a different * size. ignore the exceedingly rare case where have duplicate message * file names with different content yet the same length. (md5 etc. is a * bit slow) */ size1 = get_file_size (src); size2 = get_file_size (dst); if (size1 != size2) return mu_util_g_set_error (err, MU_ERROR_FILE, "%s already exists", dst); return TRUE; } static gboolean msg_move_check_post (const char *src, const char *dst, GError **err) { /* double check -- is the target really there? */ if (access (dst, F_OK) != 0) return mu_util_g_set_error (err, MU_ERROR_FILE, "can't find target (%s->%s)", src, dst); if (access (src, F_OK) == 0) { if (g_strcmp0(src, dst) == 0) { g_warning ("moved %s to itself", src); return TRUE; } /* this could happen if some other tool (for mail syncing) is * interfering */ g_debug ("the source is still there (%s->%s)", src, dst); } return TRUE; } /* use GIO to move files; this is slower than rename() so only use * this when needed: when moving across filesystems */ static gboolean msg_move_g_file (const char* src, const char *dst, GError **err) { GFile *srcfile, *dstfile; gboolean res; srcfile = g_file_new_for_path (src); dstfile = g_file_new_for_path (dst); res = g_file_move (srcfile, dstfile, G_FILE_COPY_NONE, NULL, NULL, NULL, err); g_clear_object (&srcfile); g_clear_object (&dstfile); return res; } static gboolean msg_move (const char* src, const char *dst, GError **err) { if (!msg_move_check_pre (src, dst, err)) return FALSE; if (rename (src, dst) == 0) /* seems it worked. */ return msg_move_check_post (src, dst, err); if (errno != EXDEV) /* some unrecoverable error occurred */ return mu_util_g_set_error (err, MU_ERROR_FILE, "error moving %s -> %s", src, dst); /* he EXDEV case -- source and target live on different filesystems */ return msg_move_g_file (src, dst, err); } char* Mu::mu_maildir_move_message (const char* oldpath, const char* targetmdir, MuFlags newflags, gboolean ignore_dups, gboolean new_name, GError **err) { char *newfullpath; gboolean rv; gboolean src_is_target; g_return_val_if_fail (oldpath, FALSE); /* first try *without* changing the name (as per new_name), since * src_is_target shouldn't use a changed name */ newfullpath = mu_maildir_get_new_path (oldpath, targetmdir, newflags, FALSE); if (!newfullpath) { mu_util_g_set_error (err, MU_ERROR_FILE, "failed to determine targetpath"); return NULL; } src_is_target = (g_strcmp0 (oldpath, newfullpath) == 0); if (!ignore_dups && src_is_target) { mu_util_g_set_error (err, MU_ERROR_FILE_TARGET_EQUALS_SOURCE, "target equals source"); return NULL; } /* if we generated file is not the same (modulo flags), create a fully * new name in the new_name case */ if (!src_is_target && new_name) { g_free(newfullpath); newfullpath = mu_maildir_get_new_path (oldpath, targetmdir, newflags, new_name); if (!newfullpath) { mu_util_g_set_error (err, MU_ERROR_FILE, "failed to determine targetpath"); return NULL; } } if (!src_is_target) { g_debug ("moving %s (%s, %x, %d) --> %s", oldpath, targetmdir, newflags, new_name, newfullpath); rv = msg_move (oldpath, newfullpath, err); if (!rv) { g_free (newfullpath); return NULL; } } return newfullpath; } ������������������������������������������������������������mu-1.6.10/lib/mu-maildir.hh�������������������������������������������������������������������������0000664�0000000�0000000�00000013735�14143670036�0015364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MAILDIR_HH__ #define MU_MAILDIR_HH__ #include <glib.h> #include <time.h> #include <sys/types.h> /* for mode_t */ #include <utils/mu-util.h> #include <mu-flags.hh> namespace Mu { /** * create a new maildir. if parts of the maildir already exists, those * will simply be ignored. IOW, if you try to create the same maildir * twice, the second will simply be a no-op (without any errors). * Note, if the function fails 'halfway', it will *not* try to remove * the parts the were created. it *will* create any parent dirs that * are not yet existent. * * * @param path the path (missing components will be created, as in 'mkdir -p') * @param mode the file mode (e.g., 0755) * @param noindex add a .noindex file to the maildir, so it will be excluded * from indexing by 'mu index' * @param err if function returns FALSE, receives error * information. err may be NULL. * * @return TRUE if creation succeeded (or already existed), FALSE otherwise */ gboolean mu_maildir_mkdir (const char* path, mode_t mode, gboolean noindex, GError **err); /** * create a symbolic link to a mail message * * @param src the full path to the source message * @param targetpath the path to the target maildir; ie., *not* * MyMaildir/cur, but just MyMaildir/. The function will figure out * the correct subdir then. * @param err if function returns FALSE, err may contain extra * information. if err is NULL, does nothing * * @return */ gboolean mu_maildir_link (const char* src, const char *targetpath, GError **err); /** * recursively delete all the symbolic links in a directory tree * * @param dir top dir * @param err if function returns FALSE, err may contain extra * information. if err is NULL, does nothing * * @return TRUE if it worked, FALSE in case of error */ gboolean mu_maildir_clear_links (const char* dir, GError **err); /** * whether the directory path ends in '/cur/' or '/new/' * * @param path some path */ gboolean mu_maildir_is_leaf_dir (const char *path); /** * get the Maildir flags from the full path of a mailfile. The flags * are as specified in http://cr.yp.to/proto/maildir.html, plus * MU_MSG_FLAG_NEW for new messages, ie the ones that live in * new/. The flags are logically OR'ed. Note that the file does not * have to exist; the flags are based on the path only. * * @param pathname of a mailfile; it does not have to refer to an * actual message * * @return the flags, or MU_MSG_FILE_FLAG_UNKNOWN in case of error */ MuFlags mu_maildir_get_flags_from_path (const char* pathname); /** * get the new pathname for a message, based on the old path and the * new flags and (optionally) a new maildir. Note that * setting/removing the MU_FLAG_NEW will change the directory in which * a message lives. The flags are as specified in * http://cr.yp.to/proto/maildir.html, plus MU_FLAG_NEW for new * messages, ie the ones that live in new/. The flags are logically * OR'ed. Note that the file does not have to exist; the flags are * based on the path only. * * * @param oldpath the old (current) full path to the message * (including the filename) * @param new_mdir the new maildir for this message, or NULL to keep * it in the current one. The maildir is the absolute file system * path, without the 'cur' or 'new' * @param new_flags the new flags for this message * @param new_name whether to create a new unique name, or keep the * old one * * @return a new path name; use g_free when done with. NULL in case of * error. */ char* mu_maildir_get_new_path (const char *oldpath, const char *new_mdir, MuFlags new_flags, gboolean new_name) G_GNUC_WARN_UNUSED_RESULT; /** * get the maildir for a certain message path, ie, the path *before* * cur/ or new/ * * @param path path for some message * * @return the maildir (free with g_free), or NULL in case of error */ char* mu_maildir_get_maildir_from_path (const char* path) G_GNUC_WARN_UNUSED_RESULT; /** * move a message file to another maildir; the function returns the full path to * the new message. if the target file already exists, it is overwritten. * * @param msgpath an absolute file system path to an existing message in an * actual maildir * @param targetmdir the target maildir; note that this the base-level * Maildir, ie. /home/user/Maildir/archive, and must _not_ include the * 'cur' or 'new' part. Note that the target maildir must be on the * same filesystem. If you specify NULL for targetmdir, only the flags * of the message are affected; note that this may still involve a * moved to another directory (say, from new/ to cur/) * @param flags to set for the target (influences the filename, path) * @param ignore_dups whether to silently ignore the src=target case * (and return TRUE) * @param new_name whether to create a new unique name, or keep the * old one * @param err receives error information * * @return return the full path name of the target file (g_free) if * the move succeeded, NULL otherwise */ char* mu_maildir_move_message (const char* oldpath, const char* targetmdir, MuFlags newflags, gboolean ignore_dups, gboolean new_name, GError **err) G_GNUC_WARN_UNUSED_RESULT; } // namespace Mu #endif /*MU_MAILDIR_HH__*/ �����������������������������������mu-1.6.10/lib/mu-msg-crypto.cc����������������������������������������������������������������������0000664�0000000�0000000�00000024143�14143670036�0016030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <string.h> #include "mu-msg.hh" #include "mu-msg-priv.hh" #include "mu-msg-part.hh" #include "utils/mu-date.h" #include <gmime/gmime.h> #include <gmime/gmime-multipart-signed.h> using namespace Mu; static const char* get_pubkey_algo_name (GMimePubKeyAlgo algo) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (algo) { case GMIME_PUBKEY_ALGO_DEFAULT: return "default"; case GMIME_PUBKEY_ALGO_RSA: return "RSA"; case GMIME_PUBKEY_ALGO_RSA_E: return "RSA (encryption only)"; case GMIME_PUBKEY_ALGO_RSA_S: return "RSA (signing only)"; case GMIME_PUBKEY_ALGO_ELG_E: return "ElGamal (encryption only)"; case GMIME_PUBKEY_ALGO_DSA: return "DSA"; case GMIME_PUBKEY_ALGO_ELG: return "ElGamal"; default: return "unknown pubkey algorithm"; } #pragma GCC diagnostic pop } static const gchar* get_digestkey_algo_name (GMimeDigestAlgo algo) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (algo) { case GMIME_DIGEST_ALGO_DEFAULT: return "default"; case GMIME_DIGEST_ALGO_MD5: return "MD5"; case GMIME_DIGEST_ALGO_SHA1: return "SHA-1"; case GMIME_DIGEST_ALGO_RIPEMD160: return "RIPEMD160"; case GMIME_DIGEST_ALGO_MD2: return "MD2"; case GMIME_DIGEST_ALGO_TIGER192: return "TIGER-192"; case GMIME_DIGEST_ALGO_HAVAL5160: return "HAVAL-5-160"; case GMIME_DIGEST_ALGO_SHA256: return "SHA-256"; case GMIME_DIGEST_ALGO_SHA384: return "SHA-384"; case GMIME_DIGEST_ALGO_SHA512: return "SHA-512"; case GMIME_DIGEST_ALGO_SHA224: return "SHA-224"; case GMIME_DIGEST_ALGO_MD4: return "MD4"; default: return "unknown digest algorithm"; } #pragma GCC diagnostic pop } /* get data from the 'certificate' */ static char* get_cert_data (GMimeCertificate *cert) { const char /**email,*/ *name, *digest_algo, *pubkey_algo, *keyid, *trust; /* email = g_mime_certificate_get_email (cert); */ name = g_mime_certificate_get_name (cert); keyid = g_mime_certificate_get_key_id (cert); digest_algo = get_digestkey_algo_name (g_mime_certificate_get_digest_algo (cert)); pubkey_algo = get_pubkey_algo_name (g_mime_certificate_get_pubkey_algo (cert)); switch (g_mime_certificate_get_trust (cert)) { case GMIME_TRUST_UNKNOWN: trust = "unknown"; break; case GMIME_TRUST_UNDEFINED: trust = "undefined"; break; case GMIME_TRUST_NEVER: trust = "never"; break; case GMIME_TRUST_MARGINAL: trust = "marginal"; break; case GMIME_TRUST_FULL: trust = "full"; break; case GMIME_TRUST_ULTIMATE: trust = "ultimate"; break; default: g_return_val_if_reached (NULL); } return g_strdup_printf ( "signer:%s, key:%s (%s,%s), trust:%s", name ? name : "?", /* email ? email : "?", */ keyid, pubkey_algo, digest_algo, trust); } static char* get_signature_status (GMimeSignatureStatus status) { size_t n; GString *descr; struct { GMimeSignatureStatus status; const char *name; } status_info[] = { { GMIME_SIGNATURE_STATUS_VALID, "valid" }, { GMIME_SIGNATURE_STATUS_GREEN, "green" }, { GMIME_SIGNATURE_STATUS_RED, "red" }, { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key revoked" }, { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key expired" }, { GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "signature expired" }, { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key missing" }, { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl missing" }, { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl too old" }, { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad policy" }, { GMIME_SIGNATURE_STATUS_SYS_ERROR, "system error" }, { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu conflict " }, }; descr = g_string_new(""); for (n = 0; n != G_N_ELEMENTS(status_info); ++n) { if (!(status & status_info[n].status)) continue; g_string_append_printf (descr, "%s%s", descr->len > 0 ? ", " : "", status_info[n].name); } return g_string_free (descr, FALSE); } /* get a human-readable report about the signature */ static char* get_verdict_report (GMimeSignature *msig) { time_t t; const char *created, *expires; gchar *certdata, *report, *status; GMimeSignatureStatus sigstat; sigstat = g_mime_signature_get_status (msig); status = get_signature_status(sigstat); t = g_mime_signature_get_created (msig); created = (t == 0 || t == (time_t)-1) ? "?" : mu_date_str_s ("%x", t); t = g_mime_signature_get_expires (msig); expires = (t == 0 || t == (time_t)-1) ? "?" : mu_date_str_s ("%x", t); certdata = get_cert_data (g_mime_signature_get_certificate (msig)); report = g_strdup_printf ("%s; created:%s, expires:%s, %s", status, created, expires, certdata ? certdata : "?"); g_free (certdata); g_free (status); return report; } static char* get_signers (GHashTable *signerhash) { GString *gstr; GHashTableIter iter; char *name; if (!signerhash || g_hash_table_size(signerhash) == 0) return NULL; gstr = g_string_new (NULL); g_hash_table_iter_init (&iter, signerhash); while (g_hash_table_iter_next (&iter, reinterpret_cast<void**>(&name), NULL)) { if (gstr->len != 0) g_string_append_c (gstr, ','); gstr = g_string_append (gstr, name); } return g_string_free (gstr, FALSE); } static MuMsgPartSigStatusReport* get_status_report (GMimeSignatureList *sigs) { int i; MuMsgPartSigStatus status; MuMsgPartSigStatusReport *status_report; char *report; GHashTable *signerhash; status = MU_MSG_PART_SIG_STATUS_GOOD; /* let's start positive! */ signerhash = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0, report = NULL; i != g_mime_signature_list_length (sigs); ++i) { GMimeSignature *msig; GMimeCertificate *cert; GMimeSignatureStatus sigstat; gchar *rep; msig = g_mime_signature_list_get_signature (sigs, i); sigstat = g_mime_signature_get_status (msig); /* downgrade our expectations */ if ((sigstat & GMIME_SIGNATURE_STATUS_ERROR_MASK) && status != MU_MSG_PART_SIG_STATUS_ERROR) status = MU_MSG_PART_SIG_STATUS_ERROR; else if ((sigstat & GMIME_SIGNATURE_STATUS_RED) && status == MU_MSG_PART_SIG_STATUS_GOOD) status = MU_MSG_PART_SIG_STATUS_BAD; rep = get_verdict_report (msig); report = g_strdup_printf ("%s%s%d: %s", report ? report : "", report ? "; " : "", i + 1, rep); g_free (rep); cert = g_mime_signature_get_certificate (msig); if (cert && g_mime_certificate_get_name (cert)) g_hash_table_add ( signerhash, (gpointer)g_mime_certificate_get_name (cert)); } status_report = g_slice_new0 (MuMsgPartSigStatusReport); status_report->verdict = status; status_report->report = report; status_report->signers = get_signers(signerhash); g_hash_table_unref (signerhash); return status_report; } void Mu::mu_msg_part_sig_status_report_destroy (MuMsgPartSigStatusReport *report) { if (!report) return; g_free ((char*)report->report); g_free ((char*)report->signers); g_slice_free (MuMsgPartSigStatusReport, report); } static inline void tag_with_sig_status(GObject *part, MuMsgPartSigStatusReport *report) { g_object_set_data_full (part, SIG_STATUS_REPORT, report, (GDestroyNotify)mu_msg_part_sig_status_report_destroy); } void Mu::mu_msg_crypto_verify_part (GMimeMultipartSigned *sig, MuMsgOptions opts, GError **err) { /* the signature status */ MuMsgPartSigStatusReport *report; GMimeSignatureList *sigs; g_return_if_fail (GMIME_IS_MULTIPART_SIGNED(sig)); sigs = g_mime_multipart_signed_verify (sig, GMIME_VERIFY_NONE, err); if (!sigs) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "verification failed"); return; } report = get_status_report (sigs); g_clear_object (&sigs); /* tag this part with the signature status check */ tag_with_sig_status(G_OBJECT(sig), report); } static inline void check_decrypt_result(GMimeMultipartEncrypted *part, GMimeDecryptResult *res, GError **err) { GMimeSignatureList *sigs; MuMsgPartSigStatusReport *report; if (res) { /* Check if the decrypted part had any embed signatures */ sigs = res->signatures; if (sigs) { report = get_status_report (sigs); g_mime_signature_list_clear (sigs); /* tag this part with the signature status check */ tag_with_sig_status(G_OBJECT(part), report); } else { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "verification failed"); } g_object_unref (res); } } GMimeObject* /* this is declared in mu-msg-priv.h */ Mu::mu_msg_crypto_decrypt_part (GMimeMultipartEncrypted *enc, MuMsgOptions opts, MuMsgPartPasswordFunc func, gpointer user_data, GError **err) { GMimeObject *dec; GMimeDecryptResult *res; g_return_val_if_fail (GMIME_IS_MULTIPART_ENCRYPTED(enc), NULL); res = NULL; dec = g_mime_multipart_encrypted_decrypt (enc, GMIME_DECRYPT_NONE, NULL, &res, err); check_decrypt_result(enc, res, err); if (!dec) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "decryption failed"); return NULL; } return dec; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-doc.cc�������������������������������������������������������������������������0000664�0000000�0000000�00000006272�14143670036�0015260�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <stdlib.h> #include <iostream> #include <string.h> #include <errno.h> #include <xapian.h> #include "mu-msg-fields.h" #include "mu-msg-doc.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" #include "utils/mu-date.h" #include "utils/mu-utils.hh" using namespace Mu; struct Mu::MuMsgDoc { MuMsgDoc (Xapian::Document *doc): _doc (doc) { } ~MuMsgDoc () { delete _doc; } const Xapian::Document doc() const { return *_doc; } private: Xapian::Document *_doc; }; MuMsgDoc* Mu::mu_msg_doc_new (XapianDocument *doc, GError **err) { g_return_val_if_fail (doc, NULL); try { return new MuMsgDoc ((Xapian::Document*)doc); } MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN, NULL); return FALSE; } void Mu::mu_msg_doc_destroy (MuMsgDoc *self) { try { delete self; } MU_XAPIAN_CATCH_BLOCK; } gchar* Mu::mu_msg_doc_get_str_field (MuMsgDoc *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), NULL); // disable this check: // g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL); // because it's useful to get numerical field as strings, // for example when sorting (which is much faster if don't // have to convert to numbers first, esp. when it's a date // time_t) try { const std::string s (self->doc().get_value(mfid)); return s.empty() ? NULL : g_strdup (s.c_str()); } MU_XAPIAN_CATCH_BLOCK_RETURN(NULL); } GSList* Mu::mu_msg_doc_get_str_list_field (MuMsgDoc *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), NULL); g_return_val_if_fail (mu_msg_field_is_string_list(mfid), NULL); try { /* return a comma-separated string as a GSList */ const std::string s (self->doc().get_value(mfid)); return s.empty() ? NULL : mu_str_to_list(s.c_str(),',',TRUE); } MU_XAPIAN_CATCH_BLOCK_RETURN(NULL); } gint64 Mu::mu_msg_doc_get_num_field (MuMsgDoc *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, -1); g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), -1); g_return_val_if_fail (mu_msg_field_is_numeric(mfid), -1); try { const std::string s (self->doc().get_value(mfid)); if (s.empty()) return 0; else if (mfid == MU_MSG_FIELD_ID_DATE || mfid == MU_MSG_FIELD_ID_SIZE) return strtol (s.c_str(), NULL, 10); else { return static_cast<gint64> (Xapian::sortable_unserialise(s)); } } MU_XAPIAN_CATCH_BLOCK_RETURN(-1); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-doc.hh�������������������������������������������������������������������������0000664�0000000�0000000�00000005417�14143670036�0015272�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MSG_DOC_HH__ #define MU_MSG_DOC_HH__ #include <glib.h> #include <utils/mu-util.h> namespace Mu { struct MuMsgDoc; /** * create a new MuMsgDoc instance * * @param doc a Xapian::Document* (you'll need to cast the * Xapian::Document* to XapianDocument*, because only C (not C++) is * allowed in this header file. MuMsgDoc takes _ownership_ of this pointer; * don't touch it afterwards * @param err receives error info, or NULL * * @return a new MuMsgDoc instance (free with mu_msg_doc_destroy), or * NULL in case of error. */ MuMsgDoc* mu_msg_doc_new (XapianDocument *doc, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * destroy a MuMsgDoc instance -- free all the resources. Note, after * destroying, any strings returned from mu_msg_doc_get_str_field with * do_free==FALSE are no longer valid * * @param self a MuMsgDoc instance */ void mu_msg_doc_destroy (MuMsgDoc *self); /** * get a string parameter from the msgdoc * * @param self a MuMsgDoc instance * @param mfid a MuMsgFieldId for a string field * * @return a string for the given field (see do_free), or NULL in case of error. * free with g_free */ gchar* mu_msg_doc_get_str_field (MuMsgDoc *self, MuMsgFieldId mfid) G_GNUC_WARN_UNUSED_RESULT; /** * get a string-list parameter from the msgdoc * * @param self a MuMsgDoc instance * @param mfid a MuMsgFieldId for a string-list field * * @return a list for the given field (see do_free), or NULL in case * of error. free with mu_str_free_list */ GSList* mu_msg_doc_get_str_list_field (MuMsgDoc *self, MuMsgFieldId mfid) G_GNUC_WARN_UNUSED_RESULT; /** * * get a numeric parameter from the msgdoc * * @param self a MuMsgDoc instance * @param mfid a MuMsgFieldId for a numeric field * * @return the numerical value, or -1 in case of error. You'll need to * cast this value to the actual type (e.g. time_t for MU_MSG_FIELD_ID_DATE) */ gint64 mu_msg_doc_get_num_field (MuMsgDoc *self, MuMsgFieldId mfid); } // namespace Mu #endif /*MU_MSG_DOC_HH__*/ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-fields.c�����������������������������������������������������������������������0000664�0000000�0000000�00000023323�14143670036�0015612�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2008-2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <string.h> #include "mu-msg-fields.h" /* * note: the differences for our purposes between a xapian field and a * term: - there is only a single value for some item in per document * (msg), ie. one value containing the list of To: addresses - there * can be multiple terms, each containing e.g. one of the To: * addresses - searching uses terms, but to display some field, it * must be in the value (at least when using MuMsgIter) */ enum _FieldFlags { FLAG_GMIME = 1 << 0, /* field retrieved through * gmime */ FLAG_XAPIAN_INDEX = 1 << 1, /* field is indexed in * xapian (i.e., the text * is processed */ FLAG_XAPIAN_TERM = 1 << 2, /* field stored as term in * xapian (so it can be searched) */ FLAG_XAPIAN_VALUE = 1 << 3, /* field stored as value in * xapian (so the literal * value can be * retrieved) */ FLAG_XAPIAN_CONTACT = 1 << 4, /* field contains one or more * e-mail-addresses */ FLAG_XAPIAN_BOOLEAN = 1 << 5, /* use 'add_boolean_prefix' * for Xapian queries; * wildcards do NOT WORK * for such fields */ FLAG_DONT_CACHE = 1 << 6, /* don't cache this field in * the MuMsg cache */ FLAG_RANGE_FIELD = 1 << 7 /* whether this is a range field */ }; typedef enum _FieldFlags FieldFlags; /* * this struct describes the fields of an e-mail /*/ struct _MuMsgField { MuMsgFieldId _id; /* the id of the field */ MuMsgFieldType _type; /* the type of the field */ const char *_name; /* the name of the field */ const char _shortcut; /* the shortcut for use in * --fields and sorting */ const char _xprefix; /* the Xapian-prefix */ FieldFlags _flags; /* the flags that tells us * what to do */ }; typedef struct _MuMsgField MuMsgField; /* the name and shortcut fields must be lower case, or they might be * misinterpreted by the query-preprocesser which turns queries into * lowercase */ static const MuMsgField FIELD_DATA[] = { { MU_MSG_FIELD_ID_BCC, MU_MSG_FIELD_TYPE_STRING, "bcc" , 'h', 'H', /* 'hidden */ FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_BODY_TEXT, MU_MSG_FIELD_TYPE_STRING, "body", 'b', 'B', FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_DONT_CACHE }, { MU_MSG_FIELD_ID_BODY_HTML, MU_MSG_FIELD_TYPE_STRING, "bodyhtml", 0, 0, FLAG_GMIME | FLAG_DONT_CACHE }, { MU_MSG_FIELD_ID_CC, MU_MSG_FIELD_TYPE_STRING, "cc", 'c', 'C', FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_DATE, MU_MSG_FIELD_TYPE_TIME_T, "date", 'd', 'D', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE | FLAG_XAPIAN_BOOLEAN | FLAG_RANGE_FIELD }, { MU_MSG_FIELD_ID_EMBEDDED_TEXT, MU_MSG_FIELD_TYPE_STRING, "embed", 'e', 'E', FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_DONT_CACHE }, { MU_MSG_FIELD_ID_FILE, MU_MSG_FIELD_TYPE_STRING, "file" , 'j', 'J', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_DONT_CACHE }, { MU_MSG_FIELD_ID_FLAGS, MU_MSG_FIELD_TYPE_INT, "flag", 'g', 'G', /* flaGs */ FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_FROM, MU_MSG_FIELD_TYPE_STRING, "from", 'f', 'F', FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_MAILDIR, MU_MSG_FIELD_TYPE_STRING, "maildir", 'm', 'M', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_MAILING_LIST, MU_MSG_FIELD_TYPE_STRING, "list", 'v', 'V', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_MIME, MU_MSG_FIELD_TYPE_STRING, "mime" , 'y', 'Y', FLAG_XAPIAN_TERM }, { MU_MSG_FIELD_ID_MSGID, MU_MSG_FIELD_TYPE_STRING, "msgid", 'i', 'I', /* 'i' for Id */ FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_PATH, MU_MSG_FIELD_TYPE_STRING, "path", 'l', 'L', /* 'l' for location */ FLAG_GMIME | FLAG_XAPIAN_VALUE | FLAG_XAPIAN_BOOLEAN }, { MU_MSG_FIELD_ID_PRIO, MU_MSG_FIELD_TYPE_INT, "prio", 'p', 'P', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_REFS, MU_MSG_FIELD_TYPE_STRING_LIST, "refs", 'r', 'R', FLAG_GMIME | FLAG_XAPIAN_VALUE }, { MU_MSG_FIELD_ID_SIZE, MU_MSG_FIELD_TYPE_BYTESIZE, "size", 'z', 'Z', /* siZe */ FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE | FLAG_RANGE_FIELD }, { MU_MSG_FIELD_ID_SUBJECT, MU_MSG_FIELD_TYPE_STRING, "subject", 's', 'S', FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_XAPIAN_VALUE | FLAG_XAPIAN_TERM }, { MU_MSG_FIELD_ID_TAGS, MU_MSG_FIELD_TYPE_STRING_LIST, "tag", 'x', 'X', FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE }, { /* remember which thread this message is in */ MU_MSG_FIELD_ID_THREAD_ID, MU_MSG_FIELD_TYPE_STRING, "thread", 0, 'W', FLAG_XAPIAN_TERM }, { MU_MSG_FIELD_ID_TO, MU_MSG_FIELD_TYPE_STRING, "to", 't', 'T', FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE }, { /* special, internal field, to get a unique key */ MU_MSG_FIELD_ID_UID, MU_MSG_FIELD_TYPE_STRING, "uid", 0, 'U', FLAG_XAPIAN_TERM } /* note, mu-store also use the 'Q' internal prefix for its uids */ }; /* the MsgField data in an array, indexed by the MsgFieldId; * this allows for O(1) access */ static MuMsgField* _msg_field_data[MU_MSG_FIELD_ID_NUM]; static const MuMsgField* mu_msg_field (MuMsgFieldId id) { static gboolean _initialized = FALSE; /* initialize the array, but only once... */ if (G_UNLIKELY(!_initialized)) { int i; for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i) _msg_field_data[FIELD_DATA[i]._id] = (MuMsgField*)&FIELD_DATA[i]; _initialized = TRUE; } return _msg_field_data[id]; } void mu_msg_field_foreach (MuMsgFieldForeachFunc func, gconstpointer data) { int i; for (i = 0; i != MU_MSG_FIELD_ID_NUM; ++i) func (i, data); } MuMsgFieldId mu_msg_field_id_from_name (const char* str, gboolean err) { int i; g_return_val_if_fail (str, MU_MSG_FIELD_ID_NONE); for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i) if (g_strcmp0(str, FIELD_DATA[i]._name) == 0) return FIELD_DATA[i]._id; if (err) g_return_val_if_reached (MU_MSG_FIELD_ID_NONE); return MU_MSG_FIELD_ID_NONE; } MuMsgFieldId mu_msg_field_id_from_shortcut (char kar, gboolean err) { int i; for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i) if (kar == FIELD_DATA[i]._shortcut) return FIELD_DATA[i]._id; if (err) g_return_val_if_reached (MU_MSG_FIELD_ID_NONE); return MU_MSG_FIELD_ID_NONE; } gboolean mu_msg_field_gmime (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_GMIME ? TRUE: FALSE; } gboolean mu_msg_field_xapian_index (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & (FLAG_XAPIAN_INDEX | FLAG_XAPIAN_CONTACT) ? TRUE: FALSE; } gboolean mu_msg_field_xapian_value (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_XAPIAN_VALUE ? TRUE: FALSE; } gboolean mu_msg_field_xapian_term (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_XAPIAN_TERM ? TRUE: FALSE; } gboolean mu_msg_field_is_range_field (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_RANGE_FIELD ? TRUE: FALSE; } gboolean mu_msg_field_uses_boolean_prefix (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_XAPIAN_BOOLEAN ? TRUE:FALSE; } gboolean mu_msg_field_is_cacheable (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); /* note the FALSE: TRUE */ return mu_msg_field(id)->_flags & FLAG_DONT_CACHE ? FALSE : TRUE; } gboolean mu_msg_field_xapian_contact (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE); return mu_msg_field(id)->_flags & FLAG_XAPIAN_CONTACT ? TRUE: FALSE; } gboolean mu_msg_field_is_numeric (MuMsgFieldId mfid) { MuMsgFieldType type; g_return_val_if_fail (mu_msg_field_id_is_valid(mfid),FALSE); type = mu_msg_field_type (mfid); return type == MU_MSG_FIELD_TYPE_BYTESIZE || type == MU_MSG_FIELD_TYPE_TIME_T || type == MU_MSG_FIELD_TYPE_INT; } const char* mu_msg_field_name (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),NULL); return mu_msg_field(id)->_name; } char mu_msg_field_shortcut (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),0); return mu_msg_field(id)->_shortcut; } char mu_msg_field_xapian_prefix (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id),0); return mu_msg_field(id)->_xprefix; } MuMsgFieldType mu_msg_field_type (MuMsgFieldId id) { g_return_val_if_fail (mu_msg_field_id_is_valid(id), MU_MSG_FIELD_TYPE_NONE); return mu_msg_field(id)->_type; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-fields.h�����������������������������������������������������������������������0000664�0000000�0000000�00000017456�14143670036�0015631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_MSG_FIELDS_H__ #define __MU_MSG_FIELDS_H__ #include <glib.h> G_BEGIN_DECLS /* don't change the order, add new types at the end, as these numbers * are used in the database */ enum _MuMsgFieldId { /* first all the string-based ones */ MU_MSG_FIELD_ID_BCC = 0, MU_MSG_FIELD_ID_BODY_HTML, MU_MSG_FIELD_ID_BODY_TEXT, MU_MSG_FIELD_ID_CC, MU_MSG_FIELD_ID_EMBEDDED_TEXT, MU_MSG_FIELD_ID_FILE, MU_MSG_FIELD_ID_FROM, MU_MSG_FIELD_ID_MAILDIR, MU_MSG_FIELD_ID_MIME, /* mime-type */ MU_MSG_FIELD_ID_MSGID, MU_MSG_FIELD_ID_PATH, MU_MSG_FIELD_ID_SUBJECT, MU_MSG_FIELD_ID_TO, MU_MSG_FIELD_ID_UID, /* special, generated from path */ /* string list items... */ MU_MSG_FIELD_ID_REFS, MU_MSG_FIELD_ID_TAGS, /* then the numerical ones */ MU_MSG_FIELD_ID_DATE, MU_MSG_FIELD_ID_FLAGS, MU_MSG_FIELD_ID_PRIO, MU_MSG_FIELD_ID_SIZE, /* add new ones here... */ MU_MSG_FIELD_ID_MAILING_LIST, /* mailing list */ MU_MSG_FIELD_ID_THREAD_ID, MU_MSG_FIELD_ID_NUM }; typedef guint8 MuMsgFieldId; /* some specials... */ static const MuMsgFieldId MU_MSG_FIELD_ID_NONE = (MuMsgFieldId)-1; #define MU_MSG_STRING_FIELD_ID_NUM (MU_MSG_FIELD_ID_UID + 1) /* this is a shortcut for To/From/Cc/Bcc in queries; handled specially * in mu-query.cc and mu-str.c */ #define MU_MSG_FIELD_PSEUDO_CONTACT "contact" /* this is a shortcut for To/Cc/Bcc in queries; handled specially in * mu-query.cc and mu-str.c */ #define MU_MSG_FIELD_PSEUDO_RECIP "recip" #define mu_msg_field_id_is_valid(MFID) \ ((MFID) < MU_MSG_FIELD_ID_NUM) /* don't change the order, add new types at the end (before _NUM)*/ enum _MuMsgFieldType { MU_MSG_FIELD_TYPE_STRING, MU_MSG_FIELD_TYPE_STRING_LIST, MU_MSG_FIELD_TYPE_BYTESIZE, MU_MSG_FIELD_TYPE_TIME_T, MU_MSG_FIELD_TYPE_INT, MU_MSG_FIELD_TYPE_NUM }; typedef guint8 MuMsgFieldType; static const MuMsgFieldType MU_MSG_FIELD_TYPE_NONE = (MuMsgFieldType)-1; typedef void (*MuMsgFieldForeachFunc) (MuMsgFieldId id, gconstpointer data); /** * iterator over all possible message fields * * @param func a function called for each field * @param data a user data pointer passed the callback function */ void mu_msg_field_foreach (MuMsgFieldForeachFunc func, gconstpointer data); /** * get the name of the field -- this a name that can be use in queries, * ie. 'subject:foo', with 'subject' being the name * * @param id a MuMsgFieldId * * @return the name of the field as a constant string, or * NULL if the field is unknown */ const char* mu_msg_field_name (MuMsgFieldId id) G_GNUC_PURE; /** * get the shortcut of the field -- this a shortcut that can be use in * queries, ie. 's:foo', with 's' meaning 'subject' being the name * * @param id a MuMsgFieldId * * @return the shortcut character, or 0 if the field is unknown */ char mu_msg_field_shortcut (MuMsgFieldId id) G_GNUC_PURE; /** * get the xapian prefix of the field -- that is, the prefix used in * the Xapian database to identify the field * * @param id a MuMsgFieldId * * @return the xapian prefix char or 0 if the field is unknown */ char mu_msg_field_xapian_prefix (MuMsgFieldId id) G_GNUC_PURE; /** * get the type of the field (string, size, time etc.) * * @param field a MuMsgField * * @return the type of the field (a #MuMsgFieldType), or * MU_MSG_FIELD_TYPE_NONE if it is not found */ MuMsgFieldType mu_msg_field_type (MuMsgFieldId id) G_GNUC_PURE; /** * is the field a string? * * @param id a MuMsgFieldId * * @return TRUE if the field a string, FALSE otherwise */ #define mu_msg_field_is_string(MFID)\ (mu_msg_field_type((MFID))==MU_MSG_FIELD_TYPE_STRING?TRUE:FALSE) /** * is the field a string-list? * * @param id a MuMsgFieldId * * @return TRUE if the field a string-list, FALSE otherwise */ #define mu_msg_field_is_string_list(MFID)\ (mu_msg_field_type((MFID))==MU_MSG_FIELD_TYPE_STRING_LIST?TRUE:FALSE) /** * is the field numeric (has type MU_MSG_FIELD_TYPE_(BYTESIZE|TIME_T|INT))? * * @param id a MuMsgFieldId * * @return TRUE if the field is numeric, FALSE otherwise */ gboolean mu_msg_field_is_numeric (MuMsgFieldId id) G_GNUC_PURE; /** * whether the field value should be cached (in MuMsg) -- we cache * values so we can use the MuMsg without needing to keep the * underlying data source (the GMimeMessage or the database ptr) alive * in practice, the fields we *don't* cache are the message body * (html, txt), because they take too much memory */ gboolean mu_msg_field_is_cacheable (MuMsgFieldId id) G_GNUC_PURE; /** * is the field Xapian-indexable? That is, should this field be * indexed in the Xapian database, so we can use the all the * phrasing, stemming etc. magic * * @param id a MuMsgFieldId * * @return TRUE if the field is Xapian-enabled, FALSE otherwise */ gboolean mu_msg_field_xapian_index (MuMsgFieldId id) G_GNUC_PURE; /** * should this field be stored as a xapian term? * * @param id a MuMsgFieldId * * @return TRUE if the field is Xapian-enabled, FALSE otherwise */ gboolean mu_msg_field_xapian_term (MuMsgFieldId id) G_GNUC_PURE; /** * should this field be stored as a xapian value? * * @param field a MuMsgField * * @return TRUE if the field is Xapian-enabled, FALSE otherwise */ gboolean mu_msg_field_xapian_value (MuMsgFieldId id) G_GNUC_PURE; /** * whether we should use add_boolean_prefix (see Xapian documentation) * for this field in queries. Used in mu-query.cc * * @param id a MuMsgFieldId * * @return TRUE if this field wants add_boolean_prefix, FALSE * otherwise */ gboolean mu_msg_field_uses_boolean_prefix (MuMsgFieldId id) G_GNUC_PURE; /** * is this a range-field? ie. date, or size * * @param id a MuMsgField * * @return TRUE if this field is a range field, FALSE otherwise */ gboolean mu_msg_field_is_range_field (MuMsgFieldId id) G_GNUC_PURE; /** * should this field be stored as contact information? This means that * e-mail address will be stored as terms, and names will be indexed * * @param id a MuMsgFieldId * * @return TRUE if the field should be stored as contact information, * FALSE otherwise */ gboolean mu_msg_field_xapian_contact (MuMsgFieldId id) G_GNUC_PURE; /** * is the field gmime-enabled? That is, can be field be retrieved * using GMime? * * @param id a MuMsgFieldId * * @return TRUE if the field is Gmime-enabled, FALSE otherwise */ gboolean mu_msg_field_gmime (MuMsgFieldId id) G_GNUC_PURE; /** * get the corresponding MuMsgField for a name (as in mu_msg_field_name) * * @param str a name * @param err, if TRUE, when the shortcut is not found, will issue a * g_critical warning * * @return a MuMsgField, or NULL if it could not be found */ MuMsgFieldId mu_msg_field_id_from_name (const char* str, gboolean err) G_GNUC_PURE; /** * get the corresponding MuMsgField for a shortcut (as in mu_msg_field_shortcut) * * @param kar a shortcut character * @param err, if TRUE, when the shortcut is not found, will issue a * g_critical warning * * @return a MuMsgField, or NULL if it could not be found */ MuMsgFieldId mu_msg_field_id_from_shortcut (char kar, gboolean err) G_GNUC_PURE; G_END_DECLS #endif /*__MU_MSG_FIELDS_H__*/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-file.cc������������������������������������������������������������������������0000664�0000000�0000000�00000046303�14143670036�0015431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <array> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <ctype.h> #include <inttypes.h> #include <gmime/gmime.h> #include "mu-maildir.hh" #include "mu-store.hh" #include "mu-msg-priv.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" using namespace Mu; static gboolean init_file_metadata (MuMsgFile *self, const char* path, const char *mdir, GError **err); static gboolean init_mime_msg (MuMsgFile *msg, const char *path, GError **err); MuMsgFile* Mu::mu_msg_file_new (const char* filepath, const char *mdir, GError **err) { MuMsgFile *self; g_return_val_if_fail (filepath, NULL); self = g_new0(MuMsgFile, 1); if (!init_file_metadata (self, filepath, mdir, err)) { mu_msg_file_destroy (self); return NULL; } if (!init_mime_msg (self, filepath, err)) { mu_msg_file_destroy (self); return NULL; } return self; } void Mu::mu_msg_file_destroy (MuMsgFile *self) { if (!self) return; g_clear_object(&self->_mime_msg); g_free(self->_path); g_free(self->_maildir); g_free(self->_sha1); g_free (self); } static gboolean init_file_metadata (MuMsgFile *self, const char* path, const gchar* mdir, GError **err) { struct stat statbuf; if (!g_path_is_absolute (path)) { mu_util_g_set_error (err, MU_ERROR_FILE, "path '%s' is not absolute", path); return FALSE; } if (access (path, R_OK) != 0) { mu_util_g_set_error (err, MU_ERROR_FILE, "cannot read file %s: %s", path, g_strerror(errno)); return FALSE; } if (stat (path, &statbuf) < 0) { mu_util_g_set_error (err, MU_ERROR_FILE, "cannot stat %s: %s", path, g_strerror(errno)); return FALSE; } if (!S_ISREG(statbuf.st_mode)) { mu_util_g_set_error (err, MU_ERROR_FILE, "not a regular file: %s", path); return FALSE; } self->_timestamp = statbuf.st_mtime; self->_size = (size_t)statbuf.st_size; self->_path = mu_canonicalize_filename(path, NULL); self->_maildir = g_strdup(mdir ? mdir : ""); return TRUE; } static char* calculate_sha1 (FILE *file) { std::array<uint8_t, 4096> buf{}; char *sha1{}; GChecksum *checksum{g_checksum_new(G_CHECKSUM_SHA256)}; while (true) { const auto n = ::fread(buf.data(), 1, buf.size(), file); if (n == 0) break; g_checksum_update (checksum, buf.data(), n); } if (::ferror(file)) g_warning ("error reading file"); else sha1 = g_strdup(g_checksum_get_string(checksum)); g_checksum_free(checksum); return sha1; } static GMimeStream* get_mime_stream (MuMsgFile *self, const char *path, GError **err) { FILE *file; GMimeStream *stream; file = fopen (path, "r"); if (!file) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "cannot open %s: %s", path, g_strerror (errno)); return NULL; } stream = g_mime_stream_file_new (file); if (!stream) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "cannot create mime stream for %s", path); fclose (file); return NULL; } self->_sha1 = calculate_sha1(file); if (!self->_sha1) { ::fclose(file); g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "failed to get sha-1 for %s", path); return NULL; } return stream; } static gboolean init_mime_msg (MuMsgFile *self, const char* path, GError **err) { GMimeStream *stream; GMimeParser *parser; stream = get_mime_stream (self, path, err); if (!stream) return FALSE; parser = g_mime_parser_new_with_stream (stream); g_object_unref (stream); if (!parser) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "cannot create mime parser for %s", path); return FALSE; } self->_mime_msg = g_mime_parser_construct_message (parser, NULL); g_object_unref (parser); if (!self->_mime_msg) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "message seems invalid, ignoring (%s)", path); return FALSE; } return TRUE; } static char* get_recipient (MuMsgFile *self, GMimeAddressType atype) { char *recip; InternetAddressList *recips; recips = g_mime_message_get_addresses (self->_mime_msg, atype); /* FALSE --> don't encode */ recip = (char*)internet_address_list_to_string (recips, NULL, FALSE); if (recip && !g_utf8_validate (recip, -1, NULL)) { g_debug ("invalid recipient in %s\n", self->_path); mu_str_asciify_in_place (recip); /* ugly... */ } if (mu_str_is_empty(recip)) { g_free (recip); return NULL; } if (recip) mu_str_remove_ctrl_in_place (recip); return recip; } /* * let's try to guess the mailing list from some other * headers in the mail */ static gchar* get_fake_mailing_list_maybe (MuMsgFile *self) { const char* hdr; hdr = g_mime_object_get_header (GMIME_OBJECT(self->_mime_msg), "X-Feed2Imap-Version"); if (!hdr) return NULL; /* looks like a feed2imap header; guess the source-blog * from the msgid */ { const char *msgid, *e; msgid = g_mime_message_get_message_id (self->_mime_msg); if (msgid && (e = strchr (msgid, '-'))) return g_strndup (msgid, e - msgid); } return NULL; } static gchar* get_mailing_list (MuMsgFile *self) { char *dechdr, *res; const char *hdr, *b, *e; hdr = g_mime_object_get_header (GMIME_OBJECT(self->_mime_msg), "List-Id"); if (mu_str_is_empty (hdr)) return get_fake_mailing_list_maybe (self); dechdr = g_mime_utils_header_decode_phrase (NULL, hdr); if (!dechdr) return NULL; e = NULL; b = strchr (dechdr, '<'); if (b) e = strchr (b, '>'); if (b && e) res = g_strndup (b + 1, e - b - 1); else res = g_strdup (dechdr); g_free (dechdr); return res; } static gboolean looks_like_attachment (GMimeObject *part) { GMimeContentDisposition *disp; GMimeContentType *ctype; const char *dispstr; guint u; const struct { const char *type; const char *sub_type; } att_types[] = { { "image", "*" }, { "audio", "*" }, { "application", "*"}, { "application", "x-patch"} }; disp = g_mime_object_get_content_disposition (part); if (!GMIME_IS_CONTENT_DISPOSITION(disp)) return FALSE; dispstr = g_mime_content_disposition_get_disposition (disp); if (g_ascii_strcasecmp (dispstr, "attachment") == 0) return TRUE; /* we also consider patches, images, audio, and non-pgp-signature * application attachments to be attachments... */ ctype = g_mime_object_get_content_type (part); if (g_mime_content_type_is_type (ctype, "*", "pgp-signature")) return FALSE; /* don't consider as a signature */ if (g_mime_content_type_is_type (ctype, "text", "*")) { if (g_mime_content_type_is_type (ctype, "*", "plain") || g_mime_content_type_is_type (ctype, "*", "html")) return FALSE; else return TRUE; } for (u = 0; u != G_N_ELEMENTS(att_types); ++u) if (g_mime_content_type_is_type ( ctype, att_types[u].type, att_types[u].sub_type)) return TRUE; return FALSE; } static void msg_cflags_cb (GMimeObject *parent, GMimeObject *part, MuFlags *flags) { if (GMIME_IS_MULTIPART_SIGNED(part)) *flags |= MU_FLAG_SIGNED; /* FIXME: An encrypted part might be signed at the same time. * In that case the signed flag is lost. */ if (GMIME_IS_MULTIPART_ENCRYPTED(part)) *flags |= MU_FLAG_ENCRYPTED; if (*flags & MU_FLAG_HAS_ATTACH) return; if (!GMIME_IS_PART(part)) return; if (*flags & MU_FLAG_HAS_ATTACH) return; if (looks_like_attachment (part)) *flags |= MU_FLAG_HAS_ATTACH; } static MuFlags get_content_flags (MuMsgFile *self) { MuFlags flags; char *ml; flags = MU_FLAG_NONE; if (GMIME_IS_MESSAGE(self->_mime_msg)) mu_mime_message_foreach (self->_mime_msg, FALSE, /* never decrypt for this */ (GMimeObjectForeachFunc)msg_cflags_cb, &flags); ml = get_mailing_list (self); if (ml) { flags |= MU_FLAG_LIST; g_free (ml); } return flags; } static MuFlags get_flags (MuMsgFile *self) { MuFlags flags; g_return_val_if_fail (self, MU_FLAG_INVALID); flags = mu_maildir_get_flags_from_path (self->_path); flags |= get_content_flags (self); /* pseudo-flag --> unread means either NEW or NOT SEEN, just * for searching convenience */ if ((flags & MU_FLAG_NEW) || !(flags & MU_FLAG_SEEN)) flags |= MU_FLAG_UNREAD; return flags; } static size_t get_size (MuMsgFile *self) { g_return_val_if_fail (self, 0); return self->_size; } static MuMsgPrio parse_prio_str (const char* priostr) { int i; struct { const char* _str; MuMsgPrio _prio; } str_prio[] = { { "high", MU_MSG_PRIO_HIGH }, { "1", MU_MSG_PRIO_HIGH }, { "2", MU_MSG_PRIO_HIGH }, { "normal", MU_MSG_PRIO_NORMAL }, { "3", MU_MSG_PRIO_NORMAL }, { "low", MU_MSG_PRIO_LOW }, { "list", MU_MSG_PRIO_LOW }, { "bulk", MU_MSG_PRIO_LOW }, { "4", MU_MSG_PRIO_LOW }, { "5", MU_MSG_PRIO_LOW } }; for (i = 0; i != G_N_ELEMENTS(str_prio); ++i) if (g_ascii_strcasecmp (priostr, str_prio[i]._str) == 0) return str_prio[i]._prio; /* e.g., last-fm uses 'fm-user'... as precedence */ return MU_MSG_PRIO_NORMAL; } static MuMsgPrio get_prio (MuMsgFile *self) { GMimeObject *obj; const char* priostr; g_return_val_if_fail (self, MU_MSG_PRIO_NONE); obj = GMIME_OBJECT(self->_mime_msg); priostr = g_mime_object_get_header (obj, "Precedence"); if (!priostr) priostr = g_mime_object_get_header (obj, "X-Priority"); if (!priostr) priostr = g_mime_object_get_header (obj, "Importance"); return priostr ? parse_prio_str (priostr) : MU_MSG_PRIO_NORMAL; } /* NOTE: buffer will be *freed* or returned unchanged */ static char* convert_to_utf8 (GMimePart *part, char *buffer) { GMimeContentType *ctype; const char* charset; ctype = g_mime_object_get_content_type (GMIME_OBJECT(part)); g_return_val_if_fail (GMIME_IS_CONTENT_TYPE(ctype), NULL); /* of course, the charset specified may be incorrect... */ charset = g_mime_content_type_get_parameter (ctype, "charset"); if (charset) { char *utf8; if ((utf8 = mu_str_convert_to_utf8 (buffer, g_mime_charset_iconv_name (charset)))) { g_free (buffer); buffer = utf8; } } else if (!g_utf8_validate (buffer, -1, NULL)) { /* if it's already utf8, nothing to do otherwise: no charset at all, or conversion failed; ugly * hack: replace all non-ascii chars with '.' */ mu_str_asciify_in_place (buffer); } return buffer; } static gchar* stream_to_string (GMimeStream *stream, size_t buflen) { char *buffer; ssize_t bytes; buffer = g_new(char, buflen + 1); g_mime_stream_reset (stream); /* we read everything in one go */ bytes = g_mime_stream_read (stream, buffer, buflen); if (bytes < 0) { g_warning ("%s: failed to read from stream", __func__); g_free (buffer); return NULL; } buffer[bytes]='\0'; return buffer; } gchar* Mu::mu_msg_mime_part_to_string (GMimePart *part, gboolean *err) { GMimeDataWrapper *wrapper; GMimeStream *stream; ssize_t buflen; char *buffer; buffer = NULL; stream = NULL; g_return_val_if_fail (err, NULL); *err = TRUE; /* guilty until proven innocent */ g_return_val_if_fail (GMIME_IS_PART(part), NULL); wrapper = g_mime_part_get_content (part); if (!wrapper) { /* this happens with invalid mails */ g_debug ("failed to create data wrapper"); goto cleanup; } stream = g_mime_stream_mem_new (); if (!stream) { g_warning ("failed to create mem stream"); goto cleanup; } buflen = g_mime_data_wrapper_write_to_stream (wrapper, stream); if (buflen <= 0) {/* empty buffer, not an error */ *err = FALSE; goto cleanup; } buffer = stream_to_string (stream, (size_t)buflen); /* convert_to_utf8 will free the old 'buffer' if needed */ buffer = convert_to_utf8 (part, buffer); *err = FALSE; cleanup: if (G_IS_OBJECT(stream)) g_object_unref (stream); return buffer; } static gboolean contains (GSList *lst, const char *str) { for (; lst; lst = g_slist_next(lst)) if (g_strcmp0 ((char*)lst->data, str) == 0) return TRUE; return FALSE; } /* * NOTE: this will get the list of references with the oldest parent * at the beginning */ static GSList* get_references (MuMsgFile *self) { GSList *msgids; unsigned u; const char *headers[] = { "References", "In-reply-to", NULL }; for (msgids = NULL, u = 0; headers[u]; ++u) { char *str; GMimeReferences *mime_refs; int i, refs_len; str = mu_msg_file_get_header (self, headers[u]); if (!str) continue; mime_refs = g_mime_references_parse (NULL, str); g_free (str); refs_len = g_mime_references_length (mime_refs); for (i = 0; i < refs_len; ++i) { const char* msgid; msgid = g_mime_references_get_message_id (mime_refs, i); /* don't include duplicates */ if (msgid && !contains (msgids, msgid)) /* explicitly ensure it's utf8-safe, * as GMime does not ensure that */ msgids = g_slist_prepend (msgids, g_strdup((msgid))); } g_mime_references_free (mime_refs); } /* reverse, because we used g_slist_prepend for performance * reasons */ return g_slist_reverse (msgids); } /* see: http://does-not-exist.org/mail-archives/mutt-dev/msg08249.html */ static GSList* get_tags (MuMsgFile *self) { GSList *lst; unsigned u; struct { const char *header; char sepa; } tagfields[] = { { "X-Label", ' ' }, { "X-Keywords", ',' }, { "Keywords", ' ' } }; for (lst = NULL, u = 0; u != G_N_ELEMENTS(tagfields); ++u) { gchar *hdr; hdr = mu_msg_file_get_header (self, tagfields[u].header); if (hdr) { GSList *hlst; hlst = mu_str_to_list (hdr, tagfields[u].sepa, TRUE); if (lst) (g_slist_last (lst))->next = hlst; else lst = hlst; g_free (hdr); } } return lst; } static char* cleanup_maybe (const char *str, gboolean *do_free) { char *s; if (!str) return NULL; if (!g_utf8_validate(str, -1, NULL)) { if (*do_free) s = mu_str_asciify_in_place ((char*)str); else { *do_free = TRUE; s = mu_str_asciify_in_place(g_strdup (str)); } } else s = (char*)str; mu_str_remove_ctrl_in_place (s); return s; } G_GNUC_CONST static GMimeAddressType address_type (MuMsgFieldId mfid) { switch (mfid) { case MU_MSG_FIELD_ID_BCC : return GMIME_ADDRESS_TYPE_BCC; case MU_MSG_FIELD_ID_CC : return GMIME_ADDRESS_TYPE_CC; case MU_MSG_FIELD_ID_TO : return GMIME_ADDRESS_TYPE_TO; case MU_MSG_FIELD_ID_FROM: return GMIME_ADDRESS_TYPE_FROM; default: g_return_val_if_reached ((GMimeAddressType)-1); } } static gchar* get_msgid (MuMsgFile *self, gboolean *do_free) { const char *msgid{g_mime_message_get_message_id (self->_mime_msg)}; if (msgid && strlen(msgid) < Store::MaxTermLength) { *do_free = FALSE; return (char*)msgid; } // if there's no valid message-id, synthesize one; // based on the contents so it stays valid if moved around. *do_free = TRUE; return g_strdup_printf ("%s@mu", self->_sha1); } char* Mu::mu_msg_file_get_str_field (MuMsgFile *self, MuMsgFieldId mfid, gboolean *do_free) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL); *do_free = FALSE; /* default */ switch (mfid) { case MU_MSG_FIELD_ID_BCC: case MU_MSG_FIELD_ID_CC: case MU_MSG_FIELD_ID_FROM: case MU_MSG_FIELD_ID_TO: *do_free = TRUE; return get_recipient (self, address_type(mfid)); case MU_MSG_FIELD_ID_PATH: return self->_path; case MU_MSG_FIELD_ID_MAILING_LIST: *do_free = TRUE; return (char*)get_mailing_list (self); case MU_MSG_FIELD_ID_SUBJECT: return (char*)cleanup_maybe (g_mime_message_get_subject (self->_mime_msg), do_free); case MU_MSG_FIELD_ID_MSGID: return get_msgid (self, do_free); case MU_MSG_FIELD_ID_MAILDIR: return self->_maildir; case MU_MSG_FIELD_ID_BODY_TEXT: /* use mu_msg_get_body_text */ case MU_MSG_FIELD_ID_BODY_HTML: /* use mu_msg_get_body_html */ case MU_MSG_FIELD_ID_EMBEDDED_TEXT: g_warning ("%s is not retrievable through: %s", mu_msg_field_name (mfid), __func__); return NULL; default: g_return_val_if_reached (NULL); } } GSList* Mu::mu_msg_file_get_str_list_field (MuMsgFile *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (mu_msg_field_is_string_list(mfid), NULL); switch (mfid) { case MU_MSG_FIELD_ID_REFS: return get_references (self); case MU_MSG_FIELD_ID_TAGS: return get_tags (self); default: g_return_val_if_reached (NULL); } } gint64 Mu::mu_msg_file_get_num_field (MuMsgFile *self, const MuMsgFieldId mfid) { g_return_val_if_fail (self, -1); g_return_val_if_fail (mu_msg_field_is_numeric(mfid), -1); switch (mfid) { case MU_MSG_FIELD_ID_DATE: { GDateTime *dt; dt = g_mime_message_get_date (self->_mime_msg); return dt ? g_date_time_to_unix (dt) : 0; } case MU_MSG_FIELD_ID_FLAGS: return (gint64)get_flags(self); case MU_MSG_FIELD_ID_PRIO: return (gint64)get_prio(self); case MU_MSG_FIELD_ID_SIZE: return (gint64)get_size(self); default: g_return_val_if_reached (-1); } } char* Mu::mu_msg_file_get_header (MuMsgFile *self, const char *header) { const gchar *hdr; g_return_val_if_fail (self, NULL); g_return_val_if_fail (header, NULL); /* sadly, g_mime_object_get_header may return non-ascii; * so, we need to ensure that */ hdr = g_mime_object_get_header (GMIME_OBJECT(self->_mime_msg), header); return hdr ? mu_str_utf8ify(hdr) : NULL; } struct _ForeachData { GMimeObjectForeachFunc user_func; gpointer user_data; gboolean decrypt; }; typedef struct _ForeachData ForeachData; static void foreach_cb (GMimeObject *parent, GMimeObject *part, ForeachData *fdata) { /* invoke the callback function */ fdata->user_func (parent, part, fdata->user_data); /* maybe iterate over decrypted parts */ if (fdata->decrypt && GMIME_IS_MULTIPART_ENCRYPTED (part)) { GMimeObject *dec; dec = mu_msg_crypto_decrypt_part (GMIME_MULTIPART_ENCRYPTED(part), MU_MSG_OPTION_NONE, NULL, NULL, NULL); if (!dec) return; if (GMIME_IS_MULTIPART (dec)) g_mime_multipart_foreach ( (GMIME_MULTIPART(dec)), (GMimeObjectForeachFunc)foreach_cb, fdata); else foreach_cb (parent, dec, fdata); g_object_unref (dec); } } void Mu::mu_mime_message_foreach (GMimeMessage *msg, gboolean decrypt, GMimeObjectForeachFunc func, gpointer user_data) { ForeachData fdata; g_return_if_fail (GMIME_IS_MESSAGE (msg)); g_return_if_fail (func); fdata.user_func = func; fdata.user_data = user_data; fdata.decrypt = decrypt; g_mime_message_foreach (msg, (GMimeObjectForeachFunc)foreach_cb, &fdata); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-file.hh������������������������������������������������������������������������0000664�0000000�0000000�00000006012�14143670036�0015434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MSG_FILE_HH__ #define MU_MSG_FILE_HH__ namespace Mu { struct MuMsgFile; /** * create a new message from a file * * @param path full path to the message * @param mdir * @param err error to receive (when function returns NULL), or NULL * * @return a new MuMsg, or NULL in case of error */ MuMsgFile *mu_msg_file_new (const char *path, const char* mdir, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * destroy a MuMsgFile object * * @param self object to destroy, or NULL */ void mu_msg_file_destroy (MuMsgFile *self); /** * get a specific header * * @param self a MuMsgFile instance * @param header a header (e.g. 'X-Mailer' or 'List-Id') * * @return the value of the header or NULL if not found; free with g_free */ char* mu_msg_file_get_header (MuMsgFile *self, const char *header); /** * get a string value for this message * * @param self a valid MuMsgFile * @param msfid the message field id to get (must be of type string) * @param do_free receives TRUE or FALSE, conveying if this string * should be owned & freed (TRUE) or not by caller. In case 'FALSE', * this function should be treated as if it were returning a const * char*, and note that in that case the string is only valid as long * as the MuMsgFile is alive, ie. before mu_msg_file_destroy * * @return a string, or NULL */ char* mu_msg_file_get_str_field (MuMsgFile *self, MuMsgFieldId msfid, gboolean *do_free) G_GNUC_WARN_UNUSED_RESULT; /** * get a string-list value for this message * * @param self a valid MuMsgFile * @param msfid the message field id to get (must be of type string-list) * * @return a GSList*, or NULL; free with mu_str_free_list */ GSList* mu_msg_file_get_str_list_field (MuMsgFile *self, MuMsgFieldId msfid) G_GNUC_WARN_UNUSED_RESULT; /** * get a numeric value for this message -- the return value should be * cast into the actual type, e.g., time_t, MuMsgPrio etc. * * @param self a valid MuMsgFile * @param msfid the message field id to get (must be string-based one) * * @return the numeric value, or -1 in case of error */ gint64 mu_msg_file_get_num_field (MuMsgFile *self, MuMsgFieldId mfid); } // namespace Mu #endif /*MU_MSG_FILE_HH__*/ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-part.cc������������������������������������������������������������������������0000664�0000000�0000000�00000060400�14143670036�0015452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <string.h> #include <unistd.h> #include "mu-msg.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" #include "mu-msg-priv.hh" #include "mu-msg-part.hh" using namespace Mu; struct _DoData { GMimeObject *mime_obj; unsigned index; }; typedef struct _DoData DoData; static void do_it_with_index (MuMsg *msg, MuMsgPart *part, DoData *ddata) { if (ddata->mime_obj) return; if (part->index == ddata->index) { /* Add a reference to this object, this way if it is * encrypted it will not be garbage collected before * we are done with it. */ g_object_ref (part->data); ddata->mime_obj = (GMimeObject*)part->data; } } static GMimeObject* get_mime_object_at_index (MuMsg *msg, MuMsgOptions opts, unsigned index) { DoData ddata; ddata.mime_obj = NULL; ddata.index = index; /* wipe out some irrelevant options */ opts &= (MuMsgOptions)~MU_MSG_OPTION_VERIFY; opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES; mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)do_it_with_index, &ddata); return ddata.mime_obj; } typedef gboolean (*MuMsgPartMatchFunc) (MuMsgPart *, gpointer); struct _MatchData { MuMsgPartMatchFunc match_func; gpointer user_data; int index; }; typedef struct _MatchData MatchData; static void check_match (MuMsg *msg, MuMsgPart *part, MatchData *mdata) { if (mdata->index != -1) return; if (mdata->match_func (part, mdata->user_data)) mdata->index = part->index; } static int get_matching_part_index (MuMsg *msg, MuMsgOptions opts, MuMsgPartMatchFunc func, gpointer user_data) { MatchData mdata; mdata.match_func = func; mdata.user_data = user_data; mdata.index = -1; mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)check_match, &mdata); return mdata.index; } static void accumulate_text_message (MuMsg *msg, MuMsgPart *part, GString **gstrp) { const gchar *str; char *adrs; GMimeMessage *mimemsg; InternetAddressList *addresses; /* put sender, recipients and subject in the string, so they * can be indexed as well */ mimemsg = GMIME_MESSAGE (part->data); addresses = g_mime_message_get_addresses (mimemsg, GMIME_ADDRESS_TYPE_FROM); adrs = internet_address_list_to_string (addresses, NULL, FALSE); g_string_append_printf (*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : ""); g_free (adrs); str = g_mime_message_get_subject (mimemsg); g_string_append_printf (*gstrp, "%s%s", str ? str : "", str ? "\n" : ""); addresses = g_mime_message_get_all_recipients (mimemsg); adrs = internet_address_list_to_string (addresses, NULL, FALSE); g_object_unref (addresses); g_string_append_printf (*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : ""); g_free (adrs); } static void accumulate_text_part (MuMsg *msg, MuMsgPart *part, GString **gstrp) { GMimeContentType *ctype; gboolean err; char *txt; ctype = g_mime_object_get_content_type ((GMimeObject*)part->data); if (!g_mime_content_type_is_type (ctype, "text", "plain")) return; /* not plain text */ txt = mu_msg_mime_part_to_string((GMimePart*)part->data, &err); if (txt) g_string_append (*gstrp, txt); g_free (txt); } static void accumulate_text (MuMsg *msg, MuMsgPart *part, GString **gstrp) { if (GMIME_IS_MESSAGE(part->data)) accumulate_text_message (msg, part, gstrp); else if (GMIME_IS_PART (part->data)) accumulate_text_part (msg, part, gstrp); } /* declaration, so we can use it earlier */ static gboolean handle_mime_object (MuMsg *msg, GMimeObject *mobj, GMimeObject *parent, MuMsgOptions opts, unsigned *index, gboolean decrypted, MuMsgPartForeachFunc func, gpointer user_data); static char* get_text_from_mime_msg (MuMsg *msg, GMimeMessage *mmsg, MuMsgOptions opts) { GString *gstr; unsigned index; index = 1; gstr = g_string_sized_new (4096); handle_mime_object (msg, mmsg->mime_part, (GMimeObject *) mmsg, opts, &index, FALSE, (MuMsgPartForeachFunc)accumulate_text, &gstr); return g_string_free (gstr, FALSE); } char* Mu::mu_msg_part_get_text (MuMsg *msg, MuMsgPart *self, MuMsgOptions opts) { GMimeObject *mobj; GMimeMessage *mime_msg; gboolean err; g_return_val_if_fail (msg, NULL); g_return_val_if_fail (self && GMIME_IS_OBJECT(self->data), NULL); mobj = (GMimeObject*)self->data; err = FALSE; if (GMIME_IS_PART (mobj)) { if (self->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN) return mu_msg_mime_part_to_string ((GMimePart*)mobj, &err); else return NULL; /* non-text MimePart */ } mime_msg = NULL; if (GMIME_IS_MESSAGE_PART (mobj)) mime_msg = g_mime_message_part_get_message ((GMimeMessagePart*)mobj); else if (GMIME_IS_MESSAGE (mobj)) mime_msg = (GMimeMessage*)mobj; /* apparently, g_mime_message_part_get_message may still * return NULL */ if (mime_msg) return get_text_from_mime_msg (msg, mime_msg, opts); return NULL; } /* note: this will return -1 in case of error or if the size is * unknown */ static ssize_t get_part_size (GMimePart *part) { GMimeDataWrapper *wrapper; GMimeStream *stream; wrapper = g_mime_part_get_content (part); if (!GMIME_IS_DATA_WRAPPER(wrapper)) return -1; stream = g_mime_data_wrapper_get_stream (wrapper); if (!stream) return -1; /* no stream -> size is 0 */ else return g_mime_stream_length (stream); /* NOTE: stream/wrapper are owned by gmime, no unreffing */ } static char* cleanup_filename (char *fname) { GString *gstr; gchar *cur; gunichar uc; gstr = g_string_sized_new (strlen (fname)); /* replace control characters, slashes, and colons by '-' */ for (cur = fname; cur && *cur; cur = g_utf8_next_char (cur)) { uc = g_utf8_get_char (cur); if (g_unichar_iscntrl (uc) || uc == '/' || uc == ':' || uc == ';') g_string_append_unichar (gstr, '-'); else g_string_append_unichar (gstr, uc); } g_free (fname); return g_string_free (gstr, FALSE); } /* * when a part doesn't have a filename, it can be useful to 'guess' one based on * its mime-type, which allows other tools to handle them correctly, e.g. from * mu4e. * * For now, we only handle calendar invitations in that way, but others may * follow. */ static char* guess_file_name (GMimeObject *mobj, unsigned index) { GMimeContentType *ctype; ctype = g_mime_object_get_content_type (mobj); /* special case for calendars; map to '.vcs' */ if (g_mime_content_type_is_type (ctype, "text", "calendar")) return g_strdup_printf ("vcal-%u.vcs", index); /* fallback */ return g_strdup_printf ("%u.msgpart", index); } static char* mime_part_get_filename (GMimeObject *mobj, unsigned index, gboolean construct_if_needed) { gchar *fname; fname = NULL; if (GMIME_IS_PART (mobj)) { /* the easy case: the part has a filename */ fname = (gchar*)g_mime_part_get_filename (GMIME_PART(mobj)); if (fname) /* don't include directory components */ fname = g_path_get_basename (fname); } if (!fname && !construct_if_needed) return NULL; if (GMIME_IS_MESSAGE_PART(mobj)) { GMimeMessage *msg; const char *subj; msg = g_mime_message_part_get_message (GMIME_MESSAGE_PART(mobj)); subj = g_mime_message_get_subject (msg); fname = g_strdup_printf ("%s.eml", subj ? subj : "message"); } if (!fname) fname = guess_file_name (mobj, index); /* replace control characters, slashes, and colons */ fname = cleanup_filename (fname); return fname; } char* Mu::mu_msg_part_get_filename (MuMsgPart *mpart, gboolean construct_if_needed) { g_return_val_if_fail (mpart, NULL); g_return_val_if_fail (GMIME_IS_OBJECT(mpart->data), NULL); return mime_part_get_filename ((GMimeObject*)mpart->data, mpart->index, construct_if_needed); } const gchar* Mu::mu_msg_part_get_content_id (MuMsgPart *mpart) { g_return_val_if_fail (mpart, NULL); g_return_val_if_fail (GMIME_IS_OBJECT(mpart->data), NULL); return g_mime_object_get_content_id((GMimeObject*)mpart->data); } static MuMsgPartType get_disposition (GMimeObject *mobj) { const char *disp; disp = g_mime_object_get_disposition (mobj); if (!disp) return MU_MSG_PART_TYPE_NONE; if (strcasecmp (disp, GMIME_DISPOSITION_ATTACHMENT) == 0) return MU_MSG_PART_TYPE_ATTACHMENT; if (strcasecmp (disp, GMIME_DISPOSITION_INLINE) == 0) return MU_MSG_PART_TYPE_INLINE; return MU_MSG_PART_TYPE_NONE; } /* call 'func' with information about this MIME-part */ static inline void check_signature (MuMsg *msg, GMimeMultipartSigned *part, MuMsgOptions opts) { GError *err; err = NULL; mu_msg_crypto_verify_part (part, opts, &err); if (err) { g_warning ("error verifying signature: %s", err->message); g_clear_error (&err); } } /* Note: this is function will be called by GMime when it needs a * password. However, GMime <= 2.6.10 does not handle * getting passwords correctly, so this might fail. see: * password_requester in mu-msg-crypto.c */ static gchar* get_console_pw (const char* user_id, const char *prompt_ctx, gboolean reprompt, gpointer user_data) { char *prompt, *pass; if (!g_mime_check_version(2,6,11)) g_printerr ( "*** the gmime library you are using has version " "%u.%u.%u (<= 2.6.10)\n" "*** this version has a bug in its password " "retrieval routine, and probably won't work.\n", gmime_major_version, gmime_minor_version, gmime_micro_version); if (reprompt) g_print ("Authentication failed. Please try again\n"); prompt = g_strdup_printf ("Password for %s: ", user_id); pass = mu_util_read_password (prompt); g_free (prompt); return pass; } static gboolean handle_encrypted_part (MuMsg *msg, GMimeMultipartEncrypted *part, MuMsgOptions opts, unsigned *index, MuMsgPartForeachFunc func, gpointer user_data) { GError *err; gboolean rv; GMimeObject *dec; MuMsgPartPasswordFunc pw_func; if (opts & MU_MSG_OPTION_CONSOLE_PASSWORD) pw_func = (MuMsgPartPasswordFunc)get_console_pw; else pw_func = NULL; err = NULL; dec = mu_msg_crypto_decrypt_part (part, opts, pw_func, NULL, &err); if (err) { g_warning ("error decrypting part: %s", err->message); g_clear_error (&err); } if (dec) { rv = handle_mime_object (msg, dec, (GMimeObject *) part, opts, index, TRUE, func, user_data); g_object_unref (dec); } else { /* On failure to decrypt, list the encrypted part as * an attachment */ GMimeObject *encrypted; encrypted = g_mime_multipart_get_part ( GMIME_MULTIPART (part), 1); g_return_val_if_fail (GMIME_IS_PART(encrypted), FALSE); rv = handle_mime_object (msg, encrypted, (GMimeObject *) part, opts, index, FALSE, func, user_data); } return rv; } static gboolean looks_like_text_body_part (GMimeContentType *ctype) { unsigned u; static struct { const char *type; const char *subtype; } types[] = { { "text", "plain" }, { "text", "x-markdown" }, { "text", "x-diff" }, { "text", "x-patch" }, { "application", "x-patch"} /* possible other types */ }; for (u = 0; u != G_N_ELEMENTS(types); ++u) if (g_mime_content_type_is_type ( ctype, types[u].type, types[u].subtype)) return TRUE; return FALSE; } static MuMsgPartSigStatusReport* copy_status_report_maybe (GObject *obj) { MuMsgPartSigStatusReport *report, *copy; report = (MuMsgPartSigStatusReport*) g_object_get_data (obj, SIG_STATUS_REPORT); if (!report) return NULL; /* nothing to copy */ copy = g_slice_new0(MuMsgPartSigStatusReport); copy->verdict = report->verdict; if (report->report) copy->report = g_strdup (report->report); if (report->signers) copy->signers = g_strdup (report->signers); return copy; } /* call 'func' with information about this MIME-part */ static gboolean handle_part (MuMsg *msg, GMimePart *part, GMimeObject *parent, MuMsgOptions opts, unsigned *index, gboolean decrypted, MuMsgPartForeachFunc func, gpointer user_data) { GMimeContentType *ct; MuMsgPart msgpart; memset (&msgpart, 0, sizeof(MuMsgPart)); msgpart.size = get_part_size (part); msgpart.part_type = MU_MSG_PART_TYPE_LEAF; msgpart.part_type |= get_disposition ((GMimeObject*)part); if (decrypted) msgpart.part_type |= MU_MSG_PART_TYPE_DECRYPTED; else if ((opts & MU_MSG_OPTION_DECRYPT) && GMIME_IS_MULTIPART_ENCRYPTED (parent)) msgpart.part_type |= MU_MSG_PART_TYPE_ENCRYPTED; ct = g_mime_object_get_content_type ((GMimeObject*)part); if (GMIME_IS_CONTENT_TYPE(ct)) { msgpart.type = g_mime_content_type_get_media_type (ct); msgpart.subtype = g_mime_content_type_get_media_subtype (ct); /* store in the part_type as well, for quick checking */ if (looks_like_text_body_part (ct)) msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_PLAIN; else if (g_mime_content_type_is_type (ct, "text", "html")) msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_HTML; } /* put the verification info in the pgp-signature and every * descendent of a pgp-encrypted part */ msgpart.sig_status_report = NULL; if (g_ascii_strcasecmp (msgpart.subtype, "pgp-signature") == 0 || decrypted) { msgpart.sig_status_report = copy_status_report_maybe (G_OBJECT(parent)); if (msgpart.sig_status_report) msgpart.part_type |= MU_MSG_PART_TYPE_SIGNED; } msgpart.data = (gpointer)part; msgpart.index = (*index)++; func (msg, &msgpart, user_data); mu_msg_part_sig_status_report_destroy (msgpart.sig_status_report); return TRUE; } /* call 'func' with information about this MIME-part */ static gboolean handle_message_part (MuMsg *msg, GMimeMessagePart *mimemsgpart, GMimeObject *parent, MuMsgOptions opts, unsigned *index, gboolean decrypted, MuMsgPartForeachFunc func, gpointer user_data) { MuMsgPart msgpart; memset (&msgpart, 0, sizeof(MuMsgPart)); msgpart.type = "message"; msgpart.subtype = "rfc822"; msgpart.index = (*index)++; /* msgpart.size = 0; /\* maybe calculate this? *\/ */ msgpart.part_type = MU_MSG_PART_TYPE_MESSAGE; msgpart.part_type |= get_disposition ((GMimeObject*)mimemsgpart); msgpart.data = (gpointer)mimemsgpart; func (msg, &msgpart, user_data); if (opts & MU_MSG_OPTION_RECURSE_RFC822) { GMimeMessage *mmsg; /* may return NULL for some * messages */ mmsg = g_mime_message_part_get_message (mimemsgpart); if (mmsg) return handle_mime_object (msg, mmsg->mime_part, parent, opts, index, decrypted, func, user_data); } return TRUE; } static gboolean handle_multipart (MuMsg *msg, GMimeMultipart *mpart, GMimeObject *parent, MuMsgOptions opts, unsigned *index, gboolean decrypted, MuMsgPartForeachFunc func, gpointer user_data) { gboolean res; GMimeObject *part; guint i; res = TRUE; for (i = 0; i < mpart->children->len; i++) { part = (GMimeObject *) mpart->children->pdata[i]; res &= handle_mime_object (msg, part, parent, opts, index, decrypted, func, user_data); } return res; } static gboolean handle_mime_object (MuMsg *msg, GMimeObject *mobj, GMimeObject *parent, MuMsgOptions opts, unsigned *index, gboolean decrypted, MuMsgPartForeachFunc func, gpointer user_data) { if (GMIME_IS_PART (mobj)) return handle_part (msg, GMIME_PART(mobj), parent, opts, index, decrypted, func, user_data); else if (GMIME_IS_MESSAGE_PART (mobj)) return handle_message_part (msg, GMIME_MESSAGE_PART(mobj), parent, opts, index, decrypted, func, user_data); else if ((opts & MU_MSG_OPTION_VERIFY) && GMIME_IS_MULTIPART_SIGNED (mobj)) { check_signature (msg, GMIME_MULTIPART_SIGNED (mobj), opts); return handle_multipart (msg, GMIME_MULTIPART (mobj), mobj, opts, index, decrypted, func, user_data); } else if ((opts & MU_MSG_OPTION_DECRYPT) && GMIME_IS_MULTIPART_ENCRYPTED (mobj)) return handle_encrypted_part (msg, GMIME_MULTIPART_ENCRYPTED (mobj), opts, index, func, user_data); else if (GMIME_IS_MULTIPART (mobj)) return handle_multipart (msg, GMIME_MULTIPART (mobj), parent, opts, index, decrypted, func, user_data); return TRUE; } gboolean Mu::mu_msg_part_foreach (MuMsg *msg, MuMsgOptions opts, MuMsgPartForeachFunc func, gpointer user_data) { unsigned index; index = 1; g_return_val_if_fail (msg, FALSE); if (!mu_msg_load_msg_file (msg, NULL)) return FALSE; return handle_mime_object (msg, msg->_file->_mime_msg->mime_part, (GMimeObject *) msg->_file->_mime_msg, opts, &index, FALSE, func, user_data); } static gboolean write_part_to_fd (GMimePart *part, int fd, GError **err) { GMimeStream *stream; GMimeDataWrapper *wrapper; gboolean rv; stream = g_mime_stream_fs_new (fd); if (!GMIME_IS_STREAM(stream)) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to create stream"); return FALSE; } g_mime_stream_fs_set_owner (GMIME_STREAM_FS(stream), FALSE); wrapper = g_mime_part_get_content (part); if (!GMIME_IS_DATA_WRAPPER(wrapper)) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to create wrapper"); g_object_unref (stream); return FALSE; } g_object_ref (part); /* FIXME: otherwise, the unrefs below * give errors...*/ if (g_mime_data_wrapper_write_to_stream (wrapper, stream) == -1) { rv = FALSE; g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to write to stream"); } else rv = TRUE; /* g_object_unref (wrapper); we don't own it */ g_object_unref (stream); return rv; } static gboolean write_object_to_fd (GMimeObject *obj, int fd, GError **err) { gchar *str; str = g_mime_object_to_string (obj, NULL); if (!str) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "could not get string from object"); return FALSE; } if (write (fd, str, strlen(str)) == -1) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to write object: %s", g_strerror(errno)); return FALSE; } return TRUE; } static gboolean save_object (GMimeObject *obj, MuMsgOptions opts, const char *fullpath, GError **err) { int fd; gboolean rv; gboolean use_existing, overwrite; use_existing = opts & MU_MSG_OPTION_USE_EXISTING; overwrite = opts & MU_MSG_OPTION_OVERWRITE; /* don't try to overwrite when we already have it; useful when * you're sure it's not a different file with the same name */ if (use_existing && access (fullpath, F_OK) == 0) return TRUE; /* ok, try to create the file */ fd = mu_util_create_writeable_fd (fullpath, 0600, overwrite); if (fd == -1) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "could not open '%s' for writing: %s", fullpath, errno ? g_strerror(errno) : "error"); return FALSE; } if (GMIME_IS_PART (obj)) rv = write_part_to_fd ((GMimePart*)obj, fd, err); else rv = write_object_to_fd (obj, fd, err); if (close (fd) != 0 && !err) { /* don't write on top of old err */ g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "could not close '%s': %s", fullpath, errno ? g_strerror(errno) : "error"); return FALSE; } return rv; } gchar* Mu::mu_msg_part_get_path (MuMsg *msg, MuMsgOptions opts, const char* targetdir, unsigned index, GError **err) { char *fname, *filepath; GMimeObject* mobj; g_return_val_if_fail (msg, NULL); if (!mu_msg_load_msg_file (msg, NULL)) return NULL; mobj = get_mime_object_at_index (msg, opts, index); if (!mobj){ mu_util_g_set_error (err, MU_ERROR_GMIME, "cannot find part %u", index); return NULL; } fname = mime_part_get_filename (mobj, index, TRUE); filepath = g_build_path (G_DIR_SEPARATOR_S, targetdir ? targetdir : "", fname, NULL); /* Unref it since it was referenced earlier by * get_mime_object_at_index */ g_object_unref (mobj); g_free (fname); return filepath; } gchar* Mu::mu_msg_part_get_cache_path (MuMsg *msg, MuMsgOptions opts, guint partid, GError **err) { char *dirname, *filepath; const char* path; g_return_val_if_fail (msg, NULL); if (!mu_msg_load_msg_file (msg, NULL)) return NULL; path = mu_msg_get_path (msg); /* g_compute_checksum_for_string may be better, but requires * rel. new glib (2.16) */ dirname = g_strdup_printf ("%s%c%x%c%u", mu_util_cache_dir(), G_DIR_SEPARATOR, g_str_hash (path), G_DIR_SEPARATOR, partid); if (!mu_util_create_dir_maybe (dirname, 0700, FALSE)) { mu_util_g_set_error (err, MU_ERROR_FILE, "failed to create dir %s", dirname); g_free (dirname); return NULL; } filepath = mu_msg_part_get_path (msg, opts, dirname, partid, err); g_free (dirname); return filepath; } gboolean Mu::mu_msg_part_save (MuMsg *msg, MuMsgOptions opts, const char *fullpath, guint partidx, GError **err) { gboolean rv; GMimeObject *part; g_return_val_if_fail (msg, FALSE); g_return_val_if_fail (fullpath, FALSE); g_return_val_if_fail (!((opts & MU_MSG_OPTION_OVERWRITE) && (opts & MU_MSG_OPTION_USE_EXISTING)), FALSE); rv = FALSE; if (!mu_msg_load_msg_file (msg, err)) return rv; part = get_mime_object_at_index (msg, opts, partidx); /* special case: convert a message-part into a message */ if (GMIME_IS_MESSAGE_PART (part)) part = (GMimeObject*)g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); if (!part) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "part %u does not exist", partidx); else if (!GMIME_IS_PART(part) && !GMIME_IS_MESSAGE(part)) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "unexpected type %s for part %u", G_OBJECT_TYPE_NAME((GObject*)part), partidx); else rv = save_object (part, opts, fullpath, err); g_clear_object(&part); return rv; } gchar* Mu::mu_msg_part_save_temp (MuMsg *msg, MuMsgOptions opts, guint partidx, GError **err) { gchar *filepath; filepath = mu_msg_part_get_cache_path (msg, opts, partidx, err); if (!filepath) return NULL; if (!mu_msg_part_save (msg, opts, filepath, partidx, err)) { g_free (filepath); return NULL; } return filepath; } static gboolean match_cid (MuMsgPart *mpart, const char *cid) { const char *this_cid; this_cid = g_mime_object_get_content_id ((GMimeObject*)mpart->data); return g_strcmp0 (this_cid, cid) ? TRUE : FALSE; } int Mu::mu_msg_find_index_for_cid (MuMsg *msg, MuMsgOptions opts, const char *sought_cid) { const char* cid; g_return_val_if_fail (msg, -1); g_return_val_if_fail (sought_cid, -1); if (!mu_msg_load_msg_file (msg, NULL)) return -1; cid = g_str_has_prefix (sought_cid, "cid:") ? sought_cid + 4 : sought_cid; return get_matching_part_index (msg, opts, (MuMsgPartMatchFunc)match_cid, (gpointer)cid); } struct _RxMatchData { GSList *_lst; const GRegex *_rx; guint _idx; }; typedef struct _RxMatchData RxMatchData; static void match_filename_rx (MuMsg *msg, MuMsgPart *mpart, RxMatchData *mdata) { char *fname; fname = mu_msg_part_get_filename (mpart, FALSE); if (!fname) return; if (g_regex_match (mdata->_rx, fname, (GRegexMatchFlags)0, NULL)) mdata->_lst = g_slist_prepend (mdata->_lst, GUINT_TO_POINTER(mpart->index)); g_free (fname); } GSList* Mu::mu_msg_find_files (MuMsg *msg, MuMsgOptions opts, const GRegex *pattern) { RxMatchData mdata; g_return_val_if_fail (msg, NULL); g_return_val_if_fail (pattern, NULL); if (!mu_msg_load_msg_file (msg, NULL)) return NULL; mdata._lst = NULL; mdata._rx = pattern; mdata._idx = 0; mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)match_filename_rx, &mdata); return mdata._lst; } gboolean Mu::mu_msg_part_maybe_attachment (MuMsgPart *part) { g_return_val_if_fail (part, FALSE); /* attachments must be leaf parts */ if (!(part->part_type & MU_MSG_PART_TYPE_LEAF)) return FALSE; /* parts other than text/plain, text/html are considered * attachments as well */ if (!(part->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN) && !(part->part_type & MU_MSG_PART_TYPE_TEXT_HTML)) return TRUE; return part->part_type & MU_MSG_PART_TYPE_ATTACHMENT ? TRUE : FALSE; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-part.hh������������������������������������������������������������������������0000664�0000000�0000000�00000016530�14143670036�0015471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MSG_PART_HH__ #define MU_MSG_PART_HH__ #include "utils/mu-utils.hh" #include <glib.h> #include <unistd.h> /* for ssize_t */ namespace Mu { #define SIG_STATUS_REPORT "sig-status-report" enum MuMsgPartType { MU_MSG_PART_TYPE_NONE = 0, /* MIME part without children */ MU_MSG_PART_TYPE_LEAF = 1 << 1, /* an RFC822 message part? */ MU_MSG_PART_TYPE_MESSAGE = 1 << 2, /* disposition inline? */ MU_MSG_PART_TYPE_INLINE = 1 << 3, /* disposition attachment? */ MU_MSG_PART_TYPE_ATTACHMENT = 1 << 4, /* a signed part? */ MU_MSG_PART_TYPE_SIGNED = 1 << 5, /* an encrypted part? */ MU_MSG_PART_TYPE_ENCRYPTED = 1 << 6, /* a decrypted part? */ MU_MSG_PART_TYPE_DECRYPTED = 1 << 7, /* a text/plain part? */ MU_MSG_PART_TYPE_TEXT_PLAIN = 1 << 8, /* a text/html part? */ MU_MSG_PART_TYPE_TEXT_HTML = 1 << 9 }; MU_ENABLE_BITOPS(MuMsgPartType); /* the signature status */ enum _MuMsgPartSigStatus { MU_MSG_PART_SIG_STATUS_UNSIGNED = 0, MU_MSG_PART_SIG_STATUS_GOOD, MU_MSG_PART_SIG_STATUS_BAD, MU_MSG_PART_SIG_STATUS_ERROR, MU_MSG_PART_SIG_STATUS_FAIL }; typedef enum _MuMsgPartSigStatus MuMsgPartSigStatus; typedef struct { MuMsgPartSigStatus verdict; const char *report; const char *signers; } MuMsgPartSigStatusReport; /** * destroy a MuMsgPartSignatureStatusReport object * * @param report a MuMsgPartSignatureStatusReport object */ void mu_msg_part_sig_status_report_destroy (MuMsgPartSigStatusReport *report); struct _MuMsgPart { /* index of this message part */ unsigned index; /* cid */ /* const char *content_id; */ /* content-type: type/subtype, ie. text/plain */ const char *type; const char *subtype; /* size of the part; or < 0 if unknown */ ssize_t size; gpointer data; /* opaque data */ MuMsgPartType part_type; MuMsgPartSigStatusReport *sig_status_report; }; typedef struct _MuMsgPart MuMsgPart; /** * get some appropriate file name for the mime-part * * @param mpart a MuMsgPart * @param construct_if_needed if there is no * real filename, construct one. * * @return the file name (free with g_free) */ char *mu_msg_part_get_filename (MuMsgPart *mpart, gboolean construct_if_needed) G_GNUC_WARN_UNUSED_RESULT; /** * get appropriate content id for the mime-part * * @param mpart a MuMsgPart * * @return const content id */ const gchar* mu_msg_part_get_content_id (MuMsgPart *mpart) G_GNUC_WARN_UNUSED_RESULT; /** * get the text in the MuMsgPart (ie. in its GMimePart) * * @param msg a MuMsg * @param part a MuMsgPart * @param opts MuMsgOptions * * @return utf8 string for this MIME part, to be freed by caller */ char* mu_msg_part_get_text (MuMsg *msg, MuMsgPart *part, MuMsgOptions opts) G_GNUC_WARN_UNUSED_RESULT; /** * does this msg part look like an attachment? * * @param part a message part * * @return TRUE if it looks like an attachment, FALSE otherwise */ gboolean mu_msg_part_maybe_attachment (MuMsgPart *part); /** * save a specific attachment to some targetdir * * @param msg a valid MuMsg instance * @param opts mu-message options (OVERWRITE/USE_EXISTING) * @gchar filepath the filepath to save * @param partidx index of the attachment you want to save * @param err receives error information (when function returns NULL) * * @return full path to the message part saved or NULL in case or * error; free with g_free */ gboolean mu_msg_part_save (MuMsg *msg, MuMsgOptions opts, const char *filepath, guint partidx, GError **err); /** * save a message part to a temporary file and return the full path to * this file * * @param msg a MuMsg message * @param opts mu-message options (OVERWRITE/USE_EXISTING) * @param partidx index of the part to save * @param err receives error information if any * * @return the full path to the temp file, or NULL in case of error */ gchar* mu_msg_part_save_temp (MuMsg *msg, MuMsgOptions opts, guint partidx, GError **err) G_GNUC_WARN_UNUSED_RESULT; /** * get a filename for the saving the message part; try the filename * specified for the message part if any, otherwise determine a unique * name based on the partidx and the message path * * @param msg a msg * @param opts mu-message options * @param targetdir where to store the part * @param partidx the part for which to determine a filename * @param err receives error information (when function returns NULL) * * @return a filepath (g_free when done with it) or NULL in case of error */ gchar* mu_msg_part_get_path (MuMsg *msg, MuMsgOptions opts, const char* targetdir, guint partidx, GError **err) G_GNUC_WARN_UNUSED_RESULT; /** * get a full path name for a file for saving the message part INDEX; * this path is unique (1:1) for this particular message and part for * this user. Thus, it can be used as a cache. * * Will create the directory if needed. * * @param msg a msg * @param opts mu-message options * @param partidx the part for which to determine a filename * @param err receives error information (when function returns NULL) * * @return a filepath (g_free when done with it) or NULL in case of error */ gchar* mu_msg_part_get_cache_path (MuMsg *msg, MuMsgOptions opts, guint partidx, GError **err) G_GNUC_WARN_UNUSED_RESULT; /** * get the part index for the message part with a certain content-id * * @param msg a message * @param content_id a content-id to search * * @return the part index number of the found part, or -1 if it was not found */ int mu_msg_find_index_for_cid (MuMsg *msg, MuMsgOptions opts, const char* content_id); /** * retrieve a list of indices for mime-parts with filenames matching a regex * * @param msg a message * @param opts * @param a regular expression to match the filename with * * @return a list with indices for the files matching the pattern; the * indices are the GPOINTER_TO_UINT(lst->data) of the list. They must * be freed with g_slist_free */ GSList* mu_msg_find_files (MuMsg *msg, MuMsgOptions opts, const GRegex *pattern); typedef void (*MuMsgPartForeachFunc) (MuMsg *msg, MuMsgPart*, gpointer); /** * call a function for each of the mime part in a message * * @param msg a valid MuMsg* instance * @param func a callback function to call for each contact; when * the callback does not return TRUE, it won't be called again * @param user_data a user-provide pointer that will be passed to the callback * @param options, bit-wise OR'ed * * @return FALSE in case of error, TRUE otherwise */ gboolean mu_msg_part_foreach (MuMsg *msg, MuMsgOptions opts, MuMsgPartForeachFunc func, gpointer user_data); } // namespace Mu #endif /*MU_MSG_PART_HH__*/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-prio.c�������������������������������������������������������������������������0000664�0000000�0000000�00000003175�14143670036�0015320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-msg-prio.h" const char* mu_msg_prio_name (MuMsgPrio prio) { switch (prio) { case MU_MSG_PRIO_LOW : return "low"; case MU_MSG_PRIO_NORMAL : return "normal"; case MU_MSG_PRIO_HIGH : return "high"; default : g_return_val_if_reached (NULL); } } MuMsgPrio mu_msg_prio_from_char (char k) { g_return_val_if_fail (k == 'l' || k == 'n' || k == 'h', MU_MSG_PRIO_NONE); return (MuMsgPrio)k; } char mu_msg_prio_char (MuMsgPrio prio) { if (!(prio == 'l' || prio == 'n' || prio == 'h')) { g_warning ("prio: %c", (char)prio); } g_return_val_if_fail (prio == 'l' || prio == 'n' || prio == 'h', 0); return (char)prio; } void mu_msg_prio_foreach (MuMsgPrioForeachFunc func, gpointer user_data) { g_return_if_fail (func); func (MU_MSG_PRIO_LOW, user_data); func (MU_MSG_PRIO_NORMAL, user_data); func (MU_MSG_PRIO_HIGH, user_data); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-prio.h�������������������������������������������������������������������������0000664�0000000�0000000�00000004300�14143670036�0015314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_MSG_PRIO_H__ #define __MU_MSG_PRIO_H__ #include <glib.h> G_BEGIN_DECLS enum _MuMsgPrio { MU_MSG_PRIO_LOW = 'l', MU_MSG_PRIO_NORMAL = 'n', MU_MSG_PRIO_HIGH = 'h' }; typedef enum _MuMsgPrio MuMsgPrio; static const MuMsgPrio MU_MSG_PRIO_NONE = (MuMsgPrio)0; /** * get a printable name for the message priority * (ie., MU_MSG_PRIO_LOW=>"low" etc.) * * @param prio a message priority * * @return a printable name for this priority */ const char* mu_msg_prio_name (MuMsgPrio prio) G_GNUC_CONST; /** * get the MuMsgPriority corresponding to a one-character shortcut * ('l'=>MU_MSG_PRIO_, 'n'=>MU_MSG_PRIO_NORMAL or * 'h'=>MU_MSG_PRIO_HIGH) * * @param k a character * * @return a message priority */ MuMsgPrio mu_msg_prio_from_char (char k) G_GNUC_CONST; /** * get the one-character shortcut corresponding to a message priority * ('l'=>MU_MSG_PRIO_, 'n'=>MU_MSG_PRIO_NORMAL or * 'h'=>MU_MSG_PRIO_HIGH) * * @param prio a message priority * * @return a shortcut character or 0 in case of error */ char mu_msg_prio_char (MuMsgPrio prio) G_GNUC_CONST; typedef void (*MuMsgPrioForeachFunc) (MuMsgPrio prio, gpointer user_data); /** * call a function for each message priority * * @param func a callback function * @param user_data a user pointer to pass to the callback */ void mu_msg_prio_foreach (MuMsgPrioForeachFunc func, gpointer user_data); G_END_DECLS #endif /*__MU_MSG_PRIO_H__*/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-priv.hh������������������������������������������������������������������������0000664�0000000�0000000�00000010114�14143670036�0015473�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MSG_PRIV_HH__ #define MU_MSG_PRIV_HH__ #include <gmime/gmime.h> #include <stdlib.h> #include <mu-msg.hh> #include <mu-msg-file.hh> #include <mu-msg-doc.hh> #include "mu-msg-part.hh" namespace Mu { struct MuMsgFile { GMimeMessage *_mime_msg; time_t _timestamp; size_t _size; char *_path; char *_maildir; char *_sha1; }; /* we put the the MuMsg definition in this separate -priv file, so we * can split the mu_msg implementations over separate files */ struct MuMsg { guint _refcount; /* our two backend */ MuMsgFile *_file; /* based on GMime, ie. a file on disc */ MuMsgDoc *_doc; /* based on Xapian::Document */ /* lists where we push allocated strings / GSLists of string * so we can free them when the struct gets destroyed (and we * can return them as 'const to callers) */ GSList *_free_later_str; GSList *_free_later_lst; }; /** * convert a GMimePart to a string * * @param part a GMimePart * @param err will receive TRUE if there was an error, FALSE * otherwise. Must NOT be NULL. * * @return utf8 string for this MIME part, to be freed by caller */ gchar* mu_msg_mime_part_to_string (GMimePart *part, gboolean *err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * Like g_mime_message_foreach, but will recurse into encrypted parts * if @param decrypt is TRUE and mu was built with crypto support * * @param msg a GMimeMessage * @param decrypt whether to try to automatically decrypt * @param func user callback function for each part * @param user_data user point passed to callback function * @param err receives error information * */ void mu_mime_message_foreach (GMimeMessage *msg, gboolean decrypt, GMimeObjectForeachFunc func, gpointer user_data); /** * callback function to retrieve a password from the user * * @param user_id the user name / id to get the password for * @param prompt_ctx a string containing some helpful context for the prompt * @param reprompt whether this is a reprompt after an earlier, incorrect password * @param user_data the user_data pointer passed to mu_msg_part_decrypt_foreach * * @return a newly allocated (g_free'able) string */ typedef char* (*MuMsgPartPasswordFunc) (const char *user_id, const char *prompt_ctx, gboolean reprompt, gpointer user_data); /** * verify the signature of a signed message part * * @param sig a signed message part * @param opts message options * @param err receive error information * * @return a status report object, free with mu_msg_part_sig_status_report_destroy */ void mu_msg_crypto_verify_part (GMimeMultipartSigned *sig, MuMsgOptions opts, GError **err); /** * decrypt the given encrypted mime multipart * * @param enc encrypted part * @param opts options * @param password_func callback function to retrieve as password (or NULL) * @param user_data pointer passed to the password func * @param err receives error data * * @return the decrypted part, or NULL in case of error */ GMimeObject* mu_msg_crypto_decrypt_part (GMimeMultipartEncrypted *enc, MuMsgOptions opts, MuMsgPartPasswordFunc func, gpointer user_data, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; } // namespace Mu #endif /*MU_MSG_PRIV_HH__*/ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg-sexp.cc������������������������������������������������������������������������0000664�0000000�0000000�00000034124�14143670036�0015467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <string.h> #include <ctype.h> #include "mu-query-results.hh" #include "utils/mu-str.h" #include "mu-msg.hh" #include "mu-msg-part.hh" #include "mu-maildir.hh" using namespace Mu; static void add_prop_nonempty (Sexp::List& list, const char* elm, const GSList *str_lst) { Sexp::List elms; while (str_lst) { elms.add(Sexp::make_string((const char*)str_lst->data)); str_lst = g_slist_next(str_lst); } if (!elms.empty()) list.add_prop(elm, Sexp::make_list(std::move(elms))); } static void add_prop_nonempty (Sexp::List& list, const char* name, const char *str) { if (str && str[0]) list.add_prop(name, Sexp::make_string(str)); } static Sexp make_contact_sexp (MuMsgContact *c) { // a cons-pair...perhaps make this a plist too? Sexp::List contact; if (mu_msg_contact_name(c)) contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_name(c)))); else contact.add(Sexp::make_symbol("nil")); contact.add(Sexp::make_symbol(".")); contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_email(c)))); return Sexp::make_list(std::move(contact)); } static void add_list_post (Sexp::List& list, MuMsg *msg) { /* some mailing lists do not set the reply-to; see pull #1278. So for * those cases, check the List-Post address and use that instead */ GMatchInfo *minfo; GRegex *rx; const char* list_post; list_post = mu_msg_get_header (msg, "List-Post"); if (!list_post) return; rx = g_regex_new ("<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?", G_REGEX_CASELESS, (GRegexMatchFlags)0, NULL); g_return_if_fail(rx); if (g_regex_match (rx, list_post, (GRegexMatchFlags)0, &minfo)) { auto address = (char*)g_match_info_fetch (minfo, 1); MuMsgContact contact{NULL, address}; list.add_prop(":list-post", Sexp::make_list(make_contact_sexp(&contact))); g_free(address); } g_match_info_free (minfo); g_regex_unref (rx); } struct _ContactData { Sexp::List from, to, cc, bcc, reply_to; }; typedef struct _ContactData ContactData; static gboolean each_contact (MuMsgContact *c, ContactData *cdata) { switch (mu_msg_contact_type (c)) { case MU_MSG_CONTACT_TYPE_FROM: cdata->from.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_TO: cdata->to.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_CC: cdata->cc.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_BCC: cdata->bcc.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_REPLY_TO: cdata->reply_to.add(make_contact_sexp(c)); break; default: g_return_val_if_reached (FALSE); return FALSE; } return TRUE; } static void add_prop_nonempty_list(Sexp::List& list, std::string&& name, Sexp::List&& sexp) { if (sexp.empty()) return; list.add_prop(std::move(name), Sexp::make_list(std::move(sexp))); } static void add_contacts (Sexp::List& list, MuMsg *msg) { ContactData cdata{}; mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact, &cdata); add_prop_nonempty_list (list, ":from", std::move(cdata.from)); add_prop_nonempty_list (list, ":to", std::move(cdata.to)); add_prop_nonempty_list (list, ":cc", std::move(cdata.cc)); add_prop_nonempty_list (list, ":bcc", std::move(cdata.bcc)); add_prop_nonempty_list (list, ":reply-to", std::move(cdata.reply_to)); add_list_post (list, msg); } typedef struct { Sexp::List flaglist; MuFlags msgflags; } FlagData; static void each_flag (MuFlags flag, FlagData *fdata) { if (flag & fdata->msgflags) fdata->flaglist.add(Sexp::make_symbol(mu_flag_name(flag))); } static void add_flags (Sexp::List& list, MuMsg *msg) { FlagData fdata{}; fdata.msgflags = mu_msg_get_flags (msg); mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata); if (!fdata.flaglist.empty()) list.add_prop(":flags", Sexp::make_list(std::move(fdata.flaglist))); } static char* get_temp_file (MuMsg *msg, MuMsgOptions opts, unsigned index) { char *path; GError *err; err = NULL; path = mu_msg_part_get_cache_path (msg, opts, index, &err); if (!path) goto errexit; if (!mu_msg_part_save (msg, opts, path, index, &err)) goto errexit; return path; errexit: g_warning ("failed to save mime part: %s", err->message ? err->message : "something went wrong"); g_clear_error (&err); g_free (path); return NULL; } static gchar* get_temp_file_maybe (MuMsg *msg, MuMsgPart *part, MuMsgOptions opts) { opts = (MuMsgOptions)((int)opts | (int)MU_MSG_OPTION_USE_EXISTING); if (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) || g_ascii_strcasecmp (part->type, "image") != 0) return NULL; else return get_temp_file (msg, opts, part->index); } struct PartInfo { Sexp::List parts; MuMsgOptions opts; }; static void sig_verdict (Sexp::List& partlist, MuMsgPart *mpart) { MuMsgPartSigStatusReport *report = mpart->sig_status_report; if (!report) return; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (report->verdict) { case MU_MSG_PART_SIG_STATUS_GOOD: partlist.add_prop(":signature", Sexp::make_symbol("verified")); break; case MU_MSG_PART_SIG_STATUS_BAD: partlist.add_prop(":signature", Sexp::make_symbol("bad")); break; case MU_MSG_PART_SIG_STATUS_ERROR: partlist.add_prop(":signature", Sexp::make_symbol("unverified")); break; default: break; } #pragma GCC diagnostic pop if (report->signers) partlist.add_prop(":signers", Sexp::make_string(report->signers)); } static void dec_verdict (Sexp::List& partlist, MuMsgPart *mpart) { if (mpart->part_type & MU_MSG_PART_TYPE_DECRYPTED) partlist.add_prop(":decryption", Sexp::make_symbol("succeeded")); else if (mpart->part_type & MU_MSG_PART_TYPE_ENCRYPTED) partlist.add_prop(":decryption", Sexp::make_symbol("failed")); } static Sexp make_part_types (MuMsgPartType ptype) { struct PartTypes { MuMsgPartType ptype; const char* name; } ptypes[] = { { MU_MSG_PART_TYPE_LEAF, "leaf" }, { MU_MSG_PART_TYPE_MESSAGE, "message" }, { MU_MSG_PART_TYPE_INLINE, "inline" }, { MU_MSG_PART_TYPE_ATTACHMENT, "attachment" }, { MU_MSG_PART_TYPE_SIGNED, "signed" }, { MU_MSG_PART_TYPE_ENCRYPTED, "encrypted" } }; Sexp::List list; for (auto u = 0U; u!= G_N_ELEMENTS(ptypes); ++u) if (ptype & ptypes[u].ptype) list.add(Sexp::make_symbol(ptypes[u].name)); return Sexp::make_list(std::move(list)); } static void each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo) { auto mimetype = format("%s/%s", part->type ? part->type : "application", part->subtype ? part->subtype : "octet-stream"); auto maybe_attach = Sexp::make_symbol(mu_msg_part_maybe_attachment (part) ? "t" : "nil"); Sexp::List partlist; partlist.add_prop(":index", Sexp::make_number(part->index)); partlist.add_prop(":mime-type", Sexp::make_string(mimetype)); partlist.add_prop(":size", Sexp::make_number(part->size)); dec_verdict (partlist, part); sig_verdict (partlist, part); if (part->part_type) partlist.add_prop(":type", make_part_types(part->part_type)); char *fname = mu_msg_part_get_filename (part, TRUE); if (fname) partlist.add_prop(":name", Sexp::make_string(fname)) ; g_free (fname); if (mu_msg_part_maybe_attachment (part)) partlist.add_prop(":attachment", Sexp::make_symbol("t")); const auto cid{ mu_msg_part_get_content_id(part)}; if (cid) partlist.add_prop(":cid", Sexp::make_string(cid)); char *tempfile = get_temp_file_maybe (msg, part, pinfo->opts); if (tempfile) partlist.add_prop (":temp", Sexp::make_string(tempfile)); g_free (tempfile); pinfo->parts.add(Sexp::make_list(std::move(partlist))); } static void add_parts (Sexp::List& items, MuMsg *msg, MuMsgOptions opts) { PartInfo pinfo; pinfo.opts = opts; if (mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo) && !pinfo.parts.empty()) items.add_prop(":parts", Sexp::make_list(std::move(pinfo.parts))); } static void add_message_file_parts (Sexp::List& items, MuMsg *msg, MuMsgOptions opts) { GError *err{NULL}; if (!mu_msg_load_msg_file (msg, &err)) { g_warning ("failed to load message file: %s", err ? err->message : "some error occurred"); g_clear_error (&err); return; } add_parts (items, msg, opts); add_contacts (items, msg); /* add the user-agent / x-mailer */ auto str = mu_msg_get_header (msg, "User-Agent"); if (!str) str = mu_msg_get_header (msg, "X-Mailer"); add_prop_nonempty (items, ":user-agent", str); add_prop_nonempty (items, ":body-txt-params", mu_msg_get_body_text_content_type_parameters (msg, opts)); add_prop_nonempty (items, ":body-txt", mu_msg_get_body_text(msg, opts)); add_prop_nonempty (items, ":body-html", mu_msg_get_body_html(msg, opts)); } static void add_date_and_size (Sexp::List& items, MuMsg *msg) { auto t = mu_msg_get_date (msg); if (t == (time_t)-1) /* invalid date? */ t = 0; Sexp::List dlist; dlist.add(Sexp::make_number((unsigned)(t >> 16))); dlist.add(Sexp::make_number((unsigned)(t & 0xffff))); dlist.add(Sexp::make_number(0)); items.add_prop(":date", Sexp::make_list(std::move(dlist))); auto s = mu_msg_get_size (msg); if (s == (size_t)-1) /* invalid size? */ s = 0; items.add_prop(":size", Sexp::make_number(s)); } static void add_tags (Sexp::List& items, MuMsg *msg) { Sexp::List taglist; for (auto tags = mu_msg_get_tags(msg); tags; tags = g_slist_next(tags)) taglist.add(Sexp::make_string((const char*)tags->data)); if (!taglist.empty()) items.add_prop(":tags", Sexp::make_list(std::move(taglist))); } Mu::Sexp::List Mu::msg_to_sexp_list (MuMsg *msg, unsigned docid, MuMsgOptions opts) { g_return_val_if_fail (msg, Sexp::List()); g_return_val_if_fail (!((opts & MU_MSG_OPTION_HEADERS_ONLY) && (opts & MU_MSG_OPTION_EXTRACT_IMAGES)), Sexp::List()); Sexp::List items; if (docid != 0) items.add_prop(":docid", Sexp::make_number(docid)); add_prop_nonempty (items, ":subject", mu_msg_get_subject (msg)); add_prop_nonempty (items, ":message-id", mu_msg_get_msgid (msg)); add_prop_nonempty (items, ":mailing-list", mu_msg_get_mailing_list (msg)); add_prop_nonempty (items, ":path", mu_msg_get_path (msg)); add_prop_nonempty (items, ":maildir", mu_msg_get_maildir (msg)); items.add_prop(":priority", Sexp::make_symbol(mu_msg_prio_name(mu_msg_get_prio(msg)))); /* in the no-headers-only case (see below) we get a more complete list of contacts, so no * need to get them here if that's the case */ if (opts & MU_MSG_OPTION_HEADERS_ONLY) add_contacts (items, msg); add_prop_nonempty (items, ":references", mu_msg_get_references (msg)); add_prop_nonempty (items, ":in-reply-to", mu_msg_get_header (msg, "In-Reply-To")); add_date_and_size (items, msg); add_flags (items, msg); add_tags (items, msg); /* headers are retrieved from the database, views from the * message file file attr things can only be gotten from the * file (ie., mu view), not from the database (mu find). */ if (!(opts & MU_MSG_OPTION_HEADERS_ONLY)) add_message_file_parts (items, msg, opts); return items; } Mu::Sexp Mu::msg_to_sexp (MuMsg *msg, unsigned docid, MuMsgOptions opts) { return Sexp::make_list(msg_to_sexp_list(msg, docid, opts)); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg.cc�����������������������������������������������������������������������������0000664�0000000�0000000�00000055211�14143670036�0014512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <ctype.h> #include <gmime/gmime.h> #include "mu-msg-priv.hh" /* include before mu-msg.h */ #include "mu-msg.hh" #include "utils/mu-str.h" #include "mu-maildir.hh" using namespace Mu; /* note, we do the gmime initialization here rather than in * mu-runtime, because this way we don't need mu-runtime for simple * cases -- such as our unit tests. Also note that we need gmime init * even for the doc backend, as we use the address parsing functions * also there. */ static gboolean _gmime_initialized = FALSE; static void gmime_init (void) { g_return_if_fail (!_gmime_initialized); g_mime_init(); _gmime_initialized = TRUE; } static void gmime_uninit (void) { g_return_if_fail (_gmime_initialized); g_mime_shutdown(); _gmime_initialized = FALSE; } static MuMsg* msg_new (void) { MuMsg *self; self = g_new0 (MuMsg, 1); self->_refcount = 1; return self; } MuMsg* Mu::mu_msg_new_from_file (const char *path, const char *mdir, GError **err) { MuMsg *self; MuMsgFile *msgfile; gint64 start; g_return_val_if_fail (path, NULL); start = g_get_monotonic_time(); if (G_UNLIKELY(!_gmime_initialized)) { gmime_init (); atexit (gmime_uninit); } msgfile = mu_msg_file_new (path, mdir, err); if (!msgfile) return NULL; self = msg_new (); self->_file = msgfile; g_debug ("created message from %s in %" G_GINT64_FORMAT " μs", path, g_get_monotonic_time() - start); return self; } MuMsg* Mu::mu_msg_new_from_doc (XapianDocument *doc, GError **err) { MuMsg *self; MuMsgDoc *msgdoc; g_return_val_if_fail (doc, NULL); if (G_UNLIKELY(!_gmime_initialized)) { gmime_init (); atexit (gmime_uninit); } msgdoc = mu_msg_doc_new (doc, err); if (!msgdoc) return NULL; self = msg_new (); self->_doc = msgdoc; return self; } static void mu_msg_destroy (MuMsg *self) { if (!self) return; mu_msg_file_destroy (self->_file); mu_msg_doc_destroy (self->_doc); { /* cleanup the strings / lists we stored */ mu_str_free_list (self->_free_later_str); for (auto cur = self->_free_later_lst; cur; cur = g_slist_next(cur)) g_slist_free_full((GSList*)cur->data, g_free); g_slist_free (self->_free_later_lst); } g_free (self); } MuMsg* Mu::mu_msg_ref (MuMsg *self) { g_return_val_if_fail (self, NULL); ++self->_refcount; return self; } void Mu::mu_msg_unref (MuMsg *self) { g_return_if_fail (self); g_return_if_fail (self->_refcount >= 1); if (--self->_refcount == 0) mu_msg_destroy (self); } static const gchar* free_later_str (MuMsg *self, gchar *str) { if (str) self->_free_later_str = g_slist_prepend (self->_free_later_str, str); return str; } static const GSList* free_later_lst (MuMsg *self, GSList *lst) { if (lst) self->_free_later_lst = g_slist_prepend (self->_free_later_lst, lst); return lst; } /* use this instead of mu_msg_get_path so we don't get into infinite * regress...*/ static const char* get_path (MuMsg *self) { char *val; gboolean do_free; do_free = TRUE; val = NULL; if (self->_doc) val = mu_msg_doc_get_str_field (self->_doc, MU_MSG_FIELD_ID_PATH); /* not in the cache yet? try to get it from the file backend, * in case we are using that */ if (!val && self->_file) val = mu_msg_file_get_str_field (self->_file, MU_MSG_FIELD_ID_PATH, &do_free); /* shouldn't happen */ if (!val) g_warning ("%s: message without path?!", __func__); return free_later_str (self, val); } /* for some data, we need to read the message file from disk */ gboolean Mu::mu_msg_load_msg_file (MuMsg *self, GError **err) { const char *path; g_return_val_if_fail (self, FALSE); if (self->_file) return TRUE; /* nothing to do */ if (!(path = get_path (self))) { mu_util_g_set_error (err, MU_ERROR_INTERNAL, "cannot get path for message"); return FALSE; } self->_file = mu_msg_file_new (path, NULL, err); return (self->_file != NULL); } void Mu::mu_msg_unload_msg_file (MuMsg *msg) { g_return_if_fail (msg); mu_msg_file_destroy (msg->_file); msg->_file = NULL; } static const GSList* get_str_list_field (MuMsg *self, MuMsgFieldId mfid) { GSList *val; val = NULL; if (self->_doc && mu_msg_field_xapian_value (mfid)) val = mu_msg_doc_get_str_list_field (self->_doc, mfid); else if (mu_msg_field_gmime (mfid)) { /* if we don't have a file object yet, we need to * create it from the file on disk */ if (!mu_msg_load_msg_file (self, NULL)) return NULL; val = mu_msg_file_get_str_list_field (self->_file, mfid); } return free_later_lst (self, val); } static const char* get_str_field (MuMsg *self, MuMsgFieldId mfid) { char *val; gboolean do_free; do_free = TRUE; val = NULL; if (self->_doc && mu_msg_field_xapian_value (mfid)) val = mu_msg_doc_get_str_field (self->_doc, mfid); else if (mu_msg_field_gmime (mfid)) { /* if we don't have a file object yet, we need to * create it from the file on disk */ if (!mu_msg_load_msg_file (self, NULL)) return NULL; val = mu_msg_file_get_str_field (self->_file, mfid, &do_free); } else val = NULL; return do_free ? free_later_str (self, val) : val; } static gint64 get_num_field (MuMsg *self, MuMsgFieldId mfid) { guint64 val; val = -1; if (self->_doc && mu_msg_field_xapian_value (mfid)) val = mu_msg_doc_get_num_field (self->_doc, mfid); else { /* if we don't have a file object yet, we need to * create it from the file on disk */ if (!mu_msg_load_msg_file (self, NULL)) return -1; val = mu_msg_file_get_num_field (self->_file, mfid); } return val; } const char* Mu::mu_msg_get_header (MuMsg *self, const char *header) { GError *err; g_return_val_if_fail (self, NULL); g_return_val_if_fail (header, NULL); /* if we don't have a file object yet, we need to * create it from the file on disk */ err = NULL; if (!mu_msg_load_msg_file (self, &err)) { g_warning ("failed to load message file: %s", err ? err->message : "something went wrong"); return NULL; } return free_later_str (self, mu_msg_file_get_header (self->_file, header)); } time_t Mu::mu_msg_get_timestamp (MuMsg *self) { const char *path; struct stat statbuf; g_return_val_if_fail (self, 0); if (self->_file) return self->_file->_timestamp; path = mu_msg_get_path (self); if (!path || stat (path, &statbuf) < 0) return 0; return statbuf.st_mtime; } const char* Mu::mu_msg_get_path (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_PATH); } const char* Mu::mu_msg_get_subject (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_SUBJECT); } const char* Mu::mu_msg_get_msgid (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_MSGID); } const char* Mu::mu_msg_get_mailing_list (MuMsg *self) { const char *ml; char *decml; g_return_val_if_fail (self, NULL); ml = get_str_field (self, MU_MSG_FIELD_ID_MAILING_LIST); if (!ml) return NULL; decml = g_mime_utils_header_decode_text (NULL, ml); if (!decml) return NULL; return free_later_str (self, decml); } const char* Mu::mu_msg_get_maildir (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_MAILDIR); } const char* Mu::mu_msg_get_from (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_FROM); } const char* Mu::mu_msg_get_to (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_TO); } const char* Mu::mu_msg_get_cc (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_CC); } const char* Mu::mu_msg_get_bcc (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_field (self, MU_MSG_FIELD_ID_BCC); } time_t Mu::mu_msg_get_date (MuMsg *self) { g_return_val_if_fail (self, (time_t)-1); return (time_t)get_num_field (self, MU_MSG_FIELD_ID_DATE); } MuFlags Mu::mu_msg_get_flags (MuMsg *self) { g_return_val_if_fail (self, MU_FLAG_NONE); return (MuFlags)get_num_field (self, MU_MSG_FIELD_ID_FLAGS); } size_t Mu::mu_msg_get_size (MuMsg *self) { g_return_val_if_fail (self, (size_t)-1); return (size_t)get_num_field (self, MU_MSG_FIELD_ID_SIZE); } MuMsgPrio Mu::mu_msg_get_prio (MuMsg *self) { g_return_val_if_fail (self, MU_MSG_PRIO_NORMAL); return (MuMsgPrio)get_num_field (self, MU_MSG_FIELD_ID_PRIO); } struct _BodyData { GString *gstr; gboolean want_html; }; typedef struct _BodyData BodyData; static void accumulate_body (MuMsg *msg, MuMsgPart *mpart, BodyData *bdata) { char *txt; GMimePart *mimepart; gboolean has_err, is_plain, is_html; if (!GMIME_IS_PART(mpart->data)) return; if (mpart->part_type & MU_MSG_PART_TYPE_ATTACHMENT) return; mimepart = (GMimePart*)mpart->data; is_html = mpart->part_type & MU_MSG_PART_TYPE_TEXT_HTML; is_plain = mpart->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN; txt = NULL; has_err = TRUE; if ((bdata->want_html && is_html) || (!bdata->want_html && is_plain)) txt = mu_msg_mime_part_to_string (mimepart, &has_err); if (!has_err && txt) bdata->gstr = g_string_append (bdata->gstr, txt); g_free (txt); } static char* get_body (MuMsg *self, MuMsgOptions opts, gboolean want_html) { BodyData bdata; bdata.want_html = want_html; bdata.gstr = g_string_sized_new (4096); /* wipe out some irrelevant options */ opts &= ~MU_MSG_OPTION_VERIFY; opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES; mu_msg_part_foreach (self, opts, (MuMsgPartForeachFunc)accumulate_body, &bdata); if (bdata.gstr->len == 0) { g_string_free (bdata.gstr, TRUE); return NULL; } else return g_string_free (bdata.gstr, FALSE); } typedef struct { GMimeContentType *ctype; gboolean want_html; } ContentTypeData; static void find_content_type (MuMsg *msg, MuMsgPart *mpart, ContentTypeData *cdata) { GMimePart *wanted; if (!GMIME_IS_PART(mpart->data)) return; /* text-like attachments are included when in text-mode */ if (!cdata->want_html && (mpart->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN)) wanted = (GMimePart*)mpart->data; else if (!(mpart->part_type & MU_MSG_PART_TYPE_ATTACHMENT) && cdata->want_html && (mpart->part_type & MU_MSG_PART_TYPE_TEXT_HTML)) wanted = (GMimePart*)mpart->data; else wanted = NULL; if (wanted) cdata->ctype = g_mime_object_get_content_type ( GMIME_OBJECT(wanted)); } static const GSList* get_content_type_parameters (MuMsg *self, MuMsgOptions opts, gboolean want_html) { ContentTypeData cdata; cdata.want_html = want_html; cdata.ctype = NULL; /* wipe out some irrelevant options */ opts &= ~MU_MSG_OPTION_VERIFY; opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES; mu_msg_part_foreach (self, opts, (MuMsgPartForeachFunc)find_content_type, &cdata); if (cdata.ctype) { GSList *gslist; GMimeParamList *paramlist; const GMimeParam *param; int i, len; gslist = NULL; paramlist = g_mime_content_type_get_parameters (cdata.ctype); len = g_mime_param_list_length (paramlist); for (i = 0; i < len; ++i) { param = g_mime_param_list_get_parameter_at (paramlist, i); gslist = g_slist_prepend (gslist, g_strdup (param->name)); gslist = g_slist_prepend (gslist, g_strdup (param->value)); } return free_later_lst (self, g_slist_reverse (gslist)); } return NULL; } const GSList* Mu::mu_msg_get_body_text_content_type_parameters (MuMsg *self, MuMsgOptions opts) { g_return_val_if_fail (self, NULL); return get_content_type_parameters(self, opts, FALSE); } const char* Mu::mu_msg_get_body_html (MuMsg *self, MuMsgOptions opts) { g_return_val_if_fail (self, NULL); return free_later_str (self, get_body (self, opts, TRUE)); } const char* Mu::mu_msg_get_body_text (MuMsg *self, MuMsgOptions opts) { g_return_val_if_fail (self, NULL); return free_later_str (self, get_body (self, opts, FALSE)); } const GSList* Mu::mu_msg_get_references (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_list_field (self, MU_MSG_FIELD_ID_REFS); } const GSList* Mu::mu_msg_get_tags (MuMsg *self) { g_return_val_if_fail (self, NULL); return get_str_list_field (self, MU_MSG_FIELD_ID_TAGS); } const char* Mu::mu_msg_get_field_string (MuMsg *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, NULL); return get_str_field (self, mfid); } const GSList* Mu::mu_msg_get_field_string_list (MuMsg *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, NULL); return get_str_list_field (self, mfid); } gint64 Mu::mu_msg_get_field_numeric (MuMsg *self, MuMsgFieldId mfid) { g_return_val_if_fail (self, -1); return get_num_field (self, mfid); } static gboolean fill_contact (MuMsgContact *self, InternetAddress *addr, MuMsgContactType ctype) { if (!addr) return FALSE; self->full_address = internet_address_to_string ( addr, NULL, FALSE); self->name = internet_address_get_name (addr); if (mu_str_is_empty (self->name)) { self->name = NULL; } self->type = ctype; /* we only support internet mailbox addresses; if we don't * check, g_mime hits an assert */ if (INTERNET_ADDRESS_IS_MAILBOX(addr)) self->email= internet_address_mailbox_get_addr (INTERNET_ADDRESS_MAILBOX(addr)); else self->email = NULL; /* if there's no address, just a name, it's probably a local * address (without @) */ if (self->name && !self->email) self->email = self->name; /* note, the address could be NULL e.g. when the recipient is something * like 'Undisclosed recipients' */ return self->email != NULL; } static void address_list_foreach (InternetAddressList *addrlist, MuMsgContactType ctype, MuMsgContactForeachFunc func, gpointer user_data) { int i, len; if (!addrlist) return; len = internet_address_list_length(addrlist); for (i = 0; i != len; ++i) { MuMsgContact contact; gboolean keep_going; if (!fill_contact(&contact, internet_address_list_get_address (addrlist, i), ctype)) continue; keep_going = func(&contact, user_data); g_free ((char*)contact.full_address); if (!keep_going) break; } } static void addresses_foreach (const char* addrs, MuMsgContactType ctype, MuMsgContactForeachFunc func, gpointer user_data) { InternetAddressList *addrlist; if (!addrs) return; addrlist = internet_address_list_parse (NULL, addrs); if (addrlist) { address_list_foreach (addrlist, ctype, func, user_data); g_object_unref (addrlist); } } static void msg_contact_foreach_file (MuMsg *msg, MuMsgContactForeachFunc func, gpointer user_data) { int i; struct { GMimeAddressType _gmime_type; MuMsgContactType _type; } ctypes[] = { {GMIME_ADDRESS_TYPE_FROM, MU_MSG_CONTACT_TYPE_FROM}, {GMIME_ADDRESS_TYPE_REPLY_TO, MU_MSG_CONTACT_TYPE_REPLY_TO}, {GMIME_ADDRESS_TYPE_TO, MU_MSG_CONTACT_TYPE_TO}, {GMIME_ADDRESS_TYPE_CC, MU_MSG_CONTACT_TYPE_CC}, {GMIME_ADDRESS_TYPE_BCC, MU_MSG_CONTACT_TYPE_BCC}, }; for (i = 0; i != G_N_ELEMENTS(ctypes); ++i) { InternetAddressList *addrlist; addrlist = g_mime_message_get_addresses (msg->_file->_mime_msg, ctypes[i]._gmime_type); address_list_foreach (addrlist, ctypes[i]._type, func, user_data); } } static void msg_contact_foreach_doc (MuMsg *msg, MuMsgContactForeachFunc func, gpointer user_data) { addresses_foreach (mu_msg_get_from (msg), MU_MSG_CONTACT_TYPE_FROM, func, user_data); addresses_foreach (mu_msg_get_to (msg), MU_MSG_CONTACT_TYPE_TO, func, user_data); addresses_foreach (mu_msg_get_cc (msg), MU_MSG_CONTACT_TYPE_CC, func, user_data); addresses_foreach (mu_msg_get_bcc (msg), MU_MSG_CONTACT_TYPE_BCC, func, user_data); } void Mu::mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, gpointer user_data) { g_return_if_fail (msg); g_return_if_fail (func); if (msg->_file) msg_contact_foreach_file (msg, func, user_data); else if (msg->_doc) msg_contact_foreach_doc (msg, func, user_data); else g_return_if_reached (); } static int cmp_str (const char *s1, const char *s2) { if (s1 == s2) return 0; else if (!s1) return -1; else if (!s2) return 1; /* optimization 1: ascii */ if (isascii(s1[0]) && isascii(s2[0])) { int diff; diff = tolower(s1[0]) - tolower(s2[0]); if (diff != 0) return diff; } /* utf 8 */ { char *u1, *u2; int diff; u1 = g_utf8_strdown (s1, -1); u2 = g_utf8_strdown (s2, -1); diff = g_utf8_collate (u1, u2); g_free (u1); g_free (u2); return diff; } } static int cmp_subject (const char* s1, const char *s2) { if (s1 == s2) return 0; else if (!s1) return -1; else if (!s2) return 1; return cmp_str ( mu_str_subject_normalize (s1), mu_str_subject_normalize (s2)); } int Mu::mu_msg_cmp (MuMsg *m1, MuMsg *m2, MuMsgFieldId mfid) { g_return_val_if_fail (m1, 0); g_return_val_if_fail (m2, 0); g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), 0); /* even though date is a numeric field, we can sort it by its * string repr. in the database, which is much faster */ if (mfid == MU_MSG_FIELD_ID_DATE || mu_msg_field_is_string (mfid)) return cmp_str (get_str_field (m1, mfid), get_str_field (m2, mfid)); if (mfid == MU_MSG_FIELD_ID_SUBJECT) return cmp_subject (get_str_field (m1, mfid), get_str_field (m2, mfid)); /* TODO: note, we cast (potentially > MAXINT to int) */ if (mu_msg_field_is_numeric (mfid)) return get_num_field(m1, mfid) - get_num_field(m2, mfid); return 0; /* TODO: handle lists */ } gboolean Mu::mu_msg_is_readable (MuMsg *self) { g_return_val_if_fail (self, FALSE); return access (mu_msg_get_path (self), R_OK) == 0 ? TRUE : FALSE; } /* we need do to determine the * /home/foo/Maildir/bar * from the /bar * that we got */ static char* get_target_mdir (MuMsg *msg, const char *target_maildir, GError **err) { char *rootmaildir, *rv; const char *maildir; gboolean not_top_level; /* maildir is the maildir stored in the message, e.g. '/foo' */ maildir = mu_msg_get_maildir(msg); if (!maildir) { mu_util_g_set_error (err, MU_ERROR_GMIME, "message without maildir"); return NULL; } /* the 'rootmaildir' is the filesystem path from root to * maildir, ie. /home/user/Maildir/foo */ rootmaildir = mu_maildir_get_maildir_from_path (mu_msg_get_path(msg)); if (!rootmaildir) { mu_util_g_set_error (err, MU_ERROR_GMIME, "cannot determine maildir"); return NULL; } /* we do a sanity check: verify that that maildir is a suffix of * rootmaildir;*/ not_top_level = TRUE; if (!g_str_has_suffix (rootmaildir, maildir) && /* special case for the top-level '/' maildir, and * remember not_top_level */ (not_top_level = (g_strcmp0 (maildir, "/") != 0))) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "path is '%s', but maildir is '%s' ('%s')", rootmaildir, mu_msg_get_maildir(msg), mu_msg_get_path (msg)); g_free (rootmaildir); return NULL; } /* if we're not at the top-level, remove the final '/' from * the rootmaildir */ if (not_top_level) rootmaildir[strlen(rootmaildir) - strlen (mu_msg_get_maildir(msg))] = '\0'; rv = g_strconcat (rootmaildir, target_maildir, NULL); g_free (rootmaildir); return rv; } /* * move a msg to another maildir, trying to maintain 'integrity', * ie. msg in 'new/' will go to new/, one in cur/ goes to cur/. be * super-paranoid here... */ gboolean Mu::mu_msg_move_to_maildir (MuMsg *self, const char *maildir, MuFlags flags, gboolean ignore_dups, gboolean new_name, GError **err) { char *newfullpath; char *targetmdir; g_return_val_if_fail (self, FALSE); g_return_val_if_fail (maildir, FALSE); /* i.e. "/inbox" */ /* targetmdir is the full path to maildir, i.e., * /home/foo/Maildir/inbox */ targetmdir = get_target_mdir (self, maildir, err); if (!targetmdir) return FALSE; newfullpath = mu_maildir_move_message (mu_msg_get_path (self), targetmdir, flags, ignore_dups, new_name, err); if (!newfullpath) { g_free (targetmdir); return FALSE; } /* clear the old backends */ mu_msg_doc_destroy (self->_doc); self->_doc = NULL; mu_msg_file_destroy (self->_file); /* and create a new one */ self->_file = mu_msg_file_new (newfullpath, maildir, err); g_free (targetmdir); g_free (newfullpath); return self->_file ? TRUE : FALSE; } /* * Rename a message-file, keeping the same flags. This is useful for tricking * some 3rd party progs such as mbsync */ gboolean Mu::mu_msg_tickle (MuMsg *self, GError **err) { g_return_val_if_fail (self, FALSE); return mu_msg_move_to_maildir (self, mu_msg_get_maildir (self), mu_msg_get_flags (self), FALSE, TRUE, err); } const char* Mu::mu_str_flags_s (MuFlags flags) { return mu_flags_to_str_s (flags, MU_FLAG_TYPE_ANY); } char* Mu::mu_str_flags (MuFlags flags) { return g_strdup (mu_str_flags_s(flags)); } static void cleanup_contact (char *contact) { char *c, *c2; /* replace "'<> with space */ for (c2 = contact; *c2; ++c2) if (*c2 == '"' || *c2 == '\'' || *c2 == '<' || *c2 == '>') *c2 = ' '; /* remove everything between '()' if it's after the 5th pos; * good to cleanup corporate contact address spam... */ c = g_strstr_len (contact, -1, "("); if (c && c - contact > 5) *c = '\0'; g_strstrip (contact); } /* this is still somewhat simplistic... */ const char* Mu::mu_str_display_contact_s (const char *str) { static gchar contact[255]; gchar *c, *c2; str = str ? str : ""; g_strlcpy (contact, str, sizeof(contact)); /* we check for '<', so we can strip out the address stuff in * e.g. 'Hello World <hello@world.xx>, but only if there is * something alphanumeric before the < */ c = g_strstr_len (contact, -1, "<"); if (c != NULL) { for (c2 = contact; c2 < c && !(isalnum(*c2)); ++c2); if (c2 != c) /* apparently, there was something, * so we can remove the <... part*/ *c = '\0'; } cleanup_contact (contact); return contact; } char* Mu::mu_str_display_contact (const char *str) { g_return_val_if_fail (str, NULL); return g_strdup (mu_str_display_contact_s (str)); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-msg.hh�����������������������������������������������������������������������������0000664�0000000�0000000�00000043616�14143670036�0014532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2010-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_MSG_HH__ #define MU_MSG_HH__ #include <mu-flags.hh> #include <mu-msg-fields.h> #include <mu-msg-prio.h> #include <utils/mu-util.h> #include <utils/mu-utils.hh> #include <utils/mu-option.hh> #include <utils/mu-sexp.hh> namespace Mu { struct MuMsg; /* options for various functions */ enum MuMsgOptions { MU_MSG_OPTION_NONE = 0, /* 1 << 0 is still free! */ /* for -> sexp conversion */ MU_MSG_OPTION_HEADERS_ONLY = 1 << 1, MU_MSG_OPTION_EXTRACT_IMAGES = 1 << 2, /* below options are for checking signatures; only effective * if mu was built with crypto support */ MU_MSG_OPTION_VERIFY = 1 << 4, MU_MSG_OPTION_AUTO_RETRIEVE = 1 << 5, MU_MSG_OPTION_USE_AGENT = 1 << 6, /* MU_MSG_OPTION_USE_PKCS7 = 1 << 7, /\* gpg is the default *\/ */ /* get password from console if needed */ MU_MSG_OPTION_CONSOLE_PASSWORD = 1 << 7, MU_MSG_OPTION_DECRYPT = 1 << 8, /* misc */ MU_MSG_OPTION_OVERWRITE = 1 << 9, MU_MSG_OPTION_USE_EXISTING = 1 << 10, /* recurse into submessages */ MU_MSG_OPTION_RECURSE_RFC822 = 1 << 11 }; MU_ENABLE_BITOPS(MuMsgOptions); /** * create a new MuMsg* instance which parses a message and provides * read access to its properties; call mu_msg_unref when done with it. * * @param path full path to an email message file * @param mdir the maildir for this message; ie, if the path is * ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can * pass NULL for this parameter, in which case some maildir-specific * information is not available. * @param err receive error information (MU_ERROR_FILE or * MU_ERROR_GMIME), or NULL. There will only be err info if the * function returns NULL * * @return a new MuMsg instance or NULL in case of error; call * mu_msg_unref when done with this message */ MuMsg *mu_msg_new_from_file (const char* filepath, const char *maildir, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * create a new MuMsg* instance based on a Xapian::Document * * @param store a MuStore ptr * @param doc a ptr to a Xapian::Document (but cast to XapianDocument, * because this is C not C++). MuMsg takes _ownership_ of this pointer; * don't touch it afterwards * @param err receive error information, or NULL. There * will only be err info if the function returns NULL * * @return a new MuMsg instance or NULL in case of error; call * mu_msg_unref when done with this message */ MuMsg *mu_msg_new_from_doc (XapianDocument* doc, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * if we don't have a message file yet (because this message is * database-backed), load it. * * @param msg a MuMsg * @param err receives error information * * @return TRUE if this succeeded, FALSE in case of error */ gboolean mu_msg_load_msg_file (MuMsg *msg, GError **err); /** * close the file-backend, if any; this function is for the use case * where you have a large amount of messages where you need some * file-backed field (body or attachments). If you don't close the * file-backend after retrieving the desired field, you'd quickly run * out of file descriptors. If this message does not have a * file-backend, do nothing. * * @param msg a message object */ void mu_msg_unload_msg_file (MuMsg *msg); /** * increase the reference count for this message * * @param msg a message * * @return the message with its reference count increased, or NULL in * case of error. */ MuMsg *mu_msg_ref (MuMsg *msg); /** * decrease the reference count for this message. if the reference * count reaches 0, the message will be destroyed. * * @param msg a message */ void mu_msg_unref (MuMsg *msg); /** * cache the values from the backend (file or db), so we don't the * backend anymore * * @param self a message */ void mu_msg_cache_values (MuMsg *self); /** * get the plain text body of this message * * @param msg a valid MuMsg* instance * @param opts options for getting the body * * @return the plain text body or NULL in case of error or if there is no * such body. the returned string should *not* be modified or freed. * The returned data is in UTF8 or NULL. */ const char* mu_msg_get_body_text (MuMsg *msg, MuMsgOptions opts); /** * get the content type parameters for the text body part * * @param msg a valid MuMsg* instance * @param opts options for getting the body * * @return the value of the requested body part content type parameter, or * NULL in case of error or if there is no such body. the returned string * should *not* be modified or freed. The returned data is in UTF8 or NULL. */ const GSList* mu_msg_get_body_text_content_type_parameters (MuMsg *self, MuMsgOptions opts); /** * get the html body of this message * * @param msg a valid MuMsg* instance * @param opts options for getting the body * * @return the html body or NULL in case of error or if there is no * such body. the returned string should *not* be modified or freed. */ const char* mu_msg_get_body_html (MuMsg *msgMu, MuMsgOptions opts); /** * get the sender (From:) of this message * * @param msg a valid MuMsg* instance * * @return the sender of this Message or NULL in case of error or if there * is no sender. the returned string should *not* be modified or freed. */ const char* mu_msg_get_from (MuMsg *msg); /** * get the recipients (To:) of this message * * @param msg a valid MuMsg* instance * * @return the sender of this Message or NULL in case of error or if there * are no recipients. the returned string should *not* be modified or freed. */ const char* mu_msg_get_to (MuMsg *msg); /** * get the carbon-copy recipients (Cc:) of this message * * @param msg a valid MuMsg* instance * * @return the Cc: recipients of this Message or NULL in case of error or if * there are no such recipients. the returned string should *not* be modified * or freed. */ const char* mu_msg_get_cc (MuMsg *msg); /** * get the blind carbon-copy recipients (Bcc:) of this message; this * field usually only appears in outgoing messages * * @param msg a valid MuMsg* instance * * @return the Bcc: recipients of this Message or NULL in case of * error or if there are no such recipients. the returned string * should *not* be modified or freed. */ const char* mu_msg_get_bcc (MuMsg *msg); /** * get the file system path of this message * * @param msg a valid MuMsg* instance * * @return the path of this Message or NULL in case of error. * the returned string should *not* be modified or freed. */ const char* mu_msg_get_path (MuMsg *msg); /** * get the maildir this message lives in; ie, if the path is * ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar * * @param msg a valid MuMsg* instance * * @return the maildir requested or NULL in case of error. The returned * string should *not* be modified or freed. */ const char* mu_msg_get_maildir (MuMsg *msg); /** * get the subject of this message * * @param msg a valid MuMsg* instance * * @return the subject of this Message or NULL in case of error or if there * is no subject. the returned string should *not* be modified or freed. */ const char* mu_msg_get_subject (MuMsg *msg); /** * get the Message-Id of this message * * @param msg a valid MuMsg* instance * * @return the Message-Id of this message (without the enclosing <>), * or a fake message-id for messages that don't have them, or NULL in * case of error. */ const char* mu_msg_get_msgid (MuMsg *msg); /** * get the mailing list for a message, i.e. the mailing-list * identifier in the List-Id header. * * @param msg a valid MuMsg* instance * * @return the mailing list id for this message (without the enclosing <>) * or NULL in case of error or if there is none. the returned string * should *not* be modified or freed. */ const char* mu_msg_get_mailing_list (MuMsg *msg); /** * get the message date/time (the Date: field) as time_t, using UTC * * @param msg a valid MuMsg* instance * * @return message date/time or 0 in case of error or if there * is no such header. */ time_t mu_msg_get_date (MuMsg *msg); /** * get the flags for this message * * @param msg valid MuMsg* instance * * @return the file/content flags as logically OR'd #MuMsgFlags or 0 * if there are none. Non-standard flags are ignored. */ MuFlags mu_msg_get_flags (MuMsg *msg); /** * get the file size in bytes of this message * * @param msg a valid MuMsg* instance * * @return the filesize */ size_t mu_msg_get_size (MuMsg *msg); /** * get some field value as string * * @param msg a valid MuMsg instance * @param field the field to retrieve; it must be a string-typed field * * @return a string that should not be freed */ const char* mu_msg_get_field_string (MuMsg *msg, MuMsgFieldId mfid); /** * get some field value as string-list * * @param msg a valid MuMsg instance * @param field the field to retrieve; it must be a string-list-typed field * * @return a list that should not be freed */ const GSList* mu_msg_get_field_string_list (MuMsg *self, MuMsgFieldId mfid); /** * get some field value as string * * @param msg a valid MuMsg instance * @param field the field to retrieve; it must be a numeric field * * @return a string that should not be freed */ gint64 mu_msg_get_field_numeric (MuMsg *msg, MuMsgFieldId mfid); /** * get the message priority for this message (MU_MSG_PRIO_LOW, * MU_MSG_PRIO_NORMAL or MU_MSG_PRIO_HIGH) the X-Priority, * X-MSMailPriority, Importance and Precedence header are checked, in * that order. if no known or explicit priority is set, * MU_MSG_PRIO_NORMAL is assumed * * @param msg a valid MuMsg* instance * * @return the message priority (!= 0) or 0 in case of error */ MuMsgPrio mu_msg_get_prio (MuMsg *msg); /** * get the timestamp (mtime) for the file containing this message * * @param msg a valid MuMsg* instance * * @return the timestamp or 0 in case of error */ time_t mu_msg_get_timestamp (MuMsg *msg); /** * get a specific header from the message. This value will _not_ be * cached * * @param self a MuMsg instance * @param header a specific header (like 'X-Mailer' or 'Organization') * * @return a header string which is valid as long as this MuMsg is */ const char* mu_msg_get_header (MuMsg *self, const char *header); /** * get the list of references (consisting of both the References and * In-Reply-To fields), with the oldest first and the direct parent as * the last one. Note, any reference (message-id) will appear at most * once, duplicates are filtered out. * * @param msg a valid MuMsg * * @return a list with the references for this msg. Don't modify/free */ const GSList* mu_msg_get_references (MuMsg *msg); /** * get the list of tags (ie., X-Label) * * @param msg a valid MuMsg * * @return a list with the tags for this msg. Don't modify/free */ const GSList* mu_msg_get_tags (MuMsg *self); /** * compare two messages for sorting * * @param m1 a message * @param m2 another message * @param mfid the message to use for the comparison * * @return negative if m1 is smaller, positive if m1 is smaller, 0 if * they are equal */ int mu_msg_cmp (MuMsg *m1, MuMsg *m2, MuMsgFieldId mfid); /** * check whether there there's a readable file behind this message * * @param self a MuMsg* * * @return TRUE if the message file is readable, FALSE otherwise */ gboolean mu_msg_is_readable (MuMsg *self); /** * move a message to another maildir; note that this does _not_ update * the database * * @param msg a message with an existing file system path in an actual * maildir * @param maildir the subdir where the message should go, relative to * rootmaildir. e.g. "/archive" * @param flags to set for the target (influences the filename, path) * @param silently ignore the src=target case (return TRUE) * @param new_name whether to create a new unique name, or keep the * old one * @param err (may be NULL) may contain error information; note if the * function return FALSE, err is not set for all error condition * (ie. not for parameter error * * @return TRUE if it worked, FALSE otherwise */ gboolean mu_msg_move_to_maildir (MuMsg *msg, const char *maildir, MuFlags flags, gboolean ignore_dups, gboolean new_name, GError **err); /** * Tickle a message -- ie., rename a message to some new semi-random name,while * maintaining the maildir and flags. This can be useful when dealing with * third-party tools such as mbsync that depend on changed filenames. * * @param msg a message with an existing file system path in an actual * maildir * @param err (may be NULL) may contain error information; note if the * function return FALSE, err is not set for all error condition * (ie. not for parameter error * * @return TRUE if it worked, FALSE otherwise */ gboolean mu_msg_tickle (MuMsg *msg, GError **err); enum _MuMsgContactType { /* Reply-To:? */ MU_MSG_CONTACT_TYPE_TO = 0, MU_MSG_CONTACT_TYPE_FROM, MU_MSG_CONTACT_TYPE_CC, MU_MSG_CONTACT_TYPE_BCC, MU_MSG_CONTACT_TYPE_REPLY_TO, MU_MSG_CONTACT_TYPE_NUM }; typedef guint MuMsgContactType; /* not a 'real' contact type */ #define MU_MSG_CONTACT_TYPE_ALL (MU_MSG_CONTACT_TYPE_NUM + 1) #define mu_msg_contact_type_is_valid(MCT)\ ((MCT) < MU_MSG_CONTACT_TYPE_NUM) typedef struct { const char *name; /**< Foo Bar */ const char *email; /**< foo@bar.cuux */ const char *full_address; /**< Foo Bar <foo@bar.cuux> */ MuMsgContactType type; /**< MU_MSG_CONTACT_TYPE_{ TO, CC, BCC, FROM, REPLY_TO} */ } MuMsgContact; /** * macro to get the name of a contact * * @param ct a MuMsgContact * * @return the name */ #define mu_msg_contact_name(ct) ((ct)->name) /** * macro to get the email address of a contact * * @param ct a MuMsgContact * * @return the address */ #define mu_msg_contact_email(ct) ((ct)->email) /** * macro to get the contact type * * @param ct a MuMsgContact * * @return the contact type */ #define mu_msg_contact_type(ct) ((ct)->type) /** * callback function * * @param contact * @param user_data a user provided data pointer * * @return TRUE if we should continue the foreach, FALSE otherwise */ typedef gboolean (*MuMsgContactForeachFunc) (MuMsgContact* contact, gpointer user_data); /** * call a function for each of the contacts in a message; the order is: * from to cc bcc (of each there are zero or more) * * @param msg a valid MuMsgGMime* instance * @param func a callback function to call for each contact; when * the callback does not return TRUE, it won't be called again * @param user_data a user-provide pointer that will be passed to the callback * */ void mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func, gpointer user_data); /** * create a 'display contact' from an email header To/Cc/Bcc/From-type address * ie., turn * "Foo Bar" <foo@bar.com> * into * Foo Bar * Note that this is based on some simple heuristics. Max length is 255 bytes. * * mu_str_display_contact_s returns a statically allocated * buffer (ie, non-reentrant), while mu_str_display_contact * returns a newly allocated string that you must free with g_free * when done with it. * * @param str a 'contact str' (ie., what is in the To/Cc/Bcc/From * fields), or NULL * * @return a newly allocated string with a display contact */ const char* mu_str_display_contact_s (const char *str) G_GNUC_CONST; char *mu_str_display_contact (const char *str) G_GNUC_WARN_UNUSED_RESULT; /** * get a display string for a given set of flags, OR'ed in * @param flags; one character per flag: * D=draft,F=flagged,N=new,P=passed,R=replied,S=seen,T=trashed * a=has-attachment,s=signed, x=encrypted * * mu_str_file_flags_s returns a ptr to a static buffer, * while mu_str_file_flags returns dynamically allocated * memory that must be freed after use. * * @param flags file flags * * @return a string representation of the flags; see above * for what to do with it */ const char* mu_str_flags_s (MuFlags flags) G_GNUC_CONST; char* mu_str_flags (MuFlags flags) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; struct QueryMatch; /** * convert the msg to a Lisp symbolic expression (for further processing in * e.g. emacs) * * @param msg a valid message * @param docid the docid for this message, or 0 * @param opts, bitwise OR'ed; * - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be * obtained from the database (this is much faster if the MuMsg is * database-backed, so no file needs to be opened) * - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary * files and include links to those in the sexp * and for message parts: * MU_MSG_OPTION_CHECK_SIGNATURES: check signatures * MU_MSG_OPTION_AUTO_RETRIEVE_KEY: attempt to retrieve keys online * MU_MSG_OPTION_USE_AGENT: attempt to use GPG-agent * MU_MSG_OPTION_USE_PKCS7: attempt to use PKCS (instead of gpg) * * @return a Mu::Sexp or a Mu::Sexp::List representing the message. */ Mu::Sexp::List msg_to_sexp_list(MuMsg *msg, unsigned docid, MuMsgOptions ops); Mu::Sexp msg_to_sexp(MuMsg *msg, unsigned docid, MuMsgOptions ops); } #endif /*MU_MSG_HH__*/ ������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-parser.cc��������������������������������������������������������������������������0000664�0000000�0000000�00000035364�14143670036�0015227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include "mu-parser.hh" #include "mu-tokenizer.hh" #include "utils/mu-utils.hh" #include "utils/mu-error.hh" #include <algorithm> using namespace Mu; // 3 precedence levels: units (NOT,()) > factors (OR) > terms (AND) // query -> <term-1> | ε // <term-1> -> <factor-1> <term-2> | ε // <term-2> -> OR|XOR <term-1> | ε // <factor-1> -> <unit> <factor-2> | ε // <factor-2> -> [AND]|AND NOT <factor-1> | ε // <unit> -> [NOT] <term-1> | ( <term-1> ) | <data> // <data> -> <value> | <range> | <regex> // <value> -> [field:]value // <range> -> [field:][lower]..[upper] // <regex> -> [field:]/regex/ #define BUG(...) Mu::Error (Error::Code::Internal, format("%u: BUG: ",__LINE__) \ + format(__VA_ARGS__)) /** * Get the "shortcut"/internal fields for the the given fieldstr or empty if there is none * * @param fieldstr a fieldstr, e.g "subject" or "s" for the subject field * * @return a vector with "exploded" values, with a code and a fullname. E.g. "s" might map * to [<"S","subject">], while "recip" could map to [<"to", "T">, <"cc", "C">, <"bcc", "B">] */ struct FieldInfo { const std::string field; const std::string prefix; bool supports_phrase; unsigned id; }; using FieldInfoVec = std::vector<FieldInfo>; using Flags = Parser::Flags; struct Parser::Private { Private(const Store& store, Flags flags): store_{store}, flags_{flags} {} std::vector<std::string> process_regex (const std::string& field, const std::regex& rx) const; Mu::Tree term_1 (Mu::Tokens& tokens, WarningVec& warnings) const; Mu::Tree term_2 (Mu::Tokens& tokens, Node::Type& op, WarningVec& warnings) const; Mu::Tree factor_1 (Mu::Tokens& tokens, WarningVec& warnings) const; Mu::Tree factor_2 (Mu::Tokens& tokens, Node::Type& op, WarningVec& warnings) const; Mu::Tree unit (Mu::Tokens& tokens, WarningVec& warnings) const; Mu::Tree data (Mu::Tokens& tokens, WarningVec& warnings) const; Mu::Tree range (const FieldInfoVec& fields, const std::string& lower, const std::string& upper, size_t pos, WarningVec& warnings) const; Mu::Tree regex (const FieldInfoVec& fields, const std::string& v, size_t pos, WarningVec& warnings) const; Mu::Tree value (const FieldInfoVec& fields, const std::string& v, size_t pos, WarningVec& warnings) const; private: const Store& store_; const Flags flags_; }; static MuMsgFieldId field_id (const std::string& field) { if (field.empty()) return MU_MSG_FIELD_ID_NONE; MuMsgFieldId id = mu_msg_field_id_from_name (field.c_str(), FALSE); if (id != MU_MSG_FIELD_ID_NONE) return id; else if (field.length() == 1) return mu_msg_field_id_from_shortcut (field[0], FALSE); else return MU_MSG_FIELD_ID_NONE; } static std::string process_value (const std::string& field, const std::string& value) { const auto id = field_id (field); if (id == MU_MSG_FIELD_ID_NONE) return value; switch(id) { case MU_MSG_FIELD_ID_PRIO: { if (!value.empty()) return std::string(1, value[0]); } break; case MU_MSG_FIELD_ID_FLAGS: { const auto flag = mu_flag_char_from_name (value.c_str()); if (flag) return std::string(1, tolower(flag)); } break; default: break; } return value; // XXX prio/flags, etc. alias } static void add_field (std::vector<FieldInfo>& fields, MuMsgFieldId id) { const auto shortcut = mu_msg_field_shortcut(id); if (!shortcut) return; // can't be searched const auto name = mu_msg_field_name (id); const auto pfx = mu_msg_field_xapian_prefix (id); if (!name || !pfx) return; fields.push_back ({{name}, {pfx}, (bool)mu_msg_field_xapian_index(id), id}); } static std::vector<FieldInfo> process_field (const std::string& field, Flags flags) { std::vector<FieldInfo> fields; if (any_of(flags & Flags::UnitTest)) { add_field(fields, MU_MSG_FIELD_ID_MSGID); return fields; } if (field == "contact" || field == "recip") { // multi fields add_field (fields, MU_MSG_FIELD_ID_TO); add_field (fields, MU_MSG_FIELD_ID_CC); add_field (fields, MU_MSG_FIELD_ID_BCC); if (field == "contact") add_field (fields, MU_MSG_FIELD_ID_FROM); } else if (field == "") { add_field (fields, MU_MSG_FIELD_ID_TO); add_field (fields, MU_MSG_FIELD_ID_CC); add_field (fields, MU_MSG_FIELD_ID_BCC); add_field (fields, MU_MSG_FIELD_ID_FROM); add_field (fields, MU_MSG_FIELD_ID_SUBJECT); add_field (fields, MU_MSG_FIELD_ID_BODY_TEXT); } else { const auto id = field_id (field); if (id != MU_MSG_FIELD_ID_NONE) add_field (fields, id); } return fields; } static bool is_range_field (const std::string& field) { const auto id = field_id (field); if (id == MU_MSG_FIELD_ID_NONE) return false; else return mu_msg_field_is_range_field (id); } struct MyRange { std::string lower; std::string upper; }; static MyRange process_range (const std::string& field, const std::string& lower, const std::string& upper) { const auto id = field_id (field); if (id == MU_MSG_FIELD_ID_NONE) return { lower, upper }; std::string l2 = lower; std::string u2 = upper; if (id == MU_MSG_FIELD_ID_DATE) { l2 = Mu::date_to_time_t_string (lower, true); u2 = Mu::date_to_time_t_string (upper, false); } else if (id == MU_MSG_FIELD_ID_SIZE) { l2 = Mu::size_to_string (lower, true); u2 = Mu::size_to_string (upper, false); } return { l2, u2 }; } std::vector<std::string> Parser::Private::process_regex (const std::string& field, const std::regex& rx) const { const auto id = field_id (field); if (id == MU_MSG_FIELD_ID_NONE) return {}; char pfx[] = { mu_msg_field_shortcut(id), '\0' }; std::vector<std::string> terms; store_.for_each_term(pfx,[&](auto&& str){ if (std::regex_search(str.c_str() + 1, rx)) // avoid copy terms.emplace_back(str); return true; }); return terms; } static Token look_ahead (const Mu::Tokens& tokens) { return tokens.front(); } static Mu::Tree empty() { return {{Node::Type::Empty}}; } Mu::Tree Parser::Private::value (const FieldInfoVec& fields, const std::string& v, size_t pos, WarningVec& warnings) const { auto val = utf8_flatten(v); if (fields.empty()) throw BUG("expected one or more fields"); if (fields.size() == 1) { const auto item = fields.front(); return Tree({Node::Type::Value, std::make_unique<Value>( item.field, item.prefix, item.id, process_value(item.field, val), item.supports_phrase)}); } // a 'multi-field' such as "recip:" Tree tree(Node{Node::Type::OpOr}); for (const auto& item: fields) tree.add_child (Tree({Node::Type::Value, std::make_unique<Value>( item.field, item.prefix, item.id, process_value(item.field, val), item.supports_phrase)})); return tree; } Mu::Tree Parser::Private::regex (const FieldInfoVec& fields, const std::string& v, size_t pos, WarningVec& warnings) const { if (v.length() < 2) throw BUG("expected regexp, got '%s'", v.c_str()); const auto rxstr = utf8_flatten(v.substr(1, v.length()-2)); try { Tree tree(Node{Node::Type::OpOr}); const auto rx = std::regex (rxstr); for (const auto& field: fields) { const auto terms = process_regex (field.field, rx); for (const auto& term: terms) { tree.add_child (Tree( {Node::Type::Value, std::make_unique<Value>(field.field, "", field.id, term)})); } } if (tree.children.empty()) return empty(); else return tree; } catch (...) { // fallback warnings.push_back ({pos, "invalid regexp"}); return value (fields, v, pos, warnings); } } Mu::Tree Parser::Private::range (const FieldInfoVec& fields, const std::string& lower, const std::string& upper, size_t pos, WarningVec& warnings) const { if (fields.empty()) throw BUG("expected field"); const auto& field = fields.front(); if (!is_range_field(field.field)) return value (fields, lower + ".." + upper, pos, warnings); auto prange = process_range (field.field, lower, upper); if (prange.lower > prange.upper) prange = process_range (field.field, upper, lower); return Tree({Node::Type::Range, std::make_unique<Range>(field.field, field.prefix, field.id, prange.lower, prange.upper)}); } Mu::Tree Parser::Private::data (Mu::Tokens& tokens, WarningVec& warnings) const { const auto token = look_ahead(tokens); if (token.type != Token::Type::Data) warnings.push_back ({token.pos, "expected: value"}); tokens.pop_front(); std::string field, val; const auto col = token.str.find (":"); if (col != 0 && col != std::string::npos && col != token.str.length()-1) { field = token.str.substr(0, col); val = token.str.substr(col + 1); } else val = token.str; auto fields = process_field (field, flags_); if (fields.empty()) {// not valid field... warnings.push_back ({token.pos, format ("invalid field '%s'", field.c_str())}); fields = process_field ("", flags_); // fallback, treat the whole of foo:bar as a value return value (fields, field + ":" + val, token.pos, warnings); } // does it look like a regexp? if (val.length() >= 2) if (val[0] == '/' && val[val.length()-1] == '/') return regex (fields, val, token.pos, warnings); // does it look like a range? const auto dotdot = val.find(".."); if (dotdot != std::string::npos) return range(fields, val.substr(0, dotdot), val.substr(dotdot + 2), token.pos, warnings); else if (is_range_field(fields.front().field)) { // range field without a range - treat as field:val..val return range (fields, val, val, token.pos, warnings); } // if nothing else, it's a value. return value (fields, val, token.pos, warnings); } Mu::Tree Parser::Private::unit (Mu::Tokens& tokens, WarningVec& warnings) const { if (tokens.empty()) { warnings.push_back ({0, "expected: unit"}); return empty(); } const auto token = look_ahead (tokens); if (token.type == Token::Type::Not) { tokens.pop_front(); Tree tree{{Node::Type::OpNot}}; tree.add_child(unit (tokens, warnings)); return tree; } if (token.type == Token::Type::Open) { tokens.pop_front(); auto tree = term_1 (tokens, warnings); if (tokens.empty()) warnings.push_back({token.pos, "expected: ')'"}); else { const auto token2 = look_ahead(tokens); if (token2.type == Token::Type::Close) tokens.pop_front(); else { warnings.push_back( {token2.pos, std::string("expected: ')' but got ") + token2.str}); } } return tree; } return data (tokens, warnings); } Mu::Tree Parser::Private::factor_2 (Mu::Tokens& tokens, Node::Type& op, WarningVec& warnings) const { if (tokens.empty()) return empty(); const auto token = look_ahead(tokens); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (token.type) { case Token::Type::And: { tokens.pop_front(); op = Node::Type::OpAnd; } break; case Token::Type::Open: case Token::Type::Data: case Token::Type::Not: op = Node::Type::OpAnd; // implicit AND break; default: return empty(); } #pragma GCC diagnostic pop return factor_1 (tokens, warnings); } Mu::Tree Parser::Private::factor_1 (Mu::Tokens& tokens, WarningVec& warnings) const { Node::Type op { Node::Type::Invalid }; auto t = unit (tokens, warnings); auto a2 = factor_2 (tokens, op, warnings); if (a2.empty()) return t; Tree tree {{op}}; tree.add_child(std::move(t)); tree.add_child(std::move(a2)); return tree; } Mu::Tree Parser::Private::term_2 (Mu::Tokens& tokens, Node::Type& op, WarningVec& warnings) const { if (tokens.empty()) return empty(); const auto token = look_ahead (tokens); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (token.type) { case Token::Type::Or: op = Node::Type::OpOr; break; case Token::Type::Xor: op = Node::Type::OpXor; break; default: if (token.type != Token::Type::Close) warnings.push_back({token.pos, "expected OR|XOR"}); return empty(); } #pragma GCC diagnostic pop tokens.pop_front(); return term_1 (tokens, warnings); } Mu::Tree Parser::Private::term_1 (Mu::Tokens& tokens, WarningVec& warnings) const { Node::Type op { Node::Type::Invalid }; auto t = factor_1 (tokens, warnings); auto o2 = term_2 (tokens, op, warnings); if (o2.empty()) return t; else { Tree tree {{op}}; tree.add_child(std::move(t)); tree.add_child(std::move(o2)); return tree; } } Mu::Parser::Parser(const Store& store, Flags flags): priv_{std::make_unique<Private>(store, flags)} {} Mu::Parser::~Parser() = default; Mu::Tree Mu::Parser::parse (const std::string& expr, WarningVec& warnings) const { try { auto tokens = tokenize (expr); if (tokens.empty()) return empty (); else return priv_->term_1 (tokens, warnings); } catch (const std::runtime_error& ex) { std::cerr << ex.what() << std::endl; return empty(); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-parser.hh��������������������������������������������������������������������������0000664�0000000�0000000�00000005074�14143670036�0015234�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef __PARSER_HH__ #define __PARSER_HH__ #include "utils/mu-utils.hh" #include <string> #include <vector> #include <memory> #include <mu-data.hh> #include <mu-tree.hh> #include <mu-store.hh> // A simple recursive-descent parser for queries. Follows the Xapian syntax, // but better handles non-alphanum; also implements regexp namespace Mu { /** * A parser warning * */ struct Warning { size_t pos{}; /**< pos in string */ const std::string msg; /**< warning message */ /** * operator== * * @param rhs right-hand side * * @return true if rhs is equal to this; false otherwise */ bool operator==(const Warning& rhs) const { return pos == rhs.pos && msg == rhs.msg; } }; using WarningVec=std::vector<Warning>; /** * operator<< * * @param os an output stream * @param w a warning * * @return the updated output stream */ inline std::ostream& operator<< (std::ostream& os, const Warning& w) { os << w.pos << ":" << w.msg; return os; } class Parser { public: enum struct Flags { None = 0, UnitTest = 1 << 0 }; /** * Construct a query parser object * * @param store a store object ptr, or none */ Parser(const Store& store, Flags=Flags::None); /** * DTOR * */ ~Parser(); /** * Parse a query string * * @param query a query string * @param warnings vec to receive warnings * * @return a parse-tree */ Tree parse (const std::string& query, WarningVec& warnings) const; private: struct Private; std::unique_ptr<Private> priv_; }; MU_ENABLE_BITOPS(Parser::Flags); } // namespace Mu #endif /* __PARSER_HH__ */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-match-deciders.cc������������������������������������������������������������0000664�0000000�0000000�00000021034�14143670036�0017737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-query-match-deciders.hh" #include "mu-query-results.hh" #include "utils/mu-option.hh" using namespace Mu; // We use a MatchDecider to gather information about the matches, and decide // whether to include them in the results. // // Note that to include the "related" messages, we need _two_ queries; the first // one to get the initial matches (called the Leader-Query) and a Related-Query, // to get the Leader matches + all messages that have a thread-id seen in the // Leader matches. // // We use the MatchDecider to gather information and use it for both queries. struct MatchDecider : public Xapian::MatchDecider { MatchDecider (QueryFlags qflags, DeciderInfo &info) : qflags_{qflags}, decider_info_{info} { } /** * Update the match structure with unreadable/duplicate flags * * @param doc a Xapian document. * * @return a new QueryMatch object */ QueryMatch make_query_match (const Xapian::Document &doc) const { QueryMatch qm{}; auto msgid{opt_string (doc, MU_MSG_FIELD_ID_MSGID) .value_or (*opt_string (doc, MU_MSG_FIELD_ID_PATH))}; if (!decider_info_.message_ids.emplace (std::move (msgid)).second) qm.flags |= QueryMatch::Flags::Duplicate; const auto path{opt_string (doc, MU_MSG_FIELD_ID_PATH)}; if (!path || ::access (path->c_str(), R_OK) != 0) qm.flags |= QueryMatch::Flags::Unreadable; return qm; } /** * Should this message be included in the results? * * @param qm a query match * * @return true or false */ bool should_include (const QueryMatch &qm) const { if (any_of (qflags_ & QueryFlags::SkipDuplicates) && any_of (qm.flags & QueryMatch::Flags::Duplicate)) return false; if (any_of (qflags_ & QueryFlags::SkipUnreadable) && any_of (qm.flags & QueryMatch::Flags::Unreadable)) return false; return true; } /** * Gather thread ids from this match. * * @param doc the document (message) * */ void gather_thread_ids (const Xapian::Document &doc) const { auto thread_id{opt_string (doc, MU_MSG_FIELD_ID_THREAD_ID)}; if (thread_id) decider_info_.thread_ids.emplace (std::move (*thread_id)); } protected: const QueryFlags qflags_; DeciderInfo & decider_info_; private: Option<std::string> opt_string (const Xapian::Document &doc, MuMsgFieldId id) const noexcept try { auto &&val{doc.get_value (id)}; return val.empty() ? Nothing : Some (val); } MU_XAPIAN_CATCH_BLOCK_RETURN (Nothing); }; struct MatchDeciderLeader final : public MatchDecider { MatchDeciderLeader (QueryFlags qflags, DeciderInfo &info) : MatchDecider (qflags, info) {} /** * operator() * * This receives the documents considered during a Xapian query, and * is to return either true (keep) or false (ignore) * * We use this to potentiallly avoid certain messages (documents): * - with QueryFlags::SkipUnreadable this will return false for message * that are not readable in the file-system * - with QueryFlags::SkipDuplicates this will return false for messages * whose message-id was seen before. * * Even if we do not skip these messages entirely, we remember whether * they were unreadable/duplicate (in the QueryMatch::Flags), so we can * quickly find that info when doing the second 'related' query. * * The "leader" query. Matches here get the Leader flag unless their * duplicates / unreadable. We check the duplicate/readable status * regardless of whether SkipDuplicates/SkipUnreadable was passed * (to gather that information); however those flags * affect our true/false verdict. * * @param doc xapian document * * @return true or false */ bool operator() (const Xapian::Document &doc) const override { // by definition, we haven't seen the docid before, // so no need to search auto it = decider_info_.matches.emplace (doc.get_docid(), make_query_match (doc)); it.first->second.flags |= QueryMatch::Flags::Leader; return should_include (it.first->second); } }; std::unique_ptr<Xapian::MatchDecider> Mu::make_leader_decider (QueryFlags qflags, DeciderInfo &info) { return std::make_unique<MatchDeciderLeader> (qflags, info); } struct MatchDeciderRelated final : public MatchDecider { MatchDeciderRelated (QueryFlags qflags, DeciderInfo &info) : MatchDecider (qflags, info) {} /** * operator() * * This receives the documents considered during a Xapian query, and * is to return either true (keep) or false (ignore) * * We use this to potentially avoid certain messages (documents): * - with QueryFlags::SkipUnreadable this will return false for message * that are not readable in the file-system * - with QueryFlags::SkipDuplicates this will return false for messages * whose message-id was seen before. * * Unlike in the "leader" decider (scroll up), we don't need to remember * messages we won't include. * * @param doc xapian document * * @return true or false */ bool operator() (const Xapian::Document &doc) const override { // we may have seen this match in the "Leader" query. const auto it = decider_info_.matches.find (doc.get_docid()); if (it != decider_info_.matches.end()) return should_include (it->second); auto qm{make_query_match (doc)}; if (should_include (qm)) { qm.flags = QueryMatch::Flags::Related; decider_info_.matches.emplace (doc.get_docid(), std::move (qm)); return true; } else return false; // nope. } }; std::unique_ptr<Xapian::MatchDecider> Mu::make_related_decider (QueryFlags qflags, DeciderInfo &info) { return std::make_unique<MatchDeciderRelated> (qflags, info); } struct MatchDeciderThread final : public MatchDecider { MatchDeciderThread (QueryFlags qflags, DeciderInfo &info) : MatchDecider{qflags, info} {} /** * operator() * * This receives the documents considered during a Xapian query, and * is to return either true (keep) or false (ignore) * * Only include documents that earlier checks have decided to include. * * @param doc xapian document * * @return true or false */ bool operator() (const Xapian::Document &doc) const override { // we may have seen this match in the "Leader" query, // or in the second (unbuounded) related query; const auto it{decider_info_.matches.find (doc.get_docid())}; return it != decider_info_.matches.end() && !it->second.thread_path.empty(); } }; std::unique_ptr<Xapian::MatchDecider> Mu::make_thread_decider (QueryFlags qflags, DeciderInfo &info) { return std::make_unique<MatchDeciderThread> (qflags, info); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-match-deciders.hh������������������������������������������������������������0000664�0000000�0000000�00000005115�14143670036�0017753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_QUERY_MATCH_DECIDERS_HH__ #define MU_QUERY_MATCH_DECIDERS_HH__ #include <unordered_set> #include <unordered_map> #include <memory> #include <xapian.h> #include "mu-query-results.hh" namespace Mu { using StringSet = std::unordered_set<std::string>; struct DeciderInfo { QueryMatches matches; StringSet thread_ids; StringSet message_ids; }; /** * Make a "leader" decider, that is, a MatchDecider for either a singular or the * first query in the leader/related pair of queries. Gather information for * threading, and the subsequent "related" query. * * @param qflags query flags * @param match_info receives information about the matches. * * @return a unique_ptr to a match decider. */ std::unique_ptr<Xapian::MatchDecider> make_leader_decider(QueryFlags qflags, DeciderInfo& info); /** * Make a "related" decider, that is, a MatchDecider for the second query * in the leader/related pair of queries. * * @param qflags query flags * @param match_info receives information about the matches. * * @return a unique_ptr to a match decider. */ std::unique_ptr<Xapian::MatchDecider> make_related_decider(QueryFlags qflags, DeciderInfo& info); /** * Make a "thread" decider, that is, a MatchDecider that removes all but the * document excepts for the ones found during initial/related searches. * * @param qflags query flags * @param match_info receives information about the matches. * * @return a unique_ptr to a match decider. */ std::unique_ptr<Xapian::MatchDecider> make_thread_decider (QueryFlags qflags, DeciderInfo& info); } // namepace Mu #endif /* MU_QUERY_MATCH_DECIDERS_HH__ */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-matches.hh�������������������������������������������������������������������0000664�0000000�0000000�00000015325�14143670036�0016527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_QUERY_MATCHES_HH__ #define MU_QUERY_MATCHES_HH__ #include <iterator> #include <xapian.h> #include <glib.h> #include <utils/mu-utils.hh> #include "mu-msg.h" namespace Mu { struct QueryMatchInfo { enum struct Flags { Seen, Preferred, Unreadable, Duplicate }; const std::string message_id; QueryMatchFlags flags; }; MU_ENABLE_BITOPS(QueryMatchInfo::Flags); using MatchInfo = std::unordered_map<Xapian::docid, QueryMatchInfo>; struct QueryResults { enum struct Flags { None, Descending, SkipUnreadable, SkipDups, DetermineThreads }; QueryResults (const Xapian::MSet& mset, MatchInfo&& match_info, Flags flags): mset_{mset}, match_info_(std::Move(match_info), flag_{flags} {} bool empty() const { return mset_.empty(); } size_t size() const { return mset_.size(); } QueryResultsIterator begin() const { return QueryResultsIterator(mset_.begin()); } QueryResultsIterator end() const { return QueryResultsIterator(mset_.end()); } private: const Xapian::MSet mset_; const Flags flags_; MatchInfo match_info_; }; /// /// This is a view over the Document MSet, which can optionally filter outlook /// unreadable / duplicate messages. /// class QueryResultsIterator { public: using iterator_category = std::output_iterator_tag; using value_type = MuMsg*; using difference_type = void; using pointer = void; using reference = void; QueryResultsIterator(Xapian::MSetIterator it, size_t max_num, MuMsgFieldId sort_field, MuMsgIterFlags flags, MatchInfo& minfo): it_{it}, match_info_{minfo} {} QueryResultsIterator& operator++() { return ++it_; return skip();} QueryResultsIterator& operator++(int) { return it_++; return skip()} /** * Get the Xapian document this iterator is pointing at, * or an empty document when looking at end(). * * @return a document */ Xapian::Document document() const() { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), {}); return it_.get_document(); } /** * Get the doc-id for the document this iterator is pointing at, or 0 * when looking at end. * * @return a doc-id. */ Xapian::docid doc_id() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), 0); return it_.docid(); } /** * Get the message-id for the document (message) this iterator is * pointing at, or "" when looking at end. * * @return a message-id */ std::string message_id() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), ""); return document().get_value(MU_MSG_FIELD_ID_MSGID); } /** * Get the file-system path for the document (message) this iterator is * pointing at, or "" when looking at end. * * @return a filesystem path */ std::string path() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), ""); return document().get_value(MU_MSG_FIELD_ID_PATH); } /** * Get the references for the document (messages) this is iterator is * pointing at, or empty if pointing at end of if no references are * available. * * @return references */ std::vector<std::string> references() const { g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), {}); return split(document().get_value(MU_MSG_FIELD_ID_REFS), ","); } private: /** * Filter out some documents * * @param forward whether to skip forward when a document is filtered * out. * * @return the first iterator that is not filtered out, or the end * iterator. */ QueryResultsIterator& maybe_skip(bool forward=true) { if (it_ = MSetIterator::end()) return *this; // nothing to do. // Find or create MatchInfo const auto msgid{message_id()}; auto mi=[&] { // seen before? auto m{match_info_.find(docid)}; if (m != match_info_.end()) return m; // nope; create. QueryMatchInfo minfo { message_id() }; // not seen before; check. if (any_of(flags_ & SkipDups) && match_info_.count(message_id())) minfo.flags |= Flags::Duplicate; // it's a duplicate if (any_of(flags_ & SkipUnreadable) && ::access(path().c_str(), R_OK) != 0) minfo.flags |= Flags::Unreadable; return match_info_.emplace_back(std::move(minfo)); }(); // note: SkipDups / SkipUnreadable are not set if // if we're not checking for those. if (any_of(mi->second.flags_ & SkipDups) || any_of(mi->second.flags_ & SkipUnreadable)) { if (forward) ++it_; else --it_; return maybe_skip(); } return *this; } Xapian::MSetIterator it_; MatchInfo& match_info_; }; }; // namespace Mu #endif /* MU_QUERY_MATCHES_HH__ */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-results.hh�������������������������������������������������������������������0000664�0000000�0000000�00000032653�14143670036�0016607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_QUERY_RESULTS_HH__ #define MU_QUERY_RESULTS_HH__ #include <algorithm> #include <limits> #include <string> #include <unordered_map> #include <unordered_set> #include <limits> #include <ostream> #include <cmath> #include <unistd.h> #include <fcntl.h> #include <xapian.h> #include <glib.h> #include <utils/mu-utils.hh> #include <utils/mu-option.hh> #include "mu-msg.hh" namespace Mu { /** * This implements a QueryResults structure, which capture the results of a * Xapian query, and a QueryResultsIterator, which gives C++-compliant iterator * to go over the results. and finally QueryThreader (in query-threader.cc) which * calculates the threads, using the JWZ algorithm. */ /// Flags that influence now matches are presented (or skipped) enum struct QueryFlags { None = 0, /**< no flags */ Descending = 1 << 0, /**< sort z->a */ SkipUnreadable = 1 << 1, /**< skip unreadable msgs */ SkipDuplicates = 1 << 2, /**< skip duplicate msgs */ IncludeRelated = 1 << 3, /**< include related msgs */ Threading = 1 << 4, /**< calculate threading info */ // internal Leader = 1 << 5, /**< This is the leader query (for internal use * only)*/ }; MU_ENABLE_BITOPS (QueryFlags); /// Stores all the essential information for sorting the results. struct QueryMatch { /// Flags for a match (message) found enum struct Flags { None = 0, /**< No Flags */ Leader = 1 << 0, /**< Mark direct matches as leader */ Related = 1 << 1, /**< A related message */ Unreadable = 1 << 2, /**< No readable file */ Duplicate = 1 << 3, /**< Message-id seen before */ Root = 1 << 10, /**< Is this the thread-root? */ First = 1 << 11, /**< Is this the first message in a thread? */ Last = 1 << 12, /**< Is this the last message in a thread? */ Orphan = 1 << 13, /**< Is this message without a parent? */ HasChild = 1 << 14, /**< Does this message have a child? */ ThreadSubject = 1 << 20, /**< Message holds subject for (sub)thread */ }; Flags flags{Flags::None}; /**< Flags */ std::string date_key; /**< The date-key (for sorting all sub-root levels) */ // the thread subject is the subject of the first message in a thread, // and any message that has a different subject compared to its predecessor // (ignoring prefixes such as Re:) // // otherwise, it is empty. std::string subject; /**< subject for this message */ size_t thread_level{}; /**< The thread level */ std::string thread_path; /**< The hex-numerial path in the thread, ie. '00:01:0a' */ std::string thread_date; /**< date of newest message in thread */ bool operator< (const QueryMatch &rhs) const { return date_key < rhs.date_key; } bool has_flag (Flags flag) const; }; MU_ENABLE_BITOPS (QueryMatch::Flags); inline bool QueryMatch::has_flag (QueryMatch::Flags flag) const { return any_of (flags & flag); } inline std::ostream & operator<< (std::ostream &os, QueryMatch::Flags mflags) { if (mflags == QueryMatch::Flags::None) { os << "<none>"; return os; } if (any_of (mflags & QueryMatch::Flags::Leader)) os << "leader "; if (any_of (mflags & QueryMatch::Flags::Unreadable)) os << "unreadable "; if (any_of (mflags & QueryMatch::Flags::Duplicate)) os << "dup "; if (any_of (mflags & QueryMatch::Flags::Root)) os << "root "; if (any_of (mflags & QueryMatch::Flags::Related)) os << "related "; if (any_of (mflags & QueryMatch::Flags::First)) os << "first "; if (any_of (mflags & QueryMatch::Flags::Last)) os << "last "; if (any_of (mflags & QueryMatch::Flags::Orphan)) os << "orphan "; if (any_of (mflags & QueryMatch::Flags::HasChild)) os << "has-child "; return os; } using QueryMatches = std::unordered_map<Xapian::docid, QueryMatch>; inline std::ostream & operator<< (std::ostream &os, const QueryMatch &qmatch) { os << "qm:[" << qmatch.thread_path << "]: " // " (" << qmatch.thread_level << "): " << "> date:<" << qmatch.date_key << "> " << "flags:{" << qmatch.flags << "}"; return os; } /// /// This is a view over the Xapian::MSet, which can optionally filter unreadable /// / duplicate messages. /// /// Note, we internally skip unreadable/duplicate messages (when asked too); those /// skipped ones do _not_ count towards the max_size /// class QueryResultsIterator { public: using iterator_category = std::output_iterator_tag; using value_type = MuMsg *; using difference_type = void; using pointer = void; using reference = void; QueryResultsIterator (Xapian::MSetIterator mset_it, QueryMatches &query_matches) : mset_it_{mset_it}, query_matches_{query_matches} { } ~QueryResultsIterator() { g_clear_pointer (&msg_, mu_msg_unref); } /** * Increment the iterator (we don't support post-increment) * * @return an updated iterator, or end() if we were already at end() */ QueryResultsIterator &operator++() { ++mset_it_; return *this; } /** * (Non)Equivalence operators * * @param rhs some other iterator * * @return true or false */ bool operator== (const QueryResultsIterator &rhs) const { return mset_it_ == rhs.mset_it_; } bool operator!= (const QueryResultsIterator &rhs) const { return mset_it_ != rhs.mset_it_; } QueryResultsIterator & operator*() { return *this; } const QueryResultsIterator &operator*() const { return *this; } /** * Get the Xapian document this iterator is pointing at, * or an empty document when looking at end(). * * @return a document */ Xapian::Document document() const { return mset_it_.get_document(); } /** * Get the doc-id for the document this iterator is pointing at, or 0 * when looking at end. * * @return a doc-id. */ Xapian::docid doc_id() const { return *mset_it_; } /** * Get the message-id for the document (message) this iterator is * pointing at, or not when not available * * @return a message-id */ Option<std::string> message_id() const noexcept { return opt_string (MU_MSG_FIELD_ID_MSGID); } /** * Get the thread-id for the document (message) this iterator is * pointing at, or Nothing. * * @return a message-id */ Option<std::string> thread_id() const noexcept { return opt_string (MU_MSG_FIELD_ID_THREAD_ID); } /** * Get the file-system path for the document (message) this iterator is * pointing at, or Nothing. * * @return a filesystem path */ Option<std::string> path() const noexcept { return opt_string (MU_MSG_FIELD_ID_PATH); } /** * Get the date for the document (message) the iterator is pointing at. * pointing at, or Nothing. * * @return a filesystem path */ Option<std::string> date() const noexcept { return opt_string (MU_MSG_FIELD_ID_DATE); } /** * Get the file-system path for the document (message) this iterator is * pointing at. * * @return the subject */ Option<std::string> subject() const noexcept { return opt_string (MU_MSG_FIELD_ID_SUBJECT); } /** * Get the references for the document (messages) this is iterator is * pointing at, or empty if pointing at end of if no references are * available. * * @return references */ std::vector<std::string> references() const noexcept { return split (document().get_value (MU_MSG_FIELD_ID_REFS), ","); } /** * Get some value from the document, or Nothing if empty. * * @param id a message field id * * @return the value */ Option<std::string> opt_string (MuMsgFieldId id) const noexcept try { auto &&val{document().get_value (id)}; return val.empty() ? Nothing : Some (val); } MU_XAPIAN_CATCH_BLOCK_RETURN (Nothing); /** * Get the Query match info for this message. * * @return the match info. */ QueryMatch &query_match() { g_assert (query_matches_.find (document().get_docid()) != query_matches_.end()); return query_matches_.find (document().get_docid())->second; } const QueryMatch &query_match() const { g_assert (query_matches_.find (document().get_docid()) != query_matches_.end()); return query_matches_.find (document().get_docid())->second; } /** * get the corresponding MuMsg for this iter; this instance is owned by * @this, and becomes invalid when iterating to the next, or @this is k * destroyed.; it's a 'floating' reference. * * @return a MuMsg* or NUL in case of error */ MuMsg *floating_msg() G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT try { auto docp{reinterpret_cast<XapianDocument *> (new Xapian::Document (document()))}; GError *err{}; g_clear_pointer (&msg_, mu_msg_unref); if (!(msg_ = mu_msg_new_from_doc (docp, &err))) { delete docp; g_warning ("failed to crate message for %s: %s", path().value_or ("<none>").c_str(), err ? err->message : "somethng went wrong"); g_clear_error (&err); } return msg_; } MU_XAPIAN_CATCH_BLOCK_RETURN (NULL); private: Xapian::MSetIterator mset_it_; QueryMatches & query_matches_; MuMsg * msg_{}; }; constexpr auto MaxQueryResultsSize = std::numeric_limits<size_t>::max(); class QueryResults { public: /// Helper types using iterator = QueryResultsIterator; using const_iterator = const iterator; /** * Construct a QueryResults object * * @param mset an Xapian::MSet with matches */ QueryResults (const Xapian::MSet &mset, QueryMatches &&query_matches) : mset_{mset}, query_matches_{std::move (query_matches)} { } /** * Is this QueryResults object empty (ie., no matches)? * * @return true are false */ bool empty() const { return mset_.empty(); } /** * Get the number of matches in this QueryResult * * @return number of matches */ size_t size() const { return mset_.size(); } /** * Get the begin iterator to the results. * * @return iterator */ iterator begin() { return QueryResultsIterator (mset_.begin(), query_matches_); } const iterator begin() const { return QueryResultsIterator (mset_.begin(), query_matches_); } /** * Get the end iterator to the results. * * @return iterator */ iterator end() { return QueryResultsIterator (mset_.end(), query_matches_); } const_iterator end() const { return QueryResultsIterator (mset_.end(), query_matches_); } /** * Get the query-matches for these QueryResults. The non-const * version can be use to _steal_ the query results, by moving * them. * * @return query-matches */ const QueryMatches &query_matches() const { return query_matches_; } QueryMatches & query_matches() { return query_matches_; } private: const Xapian::MSet mset_; mutable QueryMatches query_matches_; }; } // namespace Mu #endif /* MU_QUERY_RESULTS_HH__ */ �������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-threads.cc�������������������������������������������������������������������0000664�0000000�0000000�00000100570�14143670036�0016520�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-query-threads.hh" #include "mu-msg-fields.h" #include <set> #include <unordered_set> #include <list> #include <cassert> #include <cstring> #include <iostream> #include <iomanip> #include <utils/mu-option.hh> using namespace Mu; struct Container { using Containers = std::vector<Container*>; Container() = default; Container(Option<QueryMatch&> msg): query_match{msg} {} Container(const Container&) = delete; Container(Container&&) = default; void add_child (Container& new_child) { new_child.parent = this; children.emplace_back(&new_child); } void remove_child (Container& child) { children.erase(find_child(child)); assert(!has_child(child)); } Containers::iterator find_child (Container& child) { return std::find_if(children.begin(), children.end(), [&](auto&& c){ return c == &child; }); } Containers::const_iterator find_child (Container& child) const { return std::find_if(children.begin(), children.end(), [&](auto&& c){ return c == &child; }); } bool has_child (Container& child) const { return find_child(child) != children.cend(); } bool is_reachable(Container* other) const { auto up{ur_parent()}; return up && up == other->ur_parent(); } template <typename Func> void for_each_child (Func&& func) { auto it{children.rbegin()}; while (it != children.rend()) { auto next = std::next(it); func(*it); it = next; } } // During sorting, this is the cached value for the (recursive) date-key // of this container -- ie.. either the one from the first of its // children, or from its query-match, if it has no children. // // Note that the sub-root-levels of threads are always sorted by date, // in ascending order, regardless of whatever sorting was specified for // the root-level. std::string thread_date_key; Option<QueryMatch&> query_match; bool is_nuked{}; Container* parent{}; Containers children; using ContainerVec = std::vector<Container*>; private: const Container* ur_parent() const { assert(this->parent != this); return parent ? parent->ur_parent() : this; } }; using Containers = Container::Containers; using ContainerVec = Container::ContainerVec; static std::ostream& operator<<(std::ostream& os, const Container& container) { os << "container: " << std::right << std::setw(10) << &container << ": parent: " << std::right << std::setw(10) << container.parent << " [" << container.thread_date_key << "]" << "\n children: "; for (auto&& c: container.children) os << std::right << std::setw(10) << c << " "; os << (container.is_nuked ? " nuked" : ""); if (container.query_match) os << "\n " << container.query_match.value(); return os; } using IdTable = std::unordered_map<std::string, Container>; using DupTable = std::multimap<std::string, Container>; static void handle_duplicates (IdTable& id_table, DupTable& dup_table) { size_t n{}; for (auto&& dup: dup_table) { const auto msgid{dup.first}; auto it = id_table.find(msgid); if (it == id_table.end()) continue; // add duplicates as fake children char buf[32]; ::snprintf(buf, sizeof(buf), "dup-%zu", ++n); it->second.add_child( id_table.emplace(buf, std::move(dup.second)).first->second); } } template <typename QueryResultsType> static IdTable determine_id_table (QueryResultsType& qres) { // 1. For each query_match IdTable id_table; DupTable dups; for (auto&& mi: qres) { const auto msgid{mi.message_id().value_or(*mi.path())}; // Step 0 (non-JWZ): filter out dups, handle those at the end if (mi.query_match().has_flag(QueryMatch::Flags::Duplicate)) { dups.emplace(msgid, mi.query_match()); continue; } // 1.A If id_table contains an empty Container for this ID: // Store this query_match (query_match) in the Container's query_match (value) slot. // Else: // Create a new Container object holding this query_match (query-match); // Index the Container by Query_Match-ID auto c_it = id_table.find(msgid); auto& container = [&]()->Container& { if (c_it != id_table.end()) { if (!c_it->second.query_match) // hmm, dup? c_it->second.query_match = mi.query_match(); return c_it->second; } else { // Else: // Create a new Container object holding this query_match (query-match); // Index the Container by Query_Match-ID return id_table.emplace(msgid, mi.query_match()).first->second; } }(); // We sort by date (ascending), *except* for the root; we don't // know what query_matchs will be at the root level yet, so remember // both. Moreover, even when sorting the top-level in descending // order, still sort the thread levels below that in ascending // order. container.thread_date_key = container.query_match->date_key = mi.date().value_or(""); // initial guess for the thread-date; might be updated // later. // remember the subject, we use it to determine the (sub)thread subject container.query_match->subject = mi.subject().value_or(""); // 1.B // For each element in the query_match's References field: Container* parent_ref_container{}; for (const auto& ref: mi.references()) { // grand_<n>-parent -> grand_<n-1>-parent -> ... -> parent. // Find a Container object for the given Query_Match-ID; If it exists, use it; // otherwise make one with a null Query_Match. auto ref_container = [&]()->Container* { auto ref_it = id_table.find(ref); if (ref_it == id_table.end()) ref_it = id_table.emplace(ref,Nothing).first; return &ref_it->second; }(); // Link the References field's Containers together in the order implied // by the References header. // * If they are already linked, don't change the existing links. // // * Do not add a link if adding that link would introduce a loop: that is, // before asserting A->B, search down the children of B to see if A is // reachable, and also search down the children of A to see if B is // reachable. If either is already reachable as a child of the other, // don't add the link. if (parent_ref_container && !ref_container->parent) { if (!parent_ref_container->is_reachable(ref_container)) parent_ref_container->add_child(*ref_container); // else // g_message ("%u: reachable %s -> %s", __LINE__, msgid.c_str(), ref.c_str()); } parent_ref_container = ref_container; } // Add the query_match to the chain. if (parent_ref_container && !container.parent) { if (!parent_ref_container->is_reachable(&container)) parent_ref_container->add_child(container); // else // g_message ("%u: reachable %s -> parent", __LINE__, msgid.c_str()); } } // non-JWZ: add duplicate messages. handle_duplicates (id_table, dups); return id_table; } /// Recursively walk all containers under the root set. /// For each container: /// /// If it is an empty container with no children, nuke it. /// /// Note: Normally such containers won't occur, but they can show up when two /// query_matchs have References lines that disagree. For example, assuming A and /// B are query_matchs, and 1, 2, and 3 are references for query_matchs we haven't /// seen: /// /// A has references: 1, 2, 3 /// B has references: 1, 3 /// /// There is ambiguity as to whether 3 is a child of 1 or of 2. So, /// depending on the processing order, we might end up with either /// /// -- 1 /// |-- 2 /// \-- 3 /// |-- A /// \-- B /// /// or /// /// -- 1 /// |-- 2 <--- non root childless container! /// \-- 3 /// |-- A /// \-- B /// /// If the Container has no Query_Match, but does have children, remove this /// container but promote its children to this level (that is, splice them in /// to the current child list.) /// /// Do not promote the children if doing so would promote them to the root /// set -- unless there is only one child, in which case, do. static void prune (Container* child) { Container *container{child->parent}; for (auto& grandchild: child->children) { grandchild->parent = container; if (container) container->children.emplace_back(grandchild); } child->children.clear(); child->is_nuked = true; if (container) container->remove_child(*child); } static bool prune_empty_containers (Container& container) { Containers to_prune; container.for_each_child([&](auto& child){ if (prune_empty_containers(*child)) to_prune.emplace_back(child); }); for (auto& child: to_prune) prune (child); // Never nuke these. if (container.query_match) return false; // If it is an empty container with no children, nuke it. // // If the Container is empty, but does have children, remove this // container but promote its children to this level (that is, splice them in // to the current child list.) // // Do not promote the children if doing so would promote them to the root // set -- unless there is only one child, in which case, do. //const auto rootset_child{!container.parent->parent}; if (container.parent || container.children.size() <= 1) return true; // splice/nuke it. return false; } static void prune_empty_containers (IdTable& id_table) { for (auto&& item: id_table) { auto& child(item.second); if (child.parent) continue; // not a root child. if (prune_empty_containers(item.second)) prune(&child); } } // // Sorting. // /// Register some information about a match (i.e., message) that we can use for /// subsequent queries. using ThreadPath = std::vector<unsigned>; inline std::string to_string (const ThreadPath& tpath, size_t digits) { std::string str; str.reserve(tpath.size() * digits); bool first{true}; for (auto&& segm: tpath) { str += format("%s%0*x", first ? "" : ":", (int)digits, segm); first = false; } return str; } static bool // compare subjects, ignore anything before the last ':<space>*' subject_matches (const std::string& sub1, const std::string& sub2) { auto search_str =[](const std::string&s) -> const char* { const auto pos = s.find_last_of(':'); if (pos == std::string::npos) return s.c_str(); else { const auto pos2 = s.find_first_not_of(' ', pos + 1); return s.c_str() + (pos2 == std::string::npos ? pos : pos2); } }; //g_debug ("'%s' '%s'", search_str(sub1), search_str(sub2)); return g_strcmp0(search_str(sub1), search_str(sub2)) == 0; } static bool update_container (Container& container, bool descending, ThreadPath& tpath, size_t seg_size, const std::string& prev_subject="") { if (!container.children.empty()) { Container* first = container.children.front(); if (first->query_match) first->query_match->flags |= QueryMatch::Flags::First; Container* last = container.children.back(); if (last->query_match) last->query_match->flags |= QueryMatch::Flags::Last; } if (!container.query_match) return false; // nothing else to do. auto& qmatch(*container.query_match); if (!container.parent) qmatch.flags |= QueryMatch::Flags::Root; else if (!container.parent->query_match) qmatch.flags |= QueryMatch::Flags::Orphan; if (!container.children.empty()) qmatch.flags |= QueryMatch::Flags::HasChild; if (qmatch.has_flag(QueryMatch::Flags::Root) || prev_subject.empty() || !subject_matches(prev_subject, qmatch.subject)) qmatch.flags |= QueryMatch::Flags::ThreadSubject; if (descending && container.parent) { // trick xapian by giving it "inverse" sorting key so our // ascending-date sorted threads stay in that order tpath.back() = ((1U << (4 * seg_size)) - 1) - tpath.back(); } qmatch.thread_path = to_string(tpath, seg_size); qmatch.thread_level = tpath.size() - 1; // ensure thread root comes before its children if (descending) qmatch.thread_path += ":z"; return true; } static void update_containers (Containers& children, bool descending, ThreadPath& tpath, size_t seg_size, std::string& prev_subject) { size_t idx{0}; for (auto&& c: children) { tpath.emplace_back(idx++); if (c->query_match) { update_container(*c, descending, tpath, seg_size, prev_subject); prev_subject = c->query_match->subject; } update_containers(c->children, descending, tpath, seg_size, prev_subject); tpath.pop_back(); } } static void update_containers (ContainerVec& root_vec, bool descending, size_t n) { ThreadPath tpath; tpath.reserve (n); const auto seg_size = static_cast<size_t>(std::ceil(std::log2(n)/4.0)); /*note: 4 == std::log2(16)*/ size_t idx{0}; for (auto&& c: root_vec) { tpath.emplace_back(idx++); std::string prev_subject; if (update_container(*c, descending, tpath, seg_size)) prev_subject = c->query_match->subject; update_containers(c->children, descending, tpath, seg_size, prev_subject); tpath.pop_back(); } } static void sort_container (Container& container) { // 1. childless container. if (container.children.empty()) return; // no children; nothing to sort. // 2. container with children. // recurse, depth-first: sort the children for (auto& child: container.children) sort_container(*child); // now sort this level. std::sort(container.children.begin(), container.children.end(), [&](auto&& c1, auto&& c2) { return c1->thread_date_key < c2->thread_date_key; }); // and 'bubble up' the date of the *newest* message with a date. We // reasonably assume that it's later than its parent. const auto& newest_date = container.children.back()->thread_date_key; if (!newest_date.empty()) container.thread_date_key = newest_date; } static void sort_siblings (IdTable& id_table, bool descending) { if (id_table.empty()) return; // unsorted vec of root containers. We can // only sort these _after_ sorting the children. ContainerVec root_vec; for (auto&& item: id_table) { if (!item.second.parent && !item.second.is_nuked) root_vec.emplace_back(&item.second); } // now sort all threads _under_ the root set (by date/ascending) for (auto&& c: root_vec) sort_container(*c); // and then sort the root set. // // The difference with the sub-root containers is that at the top-level, // we can sort either in ascending or descending order, while on the // subroot level it's always in ascending order. // // Note that unless we're testing, _xapian_ will handle // the ascending/descending of the top level. std::sort(root_vec.begin(), root_vec.end(), [&](auto&& c1, auto&& c2) { #ifdef BUILD_TESTS if (descending) return c2->thread_date_key < c1->thread_date_key; else #endif /*BUILD_TESTS*/ return c1->thread_date_key < c2->thread_date_key; }); // now all is sorted... final step is to determine thread paths and // other flags. update_containers (root_vec, descending, id_table.size()); } static std::ostream& operator<<(std::ostream& os, const IdTable& id_table) { os << "------------------------------------------------\n"; for (auto&& item: id_table) { os << item.first << " => " << item.second << "\n"; } os << "------------------------------------------------\n"; std::set<std::string> ids; for (auto&& item: id_table) { if (item.second.query_match) ids.emplace(item.second.query_match->thread_path); } for (auto&& id: ids) { auto it = std::find_if(id_table.begin(), id_table.end(), [&](auto&& item) { return item.second.query_match && item.second.query_match->thread_path == id; }); assert(it != id_table.end()); os << it->first << ": " << it->second << '\n'; } return os; } template<typename Results> static void calculate_threads_real (Results& qres, bool descending) { // Step 1: build the id_table auto id_table{determine_id_table(qres)}; if (g_test_verbose()) std::cout << "*** id-table(1):\n" << id_table << "\n"; // // Step 2: get the root set // // Step 3: discard id_table // Nope: id-table owns the containers. // Step 4: prune empty containers prune_empty_containers(id_table); // Step 5: group root-set by subject. // Not implemented. // Step 6: we're done threading // Step 7: sort siblings. The segment-size is the number of hex-digits // in the thread-path string (so we can lexically compare them.) sort_siblings(id_table, descending); // Step 7a:. update querymatches for (auto&& item: id_table) { Container& c{item.second}; if (c.query_match) c.query_match->thread_date = c.thread_date_key; } // if (g_test_verbose()) // std::cout << "*** id-table(2):\n" << id_table << "\n"; } void Mu::calculate_threads (Mu::QueryResults& qres, bool descending) { calculate_threads_real(qres, descending); } #ifdef BUILD_TESTS struct MockQueryResult { MockQueryResult(const std::string& message_id_arg, const std::string& date_arg, const std::vector<std::string>& refs_arg={}): message_id_{message_id_arg}, date_{date_arg}, refs_{refs_arg} {} MockQueryResult(const std::string& message_id_arg, const std::vector<std::string>& refs_arg={}): MockQueryResult(message_id_arg, "", refs_arg) {} Option<std::string> message_id() const { return message_id_;} Option<std::string> path() const { return path_;} Option<std::string> date() const { return date_;} Option<std::string> subject() const { return subject_;} QueryMatch& query_match() { return query_match_;} const QueryMatch& query_match() const { return query_match_;} const std::vector<std::string>& references() const { return refs_;} std::string path_; std::string message_id_; QueryMatch query_match_{}; std::string date_; std::string subject_; std::vector<std::string> refs_; }; using MockQueryResults = std::vector<MockQueryResult>; G_GNUC_UNUSED static std::ostream& operator<<(std::ostream& os, const MockQueryResults& qrs) { for (auto&& mi: qrs) os << mi.query_match().thread_path << " :: " << mi.message_id().value_or("<none>") << std::endl; return os; } static void calculate_threads (MockQueryResults& qres, bool descending) { calculate_threads_real(qres, descending); } using Expected = std::vector<std::pair<std::string, std::string>>; static void assert_thread_paths (const MockQueryResults& qrs, const Expected& expected) { for (auto&& exp: expected) { auto it = std::find_if(qrs.begin(), qrs.end(), [&](auto&& qr){ return qr.message_id().value_or("") == exp.first || qr.path().value_or("") == exp.first; }); g_assert_true (it != qrs.end()); g_assert_cmpstr(exp.second.c_str(), ==, it->query_match().thread_path.c_str()); } } static void test_sort_ascending() { auto results = MockQueryResults { MockQueryResult{ "m1", "1", {"m2"} }, MockQueryResult{ "m2", "2", {"m3"} }, MockQueryResult{ "m3", "3", {}}, MockQueryResult{ "m4", "4", {}} }; calculate_threads(results, false); assert_thread_paths (results, { { "m1", "0:0:0"}, { "m2", "0:0" }, { "m3", "0" }, { "m4", "1" } }); } static void test_sort_descending() { auto results = MockQueryResults { MockQueryResult{ "m1", "1", {"m2"} }, MockQueryResult{ "m2", "2", {"m3"} }, MockQueryResult{ "m3", "3", {}}, MockQueryResult{ "m4", "4", {}} }; calculate_threads(results, true); assert_thread_paths (results, { { "m1", "1:f:f:z"}, { "m2", "1:f:z" }, { "m3", "1:z" }, { "m4", "0:z" } }); } static void test_id_table_inconsistent() { auto results = MockQueryResults { MockQueryResult{ "m1", "1", {"m2"} }, // 1->2 MockQueryResult{ "m2", "2", {"m1"} }, // 2->1 MockQueryResult{ "m3", "3", {"m3"} }, // self ref MockQueryResult{ "m4", "4", {"m3", "m5"} }, MockQueryResult{ "m5", "5", {"m4", "m4"} }, // dup parent }; calculate_threads(results, false); assert_thread_paths (results, { { "m2", "0"}, { "m1", "0:0"}, { "m3", "1"}, { "m5", "1:0"}, { "m4", "1:0:0"}, }); } static void test_dups_dup_last() { MockQueryResult r1 { "m1", "1", {} }; r1.query_match().flags |= QueryMatch::Flags::Leader; r1.path_ = "/path1"; MockQueryResult r1_dup { "m1", "1", {} }; r1_dup.query_match().flags |= QueryMatch::Flags::Duplicate; r1_dup.path_ = "/path2"; auto results = MockQueryResults {r1, r1_dup }; calculate_threads(results, false); assert_thread_paths (results, { { "/path1", "0"}, { "/path2", "0:0" }, }); } static void test_dups_dup_first() { // now dup becomes the leader; this will _demote_ // r1. MockQueryResult r1_dup { "m1", "1", {} }; r1_dup.query_match().flags |= QueryMatch::Flags::Duplicate; r1_dup.path_ = "/path1"; MockQueryResult r1 { "m1", "1", {} }; r1.query_match().flags |= QueryMatch::Flags::Leader; r1.path_ = "/path2"; auto results = MockQueryResults { r1_dup, r1 }; calculate_threads(results, false); assert_thread_paths (results, { { "/path2", "0"}, { "/path1", "0:0" }, }); } static void test_do_not_prune_root_empty_with_children() { // m7 should not be nuked auto results = MockQueryResults { MockQueryResult{ "x1", "1", {"m7"} }, MockQueryResult{ "x2", "2", {"m7"} }, }; calculate_threads(results, false); assert_thread_paths (results, { { "x1", "0:0"}, { "x2", "0:1" }, }); } static void test_prune_root_empty_with_child() { // m7 should be nuked auto results = MockQueryResults { MockQueryResult{ "m1", "1", {"m7"} }, }; calculate_threads(results, false); assert_thread_paths (results, { { "m1", "0"}, }); } static void test_prune_empty_with_children() { // m6 should be nuked auto results = MockQueryResults { MockQueryResult{ "m1", "1", {"m7", "m6"} }, MockQueryResult{ "m2", "2", {"m7", "m6"} }, }; calculate_threads(results, false); assert_thread_paths (results, { { "m1", "0:0"}, { "m2", "0:1" }, }); } static void test_thread_info_ascending() { auto results = MockQueryResults { MockQueryResult{ "m1", "5", {}}, MockQueryResult{ "m2", "1", {}}, MockQueryResult{ "m3", "3", {"m2"}}, MockQueryResult{ "m4", "2", {"m2"}}, // orphan siblings MockQueryResult{ "m10", "6", {"m9"}}, MockQueryResult{ "m11", "7", {"m9"}}, }; calculate_threads(results, false); assert_thread_paths (results, { { "m2", "0" }, // 2 { "m4", "0:0" }, // 2 { "m3", "0:1" }, // 3 { "m1", "1" }, // 5 { "m10", "2:0" }, // 6 { "m11", "2:1" }, // 7 }); g_assert_true (results[0].query_match().has_flag( QueryMatch::Flags::Root)); g_assert_true (results[1].query_match().has_flag( QueryMatch::Flags::Root | QueryMatch::Flags::HasChild)); g_assert_true (results[2].query_match().has_flag( QueryMatch::Flags::Last)); g_assert_true (results[3].query_match().has_flag( QueryMatch::Flags::First)); g_assert_true (results[4].query_match().has_flag( QueryMatch::Flags::Orphan | QueryMatch::Flags::First)); g_assert_true (results[5].query_match().has_flag( QueryMatch::Flags::Orphan | QueryMatch::Flags::Last)); } static void test_thread_info_descending() { auto results = MockQueryResults { MockQueryResult{ "m1", "5", {}}, MockQueryResult{ "m2", "1", {}}, MockQueryResult{ "m3", "3", {"m2"}}, MockQueryResult{ "m4", "2", {"m2"}}, // orphan siblings MockQueryResult{ "m10", "6", {"m9"}}, MockQueryResult{ "m11", "7", {"m9"}}, }; calculate_threads(results, true/*descending*/); assert_thread_paths (results, { { "m1", "1:z" }, // 5 { "m2", "2:z" }, // 2 { "m4", "2:f:z" }, // 2 { "m3", "2:e:z" }, // 3 { "m10", "0:f:z" }, // 6 { "m11", "0:e:z" }, // 7 }); g_assert_true (results[0].query_match().has_flag( QueryMatch::Flags::Root)); g_assert_true (results[1].query_match().has_flag( QueryMatch::Flags::Root | QueryMatch::Flags::HasChild)); g_assert_true (results[2].query_match().has_flag( QueryMatch::Flags::Last)); g_assert_true (results[3].query_match().has_flag( QueryMatch::Flags::First)); g_assert_true (results[4].query_match().has_flag( QueryMatch::Flags::Orphan | QueryMatch::Flags::Last)); g_assert_true (results[5].query_match().has_flag( QueryMatch::Flags::Orphan | QueryMatch::Flags::First)); } int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); g_test_add_func ("/threader/sort/ascending", test_sort_ascending); g_test_add_func ("/threader/sort/decending", test_sort_descending); g_test_add_func ("/threader/id-table-inconsistent", test_id_table_inconsistent); g_test_add_func ("/threader/dups/dup-last", test_dups_dup_last); g_test_add_func ("/threader/dups/dup-first", test_dups_dup_first); g_test_add_func ("/threader/prune/do-not-prune-root-empty-with-children", test_do_not_prune_root_empty_with_children); g_test_add_func ("/threader/prune/prune-root-empty-with-child", test_prune_root_empty_with_child); g_test_add_func ("/threader/prune/prune-empty-with-children", test_prune_empty_with_children); g_test_add_func ("/threader/thread-info/ascending", test_thread_info_ascending); g_test_add_func ("/threader/thread-info/descending", test_thread_info_descending); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } catch (...) { std::cerr << "caught exception\n"; return 1; } #endif /*BUILD_TESTS*/ ����������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query-threads.hh�������������������������������������������������������������������0000664�0000000�0000000�00000002563�14143670036�0016535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_QUERY_THREADS__ #define MU_QUERY_THREADS__ #include "mu-query-results.hh" namespace Mu { /** * Calculate the threads for these query results; that is, determine the * thread-paths for each message, so we can let Xapian order them in the correct * order. * * Note - threads are sorted chronologically, and the messages below the top * level are always sorted in ascending orde * * @param qres query results * @param descending whether to sort the top-level in descending order */ void calculate_threads (QueryResults& qres, bool descending); } // namespace Mu #endif /*MU_QUERY_THREADS__*/ ���������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query.cc���������������������������������������������������������������������������0000664�0000000�0000000�00000025253�14143670036�0015074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <mu-query.hh> #include <stdexcept> #include <string> #include <cctype> #include <cstring> #include <sstream> #include <cmath> #include <stdlib.h> #include <xapian.h> #include <glib/gstdio.h> #include "mu-msg-fields.h" #include "mu-query-results.hh" #include "mu-query-match-deciders.hh" #include "mu-query-threads.hh" #include <mu-xapian.hh> using namespace Mu; struct Query::Private { Private(const Store& store): store_{store}, parser_{store_} {} // New //bool calculate_threads (Xapian::Enquire& enq, size maxnum); Xapian::Enquire make_enquire (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags) const; Xapian::Enquire make_related_enquire (const StringSet& thread_ids, MuMsgFieldId sortfieldid, QueryFlags qflags) const; Option<QueryResults> run_threaded (QueryResults&& qres, Xapian::Enquire& enq, QueryFlags qflags, size_t maxnum) const; Option<QueryResults> run_singular (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const; Option<QueryResults> run_related (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const; Option<QueryResults> run (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const; const Store& store_; const Parser parser_; }; Query::Query(const Store& store): priv_{std::make_unique<Private>(store)} {} Query::Query(Query&& other) = default; Query::~Query() = default; static Xapian::Enquire& maybe_sort (Xapian::Enquire& enq, MuMsgFieldId sortfieldid, QueryFlags qflags) { if (sortfieldid != MU_MSG_FIELD_ID_NONE) enq.set_sort_by_value(static_cast<Xapian::valueno>(sortfieldid), any_of(qflags & QueryFlags::Descending)); return enq; } Xapian::Enquire Query::Private::make_enquire (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags) const { Xapian::Enquire enq{store_.database()}; if (expr.empty() || expr == R"("")") enq.set_query(Xapian::Query::MatchAll); else { WarningVec warns; const auto tree{parser_.parse(expr, warns)}; for (auto&& w: warns) g_warning ("query warning: %s", to_string(w).c_str()); enq.set_query(xapian_query(tree)); g_debug ("qtree: %s", to_string(tree).c_str()); } return maybe_sort (enq, sortfieldid, qflags); } Xapian::Enquire Query::Private::make_related_enquire (const StringSet& thread_ids, MuMsgFieldId sortfieldid, QueryFlags qflags) const { Xapian::Enquire enq{store_.database()}; static std::string pfx (1, mu_msg_field_xapian_prefix(MU_MSG_FIELD_ID_THREAD_ID)); std::vector<Xapian::Query> qvec; for (auto&& t: thread_ids) qvec.emplace_back(pfx + t); Xapian::Query qr{Xapian::Query::OP_OR, qvec.begin(), qvec.end()}; enq.set_query(qr); return maybe_sort (enq, sortfieldid, qflags); } struct ThreadKeyMaker: public Xapian::KeyMaker { ThreadKeyMaker (const QueryMatches& matches): match_info_(matches) {} std::string operator()(const Xapian::Document& doc) const override { const auto it{match_info_.find(doc.get_docid())}; return (it == match_info_.end()) ? "" : it->second.thread_path; } const QueryMatches& match_info_; }; Option<QueryResults> Query::Private::run_threaded (QueryResults&& qres, Xapian::Enquire& enq, QueryFlags qflags, size_t maxnum) const { const auto descending{any_of(qflags & QueryFlags::Descending)}; calculate_threads(qres, descending); ThreadKeyMaker key_maker{qres.query_matches()}; enq.set_sort_by_key(&key_maker, descending); DeciderInfo minfo; minfo.matches = qres.query_matches(); auto mset{enq.get_mset(0, maxnum, {}, make_thread_decider(qflags, minfo).get())}; mset.fetch(); return QueryResults{mset, std::move(qres.query_matches())}; } Option<QueryResults> Query::Private::run_singular (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const { // i.e. a query _without_ related messages, but still possibly // with threading. // // In the threading case, the sortfield-id is ignored, we always sort by // date (since threading the threading results are always by date.) const auto singular_qflags{qflags | QueryFlags::Leader}; const auto threading{any_of(qflags & QueryFlags::Threading)}; DeciderInfo minfo{}; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wextra" auto enq{make_enquire(expr, threading ? MU_MSG_FIELD_ID_DATE : sortfieldid, qflags)}; #pragma GCC diagnostic ignored "-Wswitch-default" #pragma GCC diagnostic pop auto mset{enq.get_mset(0, maxnum, {}, make_leader_decider(singular_qflags, minfo).get())}; mset.fetch(); auto qres{QueryResults{mset, std::move(minfo.matches)}}; return threading ? run_threaded(std::move(qres), enq, qflags, maxnum) : qres; } static Option<std::string> opt_string(const Xapian::Document& doc, MuMsgFieldId id) noexcept try { auto&& val{doc.get_value(id)}; return val.empty() ? Nothing : Some(val); } MU_XAPIAN_CATCH_BLOCK_RETURN (Nothing); Option<QueryResults> Query::Private::run_related (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const { // i.e. a query _with_ related messages and possibly with threading. // // In the threading case, the sortfield-id is ignored, we always sort by // date (since threading the threading results are always by date.); // moreover, in either threaded or non-threaded case, we sort the first // ("leader") query by date, i.e, we prefer the newest or oldest // (descending) messages. const auto leader_qflags{QueryFlags::Leader | qflags}; const auto threading{any_of(qflags & QueryFlags::Threading)}; // Run our first, "leader" query DeciderInfo minfo{}; auto enq{make_enquire(expr, MU_MSG_FIELD_ID_DATE, leader_qflags)}; const auto mset{enq.get_mset(0, maxnum, {}, make_leader_decider(leader_qflags, minfo).get())}; // Gather the thread-ids we found mset.fetch(); for (auto it = mset.begin(); it != mset.end(); ++it) { auto thread_id{opt_string(it.get_document(), MU_MSG_FIELD_ID_THREAD_ID)}; if (thread_id) minfo.thread_ids.emplace(std::move(*thread_id)); } // Now, determine the "related query". // // In the threaded-case, we search among _all_ messages, since complete // threads are preferred; no need to sort in that case since the search // is unlimited and the sorting happens during threading. auto r_enq{make_related_enquire(minfo.thread_ids, threading ? MU_MSG_FIELD_ID_NONE : sortfieldid, qflags)}; const auto r_mset{r_enq.get_mset(0, threading ? store_.size() : maxnum, {}, make_related_decider(qflags, minfo).get())}; auto qres{QueryResults{r_mset, std::move(minfo.matches)}}; return threading ? run_threaded(std::move(qres), r_enq, qflags, maxnum) : qres; } Option<QueryResults> Query::Private::run (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const { const auto eff_maxnum{maxnum == 0 ? store_.size() : maxnum}; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wextra" const auto eff_sortfield{sortfieldid == MU_MSG_FIELD_ID_NONE ? MU_MSG_FIELD_ID_DATE : sortfieldid }; #pragma GCC diagnostic pop if (any_of(qflags & QueryFlags::IncludeRelated)) return run_related (expr, eff_sortfield, qflags, eff_maxnum); else return run_singular(expr, eff_sortfield, qflags, eff_maxnum); } Option<QueryResults> Query::run (const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags, size_t maxnum) const try { // some flags are for internal use only. g_return_val_if_fail (none_of(qflags & QueryFlags::Leader), Nothing); StopWatch sw{format("ran query '%s'; related: %s; threads: %s; max-size: %zu", expr.c_str(), any_of(qflags & QueryFlags::IncludeRelated) ? "yes" : "no", any_of(qflags & QueryFlags::Threading) ? "yes" : "no", maxnum)}; return priv_->run(expr, sortfieldid, qflags, maxnum); } catch (...) { return Nothing; } size_t Query::count (const std::string& expr) const try { const auto enq{priv_->make_enquire(expr, MU_MSG_FIELD_ID_NONE, {})}; auto mset{enq.get_mset(0, priv_->store_.size())}; mset.fetch(); return mset.size(); }MU_XAPIAN_CATCH_BLOCK_RETURN (0); std::string Query::parse (const std::string& expr, bool xapian) const { WarningVec warns; const auto tree{priv_->parser_.parse(expr, warns)}; for (auto&& w: warns) g_warning ("query warning: %s", to_string(w).c_str()); if (xapian) return xapian_query(tree).get_description(); else return to_string(tree); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-query.hh���������������������������������������������������������������������������0000664�0000000�0000000�00000005520�14143670036�0015101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_QUERY_HH__ #define __MU_QUERY_HH__ #include <memory> #include <glib.h> #include <mu-store.hh> #include <mu-query-results.hh> #include <utils/mu-utils.hh> namespace Mu { class Query { public: /** * Construct a new Query instance. * * @param store a MuStore object */ Query (const Store &store); /** * DTOR * */ ~Query(); /** * Move CTOR * * @param other */ Query (Query &&other); /** * Run a query on the store * * @param expr the search expression * @param sortfieldid the sortfield-id. If the field is NONE, sort by DATE * @param flags query flags * @param maxnum maximum number of results to return. 0 for 'no limit' * * @return the query-results, or Nothing in case of error. */ Option<QueryResults> run (const std::string &expr = "", MuMsgFieldId sortfieldid = MU_MSG_FIELD_ID_NONE, QueryFlags flags = QueryFlags::None, size_t maxnum = 0) const; /** * run a Xapian query to count the number of matches; for the syntax, please * refer to the mu-query manpage * * @param expr the search expression; use "" to match all messages * * @return the number of matches */ size_t count (const std::string &expr = "") const; /** * For debugging, get the internal string representation of the parsed * query * * @param expr a xapian search expression * @param xapian if true, show Xapian's internal representation, * otherwise, mu's. * @return the string representation of the query */ std::string parse (const std::string &expr, bool xapian) const; private: struct Private; std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /*__MU_QUERY_HH__*/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-runtime.cc�������������������������������������������������������������������������0000664�0000000�0000000�00000007637�14143670036�0015420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2019-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-runtime.hh" #include "utils/mu-util.h" #include "utils/mu-logger.hh" #include <locale.h> /* for setlocale() */ #include <string> #include <unordered_map> static std::unordered_map<MuRuntimePath, std::string> RuntimePaths; constexpr auto PartsDir = "parts"; constexpr auto LogDir = "log"; constexpr auto XapianDir = "xapian"; constexpr auto MuName = "mu"; constexpr auto Bookmarks = "bookmarks"; static const std::string Sepa{G_DIR_SEPARATOR_S}; static void init_paths_xdg () { RuntimePaths.emplace(MU_RUNTIME_PATH_XAPIANDB, g_get_user_cache_dir() + Sepa + MuName + Sepa + XapianDir); RuntimePaths.emplace(MU_RUNTIME_PATH_CACHE, g_get_user_cache_dir() + Sepa + MuName); RuntimePaths.emplace(MU_RUNTIME_PATH_MIMECACHE, g_get_user_cache_dir() + Sepa + MuName + Sepa + PartsDir); RuntimePaths.emplace(MU_RUNTIME_PATH_LOGDIR, g_get_user_cache_dir() + Sepa + MuName); RuntimePaths.emplace(MU_RUNTIME_PATH_BOOKMARKS, g_get_user_config_dir() + Sepa + MuName); } static void init_paths_muhome (const char *muhome) { RuntimePaths.emplace(MU_RUNTIME_PATH_XAPIANDB, muhome + Sepa + XapianDir); RuntimePaths.emplace(MU_RUNTIME_PATH_CACHE, muhome); RuntimePaths.emplace(MU_RUNTIME_PATH_MIMECACHE, muhome + Sepa + PartsDir); RuntimePaths.emplace(MU_RUNTIME_PATH_LOGDIR, muhome + Sepa + LogDir); RuntimePaths.emplace(MU_RUNTIME_PATH_BOOKMARKS, muhome + Sepa + Bookmarks); } gboolean mu_runtime_init (const char* muhome, const char *name, gboolean debug) { g_return_val_if_fail (RuntimePaths.empty(), FALSE); g_return_val_if_fail (name, FALSE); setlocale (LC_ALL, ""); if (muhome) init_paths_muhome (muhome); else init_paths_xdg(); for (const auto& d: RuntimePaths ) { char* dir; if (d.first == MU_RUNTIME_PATH_BOOKMARKS) // special case dir = g_path_get_dirname (d.second.c_str()); else dir = g_strdup (d.second.c_str()); auto ok = mu_util_create_dir_maybe (dir, 0700, TRUE); if (!ok) { g_critical ("failed to create %s", dir); g_free (dir); mu_runtime_uninit(); return FALSE; } g_free (dir); } const auto log_path = RuntimePaths[MU_RUNTIME_PATH_LOGDIR] + Sepa + name + ".log"; using namespace Mu; LogOptions opts{LogOptions::None}; if (debug) opts |= (LogOptions::Debug | LogOptions::None); Mu::log_init(log_path, opts); return TRUE; } void mu_runtime_uninit (void) { RuntimePaths.clear(); Mu::log_uninit(); } const char* mu_runtime_path (MuRuntimePath path) { const auto it = RuntimePaths.find (path); if (it == RuntimePaths.end()) return NULL; else return it->second.c_str(); } �������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-runtime.hh�������������������������������������������������������������������������0000664�0000000�0000000�00000004072�14143670036�0015420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- ** ** Copyright (C) 2012-2019 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_RUNTIME_H__ #define __MU_RUNTIME_H__ #include <glib.h> G_BEGIN_DECLS /** * initialize the mu runtime system; initializes logging and other * systems. To uninitialize, use mu_runtime_uninit * * @param muhome path where to find the mu home directory (typically, ~/.cache/mu) * @param name of the main program, ie. 'mu', 'mug' or * 'procmule'. this influences the name of the e.g. the logfile * @param debug debug-mode * * @return TRUE if succeeded, FALSE in case of error */ gboolean mu_runtime_init (const char *muhome, const char *name, gboolean debug); /** * free all resources * */ void mu_runtime_uninit (void); typedef enum { MU_RUNTIME_PATH_XAPIANDB, /* mu xapian db path */ MU_RUNTIME_PATH_BOOKMARKS, /* mu bookmarks file path */ MU_RUNTIME_PATH_CACHE, /* mu cache path */ MU_RUNTIME_PATH_MIMECACHE, /* mu cache path for attachments etc. */ MU_RUNTIME_PATH_LOGDIR, /* mu path for log files */ MU_RUNTIME_PATH_NUM } MuRuntimePath; /** * get a file system path to some 'special' file or directory * * @return ma string which should be not be modified/freed, or NULL in * case of error. */ const char* mu_runtime_path (MuRuntimePath path); G_END_DECLS #endif /*__MU_RUNTIME_H__*/ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-script.cc��������������������������������������������������������������������������0000664�0000000�0000000�00000017146�14143670036�0015235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #ifdef BUILD_GUILE #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include <libguile.h> #pragma GCC diagnostic pop #endif /*BUILD_GUILE*/ #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <dirent.h> #include <errno.h> #include <unistd.h> #include "utils/mu-str.h" #include "mu-script.hh" #include "utils/mu-util.h" /** * Structure with information about a certain script. * the values will be *freed* when MuScriptInfo is freed */ struct MuScriptInfo { char *_name; /* filename-sans-extension */ char *_path; /* full path to script */ char *_oneline; /* one-line description */ char *_descr; /* longer description */ }; /* create a new MuScriptInfo* object*/ static MuScriptInfo* script_info_new (void) { return g_slice_new0 (MuScriptInfo); } /* destroy a MuScriptInfo* object */ static void script_info_destroy (MuScriptInfo *msi) { if (!msi) return; g_free (msi->_name); g_free (msi->_path); g_free (msi->_oneline); g_free (msi->_descr); g_slice_free (MuScriptInfo, msi); } /* compare two MuScripInfo* objects (for sorting) */ static int script_info_cmp (MuScriptInfo *msi1, MuScriptInfo *msi2) { return strcmp (msi1->_name, msi2->_name); } const char* mu_script_info_name (MuScriptInfo *msi) { g_return_val_if_fail (msi, NULL); return msi->_name; } const char* mu_script_info_path (MuScriptInfo *msi) { g_return_val_if_fail (msi, NULL); return msi->_path; } const char* mu_script_info_one_line (MuScriptInfo *msi) { g_return_val_if_fail (msi, NULL); return msi->_oneline; } const char* mu_script_info_description (MuScriptInfo *msi) { g_return_val_if_fail (msi, NULL); return msi->_descr; } gboolean mu_script_info_matches_regex (MuScriptInfo *msi, const char *rxstr, GError **err) { GRegex *rx; gboolean match; g_return_val_if_fail (msi, FALSE); g_return_val_if_fail (rxstr, FALSE); rx = g_regex_new (rxstr, (GRegexCompileFlags)(G_REGEX_CASELESS|G_REGEX_OPTIMIZE), (GRegexMatchFlags)0, err); if (!rx) return FALSE; match = FALSE; if (msi->_name) match = g_regex_match (rx, msi->_name, (GRegexMatchFlags)0, NULL); if (!match && msi->_oneline) match = g_regex_match (rx, msi->_oneline,(GRegexMatchFlags)0, NULL); return match; } void mu_script_info_list_destroy (GSList *lst) { g_slist_free_full(lst, (GDestroyNotify)script_info_destroy); } static GIOChannel * open_channel (const char *path) { GError *err; GIOChannel *io_chan; err = NULL; io_chan = g_io_channel_new_file (path, "r", &err); if (!io_chan) { g_warning ("failed to open '%s': %s", path, err ? err->message : "something went wrong"); g_clear_error (&err); return NULL; } return io_chan; } static void end_channel (GIOChannel *io_chan) { GIOStatus status; GError *err; err = NULL; status = g_io_channel_shutdown (io_chan, FALSE, &err); if (status != G_IO_STATUS_NORMAL) { g_warning ("failed to shutdown io-channel: %s", err ? err->message : "something went wrong"); g_clear_error (&err); } g_io_channel_unref (io_chan); } static gboolean get_descriptions (MuScriptInfo *msi, const char *prefix) { GIOStatus io_status; GIOChannel *script_io; GError *err; char *line, *descr, *oneline; if (!prefix) return TRUE; /* not an error */ if (!(script_io = open_channel (msi->_path))) return FALSE; err = NULL; line = descr = oneline = NULL; do { g_free (line); io_status = g_io_channel_read_line (script_io, &line, NULL, NULL, &err); if (io_status != G_IO_STATUS_NORMAL) break; if (!g_str_has_prefix (line, prefix)) continue; if (!oneline) oneline = g_strdup (line + strlen (prefix)); else { char *tmp; tmp = descr; descr = g_strdup_printf ("%s%s", descr ? descr : "", line + strlen(prefix)); g_free (tmp); } } while (TRUE); if (io_status != G_IO_STATUS_EOF) { g_warning ("error reading %s: %s", msi->_path, err ? err->message : "something went wrong"); g_clear_error (&err); } end_channel (script_io); msi->_oneline = oneline; msi->_descr = descr; return TRUE; } GSList* mu_script_get_script_info_list (const char *path, const char *ext, const char *descprefix, GError **err) { DIR *dir; GSList *lst; struct dirent *dentry; g_return_val_if_fail (path, NULL); dir = opendir (path); if (!dir) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_OPEN, "failed to open '%s': %s", path, g_strerror(errno)); return NULL; } /* create a list of names, paths */ lst = NULL; while ((dentry = readdir (dir))) { MuScriptInfo *msi; /* only consider files with certain extensions, * if ext != NULL */ if (ext && !g_str_has_suffix (dentry->d_name, ext)) continue; msi = script_info_new (); msi->_name = g_strdup (dentry->d_name); if (ext) /* strip the extension */ msi->_name[strlen(msi->_name) - strlen(ext)] = '\0'; msi->_path = g_strdup_printf ("%s%c%s", path, G_DIR_SEPARATOR, dentry->d_name); /* set the one-line and long description */ get_descriptions (msi, descprefix); lst = g_slist_prepend (lst, msi); } closedir (dir); /* ignore error checking... */ return g_slist_sort (lst, (GCompareFunc)script_info_cmp); } MuScriptInfo* mu_script_find_script_with_name (GSList *lst, const char *name) { GSList *cur; g_return_val_if_fail (name, NULL); for (cur = lst; cur; cur = g_slist_next (cur)) { MuScriptInfo *msi; msi = (MuScriptInfo*)cur->data; if (g_strcmp0 (name, mu_script_info_name (msi)) == 0) return msi; } return NULL; } #ifdef BUILD_GUILE static void guile_shell (void *closure, int argc, char **argv) { scm_shell (argc, argv); } gboolean mu_script_guile_run (MuScriptInfo *msi, const char *muhome, const char **args, GError **err) { const char *s; char *mainargs, *expr; char **argv; g_return_val_if_fail (msi, FALSE); g_return_val_if_fail (muhome, FALSE); argv = g_new0 (char*, 6); argv[0] = g_strdup(GUILE_BINARY); argv[1] = g_strdup("-l"); if (access (mu_script_info_path (msi), R_OK) != 0) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_READ, "failed to read script: %s", g_strerror(errno)); return FALSE; } s = mu_script_info_path (msi); argv[2] = g_strdup (s ? s : ""); mainargs = mu_str_quoted_from_strv (args); expr = g_strdup_printf ( "(main '(\"%s\" \"--muhome=%s\" %s))", mu_script_info_name (msi), muhome, mainargs ? mainargs : ""); g_free (mainargs); argv[3] = g_strdup("-c"); argv[4] = expr; scm_boot_guile (5, argv, guile_shell, NULL); /* never reached but let's be correct(TM)*/ g_strfreev (argv); return TRUE; } #else /*!BUILD_GUILE*/ gboolean mu_script_guile_run (MuScriptInfo *msi, const char *muhome, const char **args, GError **err) { mu_util_g_set_error (err, MU_ERROR_INTERNAL, "this mu does not have guile support"); return FALSE; } #endif /*!BUILD_GUILE*/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-script.hh��������������������������������������������������������������������������0000664�0000000�0000000�00000006765�14143670036�0015254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_SCRIPT_HH__ #define MU_SCRIPT_HH__ #include <glib.h> /* Opaque structure with information about a script */ struct MuScriptInfo; /** * get the name of the script (sans-extension, if some extension was * provided to mu_script_get_scripts) * * @param msi a MuScriptInfo structure * * @return the name */ const char* mu_script_info_name (MuScriptInfo *msi); /** * get the full filesystem path of the script * * @param msi a MuScriptInfo structure * * @return the path */ const char* mu_script_info_path (MuScriptInfo *msi); /** * get a one-line description for the script * * @param msi a MuScriptInfo structure * * @return the description, or NULL if there was none */ const char* mu_script_info_one_line (MuScriptInfo *msi); /** * get a full description for the script * * @param msi a MuScriptInfo structure * * @return the description, or NULL if there was none */ const char* mu_script_info_description (MuScriptInfo *msi); /** * check whether either the name or one-line description of a * MuScriptInfo matches regular expression rxstr * * @param msi a MuScriptInfo * @param rxstr a regular expression string * @param err receives error information * * @return TRUE if it matches, FALSE if not or in case of error */ gboolean mu_script_info_matches_regex (MuScriptInfo *msi, const char *rxstr, GError **err); /** * Get the list of all scripts in path with extension ext * * @param path a file system path * @param ext an extension (e.g., ".scm"), or NULL * @param prefix for the one-line description * (e.g., ";; DESCRIPTION: "), or NULL * @param err receives error information, if any * * @return a list of Mu */ GSList *mu_script_get_script_info_list (const char *path, const char *ext, const char *descprefix, GError **err); /** * destroy a list of MuScriptInfo* objects * * @param scriptslst a list of MuScriptInfo* objects */ void mu_script_info_list_destroy (GSList *lst); /** * find the MuScriptInfo object for the first script with a certain * name, or return NULL if not found. * * @param lst a list of MuScriptInfo* objects * @param name the name to search for * * @return a MuScriptInfo* object, or NULL if not found. */ MuScriptInfo* mu_script_find_script_with_name (GSList *lst, const char *name); /** * run the guile script at path * * @param msi MuScriptInfo object for the script * @param muhome path to the mu home dir * @param args NULL-terminated array of strings (argv for the script) * @param err receives error information * * @return FALSE in case of error -- otherwise, this function will * _not return_ */ gboolean mu_script_guile_run (MuScriptInfo *msi, const char *muhome, const char **args, GError **err); #endif /*MU_SCRIPT_HH__*/ �����������mu-1.6.10/lib/mu-server.cc��������������������������������������������������������������������������0000664�0000000�0000000�00000136767�14143670036�0015252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include "mu-msg-fields.h" #include "mu-msg.hh" #include "mu-server.hh" #include <iostream> #include <string> #include <algorithm> #include <atomic> #include <thread> #include <mutex> #include <functional> #include <cstring> #include <glib.h> #include <glib/gprintf.h> #include "mu-runtime.hh" #include "mu-maildir.hh" #include "mu-query.hh" #include "index/mu-indexer.hh" #include "mu-store.hh" #include "mu-msg-part.hh" #include "mu-contacts.hh" #include "utils/mu-str.h" #include "utils/mu-utils.hh" #include "utils/mu-command-parser.hh" #include "utils/mu-readline.hh" using namespace Mu; using namespace Command; /// @brief object to manage the server-context for all commands. struct Server::Private { Private(Store& store, Output output): store_{store}, output_{output}, command_map_{make_command_map()}, query_{store_}, keep_going_{true} {} // // construction helpers // CommandMap make_command_map(); // // acccessors Store& store() { return store_; } const Store& store() const { return store_; } Indexer& indexer() { return store().indexer(); } const CommandMap& command_map() const { return command_map_; } const Query& query() const { return query_; } // // invoke // bool invoke (const std::string& expr) noexcept; // // output // void output_sexp(Sexp&& sexp) const { if (output_) output_(std::move(sexp)); } void output_sexp(Sexp::List&& lst) const { output_sexp(Sexp::make_list(std::move(lst))); } size_t output_sexp (const QueryResults& qres); // // handlers for various commands. // void add_handler (const Parameters& params); void compose_handler (const Parameters& params); void contacts_handler (const Parameters& params); void extract_handler (const Parameters& params); void find_handler (const Parameters& params); void help_handler (const Parameters& params); void index_handler (const Parameters& params); void move_handler (const Parameters& params); void mkdir_handler (const Parameters& params); void ping_handler (const Parameters& params); void quit_handler (const Parameters& params); void remove_handler (const Parameters& params); void sent_handler (const Parameters& params); void view_handler (const Parameters& params); private: // helpers Sexp build_message_sexp(MuMsg *msg, unsigned docid, const Option<QueryMatch&> qm, MuMsgOptions opts); Sexp::List move_docid (Store::Id docid, const std::string& flagstr, bool new_name, bool no_view); Sexp::List perform_move (Store::Id docid, MuMsg *msg, const std::string& maildirarg, MuFlags flags, bool new_name, bool no_view); bool maybe_mark_as_read (MuMsg *msg, Store::Id docid); bool maybe_mark_msgid_as_read (const Mu::Query& query, const char* msgid); Store& store_; Server::Output output_; const CommandMap command_map_; const Query query_; std::atomic<bool> keep_going_{}; }; static void add_thread_info (Sexp::List& items, const QueryMatch& qmatch) { Sexp::List info; auto symbol_t = []{return Sexp::make_symbol("t");}; info.add_prop(":path", Sexp::make_string(qmatch.thread_path)); info.add_prop(":level", Sexp::make_number(qmatch.thread_level)); info.add_prop(":date", Sexp::make_string(qmatch.thread_date)); Sexp::List dlist; const auto td {::atoi(qmatch.thread_date.c_str())}; dlist.add(Sexp::make_number((unsigned)(td >> 16))); dlist.add(Sexp::make_number((unsigned)(td & 0xffff))); dlist.add(Sexp::make_number(0)); info.add_prop(":date-tstamp", Sexp::make_list(std::move(dlist))); if (qmatch.has_flag(QueryMatch::Flags::Root)) info.add_prop(":root", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::Related)) info.add_prop(":related", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::First)) info.add_prop(":first-child", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::Last)) info.add_prop(":last-child", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::Orphan)) info.add_prop(":orphan", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::Duplicate)) info.add_prop(":duplicate", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::HasChild)) info.add_prop(":has-child", symbol_t()); if (qmatch.has_flag(QueryMatch::Flags::ThreadSubject)) info.add_prop(":thread-subject", symbol_t()); items.add_prop(":thread", Sexp::make_list(std::move(info))); } Sexp Server::Private::build_message_sexp (MuMsg *msg, unsigned docid, const Option<QueryMatch&> qm, MuMsgOptions opts) { auto lst{Mu::msg_to_sexp_list(msg, docid, opts)}; if (qm) add_thread_info(lst, *qm); return Sexp::make_list(std::move(lst)); } CommandMap Server::Private::make_command_map () { CommandMap cmap; using Type = Sexp::Type; cmap.emplace("add", CommandInfo{ ArgMap{ {":path", ArgInfo{Type::String, true, "file system path to the message" }}}, "add a message to the store", [&](const auto& params){add_handler(params);}}); cmap.emplace("compose", CommandInfo{ ArgMap{{":type", ArgInfo{Type::Symbol, true, "type of composition: reply/forward/edit/resend/new"}}, {":docid", ArgInfo{Type::Number, false, "document id of parent-message, if any"}}, {":decrypt", ArgInfo{Type::Symbol, false, "whether to decrypt encrypted parts (if any)" }}}, "compose a new message", [&](const auto& params){compose_handler(params);}}); cmap.emplace("contacts", CommandInfo{ ArgMap{ {":personal", ArgInfo{Type::Symbol, false, "only personal contacts" }}, {":after", ArgInfo{Type::String, false, "only contacts seen after time_t string" }}, {":tstamp", ArgInfo{Type::String, false, "return changes since tstamp" }}}, "get contact information", [&](const auto& params){contacts_handler(params);}}); cmap.emplace("extract", CommandInfo{ ArgMap{{":docid", ArgInfo{Type::Number, true, "document for the message" }}, {":index", ArgInfo{Type::Number, true, "index for the part to operate on" }}, {":action", ArgInfo{Type::Symbol, true, "what to do with the part" }}, {":decrypt", ArgInfo{Type::Symbol, false, "whether to decrypt encrypted parts (if any)" }}, {":path", ArgInfo{Type::String, false, "part for saving (for action: save)" }}, {":what", ArgInfo{Type::Symbol, false, "what to do with the part (feedback)" }}, {":param", ArgInfo{Type::String, false, "parameter for 'what'" }}}, "extract mime-parts from a message", [&](const auto& params){extract_handler(params);}}); cmap.emplace("find", CommandInfo{ ArgMap{ {":query", ArgInfo{Type::String, true, "search expression" }}, {":threads", ArgInfo{Type::Symbol, false, "whether to include threading information" }}, {":sortfield", ArgInfo{Type::Symbol, false, "the field to sort results by" }}, {":descending", ArgInfo{Type::Symbol, false, "whether to sort in descending order" }}, {":maxnum", ArgInfo{Type::Number, false, "maximum number of result (hint)" }}, {":skip-dups", ArgInfo{Type::Symbol, false, "whether to skip messages with duplicate message-ids" }}, {":include-related", ArgInfo{Type::Symbol, false, "whether to include other message related to matching ones" }}}, "query the database for messages", [&](const auto& params){find_handler(params);}}); cmap.emplace("help", CommandInfo{ ArgMap{ {":command", ArgInfo{Type::Symbol, false, "command to get information for" }}, {":full", ArgInfo{Type::Symbol, false, "show full descriptions" }}}, "get information about one or all commands", [&](const auto& params){help_handler(params);}}); cmap.emplace("index", CommandInfo{ ArgMap{ {":my-addresses", ArgInfo{Type::List, false, "list of 'my' addresses"}}, {":cleanup", ArgInfo{Type::Symbol, false, "whether to remove stale messages from the store"}}, {":lazy-check", ArgInfo{Type::Symbol, false, "whether to avoid indexing up-to-date directories"}}}, "scan maildir for new/updated/removed messages", [&](const auto& params){index_handler(params);}}); cmap.emplace("move", CommandInfo{ ArgMap{ {":docid", ArgInfo{Type::Number, false, "document-id"}}, {":msgid", ArgInfo{Type::String, false, "message-id"}}, {":flags", ArgInfo{Type::String, false, "new flags for the message"}}, {":maildir", ArgInfo{Type::String, false, "the target maildir" }}, {":rename", ArgInfo{Type::Symbol, false, "change filename when moving" }}, {":no-view", ArgInfo{Type::Symbol, false, "if set, do not hint at updating the view"}},}, "move messages and/or change their flags", [&](const auto& params){move_handler(params);}}); cmap.emplace("mkdir", CommandInfo{ ArgMap{ {":path", ArgInfo{Type::String, true, "location for the new maildir" }}}, "create a new maildir", [&](const auto& params){mkdir_handler(params);}}); cmap.emplace("ping", CommandInfo{ ArgMap{ {":queries", ArgInfo{Type::List, false, "queries for which to get read/unread numbers"}}, {":skip-dups", ArgInfo{Type::Symbol, false, "whether to exclude messages with duplicate message-ids"}},}, "ping the mu-server and get information in response", [&](const auto& params){ping_handler(params);}}); cmap.emplace("quit", CommandInfo{{}, "quit the mu server", [&](const auto& params){quit_handler(params);}}); cmap.emplace("remove", CommandInfo{ ArgMap{ {":docid", ArgInfo{Type::Number, true, "document-id for the message to remove" }}}, "remove a message from filesystem and database", [&](const auto& params){remove_handler(params);}}); cmap.emplace("sent", CommandInfo{ ArgMap{ {":path", ArgInfo{Type::String, true, "path to the message file" }} }, "tell mu about a message that was sent", [&](const auto& params){sent_handler(params);}}); cmap.emplace("view", CommandInfo{ ArgMap{{":docid", ArgInfo{Type::Number, false, "document-id"}}, {":msgid", ArgInfo{Type::String, false, "message-id"}}, {":path", ArgInfo{Type::String, false, "message filesystem path"}}, {":mark-as-read", ArgInfo{Type::Symbol, false, "mark message as read (if not already)"}}, {":extract-images", ArgInfo{Type::Symbol, false, "whether to extract images for this messages (if any)"}}, {":decrypt", ArgInfo{Type::Symbol, false, "whether to decrypt encrypted parts (if any)" }}, {":verify", ArgInfo{Type::Symbol, false, "whether to verify signatures (if any)" }} }, "view a message. exactly one of docid/msgid/path must be specified", [&](const auto& params){view_handler(params);}}); return cmap; } G_GNUC_PRINTF(2,3) static Sexp make_error (Error::Code errcode, const char* frm, ...) { char *msg{}; va_list ap; va_start (ap, frm); g_vasprintf (&msg, frm, ap); va_end (ap); Sexp::List err; err.add_prop(":error", Sexp::make_number(static_cast<int>(errcode))); err.add_prop(":message", Sexp::make_string(msg)); g_free (msg); return Sexp::make_list(std::move(err)); } bool Server::Private::invoke (const std::string& expr) noexcept { if (!keep_going_) return false; try { auto call{Sexp::Sexp::make_parse(expr)}; Command::invoke(command_map(), call); } catch (const Mu::Error& me) { output_sexp(make_error(me.code(), "%s", me.what())); } catch (const std::runtime_error& re) { output_sexp(make_error(Error::Code::Internal, "caught exception: %s", re.what())); keep_going_ = false; } catch (...) { output_sexp(make_error(Error::Code::Internal, "something went wrong: quiting")); keep_going_ = false; } return keep_going_; } static MuMsgOptions message_options (const Parameters& params) { const auto extract_images{get_bool_or(params, ":extract-images", false)}; const auto decrypt{get_bool_or(params, ":decrypt", false)}; const auto verify{get_bool_or(params, ":verify", false)}; int opts{MU_MSG_OPTION_NONE}; if (extract_images) opts |= MU_MSG_OPTION_EXTRACT_IMAGES; if (verify) opts |= MU_MSG_OPTION_VERIFY | MU_MSG_OPTION_USE_AGENT; if (decrypt) opts |= MU_MSG_OPTION_DECRYPT | MU_MSG_OPTION_USE_AGENT; return (MuMsgOptions)opts; } /* 'add' adds a message to the database, and takes two parameters: 'path', which * is the full path to the message, and 'maildir', which is the maildir this * message lives in (e.g. "/inbox"). response with an (:info ...) message with * information about the newly added message (details: see code below) */ void Server::Private::add_handler (const Parameters& params) { auto path{get_string_or(params, ":path")}; const auto docid{store().add_message(path)}; Sexp::List expr; expr.add_prop(":info", Sexp::make_symbol("add")); expr.add_prop(":path", Sexp::make_string(path)); expr.add_prop(":docid", Sexp::make_number(docid)); output_sexp(Sexp::make_list(std::move(expr))); auto msg{store().find_message(docid)}; if (!msg) throw Error(Error::Code::Store, "failed to get message at %s (docid=%u)", path.c_str(), docid); Sexp::List update; update.add_prop(":update", build_message_sexp(msg, docid, {}, MU_MSG_OPTION_VERIFY)); output_sexp(Sexp::make_list(std::move(update))); mu_msg_unref(msg); } struct PartInfo { Sexp::List attseq; MuMsgOptions opts; }; static void each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo) { /* exclude things that don't look like proper attachments, unless they're images */ if (!mu_msg_part_maybe_attachment(part)) return; GError *gerr{}; char *cachefile = mu_msg_part_save_temp ( msg, (MuMsgOptions)(pinfo->opts|MU_MSG_OPTION_OVERWRITE), part->index, &gerr); if (!cachefile) throw Error (Error::Code::File, &gerr, "failed to save part"); Sexp::List pi; pi.add_prop(":file-name", Sexp::make_string(cachefile)); pi.add_prop(":mime-type", Sexp::make_string(format("%s/%s", part->type, part->subtype))); pinfo->attseq.add(Sexp::make_list(std::move(pi))); g_free (cachefile); } /* 'compose' produces the un-changed *original* message sexp (ie., the message * to reply to, forward or edit) for a new message to compose). It takes two * parameters: 'type' with the compose type (either reply, forward or * edit/resend), and 'docid' for the message to reply to. Note, type:new does * not have an original message, and therefore does not need a docid * * In returns a (:compose <type> [:original <original-msg>] [:include] ) * message (detals: see code below) * * Note ':include' t or nil determines whether to include attachments */ void Server::Private::compose_handler (const Parameters& params) { const auto ctype{get_symbol_or(params, ":type")}; Sexp::List comp_lst; comp_lst.add_prop(":compose", Sexp::make_symbol(std::string(ctype))); if (ctype == "reply" || ctype == "forward" || ctype == "edit" || ctype == "resend") { GError *gerr{}; const unsigned docid{(unsigned)get_int_or(params, ":docid")}; auto msg{store().find_message(docid)}; if (!msg) throw Error{Error::Code::Store, &gerr, "failed to get message %u", docid}; const auto opts{message_options(params)}; comp_lst.add_prop(":original", build_message_sexp(msg, docid, {}, opts)); if (ctype == "forward") { PartInfo pinfo{}; pinfo.opts = opts; mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo); if (!pinfo.attseq.empty()) comp_lst.add_prop (":include", Sexp::make_list(std::move(pinfo.attseq))); } mu_msg_unref (msg); } else if (ctype != "new") throw Error(Error::Code::InvalidArgument, "invalid compose type '%s'", ctype.c_str()); output_sexp (std::move(comp_lst)); } void Server::Private::contacts_handler (const Parameters& params) { const auto personal = get_bool_or(params, ":personal"); const auto afterstr = get_string_or(params, ":after"); const auto tstampstr = get_string_or(params, ":tstamp"); const auto after{afterstr.empty() ? 0 : g_ascii_strtoll(date_to_time_t_string(afterstr, true).c_str(), {}, 10)}; const auto tstamp = g_ascii_strtoll (tstampstr.c_str(), NULL, 10); auto rank{0}; Sexp::List contacts; store().contacts().for_each([&](const ContactInfo& ci) { rank++; /* since the last time we got some contacts */ if (tstamp > ci.tstamp) return; /* (maybe) only include 'personal' contacts */ if (personal && !ci.personal) return; /* only include newer-than-x contacts */ if (after > ci.last_seen) return; Sexp::List contact; contact.add_prop(":address", Sexp::make_string(ci.full_address)); contact.add_prop(":rank", Sexp::make_number(rank)); contacts.add(Sexp::make_list(std::move(contact))); }); Sexp::List seq; seq.add_prop(":contacts", Sexp::make_list(std::move(contacts))); seq.add_prop(":tstamp", Sexp::make_string(format("%" G_GINT64_FORMAT, g_get_monotonic_time()))); /* dump the contacts cache as a giant sexp */ output_sexp(std::move(seq)); } static Sexp::List save_part (MuMsg *msg, unsigned docid, unsigned index, MuMsgOptions opts, const Parameters& params) { const auto path{get_string_or(params, ":path")}; if (path.empty()) throw Error{Error::Code::Command, "missing path"}; GError *gerr{}; if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | (int)MU_MSG_OPTION_OVERWRITE), path.c_str(), index, &gerr)) throw Error{Error::Code::File, &gerr, "failed to save part"}; Sexp::List seq; seq.add_prop(":info", Sexp::make_symbol("save")); seq.add_prop(":message", Sexp::make_string(format("%s has been saved", path.c_str()))); return seq; } static Sexp::List open_part (MuMsg *msg, unsigned docid, unsigned index, MuMsgOptions opts) { GError *gerr{}; char *targetpath{mu_msg_part_get_cache_path (msg, opts, index, &gerr)}; if (!targetpath) throw Error{Error::Code::File, &gerr, "failed to get cache-path"}; if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | MU_MSG_OPTION_USE_EXISTING), targetpath, index, &gerr)) { g_free(targetpath); throw Error{Error::Code::File, &gerr, "failed to save to cache-path"}; } if (!mu_util_play (targetpath, TRUE,/*allow local*/ FALSE/*allow remote*/, &gerr)) { g_free(targetpath); throw Error{Error::Code::File, &gerr, "failed to play"}; } Sexp::List seq; seq.add_prop(":info", Sexp::make_symbol("open")); seq.add_prop(":message", Sexp::make_string(format("%s has been opened", targetpath))); g_free (targetpath); return seq; } static Sexp::List temp_part (MuMsg *msg, unsigned docid, unsigned index, MuMsgOptions opts, const Parameters& params) { const auto what{get_symbol_or(params, ":what")}; if (what.empty()) throw Error{Error::Code::Command, "missing 'what'"}; const auto param{get_string_or(params, ":param")}; GError *gerr{}; char *path{mu_msg_part_get_cache_path (msg, opts, index, &gerr)}; if (!path) throw Error{Error::Code::File, &gerr, "could not get cache path"}; if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | MU_MSG_OPTION_USE_EXISTING), path, index, &gerr)) { g_free(path); throw Error{Error::Code::File, &gerr, "saving failed"}; } Sexp::List lst; lst.add_prop(":temp", Sexp::make_string(path)); lst.add_prop(":what", Sexp::make_string(what)); lst.add_prop(":docid", Sexp::make_number(docid)); if (!param.empty()) lst.add_prop(":param", Sexp::make_string(param)); g_free(path); return lst; } /* 'extract' extracts some mime part from a message */ void Server::Private::extract_handler (const Parameters& params) { const auto docid{get_int_or(params, ":docid")}; const auto index{get_int_or(params, ":index")}; const auto opts{message_options(params)}; auto msg{store().find_message(docid)}; if (!msg) throw Error{Error::Code::Store, "failed to get message"}; try { const auto action{get_symbol_or(params, ":action")}; if (action == "save") output_sexp(save_part (msg, docid, index, opts, params)); else if (action == "open") output_sexp(open_part (msg, docid, index, opts)); else if (action == "temp") output_sexp(temp_part (msg, docid, index, opts, params)); else { throw Error{Error::Code::InvalidArgument, "unknown action '%s'", action.c_str()}; } } catch (...) { mu_msg_unref (msg); throw; } } /* get a *list* of all messages with the given message id */ static std::vector<Store::Id> docids_for_msgid (const Query& q, const std::string& msgid, size_t max=100) { if (msgid.size() > Store::MaxTermLength) { throw Error(Error::Code::InvalidArgument, "invalid message-id '%s'", msgid.c_str()); } const auto xprefix{mu_msg_field_shortcut(MU_MSG_FIELD_ID_MSGID)}; /*XXX this is a bit dodgy */ auto tmp{g_ascii_strdown(msgid.c_str(), -1)}; auto expr{g_strdup_printf("%c:%s", xprefix, tmp)}; g_free(tmp); GError *gerr{}; const auto res{q.run(expr, MU_MSG_FIELD_ID_NONE, QueryFlags::None, max)}; g_free (expr); if (!res) throw Error(Error::Code::Store, &gerr, "failed to run msgid-query"); else if (res->empty()) throw Error(Error::Code::NotFound, "could not find message(s) for msgid %s", msgid.c_str()); std::vector<Store::Id> docids{}; for(auto&& mi: *res) docids.emplace_back(mi.doc_id()); return docids; } /* * creating a message object just to get a path seems a bit excessive maybe * mu_store_get_path could be added if this turns out to be a problem */ static std::string path_from_docid (const Store& store, unsigned docid) { auto msg{store.find_message(docid)}; if (!msg) throw Error(Error::Code::Store, "could not get message from store"); auto p{mu_msg_get_path(msg)}; if (!p) { mu_msg_unref(msg); throw Error(Error::Code::Store, "could not get path for message %u", docid); } std::string msgpath{p}; mu_msg_unref (msg); return msgpath; } static std::vector<Store::Id> determine_docids (const Query& q, const Parameters& params) { auto docid{get_int_or(params, ":docid", 0)}; const auto msgid{get_string_or(params, ":msgid")}; if ((docid == 0) == msgid.empty()) throw Error(Error::Code::InvalidArgument, "precisely one of docid and msgid must be specified"); if (docid != 0) return { (unsigned)docid }; else return docids_for_msgid (q, msgid.c_str()); } size_t Server::Private::output_sexp (const QueryResults& qres) { size_t n{}; for (auto&& mi: qres) { ++n; auto msg{mi.floating_msg()}; if (!msg) continue; auto qm{mi.query_match()}; output_sexp(build_message_sexp(msg, mi.doc_id(), qm, MU_MSG_OPTION_HEADERS_ONLY)); } return n; } void Server::Private::find_handler (const Parameters& params) { const auto q{get_string_or(params, ":query")}; const auto threads{get_bool_or(params, ":threads", false)}; const auto sortfieldstr{get_symbol_or(params, ":sortfield", "")}; const auto descending{get_bool_or(params, ":descending", false)}; const auto maxnum{get_int_or(params, ":maxnum", -1/*unlimited*/)}; const auto skip_dups{get_bool_or(params, ":skip-dups", false)}; const auto include_related{get_bool_or(params, ":include-related", false)}; MuMsgFieldId sort_field{MU_MSG_FIELD_ID_NONE}; if (!sortfieldstr.empty()) { sort_field = mu_msg_field_id_from_name ( sortfieldstr.c_str() + 1, FALSE); // skip ':' if (sort_field == MU_MSG_FIELD_ID_NONE) throw Error{Error::Code::InvalidArgument, "invalid sort field %s", sortfieldstr.c_str()}; } auto qflags{QueryFlags::None}; if (descending) qflags |= QueryFlags::Descending; if (skip_dups) qflags |= QueryFlags::SkipDuplicates; if (include_related) qflags |= QueryFlags::IncludeRelated; if (threads) qflags |= QueryFlags::Threading; auto qres{query().run(q, sort_field, qflags, maxnum)}; if (!qres) throw Error(Error::Code::Query, "failed to run query"); /* before sending new results, send an 'erase' message, so the frontend * knows it should erase the headers buffer. this will ensure that the * output of two finds will not be mixed. */ { Sexp::List lst; lst.add_prop(":erase", Sexp::make_symbol("t")); output_sexp(std::move(lst)); } { const auto foundnum{output_sexp (*qres)}; Sexp::List lst; lst.add_prop(":found", Sexp::make_number(foundnum)); output_sexp(std::move(lst)); } } void Server::Private::help_handler (const Parameters& params) { const auto command{get_symbol_or(params, ":command", "")}; const auto full{get_bool_or(params, ":full", !command.empty())}; if (command.empty()) { std::cout << ";; Commands are s-expressions of the form\n" << ";; (<command-name> :param1 val1 :param2 val2 ...)\n" << ";; For instance:\n;; (help :command quit)\n" << ";; to get detailed information about the 'quit'\n;;\n"; std::cout << ";; The following commands are available:\n\n"; } std::vector<std::string> names; for (auto&& name_cmd: command_map()) names.emplace_back(name_cmd.first); std::sort(names.begin(), names.end()); for (auto&& name: names) { const auto& info{command_map().find(name)->second}; if (!command.empty() && name != command) continue; if (!command.empty()) std::cout << ";; " << format("%-10s -- %s\n", name.c_str(), info.docstring.c_str()); else std::cout << ";; " << name.c_str() << " -- " << info.docstring.c_str() << '\n'; if (!full) continue; for (auto&& argname: info.sorted_argnames()) { const auto& arg{info.args.find(argname)}; std::cout << ";; " << format("%-17s : %-24s ", arg->first.c_str(), to_string(arg->second).c_str()); std::cout << " " << arg->second.docstring << "\n"; } std::cout << ";;\n"; } } static Sexp::List get_stats (const Indexer::Progress& stats, const std::string& state) { Sexp::List lst; lst.add_prop(":info", Sexp::make_symbol("index")); lst.add_prop(":status", Sexp::make_symbol(std::string{state})); lst.add_prop(":processed", Sexp::make_number(stats.processed)); lst.add_prop(":updated", Sexp::make_number(stats.updated)); lst.add_prop(":cleaned-up", Sexp::make_number(stats.removed)); return lst; } void Server::Private::index_handler (const Parameters& params) { Mu::Indexer::Config conf{}; conf.cleanup = get_bool_or(params, ":cleanup"); conf.lazy_check = get_bool_or(params, ":lazy-check"); // ignore .noupdate with an empty store. conf.ignore_noupdate = store().empty(); indexer().stop(); indexer().start(conf); while (indexer().is_running()) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); output_sexp(get_stats(indexer().progress(), "running")); } output_sexp(get_stats(indexer().progress(), "complete")); } void Server::Private::mkdir_handler (const Parameters& params) { const auto path{get_string_or(params, ":path")}; GError *gerr{}; if (!mu_maildir_mkdir(path.c_str(), 0755, FALSE, &gerr)) throw Error{Error::Code::File, &gerr, "failed to create maildir"}; Sexp::List lst; lst.add_prop(":info", Sexp::make_string("mkdir")); lst.add_prop(":message", Sexp::make_string(format("%s has been created", path.c_str()))); output_sexp(std::move(lst)); } static MuFlags get_flags (const std::string& path, const std::string& flagstr) { if (flagstr.empty()) return MU_FLAG_NONE; /* ie., ignore flags */ else { /* if there's a '+' or '-' sign in the string, it must * be a flag-delta */ if (strstr (flagstr.c_str(), "+") || strstr (flagstr.c_str(), "-")) { auto oldflags = mu_maildir_get_flags_from_path (path.c_str()); return mu_flags_from_str_delta (flagstr.c_str(), oldflags, MU_FLAG_TYPE_ANY); } else return mu_flags_from_str (flagstr.c_str(), MU_FLAG_TYPE_ANY, TRUE /*ignore invalid*/); } } Sexp::List Server::Private::perform_move (Store::Id docid, MuMsg *msg, const std::string& maildirarg, MuFlags flags, bool new_name, bool no_view) { bool different_mdir{}; auto maildir{maildirarg}; if (maildir.empty()) { maildir = mu_msg_get_maildir (msg); different_mdir = false; } else /* are we moving to a different mdir, or is it just flags? */ different_mdir = maildir != mu_msg_get_maildir(msg); GError* gerr{}; if (!mu_msg_move_to_maildir (msg, maildir.c_str(), flags, TRUE, new_name, &gerr)) throw Error{Error::Code::File, &gerr, "failed to move message"}; /* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir fields * will be updated as wel */ if (!store_.update_message (msg, docid)) throw Error{Error::Code::Store, "failed to store updated message"}; Sexp::List seq; seq.add_prop(":update", build_message_sexp (msg, docid, {}, MU_MSG_OPTION_VERIFY)); /* note, the :move t thing is a hint to the frontend that it * could remove the particular header */ if (different_mdir) seq.add_prop(":move", Sexp::make_symbol("t")); if (!no_view) seq.add_prop(":maybe-view", Sexp::make_symbol("t")); return seq; } Sexp::List Server::Private::move_docid (Store::Id docid, const std::string& flagstr, bool new_name, bool no_view) { if (docid == Store::InvalidId) throw Error{Error::Code::InvalidArgument, "invalid docid"}; auto msg{store_.find_message(docid)}; try { if (!msg) throw Error{Error::Code::Store, "failed to get message from store"}; const auto flags = flagstr.empty() ? mu_msg_get_flags (msg) : get_flags (mu_msg_get_path(msg), flagstr); if (flags == MU_FLAG_INVALID) throw Error{Error::Code::InvalidArgument, "invalid flags '%s'", flagstr.c_str()}; auto lst = perform_move(docid, msg, "", flags, new_name, no_view); mu_msg_unref (msg); return lst; } catch (...) { if (msg) mu_msg_unref (msg); throw; } } /* * 'move' moves a message to a different maildir and/or changes its * flags. parameters are *either* a 'docid:' or 'msgid:' pointing to * the message, a 'maildir:' for the target maildir, and a 'flags:' * parameter for the new flags. * * returns an (:update <new-msg-sexp>) * */ void Server::Private::move_handler (const Parameters& params) { auto maildir{get_string_or(params, ":maildir")}; const auto flagstr{get_string_or(params, ":flags")}; const auto rename{get_bool_or (params, ":rename")}; const auto no_view{get_bool_or (params, ":noupdate")}; const auto docids{determine_docids (query(), params)}; if (docids.size() > 1) { if (!maildir.empty()) // ie. duplicate message-ids. throw Mu::Error{Error::Code::Store, "can't move multiple messages at the same time"}; // multi. for (auto&& docid: docids) output_sexp(move_docid(docid, flagstr, rename, no_view)); return; } auto docid{docids.at(0)}; GError *gerr{}; auto msg{store().find_message(docid)}; if (!msg) throw Error{Error::Code::InvalidArgument, &gerr, "could not create message"}; /* if maildir was not specified, take the current one */ if (maildir.empty()) maildir = mu_msg_get_maildir (msg); /* determine the real target flags, which come from the flags-parameter * we received (ie., flagstr), if any, plus the existing message * flags. */ MuFlags flags{}; if (!flagstr.empty()) flags = get_flags (mu_msg_get_path(msg), flagstr.c_str()); else flags = mu_msg_get_flags (msg); if (flags == MU_FLAG_INVALID) { mu_msg_unref(msg); throw Error{Error::Code::InvalidArgument, "invalid flags"}; } try { output_sexp(perform_move(docid, msg, maildir, flags, rename, no_view)); } catch (...) { mu_msg_unref(msg); throw; } mu_msg_unref(msg); } void Server::Private::ping_handler (const Parameters& params) { const auto storecount{store().size()}; if (storecount == (unsigned)-1) throw Error{Error::Code::Store, "failed to read store"}; const auto queries = get_string_vec (params, ":queries"); Sexp::List qresults; for (auto&& q: queries) { const auto count{query().count(q)}; const auto unreadq{format("flag:unread AND (%s)", q.c_str())}; const auto unread{query().count(unreadq)}; Sexp::List lst; lst.add_prop(":query", Sexp::make_string(q)); lst.add_prop(":count", Sexp::make_number(count)); lst.add_prop(":unread", Sexp::make_number(unread)); qresults.add(Sexp::make_list(std::move(lst))); } Sexp::List addrs; for (auto&& addr: store().metadata().personal_addresses) addrs.add(Sexp::make_string(addr)); Sexp::List lst; lst.add_prop(":pong", Sexp::make_string("mu")); Sexp::List proplst; proplst.add_prop(":version", Sexp::make_string(VERSION)); proplst.add_prop(":personal-addresses", Sexp::make_list(std::move(addrs))); proplst.add_prop(":database-path", Sexp::make_string(store().metadata().database_path)); proplst.add_prop(":root-maildir", Sexp::make_string(store().metadata().root_maildir)); proplst.add_prop(":doccount", Sexp::make_number(storecount)); proplst.add_prop(":queries", Sexp::make_list(std::move(qresults))); lst.add_prop(":props", Sexp::make_list(std::move(proplst))); output_sexp(std::move(lst)); } void Server::Private::quit_handler (const Parameters& params) { keep_going_ = false; } void Server::Private::remove_handler (const Parameters& params) { const auto docid{get_int_or(params, ":docid")}; const auto path{path_from_docid (store(), docid)}; if (::unlink (path.c_str()) != 0 && errno != ENOENT) throw Error(Error::Code::File, "could not delete %s: %s", path.c_str(), g_strerror (errno)); if (!store().remove_message (path)) g_warning("failed to remove message @ %s (%d) from store", path.c_str(), docid); // act as if it worked. Sexp::List lst; lst.add_prop(":remove", Sexp::make_number(docid)); output_sexp(std::move(lst)); } void Server::Private::sent_handler (const Parameters& params) { const auto path{get_string_or(params, ":path")}; const auto docid{store().add_message(path)}; if (docid == Store::InvalidId) throw Error{Error::Code::Store, "failed to add path"}; Sexp::List lst; lst.add_prop (":sent", Sexp::make_symbol("t")); lst.add_prop (":path", Sexp::make_string(path)); lst.add_prop (":docid", Sexp::make_number(docid)); output_sexp (std::move(lst)); } bool Server::Private::maybe_mark_as_read (MuMsg *msg, Store::Id docid) { if (!msg) throw Error{Error::Code::Store, "missing message"}; const auto oldflags{mu_msg_get_flags (msg)}; const auto newflags{get_flags (mu_msg_get_path(msg), "+S-u-N")}; if (oldflags == newflags) return false; // nothing to do. GError* gerr{}; if (!mu_msg_move_to_maildir (msg, mu_msg_get_maildir (msg), newflags, TRUE, FALSE,/*new_name,*/ &gerr)) throw Error{Error::Code::File, &gerr, "failed to move message"}; /* after mu_msg_move_to_maildir, path will be the *new* path, and flags and maildir fields * will be updated as wel */ if (!store().update_message (msg, docid)) throw Error{Error::Code::Store, "failed to store updated message"}; /* send an update */ Sexp::List update; update.add_prop(":update", build_message_sexp(msg, docid, {}, MU_MSG_OPTION_NONE)); output_sexp(Sexp::make_list(std::move(update))); g_debug ("marked message %d as read => %s", docid, mu_msg_get_path(msg)); return true; } bool Server::Private::maybe_mark_msgid_as_read (const Mu::Query& query, const char* msgid) { if (!msgid) return false; // nothing to do. const auto docids{docids_for_msgid(query, std::string{msgid})}; for (auto&& docid: docids) { MuMsg *msg = store().find_message(docid); if (!msg) continue; try { maybe_mark_as_read(msg, docid); } catch (...) { mu_msg_unref(msg); throw; } } return true; } void Server::Private::view_handler (const Parameters& params) { Store::Id docid{Store::InvalidId}; const auto path{get_string_or(params, ":path")}; const auto mark_as_read{get_bool_or(params, ":mark-as-read")}; GError *gerr{}; MuMsg *msg{}; if (!path.empty()) { /* only use for old view (embedded msgs) */ docid = Store::InvalidId; msg = mu_msg_new_from_file (path.c_str(), NULL, &gerr); } else { docid = determine_docids(query(), params).at(0); msg = store().find_message(docid); } if (!msg) throw Error{Error::Code::Store, &gerr, "failed to find message for view"}; if (mark_as_read) { // maybe mark the main message as read. maybe_mark_as_read(msg, docid); /* maybe mark _all_ messsage with same message-id as read */ maybe_mark_msgid_as_read(query(), mu_msg_get_msgid(msg)); } Sexp::List seq; seq.add_prop(":view", build_message_sexp( msg, docid, {}, message_options(params))); mu_msg_unref(msg); output_sexp (std::move(seq)); } Server::Server(Store& store, Server::Output output): priv_{std::make_unique<Private>(store, output)} {} Server::~Server() = default; bool Server::invoke (const std::string& expr) noexcept { return priv_->invoke(expr); } ���������mu-1.6.10/lib/mu-server.hh��������������������������������������������������������������������������0000664�0000000�0000000�00000003416�14143670036�0015244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_SERVER_HH__ #define MU_SERVER_HH__ #include <memory> #include <functional> #include <utils/mu-sexp.hh> #include <mu-store.hh> namespace Mu { /** * @brief Implements the mu server, as used by mu4e. * */ class Server { public: using Output = std::function<void(Sexp&& sexp)>; /** * Construct a new server * * @param store a message store object * @param output callable for the server responses. */ Server(Store& store, Output output); /** * DTOR */ ~Server(); /** * Invoke a call on the server. * * @param expr the s-expression to call * * @return true if we the server is still ready for more * calls, false when it should quit. */ bool invoke(const std::string& expr) noexcept; private: struct Private; std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /* MU_SERVER_HH__ */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-store.cc���������������������������������������������������������������������������0000664�0000000�0000000�00000105113�14143670036�0015055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <chrono> #include <memory> #include <mutex> #include <array> #include <cstdlib> #include <stdexcept> #include <unordered_map> #include <atomic> #include <type_traits> #include <iostream> #include <cstring> #include <xapian.h> #include "mu-store.hh" #include "utils/mu-str.h" #include "utils/mu-error.hh" #include "mu-msg-part.hh" #include "utils/mu-utils.hh" using namespace Mu; static_assert(std::is_same<Store::Id, Xapian::docid>::value, "wrong type for Store::Id"); constexpr auto SchemaVersionKey = "schema-version"; constexpr auto RootMaildirKey = "maildir"; // XXX: make this 'root-maildir' constexpr auto ContactsKey = "contacts"; constexpr auto PersonalAddressesKey = "personal-addresses"; constexpr auto CreatedKey = "created"; constexpr auto BatchSizeKey = "batch-size"; constexpr auto DefaultBatchSize = 250'000U; constexpr auto MaxMessageSizeKey = "max-message-size"; constexpr auto DefaultMaxMessageSize = 100'000'000U; constexpr auto ExpectedSchemaVersion = MU_STORE_SCHEMA_VERSION; /* we cache these prefix strings, so we don't have to allocate them all * the time; this should save 10-20 string allocs per message */ G_GNUC_CONST static const std::string& prefix (MuMsgFieldId mfid) { static std::string fields[MU_MSG_FIELD_ID_NUM]; static bool initialized = false; if (G_UNLIKELY(!initialized)) { for (int i = 0; i != MU_MSG_FIELD_ID_NUM; ++i) fields[i] = std::string (1, mu_msg_field_xapian_prefix ((MuMsgFieldId)i)); initialized = true; } return fields[mfid]; } static void add_synonym_for_flag (MuFlags flag, Xapian::WritableDatabase *db) { static const std::string pfx(prefix(MU_MSG_FIELD_ID_FLAGS)); db->clear_synonyms (pfx + mu_flag_name (flag)); db->add_synonym (pfx + mu_flag_name (flag), pfx + (std::string(1, (char)(tolower(mu_flag_char(flag)))))); } static void add_synonym_for_prio (MuMsgPrio prio, Xapian::WritableDatabase *db) { static const std::string pfx (prefix(MU_MSG_FIELD_ID_PRIO)); std::string s1 (pfx + mu_msg_prio_name (prio)); std::string s2 (pfx + (std::string(1, mu_msg_prio_char (prio)))); db->clear_synonyms (s1); db->clear_synonyms (s2); db->add_synonym (s1, s2); } struct Store::Private { #define LOCKED std::lock_guard<std::mutex> l(lock_); enum struct XapianOpts {ReadOnly, Open, CreateOverwrite, InMemory }; Private (const std::string& path, bool readonly): read_only_{readonly}, db_{make_xapian_db(path, read_only_ ? XapianOpts::ReadOnly : XapianOpts::Open)}, mdata_{make_metadata(path)}, contacts_{db().get_metadata(ContactsKey), mdata_.personal_addresses} { if (!readonly) writable_db().begin_transaction(); } Private (const std::string& path, const std::string& root_maildir, const StringVec& personal_addresses, const Store::Config& conf): read_only_{false}, db_{make_xapian_db(path, XapianOpts::CreateOverwrite)}, mdata_{init_metadata(conf, path, root_maildir, personal_addresses)}, contacts_{"", mdata_.personal_addresses} { writable_db().begin_transaction(); } Private (const std::string& root_maildir, const StringVec& personal_addresses, const Store::Config& conf): read_only_{false}, db_{make_xapian_db("", XapianOpts::InMemory)}, mdata_{init_metadata(conf, "", root_maildir, personal_addresses)}, contacts_{"", mdata_.personal_addresses} { } ~Private() try { g_debug("closing store @ %s", mdata_.database_path.c_str()); if (!read_only_) { writable_db().set_metadata (ContactsKey, contacts_.serialize()); commit(); } } MU_XAPIAN_CATCH_BLOCK; std::unique_ptr<Xapian::Database> make_xapian_db (const std::string db_path, XapianOpts opts) try { switch (opts) { case XapianOpts::ReadOnly: return std::make_unique<Xapian::Database>(db_path); case XapianOpts::Open: return std::make_unique<Xapian::WritableDatabase>(db_path, Xapian::DB_OPEN); case XapianOpts::CreateOverwrite: return std::make_unique<Xapian::WritableDatabase>(db_path, Xapian::DB_CREATE_OR_OVERWRITE); case XapianOpts::InMemory: return std::make_unique<Xapian::WritableDatabase>(std::string{}, Xapian::DB_BACKEND_INMEMORY); default: throw std::logic_error ("invalid xapian options"); } } catch (const Xapian::DatabaseError& xde) { throw Mu::Error(Error::Code::Store, "failed to open store @ %s: %s", db_path.c_str(), xde.get_msg().c_str()); } catch (...) { throw Mu::Error(Error::Code::Internal, "something went wrong when opening store @ %s", db_path.c_str()); } const Xapian::Database& db() const { return *db_.get(); } Xapian::WritableDatabase& writable_db() { if (read_only_) throw Mu::Error(Error::Code::AccessDenied, "database is read-only"); return dynamic_cast<Xapian::WritableDatabase&>(*db_.get()); } void dirty () try { if (++dirtiness_ > mdata_.batch_size) commit(); } MU_XAPIAN_CATCH_BLOCK; void commit () try { g_debug("committing %zu modification(s)", dirtiness_); dirtiness_ = 0; if (mdata_.in_memory) return; // not supported in the in-memory backend. writable_db().commit_transaction(); writable_db().begin_transaction(); } MU_XAPIAN_CATCH_BLOCK; void add_synonyms () { mu_flags_foreach ((MuFlagsForeachFunc)add_synonym_for_flag, &writable_db()); mu_msg_prio_foreach ((MuMsgPrioForeachFunc)add_synonym_for_prio, &writable_db()); } time_t metadata_time_t (const std::string& key) const { const auto ts = db().get_metadata(key); return (time_t)atoll(db().get_metadata(key).c_str()); } Store::Metadata make_metadata(const std::string& db_path) { Store::Metadata mdata; mdata.database_path = db_path; mdata.schema_version = db().get_metadata(SchemaVersionKey); mdata.created = ::atoll(db().get_metadata(CreatedKey).c_str()); mdata.read_only = read_only_; mdata.batch_size = ::atoll(db().get_metadata(BatchSizeKey).c_str()); mdata.max_message_size = ::atoll(db().get_metadata(MaxMessageSizeKey).c_str()); mdata.in_memory = db_path.empty(); mdata.root_maildir = db().get_metadata(RootMaildirKey); mdata.personal_addresses = Mu::split(db().get_metadata(PersonalAddressesKey),","); return mdata; } Store::Metadata init_metadata(const Store::Config& conf, const std::string& path, const std::string& root_maildir, const StringVec& personal_addresses) { writable_db().set_metadata(SchemaVersionKey, ExpectedSchemaVersion); writable_db().set_metadata(CreatedKey, Mu::format("%" PRId64, (int64_t)::time({}))); const size_t batch_size = conf.batch_size ? conf.batch_size : DefaultBatchSize; writable_db().set_metadata(BatchSizeKey, Mu::format("%zu", batch_size)); const size_t max_msg_size = conf.max_message_size ? conf.max_message_size : DefaultMaxMessageSize; writable_db().set_metadata(MaxMessageSizeKey, Mu::format("%zu", max_msg_size)); writable_db().set_metadata(RootMaildirKey, root_maildir); std::string addrs; for (const auto& addr : personal_addresses) { // _very_ minimal check. if (addr.find(",") != std::string::npos) throw Mu::Error(Error::Code::InvalidArgument, "e-mail address '%s' contains comma", addr.c_str()); addrs += (addrs.empty() ? "": ",") + addr; } writable_db().set_metadata (PersonalAddressesKey, addrs); return make_metadata(path); } Xapian::docid add_or_update_msg (Xapian::docid docid, MuMsg *msg, GError **err); Xapian::Document new_doc_from_message (MuMsg *msg); const bool read_only_{}; std::unique_ptr<Xapian::Database> db_; const Store::Metadata mdata_; Contacts contacts_; std::unique_ptr<Indexer> indexer_; std::atomic<bool> in_transaction_{}; std::mutex lock_; size_t dirtiness_{}; mutable std::atomic<std::size_t> ref_count_{1}; }; static void hash_str (char *buf, size_t buf_size, const char *data) { g_snprintf(buf, buf_size, "016%" PRIx64, mu_util_get_hash(data)); } static std::string get_uid_term (const char* path) { char uid_term[1 + 16 + 1] = {'\0'}; uid_term[0] = mu_msg_field_xapian_prefix(MU_MSG_FIELD_ID_UID); hash_str(uid_term + 1, sizeof(uid_term)-1, path); return std::string{uid_term, sizeof(uid_term)}; } #undef LOCKED #define LOCKED std::lock_guard<std::mutex> l__(priv_->lock_) Store::Store (const std::string& path, bool readonly): priv_{std::make_unique<Private>(path, readonly)} { if (metadata().schema_version != ExpectedSchemaVersion) throw Mu::Error(Error::Code::SchemaMismatch, "expected schema-version %s, but got %s; " "please use 'mu init'", ExpectedSchemaVersion, metadata().schema_version.c_str()); } Store::Store (const std::string& path, const std::string& maildir, const StringVec& personal_addresses, const Store::Config& conf): priv_{std::make_unique<Private>(path, maildir, personal_addresses, conf)} {} Store::Store (const std::string& maildir, const StringVec& personal_addresses, const Config& conf): priv_{std::make_unique<Private>(maildir, personal_addresses, conf)} {} Store::~Store() = default; const Store::Metadata& Store::metadata() const { return priv_->mdata_; } const Contacts& Store::contacts() const { return priv_->contacts_; } const Xapian::Database& Store::database() const { return priv_->db(); } Xapian::WritableDatabase& Store::writable_database() { return priv_->writable_db(); } Indexer& Store::indexer() { LOCKED; if (metadata().read_only) throw Error{Error::Code::Store, "no indexer for read-only store"}; else if (!priv_->indexer_) priv_->indexer_ = std::make_unique<Indexer>(*this); return *priv_->indexer_.get(); } std::size_t Store::size() const { LOCKED; return priv_->db().get_doccount(); } bool Store::empty() const { return size() == 0; } static std::string maildir_from_path (const std::string& root, const std::string& path) { if (G_UNLIKELY(root.empty()) || root.length() >= path.length() || path.find(root) != 0) throw Mu::Error{Error::Code::InvalidArgument, "root '%s' is not a proper suffix of path '%s'", root.c_str(), path.c_str()}; auto mdir{path.substr(root.length())}; auto slash{mdir.rfind('/')}; if (G_UNLIKELY(slash == std::string::npos) || slash < 4) throw Mu::Error{Error::Code::InvalidArgument, "invalid path: %s", path.c_str()}; mdir.erase(slash); auto subdir=mdir.data()+slash-4; if (G_UNLIKELY(strncmp(subdir, "/cur", 4) != 0 && strncmp(subdir, "/new", 4))) throw Mu::Error{Error::Code::InvalidArgument, "cannot find '/new' or '/cur' - invalid path: %s", path.c_str()}; if (mdir.length() == 4) return "/"; mdir.erase(mdir.length()-4); return mdir; } unsigned Store::add_message (const std::string& path) { LOCKED; GError *gerr{}; const auto maildir{maildir_from_path(metadata().root_maildir, path)}; auto msg{mu_msg_new_from_file (path.c_str(), maildir.c_str(), &gerr)}; if (G_UNLIKELY(!msg)) throw Error{Error::Code::Message, "failed to create message: %s", gerr ? gerr->message : "something went wrong"}; const auto docid{priv_->add_or_update_msg (0, msg, &gerr)}; mu_msg_unref (msg); if (G_UNLIKELY(docid == InvalidId)) throw Error{Error::Code::Message, "failed to add message: %s", gerr ? gerr->message : "something went wrong"}; g_debug ("added message @ %s; docid = %u", path.c_str(), docid); priv_->dirty(); return docid; } bool Store::update_message (MuMsg *msg, unsigned docid) { GError *gerr{}; const auto docid2{priv_->add_or_update_msg (docid, msg, &gerr)}; if (G_UNLIKELY(docid != docid2)) throw Error{Error::Code::Internal, "failed to update message", gerr ? gerr->message : "something went wrong"}; g_debug ("updated message @ %s; docid = %u", mu_msg_get_path(msg), docid); priv_->dirty(); return true; } bool Store::remove_message (const std::string& path) { LOCKED; try { const std::string term{(get_uid_term(path.c_str()))}; priv_->writable_db().delete_document(term); } MU_XAPIAN_CATCH_BLOCK_RETURN (false); g_debug ("deleted message @ %s from store", path.c_str()); priv_->dirty(); return true; } void Store::remove_messages (const std::vector<Store::Id>& ids) { LOCKED; try { for (auto&& id: ids) { priv_->writable_db().delete_document(id); priv_->dirty(); } } MU_XAPIAN_CATCH_BLOCK; } time_t Store::dirstamp (const std::string& path) const { LOCKED; const auto ts = priv_->db().get_metadata(path); if (ts.empty()) return 0; else return (time_t)strtoll(ts.c_str(), NULL, 16); } void Store::set_dirstamp (const std::string& path, time_t tstamp) { LOCKED; std::array<char, 2*sizeof(tstamp)+1> data{}; const std::size_t len = g_snprintf (data.data(), data.size(), "%zx", (size_t)tstamp); priv_->writable_db().set_metadata(path, std::string{data.data(), len}); priv_->dirty(); } MuMsg* Store::find_message (unsigned docid) const { LOCKED; try { Xapian::Document *doc{new Xapian::Document{priv_->db().get_document (docid)}}; GError *gerr{}; auto msg{mu_msg_new_from_doc (reinterpret_cast<XapianDocument*>(doc), &gerr)}; if (!msg) { g_warning ("could not create message: %s", gerr ? gerr->message : "something went wrong"); g_clear_error(&gerr); } return msg; } MU_XAPIAN_CATCH_BLOCK_RETURN (nullptr); } bool Store::contains_message (const std::string& path) const { LOCKED; try { const std::string term (get_uid_term(path.c_str())); return priv_->db().term_exists (term); } MU_XAPIAN_CATCH_BLOCK_RETURN(false); } std::size_t Store::for_each_message_path (Store::ForEachMessageFunc func) const { LOCKED; size_t n{}; try { Xapian::Enquire enq{priv_->db()}; enq.set_query (Xapian::Query::MatchAll); enq.set_cutoff (0,0); Xapian::MSet matches(enq.get_mset (0, priv_->db().get_doccount())); for (auto&& it = matches.begin(); it != matches.end(); ++it, ++n) if (!func (*it, it.get_document().get_value(MU_MSG_FIELD_ID_PATH))) break; } MU_XAPIAN_CATCH_BLOCK; return n; } static MuMsgFieldId field_id (const std::string& field) { if (field.empty()) return MU_MSG_FIELD_ID_NONE; MuMsgFieldId id = mu_msg_field_id_from_name (field.c_str(), FALSE); if (id != MU_MSG_FIELD_ID_NONE) return id; else if (field.length() == 1) return mu_msg_field_id_from_shortcut (field[0], FALSE); else return MU_MSG_FIELD_ID_NONE; } std::size_t Store::for_each_term (const std::string& field, Store::ForEachTermFunc func) const { LOCKED; size_t n{}; try { const auto id = field_id (field.c_str()); if (id == MU_MSG_FIELD_ID_NONE) return {}; char pfx[] = { mu_msg_field_xapian_prefix(id), '\0' }; std::vector<std::string> terms; for (auto it = priv_->db().allterms_begin(pfx); it != priv_->db().allterms_end(pfx); ++it) { if (!func(*it)) break; } } MU_XAPIAN_CATCH_BLOCK; return n; } void Store::commit () try { LOCKED; priv_->commit(); } MU_XAPIAN_CATCH_BLOCK; static void add_terms_values_date (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { const auto dstr = Mu::date_to_time_t_string ( (time_t)mu_msg_get_field_numeric (msg, mfid)); doc.add_value ((Xapian::valueno)mfid, dstr); } static void add_terms_values_size (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { const auto szstr = Mu::size_to_string (mu_msg_get_field_numeric (msg, mfid)); doc.add_value ((Xapian::valueno)mfid, szstr); } G_GNUC_CONST static const std::string& flag_val (char flagchar) { static const std::string pfx (prefix(MU_MSG_FIELD_ID_FLAGS)), draftstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_DRAFT))), flaggedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_FLAGGED))), passedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_PASSED))), repliedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_REPLIED))), seenstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_SEEN))), trashedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_TRASHED))), newstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_NEW))), signedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_SIGNED))), cryptstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_ENCRYPTED))), attachstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_HAS_ATTACH))), unreadstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_UNREAD))), liststr (pfx + (char)tolower(mu_flag_char(MU_FLAG_LIST))); switch (flagchar) { case 'D': return draftstr; case 'F': return flaggedstr; case 'P': return passedstr; case 'R': return repliedstr; case 'S': return seenstr; case 'T': return trashedstr; case 'N': return newstr; case 'z': return signedstr; case 'x': return cryptstr; case 'a': return attachstr; case 'l': return liststr; case 'u': return unreadstr; default: g_return_val_if_reached (flaggedstr); return flaggedstr; } } /* pre-calculate; optimization */ G_GNUC_CONST static const std::string& prio_val (MuMsgPrio prio) { static const std::string pfx (prefix(MU_MSG_FIELD_ID_PRIO)); static const std::string low (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_LOW))), norm (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_NORMAL))), high (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_HIGH))); switch (prio) { case MU_MSG_PRIO_LOW: return low; case MU_MSG_PRIO_NORMAL: return norm; case MU_MSG_PRIO_HIGH: return high; default: g_return_val_if_reached (norm); return norm; } } static void // add term, truncate if needed. add_term (Xapian::Document& doc, const std::string& term) { if (term.length() < Store::MaxTermLength) doc.add_term(term); else doc.add_term(term.substr(0, Store::MaxTermLength)); } static void add_terms_values_number (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { gint64 num = mu_msg_get_field_numeric (msg, mfid); const std::string numstr (Xapian::sortable_serialise((double)num)); doc.add_value ((Xapian::valueno)mfid, numstr); if (mfid == MU_MSG_FIELD_ID_FLAGS) { const char *cur = mu_flags_to_str_s ((MuFlags)num,(MuFlagType)MU_FLAG_TYPE_ANY); g_return_if_fail (cur); while (*cur) { add_term (doc, flag_val(*cur)); ++cur; } } else if (mfid == MU_MSG_FIELD_ID_PRIO) add_term (doc, prio_val((MuMsgPrio)num)); } /* for string and string-list */ static void add_terms_values_str (Xapian::Document& doc, const char *val, MuMsgFieldId mfid) { const auto flat = Mu::utf8_flatten (val); if (mu_msg_field_xapian_index (mfid)) { Xapian::TermGenerator termgen; termgen.set_document (doc); termgen.index_text (flat, 1, prefix(mfid)); } if (mu_msg_field_xapian_term(mfid)) add_term(doc, prefix(mfid) + flat); } static void add_terms_values_string (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { const char *orig; if (!(orig = mu_msg_get_field_string (msg, mfid))) return; /* nothing to do */ /* the value is what we display in search results; the * unchanged original */ if (mu_msg_field_xapian_value(mfid)) doc.add_value ((Xapian::valueno)mfid, orig); add_terms_values_str (doc, orig, mfid); } static void add_terms_values_string_list (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { const GSList *lst; lst = mu_msg_get_field_string_list (msg, mfid); if (!lst) return; if (mu_msg_field_xapian_value (mfid)) { gchar *str; str = mu_str_from_list (lst, ','); if (str) doc.add_value ((Xapian::valueno)mfid, str); g_free (str); } if (mu_msg_field_xapian_term (mfid)) { for (; lst; lst = g_slist_next ((GSList*)lst)) add_terms_values_str (doc, (const gchar*)lst->data, mfid); } } struct PartData { PartData (Xapian::Document& doc, MuMsgFieldId mfid): _doc (doc), _mfid(mfid) {} Xapian::Document _doc; MuMsgFieldId _mfid; }; /* index non-body text parts */ static void maybe_index_text_part (MuMsg *msg, MuMsgPart *part, PartData *pdata) { char *txt; Xapian::TermGenerator termgen; /* only deal with attachments/messages; inlines are indexed as * body parts */ if (!(part->part_type & MU_MSG_PART_TYPE_ATTACHMENT) && !(part->part_type & MU_MSG_PART_TYPE_MESSAGE)) return; txt = mu_msg_part_get_text (msg, part, MU_MSG_OPTION_NONE); if (!txt) return; termgen.set_document(pdata->_doc); const auto str = Mu::utf8_flatten (txt); g_free (txt); termgen.index_text (str, 1, prefix(MU_MSG_FIELD_ID_EMBEDDED_TEXT)); } static void each_part (MuMsg *msg, MuMsgPart *part, PartData *pdata) { char *fname; static const std::string file (prefix(MU_MSG_FIELD_ID_FILE)), mime (prefix(MU_MSG_FIELD_ID_MIME)); /* save the mime type of any part */ if (part->type) { char ctype[Store::MaxTermLength + 1]; g_snprintf(ctype, sizeof(ctype), "%s/%s", part->type, part->subtype); add_term(pdata->_doc, mime + ctype); } if ((fname = mu_msg_part_get_filename (part, FALSE))) { const auto flat = Mu::utf8_flatten (fname); g_free (fname); add_term(pdata->_doc, file + flat); } maybe_index_text_part (msg, part, pdata); } static void add_terms_values_attach (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { PartData pdata (doc, mfid); mu_msg_part_foreach (msg, MU_MSG_OPTION_RECURSE_RFC822, (MuMsgPartForeachFunc)each_part, &pdata); } static void add_terms_values_body (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid) { if (mu_msg_get_flags(msg) & MU_FLAG_ENCRYPTED) return; /* ignore encrypted bodies */ auto str = mu_msg_get_body_text (msg, MU_MSG_OPTION_NONE); if (!str) /* FIXME: html->txt fallback needed */ str = mu_msg_get_body_html (msg, MU_MSG_OPTION_NONE); if (!str) return; /* no body... */ Xapian::TermGenerator termgen; termgen.set_document(doc); const auto flat = Mu::utf8_flatten(str); termgen.index_text (flat, 1, prefix(mfid)); } struct MsgDoc { Xapian::Document *_doc; MuMsg *_msg; Store::Private *_priv; /* callback data, to determine whether this message is 'personal' */ gboolean _personal; const StringVec *_my_addresses; }; static void add_terms_values_default (MuMsgFieldId mfid, MsgDoc *msgdoc) { if (mu_msg_field_is_numeric (mfid)) add_terms_values_number (*msgdoc->_doc, msgdoc->_msg, mfid); else if (mu_msg_field_is_string (mfid)) add_terms_values_string (*msgdoc->_doc, msgdoc->_msg, mfid); else if (mu_msg_field_is_string_list(mfid)) add_terms_values_string_list (*msgdoc->_doc, msgdoc->_msg, mfid); else g_return_if_reached (); } static void add_terms_values (MuMsgFieldId mfid, MsgDoc* msgdoc) { /* note: contact-stuff (To/Cc/From) will handled in * each_contact_info, not here */ if (!mu_msg_field_xapian_index(mfid) && !mu_msg_field_xapian_term(mfid) && !mu_msg_field_xapian_value(mfid)) return; switch (mfid) { case MU_MSG_FIELD_ID_DATE: add_terms_values_date (*msgdoc->_doc, msgdoc->_msg, mfid); break; case MU_MSG_FIELD_ID_SIZE: add_terms_values_size (*msgdoc->_doc, msgdoc->_msg, mfid); break; case MU_MSG_FIELD_ID_BODY_TEXT: add_terms_values_body (*msgdoc->_doc, msgdoc->_msg, mfid); break; /* note: add_terms_values_attach handles _FILE, _MIME and * _ATTACH_TEXT msgfields */ case MU_MSG_FIELD_ID_FILE: add_terms_values_attach (*msgdoc->_doc, msgdoc->_msg, mfid); break; case MU_MSG_FIELD_ID_MIME: case MU_MSG_FIELD_ID_EMBEDDED_TEXT: break; case MU_MSG_FIELD_ID_THREAD_ID: case MU_MSG_FIELD_ID_UID: break; /* already taken care of elsewhere */ default: return add_terms_values_default (mfid, msgdoc); } } static const std::string& xapian_pfx (MuMsgContact *contact) { static const std::string empty; /* use ptr to string to prevent copy... */ switch (contact->type) { case MU_MSG_CONTACT_TYPE_TO: return prefix(MU_MSG_FIELD_ID_TO); case MU_MSG_CONTACT_TYPE_FROM: return prefix(MU_MSG_FIELD_ID_FROM); case MU_MSG_CONTACT_TYPE_CC: return prefix(MU_MSG_FIELD_ID_CC); case MU_MSG_CONTACT_TYPE_BCC: return prefix(MU_MSG_FIELD_ID_BCC); default: g_warning ("unsupported contact type %u", (unsigned)contact->type); return empty; } } static void add_address_subfields (Xapian::Document& doc, const char *addr, const std::string& pfx) { const char *at, *domain_part; char *name_part; /* add "foo" and "bar.com" as terms as well for * "foo@bar.com" */ if (G_UNLIKELY(!(at = (g_strstr_len (addr, -1, "@"))))) return; name_part = g_strndup(addr, at - addr); // foo domain_part = at + 1; add_term(doc, pfx + name_part); add_term(doc, pfx + domain_part); g_free (name_part); } static gboolean each_contact_info (MuMsgContact *contact, MsgDoc *msgdoc) { /* for now, don't store reply-to addresses */ if (mu_msg_contact_type (contact) == MU_MSG_CONTACT_TYPE_REPLY_TO) return TRUE; const std::string pfx (xapian_pfx(contact)); if (pfx.empty()) return TRUE; /* unsupported contact type */ if (!mu_str_is_empty(contact->name)) { Xapian::TermGenerator termgen; termgen.set_document (*msgdoc->_doc); const auto flat = Mu::utf8_flatten(contact->name); termgen.index_text (flat, 1, pfx); } if (!mu_str_is_empty(contact->email)) { const auto flat = Mu::utf8_flatten(contact->email); add_term(*msgdoc->_doc, pfx + flat); add_address_subfields (*msgdoc->_doc, contact->email, pfx); /* store it also in our contacts cache */ auto& contacts{msgdoc->_priv->contacts_}; contacts.add(Mu::ContactInfo(contact->full_address, contact->email, contact->name ? contact->name : "", msgdoc->_personal, mu_msg_get_date(msgdoc->_msg))); } return TRUE; } Xapian::Document Store::Private::new_doc_from_message (MuMsg *msg) { Xapian::Document doc; MsgDoc docinfo = {&doc, msg, this, 0, NULL}; mu_msg_field_foreach ((MuMsgFieldForeachFunc)add_terms_values, &docinfo); mu_msg_contact_foreach (msg, [](auto contact, gpointer msgdocptr)->gboolean { auto msgdoc{reinterpret_cast<MsgDoc*>(msgdocptr)}; if (!contact->email) return FALSE; // invalid contact else if (msgdoc->_personal) return TRUE; // already deemed personal if (msgdoc->_priv->contacts_.is_personal(contact->email)) msgdoc->_personal = true; // this one's personal. return TRUE; }, &docinfo); /* also store the contact-info as separate terms, and add it * to the cache */ mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact_info, &docinfo); // g_printerr ("\n--%s\n--\n", doc.serialise().c_str()); return doc; } static void update_threading_info (MuMsg *msg, Xapian::Document& doc) { const GSList *refs; // refs contains a list of parent messages, with the oldest // one first until the last one, which is the direct parent of // the current message. of course, it may be empty. // // NOTE: there may be cases where the list is truncated; we happily // ignore that case. refs = mu_msg_get_references (msg); char thread_id[16+1]; hash_str(thread_id, sizeof(thread_id), refs ? (const char*)refs->data : mu_msg_get_msgid (msg)); add_term (doc, prefix(MU_MSG_FIELD_ID_THREAD_ID) + thread_id); doc.add_value((Xapian::valueno)MU_MSG_FIELD_ID_THREAD_ID, thread_id); } Xapian::docid Store::Private::add_or_update_msg (unsigned docid, MuMsg *msg, GError **err) { g_return_val_if_fail (msg, InvalidId); try { Xapian::Document doc (new_doc_from_message(msg)); const std::string term (get_uid_term (mu_msg_get_path(msg))); add_term (doc, term); // update the threading info if this message has a message id if (mu_msg_get_msgid (msg)) update_threading_info (msg, doc); if (docid == 0) return writable_db().replace_document (term, doc); writable_db().replace_document (docid, doc); return docid; } MU_XAPIAN_CATCH_BLOCK_G_ERROR (err, MU_ERROR_XAPIAN_STORE_FAILED); return InvalidId; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-store.hh���������������������������������������������������������������������������0000664�0000000�0000000�00000022674�14143670036�0015101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_STORE_HH__ #define __MU_STORE_HH__ #include <mu-msg.hh> #include <string> #include <vector> #include <mutex> #include <ctime> #include "mu-contacts.hh" #include <xapian.h> #include <utils/mu-utils.hh> #include <index/mu-indexer.hh> namespace Mu { class Store { public: using Id = Xapian::docid; /**< Id for a message in the store */ static constexpr Id InvalidId = 0; /**< Invalid store id */ static constexpr size_t MaxTermLength = 240; /**< Maximum length of a term, http://article.gmane.org/gmane.comp.search.xapian.general/3656 */ /** * Construct a store for an existing document database * * @param path path to the database * @param readonly whether to open the database in read-only mode */ Store (const std::string& path, bool readonly=true); struct Config { size_t max_message_size{}; /**< maximum size (in bytes) for a message, or 0 for default */ size_t batch_size{}; /**< size of batches before committing, or 0 for default */ }; /** * Construct a store for a not-yet-existing document database * * @param path path to the database * @param maildir maildir to use for this store * @param personal_addresses addresses that should be recognized as * 'personal' for identifying personal messages. */ Store (const std::string& path, const std::string& maildir, const StringVec& personal_addresses, const Config& conf); /** * Construct an in-memory, writeable store for testing * * @param maildir maildir to use for this store * @param personal_addresses addresses that should be recognized as * 'personal' for identifying personal messages. */ Store (const std::string& maildir, const StringVec& personal_addresses, const Config& conf); /** * DTOR */ ~Store(); struct Metadata { std::string database_path; /**< Full path to the Xapian database */ std::string schema_version; /**< Database schema version */ std::time_t created; /**< database creation time */ bool read_only; /**< Is the database opened read-only? */ size_t batch_size; /**< Maximum database transaction batch size */ bool in_memory; /**< Is this an in-memory database (for testing)?*/ std::string root_maildir; /**< Absolute path to the top-level maildir */ StringVec personal_addresses; /**< Personal e-mail addresses */ size_t max_message_size; /**< Maximus allowed message size */ }; /** * Get metadata about this store. * * @return the metadata */ const Metadata& metadata() const; /** * Get the Contacts object for this store * * @return the Contacts object */ const Contacts& contacts() const; /** * Get the underlying Xapian database for this store. * * @return the database */ const Xapian::Database& database() const; /** * Get the underlying writable Xapian database for this * store. Throws is this store is not writable. * * @return the writable database */ Xapian::WritableDatabase& writable_database(); /** * Get the Indexer associated with this store. It is an error to call * this on a read-only store. * * @return the indexer. */ Indexer& indexer(); /** * Add a message to the store. * * @param path the message path. * * @return the doc id of the added message */ Id add_message (const std::string& path); /** * Update a message in the store. * * @param msg a message * @param id the id for this message * * @return false in case of failure; true otherwise. */ bool update_message (MuMsg *msg, Id id); /** * Remove a message from the store. It will _not_ remove the message * fromt he file system. * * @param path the message path. * * @return true if removing happened; false otherwise. */ bool remove_message (const std::string& path); /** * Remove a number if messages from the store. It will _not_ remove the * message fromt he file system. * * @param ids vector with store ids for the message */ void remove_messages (const std::vector<Id>& ids); /** * Remove a message from the store. It will _not_ remove the message * fromt he file system. * * @param id the store id for the message */ void remove_message (Id id) { remove_messages({id}); } /** * Find message in the store. * * @param id doc id for the message to find * * @return a message (owned by caller), or nullptr */ MuMsg* find_message (Id id) const; /** * does a certain message exist in the store already? * * @param path the message path * * @return true if the message exists in the store, false otherwise */ bool contains_message (const std::string& path) const; /** * Prototype for the ForEachMessageFunc * * @param id :t store Id for the message * @param path: the absolute path to the message * * @return true if for_each should continue; false to quit */ using ForEachMessageFunc = std::function<bool(Id, const std::string&)>; /** * Call @param func for each document in the store. This takes a lock on * the store, so the func should _not_ call any other Store:: methods. * * @param func a Callable invoked for each message. * * @return the number of times func was invoked */ size_t for_each_message_path (ForEachMessageFunc func) const; /** * Prototype for the ForEachTermFunc * * @param term: * * @return true if for_each should continue; false to quit */ using ForEachTermFunc = std::function<bool(const std::string&)>; /** * Call @param func for each term for the given field in the store. This * takes a lock on the store, so the func should _not_ call any other * Store:: methods. * * @param func a Callable invoked for each message. * * @return the number of times func was invoked */ size_t for_each_term (const std::string& field, ForEachTermFunc func) const; /** * Get the timestamp for some message, or 0 if not found * * @param path the path * * @return the timestamp, or 0 if not found */ time_t message_tstamp (const std::string& path) const; /** * Get the timestamp for some directory * * @param path the path * * @return the timestamp, or 0 if not found */ time_t dirstamp (const std::string& path) const; /** * Set the timestamp for some directory * * @param path a filesystem path * @param tstamp the timestamp for that path */ void set_dirstamp (const std::string& path, time_t tstamp); /** * Get the number of documents in the document database * * @return the number */ std::size_t size() const; /** * Is the database empty? * * @return true or false */ bool empty() const; /** * Commit the current group of modifications (i.e., transaction) to disk; * This rarely needs to be called explicitly, as Store will take care of * it. */ void commit(); /** * Get a reference to the private data. For internal use. * * @return private reference. */ struct Private; std::unique_ptr<Private>& priv() { return priv_; } const std::unique_ptr<Private>& priv() const { return priv_; } private: std::unique_ptr<Private> priv_; }; } // namespace Mu #endif /* __MU_STORE_HH__ */ ��������������������������������������������������������������������mu-1.6.10/lib/mu-tokenizer.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000005344�14143670036�0015740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include "mu-tokenizer.hh" #include "utils/mu-utils.hh" #include <cctype> #include <iostream> #include <algorithm> using namespace Mu; static bool is_separator (char c) { if (isblank(c)) return true; const auto seps = std::string ("()"); return seps.find(c) != std::string::npos; } static Mu::Token op_or_value (size_t pos, const std::string& val) { auto s = val; std::transform(s.begin(), s.end(), s.begin(), ::tolower); if (s == "and") return Token{pos, Token::Type::And, val}; else if (s == "or") return Token{pos, Token::Type::Or, val}; else if (s == "xor") return Token{pos, Token::Type::Xor, val}; else if (s == "not") return Token{pos, Token::Type::Not, val}; else return Token{pos, Token::Type::Data, val}; } static void unread_char (std::string& food, char kar, size_t& pos) { food = kar + food; --pos; } static Mu::Token eat_token (std::string& food, size_t& pos) { bool quoted{}; bool escaped{}; std::string value {}; while (!food.empty()) { const auto kar = food[0]; food.erase(0, 1); ++pos; if (kar == '\\') { escaped = !escaped; if (escaped) continue; } if (kar == '"') { if (!escaped && quoted) return Token{pos, Token::Type::Data, value}; else { quoted = true; continue; } } if (!quoted && !escaped && is_separator(kar)) { if (!value.empty() && kar != ':') { unread_char (food, kar, pos); return op_or_value(pos, value); } if (quoted || isblank(kar)) continue; switch (kar) { case '(': return {pos, Token::Type::Open, "("}; case ')': return {pos, Token::Type::Close,")"}; default: break; } } value += kar; escaped = false; } return {pos, Token::Type::Data, value}; } Mu::Tokens Mu::tokenize (const std::string& s) { Tokens tokens{}; std::string food = utf8_clean(s); size_t pos{0}; if (s.empty()) return {}; while (!food.empty()) tokens.emplace_back(eat_token (food, pos)); return tokens; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-tokenizer.hh�����������������������������������������������������������������������0000664�0000000�0000000�00000006571�14143670036�0015755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef __TOKENIZER_HH__ #define __TOKENIZER_HH__ #include <string> #include <vector> #include <deque> #include <ostream> #include <stdexcept> // A simple tokenizer, which turns a string into a deque of tokens // // It recognizes '(', ')', '*' 'and', 'or', 'xor', 'not' // // Note that even if we recognizes those at the lexical level, they might be demoted to mere strings // when we're creating the parse tree. // // Furthermore, we detect ranges ("a..b") and regexps (/../) at the parser level, since we need a // bit more context to resolve ambiguities. namespace Mu { // A token struct Token { enum class Type { Data, /**< e .g., banana or date:..456 */ // Brackets Open, /**< ( */ Close, /**< ) */ // Unops Not, /**< logical not*/ // Binops And, /**< logical and */ Or, /**< logical not */ Xor, /**< logical xor */ Empty, /**< nothing */ }; size_t pos{}; /**< position in string */ Type type{}; /**< token type */ const std::string str{}; /**< data for this token */ /** * operator== * * @param rhs right-hand side * * @return true if rhs is equal to this; false otherwise */ bool operator==(const Token& rhs) const { return pos == rhs.pos && type == rhs.type && str == rhs.str; } }; /** * operator<< * * @param os an output stream * @param t a token type * * @return the updated output stream */ inline std::ostream& operator<< (std::ostream& os, Token::Type t) { switch (t) { case Token::Type::Data: os << "<data>"; break; case Token::Type::Open: os << "<open>"; break; case Token::Type::Close: os << "<close>";break; case Token::Type::Not: os << "<not>"; break; case Token::Type::And: os << "<and>"; break; case Token::Type::Or: os << "<or>"; break; case Token::Type::Xor: os << "<xor>"; break; case Token::Type::Empty: os << "<empty>"; break; default: // can't happen, but pacify compiler throw std::runtime_error ("<<bug>>"); } return os; } /** * operator<< * * @param os an output stream * @param t a token * * @return the updated output stream */ inline std::ostream& operator<< (std::ostream& os, const Token& t) { os << t.pos << ": " << t.type; if (!t.str.empty()) os << " [" << t.str << "]"; return os; } /** * Tokenize a string into a vector of tokens. The tokenization always succeeds, ie., ignoring errors * such a missing end-". * * @param s a string * * @return a deque of tokens */ using Tokens = std::deque<Token>; Tokens tokenize (const std::string& s); } // namespace Mu #endif /* __TOKENIZER_HH__ */ ���������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-tree.hh����������������������������������������������������������������������������0000664�0000000�0000000�00000005211�14143670036�0014670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef TREE_HH__ #define TREE_HH__ #include <vector> #include <string> #include <iostream> #include <mu-data.hh> #include <utils/mu-error.hh> namespace Mu { // A node in the parse tree struct Node { enum class Type { Empty, // only for empty trees OpAnd, OpOr, OpXor, OpAndNot, OpNot, Value, Range, Invalid }; Node(Type _type, std::unique_ptr<Data>&& _data): type{_type}, data{std::move(_data)} {} Node(Type _type): type{_type} {} Node(Node&& rhs) = default; Type type; std::unique_ptr<Data> data; static const char* type_name (Type t) { switch (t) { case Type::Empty: return ""; break; case Type::OpAnd: return "and"; break; case Type::OpOr: return "or"; break; case Type::OpXor: return "xor"; break; case Type::OpAndNot: return "andnot"; break; case Type::OpNot: return "not"; break; case Type::Value: return "value"; break; case Type::Range: return "range"; break; case Type::Invalid: return "<invalid>"; break; default: throw Mu::Error(Error::Code::Internal, "unexpected type"); } } static constexpr bool is_binop(Type t) { return t == Type::OpAnd || t == Type::OpAndNot || t == Type::OpOr || t == Type::OpXor; } }; inline std::ostream& operator<< (std::ostream& os, const Node& t) { os << Node::type_name(t.type); if (t.data) os << t.data; return os; } struct Tree { Tree(Node&& _node): node(std::move(_node)) {} Tree(Tree&& rhs) = default; void add_child (Tree&& child) { children.emplace_back(std::move(child)); } bool empty() const { return node.type == Node::Type::Empty; } Node node; std::vector<Tree> children; }; inline std::ostream& operator<< (std::ostream& os, const Tree& tree) { os << '(' << tree.node; for (const auto& subtree : tree.children) os << subtree; os << ')'; return os; } } // namespace Mu #endif /* TREE_HH__ */ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-xapian.cc��������������������������������������������������������������������������0000664�0000000�0000000�00000007357�14143670036�0015214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif /*HAVE_CONFIG_H*/ #include <xapian.h> #include "mu-xapian.hh" #include <utils/mu-error.hh> using namespace Mu; static Xapian::Query xapian_query_op (const Mu::Tree& tree) { Xapian::Query::op op; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (tree.node.type) { case Node::Type::OpNot: // OpNot x ::= <all> AND NOT x if (tree.children.size() != 1) throw std::runtime_error ("invalid # of children"); return Xapian::Query (Xapian::Query::OP_AND_NOT, Xapian::Query::MatchAll, xapian_query(tree.children.front())); case Node::Type::OpAnd: op = Xapian::Query::OP_AND; break; case Node::Type::OpOr: op = Xapian::Query::OP_OR; break; case Node::Type::OpXor: op = Xapian::Query::OP_XOR; break; case Node::Type::OpAndNot: op = Xapian::Query::OP_AND_NOT; break; default: throw Mu::Error (Error::Code::Internal, "invalid op"); // bug } #pragma GCC diagnostic pop std::vector<Xapian::Query> childvec; for (const auto& subtree: tree.children) childvec.emplace_back(xapian_query(subtree)); return Xapian::Query(op, childvec.begin(), childvec.end()); } static Xapian::Query make_query (const Value* val, const std::string& str, bool maybe_wildcard) { const auto vlen{str.length()}; if (!maybe_wildcard || vlen <= 1 || str[vlen - 1] != '*') return Xapian::Query(val->prefix + str); else return Xapian::Query(Xapian::Query::OP_WILDCARD, val->prefix + str.substr(0, vlen - 1)); } static Xapian::Query xapian_query_value (const Mu::Tree& tree) { const auto v = dynamic_cast<Value*> (tree.node.data.get()); if (!v->phrase) return make_query(v, v->value, true/*maybe-wildcard*/); const auto parts = split (v->value, " "); if (parts.empty()) return Xapian::Query::MatchNothing; // shouldn't happen if (parts.size() == 1) return make_query(v, parts.front(), true/*maybe-wildcard*/); std::vector<Xapian::Query> phvec; for (const auto& p: parts) phvec.emplace_back(make_query(v, p, false/*no wildcards*/)); return Xapian::Query (Xapian::Query::OP_PHRASE, phvec.begin(), phvec.end()); } static Xapian::Query xapian_query_range (const Mu::Tree& tree) { const auto r { dynamic_cast<Range *>(tree.node.data.get()) }; return Xapian::Query(Xapian::Query::OP_VALUE_RANGE, (Xapian::valueno)r->id, r->lower, r->upper); } Xapian::Query Mu::xapian_query (const Mu::Tree& tree) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" switch (tree.node.type) { case Node::Type::Empty: return Xapian::Query(); case Node::Type::OpNot: case Node::Type::OpAnd: case Node::Type::OpOr: case Node::Type::OpXor: case Node::Type::OpAndNot: return xapian_query_op (tree); case Node::Type::Value: return xapian_query_value (tree); case Node::Type::Range: return xapian_query_range (tree); default: throw Mu::Error (Error::Code::Internal, "invalid query"); // bug } #pragma GCC diagnostic pop } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/mu-xapian.hh��������������������������������������������������������������������������0000664�0000000�0000000�00000002205�14143670036�0015211�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef __XAPIAN_HH__ #define __XAPIAN_HH__ #include <xapian.h> #include <mu-parser.hh> namespace Mu { /** * Transform a parse-tree into a Xapian query object * * @param tree a parse tree * * @return a Xapian query object */ Xapian::Query xapian_query (const Mu::Tree& tree); } // namespace Mu #endif /* __XAPIAN_H__ */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-indexer.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000003314�14143670036�0015715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <vector> #include <glib.h> #include <iostream> #include <sstream> #include <unistd.h> #include "mu-indexer.hh" #include "utils/mu-utils.hh" #include "test-mu-common.h" using namespace Mu; static void test_index_maildir () { allow_warnings(); Store store{test_mu_common_get_random_tmpdir(), std::string{MU_TESTMAILDIR}}; Indexer idx{Indexer::Config{}, store}; g_assert_true (idx.start()); while (idx.is_running()) { sleep(1); } g_print ("again!\n"); g_assert_true (idx.start()); while (idx.is_running()) { sleep(1); } } int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); g_test_add_func ("/indexer/index-maildir", test_index_maildir); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } catch (...) { std::cerr << "caught exception\n"; return 1; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-common.cc���������������������������������������������������������������������0000664�0000000�0000000�00000003526�14143670036�0016173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <glib/gstdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <langinfo.h> #include <locale.h> #include "test-mu-common.hh" char* test_mu_common_get_random_tmpdir (void) { char*dir; int res; dir = g_strdup_printf ( "%s%cmu-test-%d%ctest-%x", g_get_tmp_dir(), G_DIR_SEPARATOR, getuid(), G_DIR_SEPARATOR, (int)random()*getpid()*(int)time(NULL)); res = g_mkdir_with_parents (dir, 0700); g_assert (res != -1); return dir; } const char* set_tz (const char* tz) { static const char* oldtz; oldtz = getenv ("TZ"); if (tz) setenv ("TZ", tz, 1); else unsetenv ("TZ"); tzset (); return oldtz; } gboolean set_en_us_utf8_locale (void) { setenv ("LC_ALL", "en_US.UTF-8", 1); setlocale (LC_ALL, "en_US.UTF-8"); if (strcmp (nl_langinfo(CODESET), "UTF-8") != 0) { g_print ("Note: Unit tests require the en_US.utf8 locale. " "Ignoring test cases.\n"); return FALSE; } return TRUE; } void black_hole (void) { return; /* do nothing */ } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-common.hh���������������������������������������������������������������������0000664�0000000�0000000�00000002701�14143670036�0016177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __TEST_MU_COMMON_H__ #define __TEST_MU_COMMON_H__ #include <glib.h> G_BEGIN_DECLS /** * get a dir name for a random temporary directory to do tests * * @return a random dir name, g_free when it's no longer needed */ char* test_mu_common_get_random_tmpdir (void); /** * set the output to /dev/null * */ void black_hole (void); /** * set the timezone * * @param tz timezone * * @return the old timezone */ const char* set_tz (const char* tz); /** * switch the locale to en_US.utf8, return TRUE if it succeeds * * @return TRUE if the switch succeeds, FALSE otherwise */ gboolean set_en_us_utf8_locale (void); G_END_DECLS #endif /*__TEST_MU_COMMON_H__*/ ���������������������������������������������������������������mu-1.6.10/lib/test-mu-container.cc������������������������������������������������������������������0000664�0000000�0000000�00000004167�14143670036�0016667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2014 Jakub Sitnicki <jsitnicki@gmail.com> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <glib.h> #include "test-mu-common.hh" #include "mu-container.hh" static gboolean container_has_children (const MuContainer *c) { return c && c->child; } static gboolean container_is_sibling_of (const MuContainer *c, const MuContainer *sibling) { const MuContainer *cur; for (cur = c; cur; cur = cur->next) { if (cur == sibling) return TRUE; } return container_is_sibling_of (sibling, c); } static void test_mu_container_splice_children_when_parent_has_no_siblings (void) { MuContainer *child, *parent, *root_set; child = mu_container_new (NULL, 0, "child"); parent = mu_container_new (NULL, 0, "parent"); parent = mu_container_append_children (parent, child); root_set = parent; root_set = mu_container_splice_children (root_set, parent); g_assert (root_set != NULL); g_assert (!container_has_children(parent)); g_assert (container_is_sibling_of (root_set, child)); mu_container_destroy(parent); mu_container_destroy(child); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/mu-container/mu-container-splice-children-when-parent-has-no-siblings", test_mu_container_splice_children_when_parent_has_no_siblings); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); return g_test_run (); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-date.cc�����������������������������������������������������������������������0000664�0000000�0000000�00000004400�14143670036�0015610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <locale.h> #include "test-mu-common.h" #include "mu-date.h" static void test_mu_date_interpret_begin (void) { time_t now; now = time (NULL); g_assert_cmpstr (mu_date_interpret_s ("now", TRUE) , ==, mu_date_str_s("%Y%m%d%H%M%S", now)); g_assert_cmpstr (mu_date_interpret_s ("today", TRUE) , ==, mu_date_str_s("%Y%m%d000000", now)); } static void test_mu_date_interpret_end (void) { time_t now; now = time (NULL); g_assert_cmpstr (mu_date_interpret_s ("now", FALSE) , ==, mu_date_str_s("%Y%m%d%H%M%S", now)); g_assert_cmpstr (mu_date_interpret_s ("today", FALSE) , ==, mu_date_str_s("%Y%m%d235959", now)); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/mu-str/mu_date_parse_hdwmy", test_mu_date_parse_hdwmy); g_test_add_func ("/mu-str/mu_date_complete_begin", test_mu_date_complete_begin); g_test_add_func ("/mu-str/mu_date_complete_end", test_mu_date_complete_end); g_test_add_func ("/mu-str/mu_date_interpret_begin", test_mu_date_interpret_begin); g_test_add_func ("/mu-str/mu_date_interpret_end", test_mu_date_interpret_end); g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION, (GLogFunc)black_hole, NULL); return g_test_run (); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-flags.cc����������������������������������������������������������������������0000664�0000000�0000000�00000014363�14143670036�0016000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include "mu-flags.hh" #include "test-mu-common.hh" using namespace Mu; static void test_mu_flag_char (void) { g_assert_cmpuint (mu_flag_char (MU_FLAG_DRAFT), ==, 'D'); g_assert_cmpuint (mu_flag_char (MU_FLAG_FLAGGED), ==, 'F'); g_assert_cmpuint (mu_flag_char (MU_FLAG_PASSED), ==, 'P'); g_assert_cmpuint (mu_flag_char (MU_FLAG_REPLIED), ==, 'R'); g_assert_cmpuint (mu_flag_char (MU_FLAG_SEEN), ==, 'S'); g_assert_cmpuint (mu_flag_char (MU_FLAG_TRASHED), ==, 'T'); g_assert_cmpuint (mu_flag_char (MU_FLAG_NEW), ==, 'N'); g_assert_cmpuint (mu_flag_char (MU_FLAG_SIGNED), ==, 'z'); g_assert_cmpuint (mu_flag_char (MU_FLAG_ENCRYPTED), ==, 'x'); g_assert_cmpuint (mu_flag_char (MU_FLAG_HAS_ATTACH), ==, 'a'); g_assert_cmpuint (mu_flag_char (MU_FLAG_UNREAD), ==, 'u'); g_assert_cmpuint (mu_flag_char ((MuFlags)12345), ==, 0); } static void test_mu_flag_name (void) { g_assert_cmpstr (mu_flag_name (MU_FLAG_DRAFT), ==, "draft"); g_assert_cmpstr (mu_flag_name (MU_FLAG_FLAGGED), ==, "flagged"); g_assert_cmpstr (mu_flag_name (MU_FLAG_PASSED), ==, "passed"); g_assert_cmpstr (mu_flag_name (MU_FLAG_REPLIED), ==, "replied"); g_assert_cmpstr (mu_flag_name (MU_FLAG_SEEN), ==, "seen"); g_assert_cmpstr (mu_flag_name (MU_FLAG_TRASHED), ==, "trashed"); g_assert_cmpstr (mu_flag_name (MU_FLAG_NEW), ==, "new"); g_assert_cmpstr (mu_flag_name (MU_FLAG_SIGNED), ==, "signed"); g_assert_cmpstr (mu_flag_name (MU_FLAG_ENCRYPTED), ==, "encrypted"); g_assert_cmpstr (mu_flag_name (MU_FLAG_HAS_ATTACH), ==, "attach"); g_assert_cmpstr (mu_flag_name (MU_FLAG_UNREAD), ==, "unread"); g_assert_cmpstr (mu_flag_name ((MuFlags)12345), ==, NULL); } static void test_mu_flags_to_str_s (void) { g_assert_cmpstr (mu_flags_to_str_s((MuFlags)(MU_FLAG_PASSED|MU_FLAG_SIGNED), MU_FLAG_TYPE_ANY), ==, "Pz"); g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NEW, MU_FLAG_TYPE_ANY), ==, "N"); g_assert_cmpstr (mu_flags_to_str_s((MuFlags)(MU_FLAG_HAS_ATTACH|MU_FLAG_TRASHED), MU_FLAG_TYPE_ANY), ==, "Ta"); g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NONE, MU_FLAG_TYPE_ANY), ==, ""); g_assert_cmpstr (mu_flags_to_str_s((MuFlags)(MU_FLAG_PASSED|MU_FLAG_SIGNED), MU_FLAG_TYPE_CONTENT), ==, "z"); g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NEW, MU_FLAG_TYPE_MAILDIR), ==, "N"); g_assert_cmpstr (mu_flags_to_str_s((MuFlags)(MU_FLAG_HAS_ATTACH|MU_FLAG_TRASHED), MU_FLAG_TYPE_MAILFILE), ==, "T"); g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NONE, MU_FLAG_TYPE_PSEUDO), ==, ""); } static void test_mu_flags_from_str (void) { /* note, the 3rd arg to mu_flags_from_str determines whether * invalid flags will be ignored (if TRUE) or MU_FLAG_INVALID (if FALSE) */ g_assert_cmpuint (mu_flags_from_str ("RP", MU_FLAG_TYPE_ANY, TRUE), ==, (MuFlags)( MU_FLAG_REPLIED | MU_FLAG_PASSED)); g_assert_cmpuint (mu_flags_from_str ("Nz", MU_FLAG_TYPE_ANY, TRUE), ==, MU_FLAG_NEW | MU_FLAG_SIGNED); g_assert_cmpuint (mu_flags_from_str ("axD", MU_FLAG_TYPE_ANY, TRUE), ==, (MuFlags)( MU_FLAG_HAS_ATTACH | MU_FLAG_ENCRYPTED | MU_FLAG_DRAFT)); g_assert_cmpuint (mu_flags_from_str ("RP", MU_FLAG_TYPE_MAILFILE, TRUE), ==, MU_FLAG_REPLIED | MU_FLAG_PASSED); g_assert_cmpuint (mu_flags_from_str ("Nz", MU_FLAG_TYPE_MAILFILE, TRUE), ==, MU_FLAG_NONE); /* ignore errors or not */ g_assert_cmpuint (mu_flags_from_str ("qwi", MU_FLAG_TYPE_MAILFILE, FALSE), ==, MU_FLAG_INVALID); g_assert_cmpuint (mu_flags_from_str ("qwi", MU_FLAG_TYPE_MAILFILE, TRUE), ==, 0); } static void test_mu_flags_from_str_delta (void) { g_assert_cmpuint (mu_flags_from_str_delta ("+S-R", (MuFlags)(MU_FLAG_REPLIED | MU_FLAG_DRAFT), MU_FLAG_TYPE_ANY),==, (MuFlags)(MU_FLAG_SEEN | MU_FLAG_DRAFT)); g_assert_cmpuint (mu_flags_from_str_delta ("", (MuFlags)(MU_FLAG_REPLIED | MU_FLAG_DRAFT), MU_FLAG_TYPE_ANY),==, (MuFlags)(MU_FLAG_REPLIED | MU_FLAG_DRAFT)); g_assert_cmpuint (mu_flags_from_str_delta ("-N+P+S-D", (MuFlags)(MU_FLAG_SIGNED | MU_FLAG_DRAFT), MU_FLAG_TYPE_ANY),==, (MuFlags)(MU_FLAG_PASSED | MU_FLAG_SEEN | MU_FLAG_SIGNED)); } static void test_mu_flags_custom_from_str (void) { unsigned u; struct { const char *str; const char *expected; } cases[] = { { "ABC", "ABC" }, { "PAF", "A" }, { "ShelloPwoFrDldR123", "helloworld123" }, { "SPD", NULL } }; for (u = 0; u != G_N_ELEMENTS(cases); ++u) { char *cust; cust = mu_flags_custom_from_str (cases[u].str); if (g_test_verbose()) g_print ("%s: str:%s; got:%s; expected:%s\n", __func__, cases[u].str, cust, cases[u].expected); g_assert_cmpstr (cust, ==, cases[u].expected); g_free (cust); } } int main (int argc, char *argv[]) { int rv; g_test_init (&argc, &argv, NULL); /* mu_msg_str_date */ g_test_add_func ("/mu-flags/test-mu-flag-char", test_mu_flag_char); g_test_add_func ("/mu-flags/test-mu-flag-name",test_mu_flag_name); g_test_add_func ("/mu-flags/test-mu-flags-to-str-s",test_mu_flags_to_str_s); g_test_add_func ("/mu-flags/test-mu-flags-from-str",test_mu_flags_from_str); g_test_add_func ("/mu-flags/test-mu-flags-from-str-delta",test_mu_flags_from_str_delta ); g_test_add_func ("/mu-flags/test-mu-flags-custom-from-str", test_mu_flags_custom_from_str); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); rv = g_test_run (); return rv; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-maildir.cc��������������������������������������������������������������������0000664�0000000�0000000�00000027043�14143670036�0016324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <glib/gstdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include "test-mu-common.hh" #include "mu-maildir.hh" #include "utils/mu-util.h" using namespace Mu; static void test_mu_maildir_mkdir_01 (void) { int i; gchar *tmpdir, *mdir, *tmp; const gchar *subs[] = {"tmp", "cur", "new"}; tmpdir = test_mu_common_get_random_tmpdir (); mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR, "cuux"); g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL), ==, TRUE); for (i = 0; i != G_N_ELEMENTS(subs); ++i) { gchar* dir; dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, subs[i]); g_assert_cmpuint (g_access (dir, R_OK), ==, 0); g_assert_cmpuint (g_access (dir, W_OK), ==, 0); g_free (dir); } tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex"); g_assert_cmpuint (g_access (tmp, F_OK), !=, 0); g_free (tmp); g_free (tmpdir); g_free (mdir); } static void test_mu_maildir_mkdir_02 (void) { int i; gchar *tmpdir, *mdir, *tmp; const gchar *subs[] = {"tmp", "cur", "new"}; tmpdir = test_mu_common_get_random_tmpdir (); mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR, "cuux"); g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, TRUE, NULL), ==, TRUE); for (i = 0; i != G_N_ELEMENTS(subs); ++i) { gchar* dir; dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, subs[i]); g_assert_cmpuint (g_access (dir, R_OK), ==, 0); g_assert_cmpuint (g_access (dir, W_OK), ==, 0); g_free (dir); } tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex"); g_assert_cmpuint (g_access (tmp, F_OK), ==, 0); g_free (tmp); g_free (tmpdir); g_free (mdir); } static void test_mu_maildir_mkdir_03 (void) { int i; gchar *tmpdir, *mdir, *tmp; const gchar *subs[] = {"tmp", "cur", "new"}; tmpdir = test_mu_common_get_random_tmpdir (); mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR, "cuux"); /* create part of the structure already... */ { gchar *dir; dir = g_strdup_printf ("%s%ccur", mdir, G_DIR_SEPARATOR); g_assert_cmpuint (g_mkdir_with_parents (dir, 0755), ==, 0); g_free (dir); } /* this should still work */ g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL), ==, TRUE); for (i = 0; i != G_N_ELEMENTS(subs); ++i) { gchar* dir; dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, subs[i]); g_assert_cmpuint (g_access (dir, R_OK), ==, 0); g_assert_cmpuint (g_access (dir, W_OK), ==, 0); g_free (dir); } tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex"); g_assert_cmpuint (g_access (tmp, F_OK), !=, 0); g_free (tmp); g_free (tmpdir); g_free (mdir); } static void test_mu_maildir_mkdir_04 (void) { gchar *tmpdir, *mdir; tmpdir = test_mu_common_get_random_tmpdir (); mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR, "cuux"); /* create part of the structure already... */ { gchar *dir; g_assert_cmpuint (g_mkdir_with_parents (mdir, 0755), ==, 0); dir = g_strdup_printf ("%s%ccur", mdir, G_DIR_SEPARATOR); g_assert_cmpuint (g_mkdir_with_parents (dir, 0000), ==, 0); g_free (dir); } /* this should fail now, because cur is not read/writable */ g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL), ==, (geteuid()==0 ? TRUE : FALSE)); g_free (tmpdir); g_free (mdir); } static gboolean ignore_error (const char* log_domain, GLogLevelFlags log_level, const gchar* msg, gpointer user_data) { return FALSE; /* don't abort */ } static void test_mu_maildir_mkdir_05 (void) { /* this must fail */ g_test_log_set_fatal_handler ((GTestLogFatalFunc)ignore_error, NULL); g_assert_cmpuint (mu_maildir_mkdir (NULL, 0755, TRUE, NULL), ==, FALSE); } static void test_mu_maildir_get_flags_from_path (void) { int i; struct { const char *path; MuFlags flags; } paths[] = { { "/home/foo/Maildir/test/cur/123456:2,FSR", (MuFlags)(MU_FLAG_REPLIED | MU_FLAG_SEEN | MU_FLAG_FLAGGED) }, { "/home/foo/Maildir/test/new/123456", MU_FLAG_NEW }, { /* NOTE: when in new/, the :2,.. stuff is ignored */ "/home/foo/Maildir/test/new/123456:2,FR", MU_FLAG_NEW }, { "/home/foo/Maildir/test/cur/123456:2,DTP", (MuFlags)(MU_FLAG_DRAFT | MU_FLAG_TRASHED | MU_FLAG_PASSED) }, { "/home/foo/Maildir/test/cur/123456:2,S", MU_FLAG_SEEN } }; for (i = 0; i != G_N_ELEMENTS(paths); ++i) { MuFlags flags; flags = mu_maildir_get_flags_from_path(paths[i].path); g_assert_cmpuint(flags, ==, paths[i].flags); } } static void assert_matches_regexp (const char *str, const char *rx) { if (!g_regex_match_simple (rx, str, (GRegexCompileFlags)0, (GRegexMatchFlags)0)) { if (g_test_verbose ()) g_print ("%s does not match %s", str, rx); g_assert (0); } } static void test_mu_maildir_get_new_path_new (void) { int i; struct { const char *oldpath; MuFlags flags; const char *newpath; } paths[] = { { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_REPLIED, "/home/foo/Maildir/test/cur/123456:2,R" }, { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_NEW, "/home/foo/Maildir/test/new/123456" }, { "/home/foo/Maildir/test/new/123456:2,FR", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_REPLIED), "/home/foo/Maildir/test/cur/123456:2,RS" }, { "/home/foo/Maildir/test/new/1313038887_0.697:2,", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_FLAGGED | MU_FLAG_PASSED), "/home/foo/Maildir/test/cur/1313038887_0.697:2,FPS" }, { "/home/djcb/Maildir/trash/new/1312920597.2206_16.cthulhu", MU_FLAG_SEEN, "/home/djcb/Maildir/trash/cur/1312920597.2206_16.cthulhu:2,S" } }; for (i = 0; i != G_N_ELEMENTS(paths); ++i) { char *str, *newbase; str = mu_maildir_get_new_path (paths[i].oldpath, NULL, paths[i].flags, TRUE); newbase = g_path_get_basename (str); assert_matches_regexp (newbase, "\\d+\\." "[[:xdigit:]]{16}\\." "[[:alnum:]][[:alnum:]-]+(:2,.*)?"); g_free (newbase); g_free(str); } } static void test_mu_maildir_get_new_path_01 (void) { int i; struct { const char *oldpath; MuFlags flags; const char *newpath; } paths[] = { { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_REPLIED, "/home/foo/Maildir/test/cur/123456:2,R" }, { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_NEW, "/home/foo/Maildir/test/new/123456" }, { "/home/foo/Maildir/test/new/123456:2,FR", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_REPLIED), "/home/foo/Maildir/test/cur/123456:2,RS" }, { "/home/foo/Maildir/test/new/1313038887_0.697:2,", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_FLAGGED | MU_FLAG_PASSED), "/home/foo/Maildir/test/cur/1313038887_0.697:2,FPS" }, { "/home/djcb/Maildir/trash/new/1312920597.2206_16.cthulhu", MU_FLAG_SEEN, "/home/djcb/Maildir/trash/cur/1312920597.2206_16.cthulhu:2,S" } }; for (i = 0; i != G_N_ELEMENTS(paths); ++i) { gchar *str; str = mu_maildir_get_new_path(paths[i].oldpath, NULL, paths[i].flags, FALSE); g_assert_cmpstr(str, ==, paths[i].newpath); g_free(str); } } static void test_mu_maildir_get_new_path_02 (void) { int i; struct { const char *oldpath; MuFlags flags; const char *targetdir; const char *newpath; } paths[] = { { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_REPLIED, "/home/foo/Maildir/blabla", "/home/foo/Maildir/blabla/cur/123456:2,R" }, { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_NEW, "/home/bar/Maildir/coffee", "/home/bar/Maildir/coffee/new/123456" }, { "/home/foo/Maildir/test/new/123456", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_REPLIED), "/home/cuux/Maildir/tea", "/home/cuux/Maildir/tea/cur/123456:2,RS" }, { "/home/foo/Maildir/test/new/1313038887_0.697:2,", (MuFlags)(MU_FLAG_SEEN | MU_FLAG_FLAGGED | MU_FLAG_PASSED), "/home/boy/Maildir/stuff", "/home/boy/Maildir/stuff/cur/1313038887_0.697:2,FPS" } }; for (i = 0; i != G_N_ELEMENTS(paths); ++i) { gchar *str; str = mu_maildir_get_new_path(paths[i].oldpath, paths[i].targetdir, paths[i].flags, FALSE); g_assert_cmpstr(str, ==, paths[i].newpath); g_free(str); } } static void test_mu_maildir_get_new_path_custom (void) { int i; struct { const char *oldpath; MuFlags flags; const char *targetdir; const char *newpath; } paths[] = { { "/home/foo/Maildir/test/cur/123456:2,FR", MU_FLAG_REPLIED, "/home/foo/Maildir/blabla", "/home/foo/Maildir/blabla/cur/123456:2,R" }, { "/home/foo/Maildir/test/cur/123456:2,hFeRllo123", MU_FLAG_FLAGGED, "/home/foo/Maildir/blabla", "/home/foo/Maildir/blabla/cur/123456:2,Fhello123" }, { "/home/foo/Maildir/test/cur/123456:2,abc", MU_FLAG_PASSED, "/home/foo/Maildir/blabla", "/home/foo/Maildir/blabla/cur/123456:2,Pabc" } }; for (i = 0; i != G_N_ELEMENTS(paths); ++i) { gchar *str; str = mu_maildir_get_new_path(paths[i].oldpath, paths[i].targetdir, paths[i].flags, FALSE); g_assert_cmpstr(str, ==, paths[i].newpath); g_free(str); } } static void test_mu_maildir_get_maildir_from_path (void) { unsigned u; struct { const char *path, *exp; } cases[] = { {"/home/foo/Maildir/test/cur/123456:2,FR", "/home/foo/Maildir/test"}, {"/home/foo/Maildir/lala/new/1313038887_0.697:2,", "/home/foo/Maildir/lala"} }; for (u = 0; u != G_N_ELEMENTS(cases); ++u) { gchar *mdir; mdir = mu_maildir_get_maildir_from_path (cases[u].path); g_assert_cmpstr(mdir,==,cases[u].exp); g_free (mdir); } } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); /* mu_util_maildir_mkmdir */ g_test_add_func ("/mu-maildir/mu-maildir-mkdir-01", test_mu_maildir_mkdir_01); g_test_add_func ("/mu-maildir/mu-maildir-mkdir-02", test_mu_maildir_mkdir_02); g_test_add_func ("/mu-maildir/mu-maildir-mkdir-03", test_mu_maildir_mkdir_03); g_test_add_func ("/mu-maildir/mu-maildir-mkdir-04", test_mu_maildir_mkdir_04); g_test_add_func ("/mu-maildir/mu-maildir-mkdir-05", test_mu_maildir_mkdir_05); /* get/set flags */ g_test_add_func("/mu-maildir/mu-maildir-get-new-path-new", test_mu_maildir_get_new_path_new); g_test_add_func("/mu-maildir/mu-maildir-get-new-path-01", test_mu_maildir_get_new_path_01); g_test_add_func("/mu-maildir/mu-maildir-get-new-path-02", test_mu_maildir_get_new_path_02); g_test_add_func("/mu-maildir/mu-maildir-get-new-path-custom", test_mu_maildir_get_new_path_custom); g_test_add_func("/mu-maildir/mu-maildir-get-flags-from-path", test_mu_maildir_get_flags_from_path); g_test_add_func("/mu-maildir/mu-maildir-get-maildir-from-path", test_mu_maildir_get_maildir_from_path); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); return g_test_run (); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-msg-fields.cc�����������������������������������������������������������������0000664�0000000�0000000�00000006700�14143670036�0016732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <locale.h> #include "test-mu-common.hh" #include "mu-msg-fields.h" static void test_mu_msg_field_body (void) { MuMsgFieldId field; field = MU_MSG_FIELD_ID_BODY_TEXT; g_assert_cmpstr (mu_msg_field_name(field),==, "body"); g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'b'); g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'B'); g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, FALSE); } static void test_mu_msg_field_subject (void) { MuMsgFieldId field; field = MU_MSG_FIELD_ID_SUBJECT; g_assert_cmpstr (mu_msg_field_name(field),==, "subject"); g_assert_cmpuint (mu_msg_field_shortcut(field),==, 's'); g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'S'); g_assert_cmpuint (mu_msg_field_is_numeric(field), ==,FALSE); } static void test_mu_msg_field_to (void) { MuMsgFieldId field; field = MU_MSG_FIELD_ID_TO; g_assert_cmpstr (mu_msg_field_name(field),==, "to"); g_assert_cmpuint (mu_msg_field_shortcut(field),==, 't'); g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'T'); g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, FALSE); } static void test_mu_msg_field_prio (void) { MuMsgFieldId field; field = MU_MSG_FIELD_ID_PRIO; g_assert_cmpstr (mu_msg_field_name(field),==, "prio"); g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'p'); g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'P'); g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, TRUE); } static void test_mu_msg_field_flags (void) { MuMsgFieldId field; field = MU_MSG_FIELD_ID_FLAGS; g_assert_cmpstr (mu_msg_field_name(field),==, "flag"); g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'g'); g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'G'); g_assert_cmpuint (mu_msg_field_is_numeric(field),==, TRUE); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); /* mu_msg_str_date */ g_test_add_func ("/mu-msg-fields/mu-msg-field-body", test_mu_msg_field_body); g_test_add_func ("/mu-msg-fields/mu-msg-field-subject", test_mu_msg_field_subject); g_test_add_func ("/mu-msg-fields/mu-msg-field-to", test_mu_msg_field_to); g_test_add_func ("/mu-msg-fields/mu-msg-field-prio", test_mu_msg_field_prio); g_test_add_func ("/mu-msg-fields/mu-msg-field-flags", test_mu_msg_field_flags); /* FIXME: add tests for mu_msg_str_flags; but note the * function simply calls mu_msg_field_str */ g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); return g_test_run (); } ����������������������������������������������������������������mu-1.6.10/lib/test-mu-msg.cc������������������������������������������������������������������������0000664�0000000�0000000�00000035465�14143670036�0015500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include <glib.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <locale.h> #include "test-mu-common.hh" #include "mu-msg.hh" #include "utils/mu-str.h" using namespace Mu; static MuMsg* get_msg (const char *path) { GError *err; MuMsg *msg; if (g_test_verbose ()) g_print (">> %s\n", path); err = NULL; msg = mu_msg_new_from_file (path, NULL, &err); if (!msg) { g_printerr ("failed to load %s: %s\n", path, err ? err->message : "something went wrong"); g_clear_error (&err); g_assert (0); } return msg; } static gboolean check_contact_01 (MuMsgContact *contact, int *idx) { switch (*idx) { case 0: g_assert_cmpstr (mu_msg_contact_name (contact), ==, "Mickey Mouse"); g_assert_cmpstr (mu_msg_contact_email (contact), ==, "anon@example.com"); break; case 1: g_assert_cmpstr (mu_msg_contact_name (contact), ==, "Donald Duck"); g_assert_cmpstr (mu_msg_contact_email (contact), ==, "gcc-help@gcc.gnu.org"); break; default: g_assert_not_reached (); } ++(*idx); return TRUE; } static void test_mu_msg_01 (void) { MuMsg *msg; gint i; msg = get_msg (MU_TESTMAILDIR4 "/1220863042.12663_1.mindcrime!2,S"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "Donald Duck <gcc-help@gcc.gnu.org>"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "gcc include search order"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "Mickey Mouse <anon@example.com>"); g_assert_cmpstr (mu_msg_get_msgid(msg), ==, "3BE9E6535E3029448670913581E7A1A20D852173@" "emss35m06.us.lmco.com"); g_assert_cmpstr (mu_msg_get_header(msg, "Mailing-List"), ==, "contact gcc-help-help@gcc.gnu.org; run by ezmlm"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'klub' */ ==, MU_MSG_PRIO_NORMAL); g_assert_cmpuint (mu_msg_get_date(msg), ==, 1217530645); i = 0; mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)check_contact_01, &i); g_assert_cmpint (i,==,2); mu_msg_unref (msg); } static gboolean check_contact_02 (MuMsgContact *contact, int *idx) { switch (*idx) { case 0: g_assert_cmpstr (mu_msg_contact_name (contact), ==, NULL); g_assert_cmpstr (mu_msg_contact_email (contact), ==, "anon@example.com"); break; case 1: g_assert_cmpstr (mu_msg_contact_name (contact), ==, NULL); g_assert_cmpstr (mu_msg_contact_email (contact), ==, "help-gnu-emacs@gnu.org"); break; default: g_assert_not_reached (); } ++(*idx); return TRUE; } static void test_mu_msg_02 (void) { MuMsg *msg; int i; msg = get_msg (MU_TESTMAILDIR4 "/1220863087.12663_19.mindcrime!2,S"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "help-gnu-emacs@gnu.org"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "Re: Learning LISP; Scheme vs elisp."); g_assert_cmpstr (mu_msg_get_from(msg), ==, "anon@example.com"); g_assert_cmpstr (mu_msg_get_msgid(msg), ==, "r6bpm5-6n6.ln1@news.ducksburg.com"); g_assert_cmpstr (mu_msg_get_header(msg, "Errors-To"), ==, "help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_LOW); g_assert_cmpuint (mu_msg_get_date(msg), ==, 1218051515); i = 0; mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)check_contact_02, &i); g_assert_cmpint (i,==,2); g_assert_cmpuint (mu_msg_get_flags(msg), ==, MU_FLAG_SEEN|MU_FLAG_LIST); mu_msg_unref (msg); } static void test_mu_msg_03 (void) { MuMsg *msg; const GSList *params; msg = get_msg (MU_TESTMAILDIR4 "/1283599333.1840_11.cthulhu!2,"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "Bilbo Baggins <bilbo@anotherexample.com>"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "Greetings from Lothlórien"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "Frodo Baggins <frodo@example.com>"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_NORMAL); g_assert_cmpuint (mu_msg_get_date(msg), ==, 0); g_assert_cmpstr (mu_msg_get_body_text(msg, MU_MSG_OPTION_NONE), ==, "\nLet's write some fünkÿ text\nusing umlauts.\n\nFoo.\n"); params = mu_msg_get_body_text_content_type_parameters( msg, MU_MSG_OPTION_NONE); g_assert_cmpuint (g_slist_length ((GSList*)params), ==, 2); g_assert_cmpstr ((char*)params->data,==, "charset"); params = g_slist_next(params); g_assert_cmpstr ((char*)params->data,==,"UTF-8"); g_assert_cmpuint (mu_msg_get_flags(msg), ==, MU_FLAG_UNREAD); /* not seen => unread */ mu_msg_unref (msg); } static void test_mu_msg_04 (void) { MuMsg *msg; msg = get_msg (MU_TESTMAILDIR4 "/mail5"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "George Custer <gac@example.com>"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "pics for you"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "Sitting Bull <sb@example.com>"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_NORMAL); g_assert_cmpuint (mu_msg_get_date(msg), ==, 0); g_assert_cmpuint (mu_msg_get_flags(msg), ==, MU_FLAG_HAS_ATTACH|MU_FLAG_UNREAD); mu_msg_unref (msg); } static void test_mu_msg_multimime (void) { MuMsg *msg; msg = get_msg (MU_TESTMAILDIR4 "/multimime!2,FS"); /* ie., are text parts properly concatenated? */ g_assert_cmpstr (mu_msg_get_subject(msg), ==, "multimime"); g_assert_cmpstr (mu_msg_get_body_text(msg, MU_MSG_OPTION_NONE), ==, "abcdef"); g_assert_cmpuint (mu_msg_get_flags(msg), ==, (MuFlags)(MU_FLAG_FLAGGED | MU_FLAG_SEEN | MU_FLAG_HAS_ATTACH)); mu_msg_unref (msg); } static void test_mu_msg_flags (void) { unsigned u; struct { const char *path; MuFlags flags; } msgflags [] = { { MU_TESTMAILDIR4 "/multimime!2,FS", (MuFlags)(MU_FLAG_FLAGGED | MU_FLAG_SEEN | MU_FLAG_HAS_ATTACH) }, { MU_TESTMAILDIR4 "/special!2,Sabc", MU_FLAG_SEEN } }; for (u = 0; u != G_N_ELEMENTS(msgflags); ++u) { MuMsg *msg; MuFlags flags; g_assert ((msg = get_msg (msgflags[u].path))); flags = mu_msg_get_flags (msg); if (g_test_verbose()) g_print ("=> %s [ %s, %u] <=> [ %s, %u]\n", msgflags[u].path, mu_flags_to_str_s(msgflags[u].flags, MU_FLAG_TYPE_ANY), (unsigned)msgflags[u].flags, mu_flags_to_str_s(flags, MU_FLAG_TYPE_ANY), (unsigned)flags); g_assert_cmpuint (flags ,==, msgflags[u].flags); mu_msg_unref (msg); } } static void test_mu_msg_umlaut (void) { MuMsg *msg; msg = get_msg (MU_TESTMAILDIR4 "/1305664394.2171_402.cthulhu!2,"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "Helmut Kröger <hk@testmu.xxx>"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "Motörhead"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "Mü <testmu@testmu.xx>"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_NORMAL); g_assert_cmpuint (mu_msg_get_date(msg), ==, 0); mu_msg_unref (msg); } static void test_mu_msg_references (void) { MuMsg *msg; const GSList *refs; msg = get_msg (MU_TESTMAILDIR4 "/1305664394.2171_402.cthulhu!2,"); refs = mu_msg_get_references(msg); g_assert_cmpuint (g_slist_length ((GSList*)refs), ==, 4); g_assert_cmpstr ((char*)refs->data,==, "non-exist-01@msg.id"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "non-exist-02@msg.id"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "non-exist-03@msg.id"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "non-exist-04@msg.id"); refs = g_slist_next (refs); mu_msg_unref (msg); } static void test_mu_msg_references_dups (void) { MuMsg *msg; const GSList *refs; const char *mlist; msg = get_msg (MU_TESTMAILDIR4 "/1252168370_3.14675.cthulhu!2,S"); refs = mu_msg_get_references(msg); /* make sure duplicate msg-ids are filtered out */ g_assert_cmpuint (g_slist_length ((GSList*)refs), ==, 6); g_assert_cmpstr ((char*)refs->data,==, "439C1136.90504@euler.org"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "4399DD94.5070309@euler.org"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "20051209233303.GA13812@gauss.org"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "439B41ED.2080402@euler.org"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "439A1E03.3090604@euler.org"); refs = g_slist_next (refs); g_assert_cmpstr ((char*)refs->data,==, "20051211184308.GB13513@gauss.org"); refs = g_slist_next (refs); mlist = mu_msg_get_mailing_list (msg); g_assert_cmpstr (mlist ,==, "Example of List Id"); mu_msg_unref (msg); } static void test_mu_msg_references_many (void) { MuMsg *msg; unsigned u; const GSList *refs, *cur; const char* expt_refs[] = { "e9065dac-13c1-4103-9e31-6974ca232a89@t15g2000prt.googlegroups.com", "87hbblwelr.fsf@sapphire.mobileactivedefense.com", "pql248-4va.ln1@wilbur.25thandClement.com", "ikns6r$li3$1@Iltempo.Update.UU.SE", "8762s0jreh.fsf@sapphire.mobileactivedefense.com", "ikqqp1$jv0$1@Iltempo.Update.UU.SE", "87hbbjc5jt.fsf@sapphire.mobileactivedefense.com", "ikr0na$lru$1@Iltempo.Update.UU.SE", "tO8cp.1228$GE6.370@news.usenetserver.com", "ikr6ks$nlf$1@Iltempo.Update.UU.SE", "8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk" }; msg = get_msg (MU_TESTMAILDIR2 "/bar/cur/181736.eml"); refs = mu_msg_get_references(msg); g_assert_cmpuint (G_N_ELEMENTS(expt_refs), ==, g_slist_length((GSList*)refs)); for (cur = refs, u = 0; cur; cur = g_slist_next(cur), ++u) { if (g_test_verbose()) g_print ("%u. '%s' =? '%s'\n", u, (char*)cur->data, expt_refs[u]); g_assert_cmpstr ((char*)cur->data, ==, expt_refs[u]); } mu_msg_unref (msg); } static void test_mu_msg_tags (void) { MuMsg *msg; const GSList *tags; msg = get_msg (MU_TESTMAILDIR4 "/mail1"); g_assert_cmpstr (mu_msg_get_to(msg), ==, "Julius Caesar <jc@example.com>"); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "Fere libenter homines id quod volunt credunt"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "John Milton <jm@example.com>"); g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_HIGH); g_assert_cmpuint (mu_msg_get_date(msg), ==, 1217530645); tags = mu_msg_get_tags (msg); g_assert_cmpstr ((char*)tags->data,==,"Paradise"); g_assert_cmpstr ((char*)tags->next->data,==,"losT"); g_assert_cmpstr ((char*)tags->next->next->data,==,"john"); g_assert_cmpstr ((char*)tags->next->next->next->data,==,"milton"); g_assert (!tags->next->next->next->next); mu_msg_unref (msg); } static void test_mu_msg_comp_unix_programmer (void) { MuMsg *msg; char *refs; msg = get_msg (MU_TESTMAILDIR4 "/181736.eml"); g_assert_cmpstr (mu_msg_get_to(msg), ==, NULL); g_assert_cmpstr (mu_msg_get_subject(msg), ==, "Re: Are writes \"atomic\" to readers of the file?"); g_assert_cmpstr (mu_msg_get_from(msg), ==, "Jimbo Foobarcuux <jimbo@slp53.sl.home>"); g_assert_cmpstr (mu_msg_get_msgid(msg), ==, "oktdp.42997$Te.22361@news.usenetserver.com"); refs = mu_str_from_list (mu_msg_get_references(msg), ','); g_assert_cmpstr (refs, ==, "e9065dac-13c1-4103-9e31-6974ca232a89@t15g2000prt" ".googlegroups.com," "87hbblwelr.fsf@sapphire.mobileactivedefense.com," "pql248-4va.ln1@wilbur.25thandClement.com," "ikns6r$li3$1@Iltempo.Update.UU.SE," "8762s0jreh.fsf@sapphire.mobileactivedefense.com," "ikqqp1$jv0$1@Iltempo.Update.UU.SE," "87hbbjc5jt.fsf@sapphire.mobileactivedefense.com," "ikr0na$lru$1@Iltempo.Update.UU.SE," "tO8cp.1228$GE6.370@news.usenetserver.com," "ikr6ks$nlf$1@Iltempo.Update.UU.SE," "8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk"); g_free (refs); //"jimbo@slp53.sl.home (Jimbo Foobarcuux)"; g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */ ==, MU_MSG_PRIO_NORMAL); g_assert_cmpuint (mu_msg_get_date(msg), ==, 1299603860); mu_msg_unref (msg); } static void test_mu_str_prio_01 (void) { g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_LOW), ==, "low"); g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_NORMAL), ==, "normal"); g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_HIGH), ==, "high"); } static gboolean ignore_error (const char* log_domain, GLogLevelFlags log_level, const gchar* msg, gpointer user_data) { return FALSE; /* don't abort */ } static void test_mu_str_prio_02 (void) { /* this must fail */ g_test_log_set_fatal_handler ((GTestLogFatalFunc)ignore_error, NULL); g_assert_cmpstr (mu_msg_prio_name((MuMsgPrio)666), ==, NULL); } static void test_mu_str_display_contact (void) { int i; struct { const char* word; const char* disp; } words [] = { { "\"Foo Bar\" <aap@noot.mies>", "Foo Bar"}, { "Foo Bar <aap@noot.mies>", "Foo Bar" }, { "<aap@noot.mies>", "aap@noot.mies" }, { "foo@bar.nl", "foo@bar.nl" } }; for (i = 0; i != G_N_ELEMENTS(words); ++i) g_assert_cmpstr (mu_str_display_contact_s (words[i].word), ==, words[i].disp); } int main (int argc, char *argv[]) { int rv; g_test_init (&argc, &argv, NULL); /* mu_msg_str_date */ g_test_add_func ("/mu-msg/mu-msg-01", test_mu_msg_01); g_test_add_func ("/mu-msg/mu-msg-02", test_mu_msg_02); g_test_add_func ("/mu-msg/mu-msg-03", test_mu_msg_03); g_test_add_func ("/mu-msg/mu-msg-04", test_mu_msg_04); g_test_add_func ("/mu-msg/mu-msg-multimime", test_mu_msg_multimime); g_test_add_func ("/mu-msg/mu-msg-flags", test_mu_msg_flags); g_test_add_func ("/mu-msg/mu-msg-tags", test_mu_msg_tags); g_test_add_func ("/mu-msg/mu-msg-references", test_mu_msg_references); g_test_add_func ("/mu-msg/mu-msg-references_dups", test_mu_msg_references_dups); g_test_add_func ("/mu-msg/mu-msg-references_many", test_mu_msg_references_many); g_test_add_func ("/mu-msg/mu-msg-umlaut", test_mu_msg_umlaut); g_test_add_func ("/mu-msg/mu-msg-comp-unix-programmer", test_mu_msg_comp_unix_programmer); /* mu_str_prio */ g_test_add_func ("/mu-str/mu-str-prio-01", test_mu_str_prio_01); g_test_add_func ("/mu-str/mu-str-prio-02", test_mu_str_prio_02); g_test_add_func ("/mu-str/mu-str-display_contact", test_mu_str_display_contact); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); rv = g_test_run (); return rv; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-mu-store.cc����������������������������������������������������������������������0000664�0000000�0000000�00000010217�14143670036�0016032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include <glib.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <locale.h> #include "test-mu-common.hh" #include "mu-store.hh" static std::string MuTestMaildir = Mu::canonicalize_filename(MU_TESTMAILDIR, "/"); static std::string MuTestMaildir2 = Mu::canonicalize_filename(MU_TESTMAILDIR2, "/"); static void test_store_ctor_dtor () { char *tmpdir = test_mu_common_get_random_tmpdir(); g_assert (tmpdir); Mu::Store store{tmpdir, "/tmp", {}, {}}; g_free (tmpdir); g_assert_true(store.empty()); g_assert_cmpuint (0,==,store.size()); g_assert_cmpstr (MU_STORE_SCHEMA_VERSION,==, store.metadata().schema_version.c_str()); } static void test_store_add_count_remove () { char *tmpdir = test_mu_common_get_random_tmpdir(); g_assert (tmpdir); Mu::Store store{tmpdir, MuTestMaildir, {}, {}}; g_free (tmpdir); const auto id1 = store.add_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,"); g_assert_cmpuint(id1, !=, Mu::Store::InvalidId); g_assert_cmpuint(store.size(), ==, 1); g_assert_true(store.contains_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,")); g_assert_cmpuint(store.add_message(MuTestMaildir2 + "/bar/cur/mail3"), !=, Mu::Store::InvalidId); g_assert_cmpuint(store.size(), ==, 2); g_assert_true(store.contains_message(MuTestMaildir2 + "/bar/cur/mail3")); store.remove_message(id1); g_assert_cmpuint(store.size(), ==, 1); g_assert_false(store.contains_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,")); store.remove_message (MuTestMaildir2 + "/bar/cur/mail3"); g_assert_true(store.empty()); g_assert_false(store.contains_message(MuTestMaildir2 + "/bar/cur/mail3")); } static void test_store_add_count_remove_in_memory () { Mu::Store store{MuTestMaildir, {}, {}}; g_assert_true (store.metadata().in_memory); const auto id1 = store.add_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,"); g_assert_cmpuint(id1, !=, Mu::Store::InvalidId); g_assert_cmpuint(store.size(), ==, 1); g_assert_true(store.contains_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,")); g_assert_cmpuint(store.add_message(MuTestMaildir2 + "/bar/cur/mail3"), !=, Mu::Store::InvalidId); g_assert_cmpuint(store.size(), ==, 2); g_assert_true(store.contains_message(MuTestMaildir2 + "/bar/cur/mail3")); store.remove_message(id1); g_assert_cmpuint(store.size(), ==, 1); g_assert_false(store.contains_message(MuTestMaildir + "/cur/1283599333.1840_11.cthulhu!2,")); store.remove_message (MuTestMaildir2 + "/bar/cur/mail3"); g_assert_true(store.empty()); g_assert_false(store.contains_message(MuTestMaildir2 + "/bar/cur/mail3")); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); /* mu_runtime_init/uninit */ g_test_add_func ("/store/ctor-dtor", test_store_ctor_dtor); g_test_add_func ("/store/add-count-remove", test_store_add_count_remove); g_test_add_func ("/store/in-memory/add-count-remove", test_store_add_count_remove_in_memory); // if (!g_test_verbose()) // g_log_set_handler (NULL, // G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION, // (GLogFunc)black_hole, NULL); return g_test_run (); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/test-parser.cc������������������������������������������������������������������������0000664�0000000�0000000�00000006712�14143670036�0015560�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include <vector> #include <glib.h> #include <iostream> #include <sstream> #include "test-mu-common.hh" #include "mu-parser.hh" #include "utils/mu-utils.hh" using namespace Mu; struct Case { const std::string expr; const std::string expected; WarningVec warnings; }; using CaseVec = std::vector<Case>; static void test_cases(const CaseVec& cases) { char *tmpdir = test_mu_common_get_random_tmpdir(); g_assert (tmpdir); Mu::Store dummy_store{tmpdir, "/tmp", {}, {}}; g_free (tmpdir); Parser parser{dummy_store, Parser::Flags::UnitTest}; for (const auto& casus : cases ) { WarningVec warnings; const auto tree = parser.parse (casus.expr, warnings); std::stringstream ss; ss << tree; if (g_test_verbose()) { std::cout << "\n"; std::cout << casus.expr << std::endl; std::cout << "exp:" << casus.expected << std::endl; std::cout << "got:" << ss.str() << std::endl; } assert_equal (casus.expected, ss.str()); } } static void test_basic () { CaseVec cases = { //{ "", R"#((atom :value ""))#"}, { "foo", R"#((value "msgid" "foo"))#", }, { "foo or bar", R"#((or(value "msgid" "foo")(value "msgid" "bar")))#" }, { "foo and bar", R"#((and(value "msgid" "foo")(value "msgid" "bar")))#"}, }; test_cases (cases); } static void test_complex () { CaseVec cases = { { "foo and bar or cuux", R"#((or(and(value "msgid" "foo")(value "msgid" "bar")))#" + std::string(R"#((value "msgid" "cuux")))#") }, { "a and not b", R"#((and(value "msgid" "a")(not(value "msgid" "b"))))#" }, { "a and b and c", R"#((and(value "msgid" "a")(and(value "msgid" "b")(value "msgid" "c"))))#" }, { "(a or b) and c", R"#((and(or(value "msgid" "a")(value "msgid" "b"))(value "msgid" "c")))#" }, { "a b", // implicit and R"#((and(value "msgid" "a")(value "msgid" "b")))#" }, { "a not b", // implicit and not R"#((and(value "msgid" "a")(not(value "msgid" "b"))))#" }, { "not b", // implicit and not R"#((not(value "msgid" "b")))#" } }; test_cases (cases); } G_GNUC_UNUSED static void test_range () { CaseVec cases = { { "range:a..b", // implicit and R"#((range "range" "a" "b"))#" }, }; test_cases (cases); } static void test_flatten () { CaseVec cases = { { " Mötørhęåđ", R"#((value "msgid" "motorhead"))#" } }; test_cases (cases); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/parser/basic", test_basic); g_test_add_func ("/parser/complex", test_complex); //g_test_add_func ("/parser/range", test_range); g_test_add_func ("/parser/flatten", test_flatten); return g_test_run (); } ������������������������������������������������������mu-1.6.10/lib/test-query.cc�������������������������������������������������������������������������0000664�0000000�0000000�00000004715�14143670036�0015432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include <config.h> #include <vector> #include <glib.h> #include <iostream> #include <sstream> #include <unistd.h> #include "mu-store.hh" #include "mu-query.hh" #include "index/mu-indexer.hh" #include "utils/mu-utils.hh" #include "test-mu-common.hh" using namespace Mu; static void test_query() { allow_warnings(); Store store{test_mu_common_get_random_tmpdir(), std::string{MU_TESTMAILDIR}, {},{}}; auto&& idx{store.indexer()}; g_assert_true (idx.start(Indexer::Config{})); while (idx.is_running()) { sleep(1); } auto dump_matches=[](const QueryResults& res) { size_t n{}; for (auto&& item: res) g_debug ("%02zu %s %s", ++n, item.path().value_or("<none>").c_str(), item.message_id().value_or("<none>").c_str()); }; Query q{store}; g_assert_cmpuint(store.size(),==,19); { const auto res = q.run("", MU_MSG_FIELD_ID_NONE, QueryFlags::None); g_assert_true(!!res); g_assert_cmpuint(res->size(),==,19); dump_matches(*res); } { const auto res = q.run("", MU_MSG_FIELD_ID_PATH, QueryFlags::None, 11); g_assert_true(!!res); g_assert_cmpuint(res->size(),==,11); dump_matches(*res); } } int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); g_test_add_func ("/query", test_query); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } catch (...) { std::cerr << "caught exception\n"; return 1; } ���������������������������������������������������mu-1.6.10/lib/test-tokenizer.cc���������������������������������������������������������������������0000664�0000000�0000000�00000007464�14143670036�0016303�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* ** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include <vector> #include <glib.h> #include <iostream> #include <sstream> #include "mu-tokenizer.hh" struct Case { const char *str; const Mu::Tokens tokens; }; using CaseVec = std::vector<Case>; using namespace Mu; using TT = Token::Type; static void test_cases(const CaseVec& cases) { for (const auto& casus : cases ) { const auto tokens = tokenize (casus.str); g_assert_cmpuint ((guint)tokens.size(),==,(guint)casus.tokens.size()); for (size_t u = 0; u != tokens.size(); ++u) { if (g_test_verbose()) { std::cerr << "case " << u << " " << casus.str << std::endl; std::cerr << "exp: '" << casus.tokens[u] << "'" << std::endl; std::cerr << "got: '" << tokens[u] << "'" << std::endl; } g_assert_true (tokens[u] == casus.tokens[u]); } } } static void test_basic () { CaseVec cases = { { "", {} }, { "foo", Tokens{Token{3, TT::Data, "foo"}}}, { "foo bar cuux", Tokens{Token{3, TT::Data, "foo"}, Token{7, TT::Data, "bar"}, Token{12, TT::Data, "cuux"}}}, { "\"foo bar\"", Tokens{ Token{9, TT::Data, "foo bar"}}}, // ie. ignore missing closing '"' { "\"foo bar", Tokens{ Token{8, TT::Data, "foo bar"}}}, }; test_cases (cases); } static void test_specials () { CaseVec cases = { { ")*(", Tokens{Token{1, TT::Close, ")"}, Token{2, TT::Data, "*"}, Token{3, TT::Open, "("}}}, { "\")*(\"", Tokens{Token{5, TT::Data, ")*("}}}, }; test_cases (cases); } static void test_ops () { CaseVec cases = { { "foo and bar oR cuux XoR fnorb", Tokens{Token{3, TT::Data, "foo"}, Token{7, TT::And, "and"}, Token{11, TT::Data, "bar"}, Token{14, TT::Or, "oR"}, Token{19, TT::Data, "cuux"}, Token{23, TT::Xor, "XoR"}, Token{29, TT::Data, "fnorb"}}}, { "NOT (aap or mies)", Tokens{Token{3, TT::Not, "NOT"}, Token{5, TT::Open, "("}, Token{8, TT::Data, "aap"}, Token{11, TT::Or, "or"}, Token{16, TT::Data, "mies"}, Token{17, TT::Close, ")"}}} }; test_cases (cases); } static void test_escape () { CaseVec cases = { { "foo\"bar\"", Tokens{Token{8, TT::Data, "foobar"}}}, { "\"fnorb\"", Tokens{Token{7, TT::Data, "fnorb"}}}, { "\\\"fnorb\\\"", Tokens{Token{9, TT::Data, "fnorb"}}}, { "foo\\\"bar\\\"", Tokens{Token{10, TT::Data, "foobar"}}} }; test_cases (cases); } static void test_to_string () { std::stringstream ss; for (auto&& t: tokenize ("foo and bar xor not cuux or fnorb")) ss << t << ' '; g_assert_true (ss.str() == "3: <data> [foo] 7: <and> [and] 11: <data> [bar] " "15: <xor> [xor] 19: <not> [not] 24: <data> [cuux] " "27: <or> [or] 33: <data> [fnorb] "); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/tokens/basic", test_basic); g_test_add_func ("/tokens/specials", test_specials); g_test_add_func ("/tokens/ops", test_ops); g_test_add_func ("/tokens/escape", test_escape); g_test_add_func ("/tokens/to-string", test_to_string); return g_test_run (); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0014450�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0015241�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863042.12663_1.mindcrime!2,S������������������������������������������0000664�0000000�0000000�00000014325�14143670036�0021434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-4.9 required=3.0 tests=BAYES_00,DATE_IN_PAST_96_XX, RCVD_IN_DNSWL_MED autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 5123469CB3 for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:19 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:19 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs39272wfh; Wed, 6 Aug 2008 20:15:17 -0700 (PDT) Received: by 10.65.133.8 with SMTP id k8mr2071878qbn.7.1218078916289; Wed, 06 Aug 2008 20:15:16 -0700 (PDT) Received: from sourceware.org (sourceware.org [209.132.176.174]) by mx.google.com with SMTP id 28si7904461qbw.0.2008.08.06.20.15.15; Wed, 06 Aug 2008 20:15:16 -0700 (PDT) Received-SPF: neutral (google.com: 209.132.176.174 is neither permitted nor denied by domain of gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org) client-ip=209.132.176.174; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.132.176.174 is neither permitted nor denied by domain of gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org) smtp.mail=gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org Received: (qmail 13493 invoked by alias); 7 Aug 2008 03:15:13 -0000 Received: (qmail 13485 invoked by uid 22791); 7 Aug 2008 03:15:12 -0000 Received: from mailgw1a.lmco.com (HELO mailgw1a.lmco.com) (192.31.106.7) by sourceware.org (qpsmtpd/0.31) with ESMTP; Thu, 07 Aug 2008 03:14:27 +0000 Received: from emss07g01.ems.lmco.com (relay5.ems.lmco.com [166.29.2.16])by mailgw1a.lmco.com (LM-6) with ESMTP id m773EPZH014730for <gcc-help@gcc.gnu.org>; Wed, 6 Aug 2008 21:14:25 -0600 (MDT) Received: from CONVERSION2-DAEMON.lmco.com by lmco.com (PMDF V6.3-x14 #31428) id <0K5700601NO18J@lmco.com> for gcc-help@gcc.gnu.org; Wed, 06 Aug 2008 21:14:25 -0600 (MDT) Received: from EMSS04I00.us.lmco.com ([166.17.13.135]) by lmco.com (PMDF V6.3-x14 #31428) with ESMTP id <0K5700H5MNNWGX@lmco.com> for gcc-help@gcc.gnu.org; Wed, 06 Aug 2008 21:14:20 -0600 (MDT) Received: from EMSS35M06.us.lmco.com ([158.187.107.143]) by EMSS04I00.us.lmco.com with Microsoft SMTPSVC(5.0.2195.6713); Wed, 06 Aug 2008 23:14:20 -0400 Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "Mickey Mouse" <anon@example.com> Subject: gcc include search order To: "Donald Duck" <gcc-help@gcc.gnu.org> Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Content-class: urn:content-classes:message Mailing-List: contact gcc-help-help@gcc.gnu.org; run by ezmlm Precedence: klub List-Id: <gcc-help.gcc.gnu.org> List-Unsubscribe: <mailto:gcc-help-unsubscribe-xxxx.klub=gmail.com@gcc.gnu.org> List-Archive: <http://gcc.gnu.org/ml/gcc-help/> List-Post: <mailto:gcc-help@gcc.gnu.org> List-Help: <mailto:gcc-help-help@gcc.gnu.org> Sender: gcc-help-owner@gcc.gnu.org Delivered-To: mailing list gcc-help@gcc.gnu.org Content-Length: 3024 Hi. In my unit testing I need to change some header files (target is vxWorks, which supports some things that the sun does not). So, what I do is fetch the development tree, and then in a new unit test directory I attempt to compile the unit under test. Since this is NOT vxworks, I use sed to change some of the .h files and put them in a ./changed directory. When I try to compile the file, it is still using the .h file from the original location, even though I have listed the include path for ./changed before the include path for the development tree. Here is a partial output from gcc using the -v option GNU CPP version 3.1 (cpplib) (sparc ELF) GNU C++ version 3.1 (sparc-sun-solaris2.8) compiled by GNU C version 3.1. ignoring nonexistent directory "NONE/include" #include "..." search starts here: #include <...> search starts here: . changed /export/home4/xxx/yyyy/builds/int_rel5_latest/src/mp/interface /export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/interface /usr/local/include/g++-v3 /usr/local/include/g++-v3/sparc-sun-solaris2.8 /usr/local/include/g++-v3/backward /usr/local/include /usr/local/lib/gcc-lib/sparc-sun-solaris2.8/3.1/include /usr/local/sparc-sun-solaris2.8/include /usr/include End of search list. I know the changed file is correct and that the include is not working as expected, because when I copy the file from ./changed, back into the development tree, the compilation works as expected. One more bit of information. The source that I cam compiling is in /export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app And it is including files from /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common These include files should be including the files from ./changed (when they exist) but they are ignoring the .h files in the ./changed directory and are instead using other, unchanged files in the /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common directory. The gcc command line is something like TEST_DIR="." CHANGED_DIR_NAME=changed CHANGED_FILES_DIR=${TEST_DIR}/${CHANGED_DIR_NAME} CICU_HEADER_FILES="-I ${AP_INTERFACE_FILES} -I ${AP_APP_FILES} -I ${SHARED_COMMON_FILES} -I ${SHARED_INTERFACE_FILES}" HEADERS="-I ./ -I ${CHANGED_FILES_DIR} ${CICU_HEADER_FILES}" DEFINES="-DSUNRUN -DA10_DEBUG -DJOETEST" CFLAGS="-v -c -g -O1 -pipe -Wformat -Wunused -Wuninitialized -Wshadow -Wmissing-prototypes -Wmissing-declarations" printf "Compiling the UUT File\n" gcc -fprofile-arcs -ftest-coverage ${CFLAGS} ${HEADERS} ${DEFINES} ${AP_APP_FILES}/unitUnderTest.cpp I hope this explanation is clear. If anyone knows how to fix the command line so that it gets the .h files in the "changed" directory are used instead of files in the other include directories. Thanks Joe ---------------------------------------------------- Time Flies like an Arrow. Fruit Flies like a Banana �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863060.12663_3.mindcrime!2,S������������������������������������������0000664�0000000�0000000�00000027073�14143670036�0021442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <sqlite-dev-bounces@sqlite.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00,HTML_MESSAGE autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id D724F6963B for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:27 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:27 +0300 (EEST) Received: by 10.142.51.12 with SMTP id y12cs86537wfy; Mon, 4 Aug 2008 00:38:51 -0700 (PDT) Received: by 10.151.113.5 with SMTP id q5mr272266ybm.37.1217835529913; Mon, 04 Aug 2008 00:38:49 -0700 (PDT) Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with ESMTP id 5si5754915ywd.8.2008.08.04.00.38.30; Mon, 04 Aug 2008 00:38:50 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) client-ip=67.18.92.124; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with ESMTP id 765A511C46; Mon, 4 Aug 2008 03:38:27 -0400 (EDT) X-Original-To: sqlite-dev@sqlite.org Delivered-To: sqlite-dev@sqlite.org Received: from ik-out-1112.google.com (ik-out-1112.google.com [66.249.90.176]) by sqlite.org (Postfix) with ESMTP id 4C59511C41 for <sqlite-dev@sqlite.org>; Mon, 4 Aug 2008 03:38:23 -0400 (EDT) Received: by ik-out-1112.google.com with SMTP id b32so2163423ika.0 for <sqlite-dev@sqlite.org>; Mon, 04 Aug 2008 00:38:23 -0700 (PDT) Received: by 10.210.54.19 with SMTP id c19mr14589042eba.107.1217835502549; Mon, 04 Aug 2008 00:38:22 -0700 (PDT) Received: by 10.210.115.10 with HTTP; Mon, 4 Aug 2008 00:38:22 -0700 (PDT) Message-ID: <477821040808040038s381bf382p7411451e3c1a2e4e@mail.gmail.com> Date: Mon, 4 Aug 2008 10:38:22 +0300 From: anon@example.com To: sqlite-dev@sqlite.org In-Reply-To: <73d4fc50808030747g303a170ieac567723c2d4f24@mail.gmail.com> MIME-Version: 1.0 References: <477821040808030533y41f1501dq32447b568b6e6ca5@mail.gmail.com> <73d4fc50808030747g303a170ieac567723c2d4f24@mail.gmail.com> Subject: Re: [sqlite-dev] SQLite exception A&B X-BeenThere: sqlite-dev@sqlite.org X-Mailman-Version: 2.1.9 Priority: normal Reply-To: sqlite-dev@sqlite.org List-Id: <sqlite-dev.sqlite.org> List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe> List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev> List-Post: <mailto:sqlite-dev@sqlite.org> List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help> List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=subscribe> Content-Type: multipart/mixed; boundary="===============2123623832==" Mime-version: 1.0 Sender: sqlite-dev-bounces@sqlite.org Errors-To: sqlite-dev-bounces@sqlite.org Content-Length: 8475 --===============2123623832== Content-Type: multipart/alternative; boundary="----=_Part_29556_25702991.1217835502493" ------=_Part_29556_25702991.1217835502493 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline Hi Grant, Thanks for your reply. I am using a different session for each thread, whenever a thread wishes to access the database it gets a session from the session pool and works with that session until its work is done. Most of the actions the threads are doing on the database are quite complicated and are required to be fully committed or completely ignored, so yes, I am (most of the time) explicitly beginning and committing my transactions. Regarding the SQLiteStatementImpl, I believe the Poco manual explains that sessions and statements for that matter cannot be shared between threads, therefore if you are using a session via one thread only it should work fine. My first impression was that the problem was in the Poco infrastructure (I have found several Poco related bugs in the past), but the problem ALWAYS occurs when I perform the "BEGIN IMMEDIATE" action, if it were a Poco related bug, I would expect to see it here and there without any relation to this specific statement, but that is not the case. None the less, I will also post my question on the Poco forums. Nadav. On Sun, Aug 3, 2008 at 5:47 PM, Grant Gatchel <grant.gatchel@gmail.com>wrote: > Are you using the same Poco::Session for every thread or does each call > create a new session/handle to the database? > > Are you explicitly BEGINning and COMMITting your transactions? > > In looking at the 1.3.2 branch of Poco::Data::SQLite, there appears to be a > race condition in the SQLiteStatementImpl::next() method in which the member > _nextResponse is being accessed before the SQLiteStatementImpl::hasNext() > method has a chance to interpret that value and throw an exception. > > This question might be more suitable in the Poco forums or mailinglist. > > - Grant > > On Sun, Aug 3, 2008 at 8:33 AM, nadav g <nadav.gr@gmail.com> wrote: > >> Hi All, >> >> I have been using SQLite with Poco (www.appinf.com) as my infrastructure. >> The program is running several threads that access this database very >> often and are synchronized by SQLite itself. >> Everything seems to work just fine most of time (usually days - weeks) but >> I do get an occasional exception: >> >> Exception: SQL error or missing database: Iterator Error: trying to check >> if there is a next value >> >> The backtrace leads to this statement: >> *"BEGIN IMMEDIATE"* >> >> This specific code runs numerous times before an exception occurs (if >> occurs at all) and I cannot think of any reason for it to fail later rather >> than sooner. >> It is pretty obvious that this situation occurs due to some rare thread >> state, but I could not find any information that gives me any hint as to >> what this state might be. >> >> So what I am asking is: >> 1) Does anyone know why this sort of exception occurs? >> 2) Can anyone think of a reason for such an exception to occur in the >> situation I have described? >> >> Thanks in advance, >> Nadav. >> >> >> _______________________________________________ >> sqlite-dev mailing list >> sqlite-dev@sqlite.org >> http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev >> >> > > _______________________________________________ > sqlite-dev mailing list > sqlite-dev@sqlite.org > http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev > > ------=_Part_29556_25702991.1217835502493 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline <div dir="ltr">Hi Grant,<br><br>Thanks for your reply.<br>I am using a different session for each thread, whenever a thread wishes to access the database it gets a session from the session pool and works with that session until its work is done.<br> <br>Most of the actions the threads are doing on the database are quite complicated and are required to be fully committed or completely ignored, so yes, I am (most of the time) explicitly beginning and committing my transactions.<br> <br>Regarding the SQLiteStatementImpl, I believe the Poco manual explains that sessions and statements for that matter cannot be shared between threads, therefore if you are using a session via one thread only it should work fine.<br> <br>My first impression was that the problem was in the Poco infrastructure (I have found several Poco related bugs in the past), but the problem ALWAYS occurs when I perform the "BEGIN IMMEDIATE" action, if it were a Poco related bug, I would expect to see it here and there without any relation to this specific statement, but that is not the case.<br> <br>None the less, I will also post my question on the Poco forums.<br><br>Nadav.<br><br><div class="gmail_quote">On Sun, Aug 3, 2008 at 5:47 PM, Grant Gatchel <span dir="ltr"><<a href="mailto:grant.gatchel@gmail.com">grant.gatchel@gmail.com</a>></span> wrote:<br> <blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div dir="ltr">Are you using the same Poco::Session for every thread or does each call create a new session/handle to the database?<br> <br>Are you explicitly BEGINning and COMMITting your transactions?<br><br>In looking at the 1.3.2 branch of Poco::Data::SQLite, there appears to be a race condition in the SQLiteStatementImpl::next() method in which the member _nextResponse is being accessed before the SQLiteStatementImpl::hasNext() method has a chance to interpret that value and throw an exception.<br> <br>This question might be more suitable in the Poco forums or mailinglist.<br><br>- Grant<br> <br><div class="gmail_quote"><div><div></div><div class="Wj3C7c"> On Sun, Aug 3, 2008 at 8:33 AM, nadav g <span dir="ltr"><<a href="http://nadav.gr" target="_blank">nadav.gr</a>@<a href="http://gmail.com" target="_blank">gmail.com</a>></span> wrote:<br></div></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"> <div><div></div><div class="Wj3C7c"> <div dir="ltr">Hi All,<br><br>I have been using SQLite with Poco (<a href="http://www.appinf.com" target="_blank">www.appinf.com</a>) as my infrastructure.<br>The program is running several threads that access this database very often and are synchronized by SQLite itself.<br> Everything seems to work just fine most of time (usually days - weeks) but I do get an occasional exception:<br><br>Exception: SQL error or missing database: Iterator Error: trying to check if there is a next value<br><br> The backtrace leads to this statement:<br><b>"BEGIN IMMEDIATE"</b><br><br>This specific code runs numerous times before an exception occurs (if occurs at all) and I cannot think of any reason for it to fail later rather than sooner.<br> It is pretty obvious that this situation occurs due to some rare thread state, but I could not find any information that gives me any hint as to what this state might be.<br><br>So what I am asking is:<br>1) Does anyone know why this sort of exception occurs?<br> 2) Can anyone think of a reason for such an exception to occur in the situation I have described?<br><br>Thanks in advance,<br>Nadav.<br><br></div> <br></div></div>_______________________________________________<br> sqlite-dev mailing list<br> <a href="mailto:sqlite-dev@sqlite.org" target="_blank">sqlite-dev@sqlite.org</a><br> <a href="http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev" target="_blank">http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev</a><br> <br></blockquote></div><br></div> <br>_______________________________________________<br> sqlite-dev mailing list<br> <a href="mailto:sqlite-dev@sqlite.org">sqlite-dev@sqlite.org</a><br> <a href="http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev" target="_blank">http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev</a><br> <br></blockquote></div><br></div> ------=_Part_29556_25702991.1217835502493-- --===============2123623832== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ sqlite-dev mailing list sqlite-dev@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev --===============2123623832==-- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863087.12663_15.mindcrime!2,PS����������������������������������������0000664�0000000�0000000�00000014643�14143670036�0021655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW, SPF_PASS,WHOIS_NETSOLPR autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 1A6CD69CB6 for <xxxx@localhost>; Tue, 12 Aug 2008 21:42:38 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.109] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Tue, 12 Aug 2008 21:42:38 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs123119wfh; Sun, 10 Aug 2008 22:06:31 -0700 (PDT) Received: by 10.100.166.10 with SMTP id o10mr9327844ane.0.1218431190107; Sun, 10 Aug 2008 22:06:30 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id c29si10110392anc.13.2008.08.10.22.06.29; Sun, 10 Aug 2008 22:06:30 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Received: from localhost ([127.0.0.1]:45637 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KSPbx-0006dj-96 for xxxx.klub@gmail.com; Mon, 11 Aug 2008 01:06:29 -0400 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1KSPbE-0006cQ-Nd for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:44 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1KSPbD-0006bs-Px for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:44 -0400 Received: from [199.232.76.173] (port=37426 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KSPbD-0006bk-HT for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:43 -0400 Received: from main.gmane.org ([80.91.229.2]:46446 helo=ciao.gmane.org) by monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from <geh-help-gnu-emacs@m.gmane.org>) id 1KSPbD-0003Kl-CA for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:43 -0400 Received: from list by ciao.gmane.org with local (Exim 4.43) id 1KSPb9-00080r-CX for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 05:05:39 +0000 Received: from bas2-toronto63-1088792724.dsl.bell.ca ([64.229.168.148]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for <help-gnu-emacs@gnu.org>; Mon, 11 Aug 2008 05:05:39 +0000 Received: from cpchan by bas2-toronto63-1088792724.dsl.bell.ca with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for <help-gnu-emacs@gnu.org>; Mon, 11 Aug 2008 05:05:39 +0000 X-Injected-Via-Gmane: http://gmane.org/ To: help-gnu-emacs@gnu.org From: anon@example.com Date: Mon, 11 Aug 2008 01:03:22 -0400 Organization: Linux Private Site Message-ID: <87bq00nnxh.fsf@MagnumOpus.Mercurius> References: <877iav5s49.fsf@163.com> <86hc9yc5sj.fsf@timbral.net> <877iat7udd.fsf@163.com> <87fxphcsxi.fsf@lion.rapttech.com.au> <8504ddd4-5e3b-4ed5-bf77-aa9cce81b59a@1g2000pre.googlegroups.com> <87k5es59we.fsf@lion.rapttech.com.au> <63c824e3-62b1-4a93-8fa8-2813e1f9397f@v13g2000pro.googlegroups.com> <874p5vsgg8.fsf@nonospaz.fatphil.org> <8250972e-1886-4021-80bc-376e34881c80@v39g2000pro.googlegroups.com> <87zlnnqvvs.fsf@nonospaz.fatphil.org> <57add0e0-b39d-4c71-8d2c-d3b9ddfaa1a9@1g2000pre.googlegroups.com> <87sktfnz5p.fsf@atthis.clsnet.nl> <562e1111-d9e7-4b6a-b661-3f9af13fea17@b30g2000prf.googlegroups.com> <87d4khoq97.fsf@atthis.clsnet.nl> <0fe404c5-cab8-4692-8a27-532e737a7813@i24g2000prf.googlegroups.com> Mime-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha1; protocol="application/pgp-signature" X-Complaints-To: usenet@ger.gmane.org X-Gmane-NNTP-Posting-Host: bas2-toronto63-1088792724.dsl.bell.ca X-Face: G; Z,`sm>)4t4LB/GUrgH$W`!AmfHMj,LG)Z}X0ax@s9:0>0)B&@vcm{v-le)wng)?|o]D<V6&ay<F=H{M5?$T%p!dPdJeF,au\E@TA"v22K!Zl\\mzpU4]6$ZnAI3_L)h; fpd}mn2py/7gv^|*85-D_f:07cT>\Z}0:6X User-Agent: Gnus/5.110011 (No Gnus v0.11) Emacs/23.0.60 (gnu/linux) Cancel-Lock: sha1:IKyfrl5drOw6HllHFSmWHAKEeC8= X-detected-kernel: by monty-python.gnu.org: Linux 2.6, seldom 2.4 (older, 4) Subject: Re: Can anybody tell me how to send HTML-format mail in gnus X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org> List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe> List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs> List-Post: <mailto:help-gnu-emacs@gnu.org> List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help> List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=subscribe> Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 1229 Lines: 36 --=-=-= Content-Type: text/plain Xah <xahlee@gmail.com> writes: > So, i was reading about it in Wikipedia. Although i don't have a TV, > and haven't had since 2000, but i still enjoyed the festive spirits > anyhow. After all, i'm Chinese by blood. So, in my wandering, i ran > into this welcome song on youtube: > > http://www.youtube.com/watch?v=1HEndNYVhZo What is your point? Your email is in plain text and I can click on the link just fine- it is not exactly rocket science to implement parsing of URL's to workable links in an Email program (a lot of programs does that, including Gnus). Images can be included inline if you want. Also mail markups such as *this*, **this** and _this_ have been around since the Usenet days and displayed appropriately by a number of mailers. Like others have said, most html messages that I have seen either contains useless information, or are plain spam and can introduce a host of security problems in some mailers. Charles --=-=-= Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.4-svn0 (GNU/Linux) iD8DBQFIn8gm3epPyyKbwPYRApbvAKDRirXwzMzI+NHV77+QcP3EgTPaCgCfb/6m GtNVKdYAeftaYm1nwRVoCDA= =ULo3 -----END PGP SIGNATURE----- --=-=-=-- ���������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863087.12663_19.mindcrime!2,S�����������������������������������������0000664�0000000�0000000�00000007136�14143670036�0021540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id C4D6569CB3 for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:08 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:08 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs34794wfh; Wed, 6 Aug 2008 13:40:29 -0700 (PDT) Received: by 10.100.33.13 with SMTP id g13mr1093301ang.79.1218055228418; Wed, 06 Aug 2008 13:40:28 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id d19si15908789and.17.2008.08.06.13.40.27; Wed, 06 Aug 2008 13:40:28 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Received: from localhost ([127.0.0.1]:56316 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQpo3-0007Pc-Qk for xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:40:27 -0400 From: anon@example.com Newsgroups: gnu.emacs.help Date: Wed, 6 Aug 2008 20:38:35 +0100 Message-ID: <r6bpm5-6n6.ln1@news.ducksburg.com> References: <55dbm5-qcl.ln1@news.ducksburg.com> <mailman.15710.1217599959.18990.help-gnu-emacs@gnu.org> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Trace: individual.net bABVU1hcJwWAuRwe/097AAoOXnGGeYR8G1In635iFGIyfDLPUv X-Orig-Path: news.ducksburg.com!news Cancel-Lock: sha1:wK7dsPRpNiVxpL/SfvmNzlvUR94= sha1:oepBoM0tJBLN52DotWmBBvW5wbg= User-Agent: slrn/pre0.9.9-120/mm/ao (Ubuntu Hardy) Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!feeder.erje.net!proxad.net!feeder1-2.proxad.net!feed.ac-versailles.fr!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail Xref: news.stanford.edu gnu.emacs.help:160868 To: help-gnu-emacs@gnu.org Subject: Re: Learning LISP; Scheme vs elisp. X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org> List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe> List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs> List-Post: <mailto:help-gnu-emacs@gnu.org> List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help> List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=subscribe> Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 417 Lines: 11 On 2008-08-01, Thien-Thi Nguyen wrote: > warriors attack, felling foe after foe, > few growing old til they realize: to know > what deceit is worth deflection; > such receipt reversed rejection! > then their heavy arms, e'er transformed to shields: > balanced hooked charms, ploughed deep, rich yields. Aha: the exercise for the reader is to place the parens correctly. Might take me a while to solve this puzzle. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863087.12663_5.mindcrime!2,S������������������������������������������0000664�0000000�0000000�00000007165�14143670036�0021455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <sqlite-dev-bounces@sqlite.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 32F276963F for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:34 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:34 +0300 (EEST) Received: by 10.142.51.12 with SMTP id y12cs89397wfy; Mon, 4 Aug 2008 02:41:16 -0700 (PDT) Received: by 10.150.156.20 with SMTP id d20mr963580ybe.104.1217842875596; Mon, 04 Aug 2008 02:41:15 -0700 (PDT) Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with ESMTP id 6si3605185ywi.1.2008.08.04.02.40.57; Mon, 04 Aug 2008 02:41:15 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) client-ip=67.18.92.124; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with ESMTP id 7147F11C45; Mon, 4 Aug 2008 05:40:55 -0400 (EDT) X-Original-To: sqlite-dev@sqlite.org Delivered-To: sqlite-dev@sqlite.org Received: from relay00.pair.com (relay00.pair.com [209.68.5.9]) by sqlite.org (Postfix) with SMTP id B5F901192C for <sqlite-dev@sqlite.org>; Mon, 4 Aug 2008 05:40:52 -0400 (EDT) Received: (qmail 59961 invoked from network); 4 Aug 2008 09:40:50 -0000 Received: from unknown (HELO ?192.168.0.17?) (unknown) by unknown with SMTP; 4 Aug 2008 09:40:50 -0000 X-pair-Authenticated: 87.13.75.164 Message-Id: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net> From: anon@example.com To: sqlite-dev@sqlite.org Mime-Version: 1.0 (Apple Message framework v926) Date: Mon, 4 Aug 2008 11:40:49 +0200 X-Mailer: Apple Mail (2.926) Subject: [sqlite-dev] VM optimization inside sqlite3VdbeExec X-BeenThere: sqlite-dev@sqlite.org X-Mailman-Version: 2.1.9 Precedence: list Reply-To: sqlite-dev@sqlite.org List-Id: <sqlite-dev.sqlite.org> List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe> List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev> List-Post: <mailto:sqlite-dev@sqlite.org> List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help> List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=subscribe> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: sqlite-dev-bounces@sqlite.org Errors-To: sqlite-dev-bounces@sqlite.org Content-Length: 639 Inside sqlite3VdbeExec there is a very big switch statement. In order to increase performance with few modifications to the original code, why not use this technique ? http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html With a properly defined "instructions" array, instead of the switch statement you can use something like: goto * instructions[pOp->opcode]; --- Marco Bambini http://www.sqlabs.net http://www.sqlabs.net/blog/ http://www.sqlabs.net/realsqlserver/ _______________________________________________ sqlite-dev mailing list sqlite-dev@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1220863087.12663_7.mindcrime!2,RS�����������������������������������������0000664�0000000�0000000�00000012626�14143670036�0021577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <sqlite-dev-bounces@sqlite.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 3EBAB6963B for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:35 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:35 +0300 (EEST) Received: by 10.142.51.12 with SMTP id y12cs89536wfy; Mon, 4 Aug 2008 02:48:56 -0700 (PDT) Received: by 10.150.134.21 with SMTP id h21mr7950048ybd.181.1217843335665; Mon, 04 Aug 2008 02:48:55 -0700 (PDT) Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with ESMTP id 6si5897081ywi.1.2008.08.04.02.48.35; Mon, 04 Aug 2008 02:48:55 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) client-ip=67.18.92.124; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with ESMTP id ED01611C4E; Mon, 4 Aug 2008 05:48:31 -0400 (EDT) X-Original-To: sqlite-dev@sqlite.org Delivered-To: sqlite-dev@sqlite.org Received: from mx0.security.ro (mx0.security.ro [80.96.72.194]) by sqlite.org (Postfix) with ESMTP id EB3F51192C for <sqlite-dev@sqlite.org>; Mon, 4 Aug 2008 05:48:28 -0400 (EDT) Received: (qmail 348 invoked from network); 4 Aug 2008 12:48:03 +0300 Received: from dev.security.ro (HELO ?192.168.1.70?) (192.168.1.70) by mx0.security.ro with SMTP; 4 Aug 2008 12:48:03 +0300 Message-ID: <4896D06A.8000901@security.ro> Date: Mon, 04 Aug 2008 12:48:26 +0300 From: anon@example.com User-Agent: Thunderbird 2.0.0.16 (Windows/20080708) MIME-Version: 1.0 To: sqlite-dev@sqlite.org References: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net> In-Reply-To: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net> Content-Type: multipart/mixed; boundary="------------000207070200050102060301" X-BitDefender-Scanner: Clean, Agent: BitDefender qmail 2.0.0 on mx0.security.ro X-BitDefender-Spam: No (0) X-BitDefender-SpamStamp: v1, whitelisted, total: 0 Subject: Re: [sqlite-dev] VM optimization inside sqlite3VdbeExec X-BeenThere: sqlite-dev@sqlite.org X-Mailman-Version: 2.1.9 Precedence: high Reply-To: sqlite-dev@sqlite.org List-Id: <sqlite-dev.sqlite.org> List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe> List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev> List-Post: <mailto:sqlite-dev@sqlite.org> List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help> List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=subscribe> Sender: sqlite-dev-bounces@sqlite.org Errors-To: sqlite-dev-bounces@sqlite.org Content-Length: 2212 This is a multi-part message in MIME format. --------------000207070200050102060301 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Marco Bambini wrote: > Inside sqlite3VdbeExec there is a very big switch statement. > In order to increase performance with few modifications to the > original code, why not use this technique ? > http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html > > With a properly defined "instructions" array, instead of the switch > statement you can use something like: > goto * instructions[pOp->opcode]; > --- > Marco Bambini > http://www.sqlabs.net > http://www.sqlabs.net/blog/ > http://www.sqlabs.net/realsqlserver/ > > > > _______________________________________________ > sqlite-dev mailing list > sqlite-dev@sqlite.org > http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev > All the world's not a VAX. This technique is GCC-specific. The SQLite source must be as portable as possible thus tying it to a specific compiler is out of the question. While one could conceivably use some preprocessor magic to provide alternate implementations, that would be impractical considering the sheer size of the code affected. On the other hand - perhaps you could benchmark the change and provide some data on whether this actually improves performance? --------------000207070200050102060301 Content-Type: text/x-vcard; charset=utf-8; name="mihailim.vcf" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="mihailim.vcf" begin:vcard fn:Mihai Limbasan n:Limbasan;Mihai org:SC SECPRAL COM SRL adr:;;str. Actorului nr. 9;Cluj-Napoca;Cluj;400441;Romania email;internet:mihailim@security.ro title:SoftwareDeveloper tel;work:+40 264 449579 tel;fax:+40 264 418594 tel;cell:+40 729 038302 url:http://secpral.ro/ version:2.1 end:vcard --------------000207070200050102060301 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ sqlite-dev mailing list sqlite-dev@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev --------------000207070200050102060301-- ����������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1252168370_3.14675.cthulhu!2,S��������������������������������������������0000664�0000000�0000000�00000001563�14143670036�0021157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <dfgh@floppydisk.nl> X-Spam-Checker-Version: SpamAssassin 3.1.0 (2005-09-13) on mindcrime X-Spam-Level: Delivered-To: dfgh@floppydisk.nl Message-ID: <43A09C49.9040902@euler.org> Date: Wed, 14 Dec 2005 23:27:21 +0100 From: Fred Flintstone <fred@euler.org> User-Agent: Mozilla Thunderbird 1.0.7 (X11/20051010) X-Accept-Language: nl-NL, nl, en MIME-Version: 1.0 To: dfgh@floppydisk.nl Subject: Re: xyz References: <439C1136.90504@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439B41ED.2080402@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439A1E03.3090604@euler.org> <20051211184308.GB13513@gauss.org> In-Reply-To: <20051211184308.GB13513@gauss.org> X-Enigmail-Version: 0.92.0.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-UIDL: T<?"!%LG"!cAK"!_j(#! Content-Length: 1879 Test 123. ���������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/1283599333.1840_11.cthulhu!2,���������������������������������������������0000664�0000000�0000000�00000000752�14143670036�0021033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������From: Frodo Baggins <frodo@example.com> To: Bilbo Baggins <bilbo@anotherexample.com> Subject: Greetings from =?UTF-8?B?TG90aGzDs3JpZW4=?= User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO) Fcc: .sent Organization: The Fellowship of the Ring MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Message-Id: <abcd$efgh@example.com> Let's write some fünkÿ text using umlauts. Foo. ����������������������mu-1.6.10/lib/testdir/cur/1305664394.2171_402.cthulhu!2,��������������������������������������������0000664�0000000�0000000�00000001156�14143670036�0021107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������From: =?UTF-8?B?TcO8?= <testmu@testmu.xx> To: Helmut =?UTF-8?B?S3LDtmdlcg==?= <hk@testmu.xxx> Subject: =?UTF-8?B?TW90w7ZyaGVhZA==?= User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO) References: <non-exist-01@msg.id> <non-exist-02@msg.id> <non-exist-03@msg.id> <non-exist-04@msg.id> 1n-Reply-To: <non-exist-04@msg.id> MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test for issue #38, where apparently searching for accented words in subject, to etc. fails. What about here? Queensrÿche. Mötley Crüe. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/encrypted!2,S�������������������������������������������������������������0000664�0000000�0000000�00000004630�14143670036�0017506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-path: <> Envelope-to: peter@example.com Delivery-date: Fri, 11 May 2012 16:22:03 +0300 Received: from localhost.example.com ([127.0.0.1] helo=borealis) by borealis with esmtp (Exim 4.77) id 1SSpnB-00038a-Ux for djcb@localhost; Fri, 11 May 2012 16:21:58 +0300 Delivered-To: peter@example.com From: Brian <brian@example.com> To: Peter <peter@example.com> Subject: encrypted User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:21:42 +0300 Message-ID: <!&!AAAAAAAAAYAAAAAAAAAOH1+8mkk+lLn7Gg5fke7FbCgAAAEAAAAJ7eBDgcactKhXL6r8cEnJ8BAAAAAA==@example.com> MIME-Version: 1.0 Content-Type: multipart/encrypted; boundary="=-=-="; protocol="application/pgp-encrypted" --=-=-= Content-Type: application/pgp-encrypted Version: 1 --=-=-= Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.12 (GNU/Linux) hQQOA1T38TPQrHD6EA//YXkUB4Dy09ngCRyHWbXmV3XBjuKTr8xrak5ML1kwurav gyagOHKLMU+5CKvObChiKtXhtgU0od7IC8o+ALlHevQ0XXcqNYA2KUfX8R7akq7d Xx9mA6D8P7Y/P8juUCLBpfrCi2GC42DtvPZSUu3bL/ctUJ3InPHIfHibKF2HMm7/ gUHAKY8VPJF39dLP8GLcfki6qFdeWbxgtzmuyzHfCBCLnDL0J9vpEQBpGDFMcc4v cCbmMJaiPOmRb6U4WOuRVnuXuTztLiIn0jMslzOSFDcLTVBAsrC01r71O+XZKfN4 mIfcpcWJYKM2NQW8Jwf+8Hr84uznBqs8uTTlrmppjkAHZGqGMjiQDxLhDVaCQzMy O8PSV4xT6HPlKXOwV1OLc+vm0A0RAdSBctgZg40oFn4XdB1ur8edwAkLvc0hJKaz gyTQiPaXm2Uh2cDeEx4xNgXmwCKasqc9jAlnDC2QwA33+pw3OqgZT5h1obn0fAeR mgB+iW1503DIi/96p8HLZcr2EswLEH9ViHIEaFj/vlR5BaOncsLB0SsNV/MHRvym Xg5GUjzPIiyBZ3KaR9OIBiZ5eXw+bSrPAo/CAs0Zwxag7W3CH//oK39Qo1GnkYpc 4IQxhx4IwkzqtCnripltV/kfpGu0yA/OdK8lOjkUqCwvL97o73utXIxm21Zd3mEP /iLNrduZjMCq+goz1pDAQa9Dez6VjwRuRPTqeAac8Fx/nzrVzIoIEAt36hpuaH1l KpbmHpKgsUWcrE5iYT0RRlRRtRF4PfJg8PUmP1hvw8TaEmNfT+0HgzcJB/gRsVdy gTzkzUDzGZLhRcpmM5eW4BkuUmIO7625pM6Jd3HOGyfCGSXyEZGYYeVKzv8xbzYf QM6YYKooRN9Ya2jdcWguW0sCSJO/RZ9eaORpTeOba2+Fp6w5L7lga+XM9GLfgref Cf39XX1RsmRBsrJTw0z5COf4bT8G3/IfQP0QyKWIFITiFjGmpZhLsKQ3KT4vSe/d gTY1xViVhkjvMFn3cgSOSrvktQpAhsXx0IRazN0T7pTU33a5K0SrZajY9ynFDIw9 we7XYyVwZzYEXjGih5mTH1PhWYK5fZZEKKqaz5TyYv9SeWJ+8FrHeXUKD38SQEHM qkpl9Iv17RF4Qy9uASWwRoobhKO+GykTaBSTyw8R8ctG/hfAlnaZxQ3TwNyHWyvU 9SVJsp27ulv/W9MLZtGpEMK0ckAR164Vyou1KOn200BqxbC2tJpegNeD2TP5ZtdY HIcxkgKr0haYcDnVEf1ulSxv23pZWIexbgvVCG7dRL0eB+6O28f9CWehle10MDyM 0AYyw8Da2cu7PONMovqt4nayScyGTacFBp7c2KXR9DGZ0mcBwOjL/mGRKcVWN3MG 2auCrwn2KVWmKZI3Jp0T8KhfGBnFs9lUElpDTOiED1/2bKz6Yoc385QtWx99DFMZ IWiH5wMxkWFpzjE+GHiJ09vSbTTL4JY9eu2n5nxQmtjYMBVxQm7S7qwH =0Paa -----END PGP MESSAGE----- --=-=-=-- ��������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/multimime!2,FS������������������������������������������������������������0000664�0000000�0000000�00000001160�14143670036�0017614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-path: <> Envelope-to: djcb@localhost Delivery-date: Sun, 20 May 2012 09:59:51 +0300 From: Steve Jobs <jobs@example.com> To: Bill Gates <bg@example.com> Subject: multimime User-agent: mu4e 0.9.8.4; emacs 23.3.1 Date: Sat, 19 May 2012 20:57:56 +0100 Message-ID: <m2fwaw2baz.fsf@example.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain abc --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename="test1.C" Content-Transfer-Encoding: base64 aGVyZSBpcyBhIHNpbXBsZSB0ZXN0IGZpbGUuCg== --=-=-= Content-Type: text/plain def --=-=-=-- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/multirecip!2,S������������������������������������������������������������0000664�0000000�0000000�00000000363�14143670036�0017665�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Date: Thu, 15 May 2016 14:57:25 -0200 From: To: a@example.com,b@example.com,c@example.com Cc: d@example.com,e@example.com Subject: test with multi to and cc Message-id: <3BE9E652343245@emss35m06.us.lmco.com> Message with multi cc and to. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/signed!2,S����������������������������������������������������������������0000664�0000000�0000000�00000001644�14143670036�0016764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-path: <> Envelope-to: skipio@localhost Delivery-date: Fri, 11 May 2012 16:21:57 +0300 Received: from localhost.roma.net([127.0.0.1] helo=borealis) by borealis with esmtp (Exim 4.77) id 1SSpnB-00038a-55 for djcb@localhost; Fri, 11 May 2012 16:21:57 +0300 Delivered-To: diggler@gmail.com From: Skipio <skipio@roma.net> To: Hannibal <hanni@carthago.net> Subject: signed User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:20:45 +0300 Message-ID: <878vgy97ma.fsf@roma.net> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha1; protocol="application/pgp-signature" --=-=-= Content-Type: text/plain I am signed! --=-=-= Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iEYEARECAAYFAk+tEi0ACgkQ6WrHoQF92jxTzACeKd/XxY+P7bpymWL3JBRHaW9p DpwAoKw7PDW4z/lNTkWjndVTjoO9jGhs =blXz -----END PGP SIGNATURE----- --=-=-=-- ��������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/signed-encrypted!2,S������������������������������������������������������0000664�0000000�0000000�00000004401�14143670036�0020751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-path: <> Envelope-to: karjala@localhost Delivery-date: Fri, 11 May 2012 16:37:57 +0300 From: karjala@example.com To: lapinkulta@example.com Subject: signed + encrypted User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:36:08 +0300 Message-ID: <874nrm96wn.fsf@example.com> MIME-Version: 1.0 Content-Type: multipart/encrypted; boundary="=-=-="; protocol="application/pgp-encrypted" --=-=-= Content-Type: application/pgp-encrypted Version: 1 --=-=-= Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.12 (GNU/Linux) hQQOA1T38TPQrHD6EA/+K4kSpMa7zk+qihUkQnHSq28xYxisNQx6X5DVNjA/Qx16 uZj/40ae+PoSMTVfklP+B2S/IomuTW6dwVqS7aQ3u4MTzi+YOi11k1lEMD7hR0Wb L0i48o3/iCPuCTpnOsaLZvRL06g+oTi0BF2pgz/YdsgsBTGrTb3pkDGSlLIhvh/J P8eE3OuzkXS6d8ymJKx2S2wQJrc1AFf1BgJfgc5T0iAvcV+zIMG+PIYcVd04zVpj cORFEfvGgfxWkeX+Ks3tu/l5PA1EesnoqFdNFZm+RKBg3RFsOm8tBlJ46xJjfeHg zLgifeSLy3tOX7CvWYs9torrx7s7UOI2gV8kzBqz+a7diyCMezceeQ9l0nIRybwW C9Egp8Bpfb02iXTOGdE/vRiNItQH14GKmXf4nCSwdtQUm3yzaqY9yL3xBxAlW53e YOFfPMESt+E7IlPn0c7llWGrcdrhJbUEoGOIPezES7kdeNPzi8G1lLtvT04/SSZJ QxPH5FNzSFaYFAQSdI7TR69P7L7vtLL8ndkjY49HfLFXochQQzsqrzVxzRCruHxA zbZSRptNf9SuXEaX9buO1vlFHheGvrCKzEWa6O7JD/DiyrE/zqy4jdlh9abMCouQ GWGSbn8jk6SMTQQ2Yv/VOyFqifHZp0UJD59tyIdenpxoYu5M0lwHLNVDlRjLEwUQ AIDz1tbLoM7lxs2FOKGr8QqbKIeMfL+NUmbvVIDc4mJrOlRnHh+cZYm4Z49iTl1v bYNMYgR5nY7W6rqh0ae7ZOW0h2NzpkAwTzuf1YrSjNavd9KBwOCFtAoZhRwfwFVx ju+ByHFNnf7g/R6DekHS0pSiatM0cPDJT05atEZb+13CRHHznonmLHi+VahXjrpg cIUA8Lhjdfm6Fsabo7gNZnTTRxNBqUXKK2vJF/XLbNrH5K2BH2dCCmUNtm3yFWiM DOzaw3665Y3S6MvZdyKpatbNrVoJdBpRgPxJ1YCSEituFUqHJBStay+aRb5fVkQR w3+9hWw+Ob0+2EumKbgfQ7iMwTZBCZP4VOxkoqdHvs9aWm4N7wHtXsyCew3icbJx lyUWsDx/FI+HlQRfOqeAMxmp8kKybmHNw8oGiw+uPPUHSD1NFYVm2DtwhYll3Fvs YY7r5s3yP1ZnwxMqWI3OsExVUXs8MS4UTAgO+cggO7YidPcANbBDihBFP8mTXtni Oo5n5v+/eRoLfHmnsGcaK8EkKsfFHpbqn4gxXGcBuHaTTJ/ZhbW6bi1WWZA9ExaJ IeTDtp5Bks1pJvTjCDacvgwl3rEBM6yaeIvB7575Y/GPMTOZhawhfOxV1smMmTKI JOWYb3+PuN2cvWetkjFgH8re4sRXq22DKBZHJEWYU8sH0sACAePnIr+pkrOtGeJB t1zBqZUnrupH6ptk9n/AjbQ+XSMTEKu55gSjYLAYx1EHApx52QLkdh+ej5xCIVeY 6wS1Iipkoc6/r6F7CKctupXurNY2AlD4uQIOfD6kQgkqK4PY3hsRHQA+Zqj6oRfr kxysFJZvhgt26IeBVapFs10WuYt9iHfpbPUBQUIZCLyPAh08UdVW64Uc2DvUPy+I C+3RrmTHQPP/YNKgDQaZ3ySVEDkqjaDPmXr5K0Ibaib2dtPCLcA= =pv03 -----END PGP MESSAGE----- --=-=-=-- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/cur/special!2,Sabc������������������������������������������������������������0000664�0000000�0000000�00000000536�14143670036�0017600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Date: Thu, 1 Jun 2012 14:57:25 -0200 From: "Rocky Balboa" <rocky@example.com> To: "Ivan Drago" <ivan@example.com> Subject: currying and tail optimization Message-id: <3BE9E653ef345@emss35m06.us.lmco.com> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Test 123. I'm a special message with special flags. ������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/new/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14143670036�0015241�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/new/1220863087.12663_21.mindcrime���������������������������������������������0000664�0000000�0000000�00000012701�14143670036�0021201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 6389969CB2 for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:07 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:07 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs34769wfh; Wed, 6 Aug 2008 13:38:53 -0700 (PDT) Received: by 10.100.6.13 with SMTP id 13mr4103508anf.83.1218055131215; Wed, 06 Aug 2008 13:38:51 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id b32si10199298ana.34.2008.08.06.13.38.49; Wed, 06 Aug 2008 13:38:51 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; DomainKey-Status: good (test mode) Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org; domainkeys=pass (test mode) header.From=juanma_bellon@yahoo.es Received: from localhost ([127.0.0.1]:55648 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQpmT-0005W9-AQ for xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:38:49 -0400 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1KQplz-0005U5-Pk for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:19 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1KQplw-0005Nw-OG for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:19 -0400 Received: from [199.232.76.173] (port=45465 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQplw-0005NX-I6 for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:16 -0400 Received: from n74a.bullet.mail.sp1.yahoo.com ([98.136.45.21]:29868) by monty-python.gnu.org with smtp (Exim 4.60) (envelope-from <juanma_bellon@yahoo.es>) id 1KQplw-0007EF-7Z for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:16 -0400 Received: from [216.252.122.216] by n74.bullet.mail.sp1.yahoo.com with NNFMP; 06 Aug 2008 20:38:14 -0000 Received: from [68.142.237.89] by t1.bullet.sp1.yahoo.com with NNFMP; 06 Aug 2008 20:38:14 -0000 Received: from [69.147.75.180] by t5.bullet.re3.yahoo.com with NNFMP; 06 Aug 2008 20:38:14 -0000 Received: from [127.0.0.1] by omp101.mail.re1.yahoo.com with NNFMP; 06 Aug 2008 20:38:14 -0000 X-Yahoo-Newman-Id: 778995.62909.bm@omp101.mail.re1.yahoo.com Received: (qmail 43643 invoked from network); 6 Aug 2008 20:38:14 -0000 DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=s1024; d=yahoo.es; h=Received:X-YMail-OSG:X-Yahoo-Newman-Property:From:To:Subject:Date:User-Agent:References:In-Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-Disposition:Message-Id; b=ThdHlND5CNUsLPGuk+XhCWkdUA9w7lg4hiAgx8F8egsmQteMpwUlV/Y5tfe6K3O2jzHjtsklkzWqm7WY3VAcxxD/QgxLnianK5ZQHoelDAiGaFRqu8Y42XMZso2ccCBFWUQaKo9C+KIfa3e3ci73qehVxTtmr7bxLjurcSYEBPo= ; Received: from unknown (HELO 212251170160.customer.cdi.no) (juanma_bellon@212.251.170.160 with plain) by smtp109.plus.mail.re1.yahoo.com with SMTP; 6 Aug 2008 20:38:14 -0000 X-YMail-OSG: k86L54kVM1kiZbUlYx7gayoBrCLYMFIRDL.KJLBKetNucAbwU4RjeeE1vhjw33hREaUig0CCjG7BTwIfbeZZpRmUcHbxl6gR0z6Sd3lYqA-- X-Yahoo-Newman-Property: ymail-3 From: anon@example.com To: help-gnu-emacs@gnu.org Date: Wed, 6 Aug 2008 22:38:15 +0200 User-Agent: KMail/1.9.6 (enterprise 0.20070907.709405) References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org> <mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org> <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com> In-Reply-To: <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline Message-Id: <200808062238.15634.juanma_bellon@yahoo.es> X-detected-kernel: by monty-python.gnu.org: FreeBSD 6.x (1) Subject: Re: basic question: going back to dired X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org> List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe> List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs> List-Post: <mailto:help-gnu-emacs@gnu.org> List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help> List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=subscribe> Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 361 On Thursday 31 July 2008, Xah wrote: > what's the logic of =E2=80=9COK=E2=80=9D? =46or all I know, it comes from "0 Knock-outs" (from USA civil war times, IIRC), i.e., all went really well. But this is really off-topic. =2D-=20 Juanma "Having a smoking section in a restaurant is like having a peeing section in a swimming pool." -- Edward Burr ���������������������������������������������������������������mu-1.6.10/lib/testdir/new/1220863087.12663_23.mindcrime���������������������������������������������0000664�0000000�0000000�00000012340�14143670036�0021202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id C3EF069CB3 for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:10 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:10 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs35153wfh; Wed, 6 Aug 2008 13:58:17 -0700 (PDT) Received: by 10.100.166.10 with SMTP id o10mr4182182ane.0.1218056296101; Wed, 06 Aug 2008 13:58:16 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id d34si13875743and.3.2008.08.06.13.58.14; Wed, 06 Aug 2008 13:58:16 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org; dkim=pass (test mode) header.i=@gmail.com Received: from localhost ([127.0.0.1]:33418 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQq5G-0001aY-Cr for xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:58:14 -0400 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1KQq4n-0001Z9-06 for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:45 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1KQq4l-0001V8-6c for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:44 -0400 Received: from [199.232.76.173] (port=46438 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQq4k-0001Un-V2 for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:42 -0400 Received: from ik-out-1112.google.com ([66.249.90.180]:17562) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from <lekktu@gmail.com>) id 1KQq4k-0001fk-OW for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:42 -0400 Received: by ik-out-1112.google.com with SMTP id c21so94956ika.2 for <help-gnu-emacs@gnu.org>; Wed, 06 Aug 2008 13:57:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:message-id:date:from:to :subject:cc:in-reply-to:mime-version:content-type :content-transfer-encoding:content-disposition:references; bh=TTNY9749hpg1+TXOwdaCr+zbQGhBUt3IvsjLWp+pxp0=; b=BOfudUT/SiW9V4e9+k3dXDzwm+ogdrq4m5OlO+f1H+oE6OAYGIm8dbdqDAOwUewBoS jRpfZo07YamP9rkko79SeFdQnf7UAPFAw9x7DFCm3x6muSlCcJBR7vYs1rgHOSINAn2B vQx2//lKR4fXfKNURNu+B30KrvoEmw6m2C8dI= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=message-id:date:from:to:subject:cc:in-reply-to:mime-version :content-type:content-transfer-encoding:content-disposition :references; b=UMDBulH/LwxDywEH0pfK3DbJ4u2kIZCVDLIM++PqrdcR82HjcS/O3Jhf5OFrf7Fnyj GH76xmc7zkTG/3aQy2WY6DeWCJaFarEItmhxy3h/xS+kUKeDARzNox0OzK6lIv/u9bdy f2LnFlYRJ7Q5vy3lxpxAWB4v0qCwtF9LjWFg4= Received: by 10.210.47.7 with SMTP id u7mr3100239ebu.30.1218056261587; Wed, 06 Aug 2008 13:57:41 -0700 (PDT) Received: by 10.210.71.14 with HTTP; Wed, 6 Aug 2008 13:57:41 -0700 (PDT) Message-ID: <f7ccd24b0808061357t453f5962w8b61f9a453b684d0@mail.gmail.com> Date: Wed, 6 Aug 2008 22:57:41 +0200 From: anon@example.com To: Juanma <juanma_bellon@yahoo.es> In-Reply-To: <200808062238.15634.juanma_bellon@yahoo.es> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Content-Disposition: inline References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org> <mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org> <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com> <200808062238.15634.juanma_bellon@yahoo.es> X-detected-kernel: by monty-python.gnu.org: Linux 2.6 (newer, 2) Cc: help-gnu-emacs@gnu.org Subject: Re: basic question: going back to dired X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org> List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe> List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs> List-Post: <mailto:help-gnu-emacs@gnu.org> List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help> List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=subscribe> Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 309 On Wed, Aug 6, 2008 at 22:38, Juanma <juanma_bellon@yahoo.es> wrote: > For all I know, it comes from "0 Knock-outs" (from USA civil war times, > IIRC), i.e., all went really well. See http://en.wikipedia.org/wiki/Okay#Etymology "0 knock-outs" is among the "Improbable or refuted etymologies". Juanma ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/new/1220863087.12663_25.mindcrime���������������������������������������������0000664�0000000�0000000�00000010173�14143670036�0021206�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW, SPF_PASS autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id D68E769CB5 for <xxxx@localhost>; Fri, 8 Aug 2008 20:56:25 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Fri, 08 Aug 2008 20:56:25 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs71287wfh; Fri, 8 Aug 2008 07:40:46 -0700 (PDT) Received: by 10.100.122.8 with SMTP id u8mr3824321anc.77.1218206446062; Fri, 08 Aug 2008 07:40:46 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id d35si2718351and.38.2008.08.08.07.40.45; Fri, 08 Aug 2008 07:40:46 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Received: from localhost ([127.0.0.1]:47349 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KRT93-0006Po-A3 for xxxx.klub@gmail.com; Fri, 08 Aug 2008 10:40:45 -0400 Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!news-out.readnews.com!news-xxxfer.readnews.com!panix!not-for-mail From: anon@example.com Newsgroups: gnu.emacs.help Date: Fri, 08 Aug 2008 10:07:30 -0400 Organization: PANIX Public Access Internet and UNIX, NYC Message-ID: <uwsireh25.fsf@one.dot.net> References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org> <mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org> <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com> <200808062238.15634.juanma_bellon@yahoo.es> <mailman.15958.1218056266.18990.help-gnu-emacs@gnu.org> NNTP-Posting-Host: panix5.panix.com Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Trace: reader1.panix.com 1218204439 22850 166.84.1.5 (8 Aug 2008 14:07:19 GMT) X-Complaints-To: abuse@panix.com NNTP-Posting-Date: Fri, 8 Aug 2008 14:07:19 +0000 (UTC) User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.2 (windows-nt) Cancel-Lock: sha1:Ckkp5oJPIMuAVgEHGnS/9MkZsEs= Xref: news.stanford.edu gnu.emacs.help:160963 To: help-gnu-emacs@gnu.org Subject: Re: basic question: going back to dired X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org> List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe> List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs> List-Post: <mailto:help-gnu-emacs@gnu.org> List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help> List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>, <mailto:help-gnu-emacs-request@gnu.org?subject=subscribe> Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 710 Lines: 27 I seem to remember from my early school days it was a campaign slogan for someone nick-named Kinderhook that went something like Old Kinderhook is OK - Chris "Juanma Barranquero" <lekktu@gmail.com> writes: > On Wed, Aug 6, 2008 at 22:38, Juanma <juanma_bellon@yahoo.es> wrote: > >> For all I know, it comes from "0 Knock-outs" (from USA civil war times, >> IIRC), i.e., all went really well. > > See http://en.wikipedia.org/wiki/Okay#Etymology > > "0 knock-outs" is among the "Improbable or refuted etymologies". > > Juanma > > -- (. .) =ooO=(_)=Ooo===================================== Chris McMahan | first_initiallastname@one.dot.net ================================================= �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mu-1.6.10/lib/testdir/new/1220863087.12663_9.mindcrime����������������������������������������������0000664�0000000�0000000�00000021747�14143670036�0021141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Return-Path: <sqlite-dev-bounces@sqlite.org> X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-1.2 required=3.0 tests=BAYES_00,HTML_MESSAGE, MIME_QP_LONG_LINE autolearn=no version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 4E3CF6963B for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:37 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:37 +0300 (EEST) Received: by 10.142.51.12 with SMTP id y12cs94317wfy; Mon, 4 Aug 2008 05:48:28 -0700 (PDT) Received: by 10.150.152.17 with SMTP id z17mr1245909ybd.194.1217854107583; Mon, 04 Aug 2008 05:48:27 -0700 (PDT) Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with ESMTP id 9si6334793yws.5.2008.08.04.05.47.57; Mon, 04 Aug 2008 05:48:27 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) client-ip=67.18.92.124; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with ESMTP id 4FBC111C6F; Mon, 4 Aug 2008 08:47:54 -0400 (EDT) X-Original-To: sqlite-dev@sqlite.org Delivered-To: sqlite-dev@sqlite.org Received: from cpsmtpo-eml02.kpnxchange.com (cpsmtpo-eml02.kpnxchange.com [213.75.38.151]) by sqlite.org (Postfix) with ESMTP id AA4F111C10 for <sqlite-dev@sqlite.org>; Mon, 4 Aug 2008 08:47:51 -0400 (EDT) Received: from hpsmtp-eml21.kpnxchange.com ([213.75.38.121]) by cpsmtpo-eml02.kpnxchange.com with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug 2008 14:47:50 +0200 Received: from cpbrm-eml13.kpnsp.local ([195.121.247.250]) by hpsmtp-eml21.kpnxchange.com with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug 2008 14:47:50 +0200 Received: from hpsmtp-eml30.kpnxchange.com ([10.94.53.250]) by cpbrm-eml13.kpnsp.local with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug 2008 14:47:50 +0200 Received: from localhost ([10.94.53.250]) by hpsmtp-eml30.kpnxchange.com with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug 2008 14:47:49 +0200 Content-class: urn:content-classes:message MIME-Version: 1.0 X-MimeOLE: Produced By Microsoft Exchange V6.5 Date: Mon, 4 Aug 2008 14:46:06 +0200 Message-ID: <F687EC042917A94E8BB4B0902946453AE17D6C@CPEXBE-EML18.kpnsp.local> X-MS-Has-Attach: X-MS-TNEF-Correlator: Thread-Topic: [sqlite-dev] VM optimization inside sqlite3VdbeExec Thread-Index: Acj2FjkWvteFtLHTTYeVz4ES7E2ggAAGRxeI References: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net> From: anon@example.com To: <sqlite-dev@sqlite.org> X-OriginalArrivalTime: 04 Aug 2008 12:47:49.0650 (UTC) FILETIME=[4D577720:01C8F630] Subject: Re: [sqlite-dev] VM optimization inside sqlite3VdbeExec X-BeenThere: sqlite-dev@sqlite.org X-Mailman-Version: 2.1.9 Precedence: list Reply-To: sqlite-dev@sqlite.org List-Id: <sqlite-dev.sqlite.org> List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe> List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev> List-Post: <mailto:sqlite-dev@sqlite.org> List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help> List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>, <mailto:sqlite-dev-request@sqlite.org?subject=subscribe> Content-Type: multipart/mixed; boundary="===============1911358387==" Mime-version: 1.0 Sender: sqlite-dev-bounces@sqlite.org Errors-To: sqlite-dev-bounces@sqlite.org Content-Length: 5318 This is a multi-part message in MIME format. --===============1911358387== Content-class: urn:content-classes:message Content-Type: multipart/alternative; boundary="----_=_NextPart_001_01C8F630.0FC2EC1E" This is a multi-part message in MIME format. ------_=_NextPart_001_01C8F630.0FC2EC1E Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Actually, almost every C compiler will already do what you suggest: if = the range of case labels is compact, the switch will be compiled using a = jump table. Only if the range is limited and/or sparse other techniques = will be used, such as linear search and binary search. =20 I'm pretty sure, if you perform the tests suggested by Mihai, that you = will find zero performance difference, neither better, nor worse. =20 Paul =20 ________________________________ From: anon@example.com Sent: Mon 8/4/2008 11:40 AM To: sqlite-dev@sqlite.org Subject: [sqlite-dev] VM optimization inside sqlite3VdbeExec Inside sqlite3VdbeExec there is a very big switch statement. In order to increase performance with few modifications to the=20 original code, why not use this technique ? http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html = <http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html>=20 With a properly defined "instructions" array, instead of the switch=20 statement you can use something like: goto * instructions[pOp->opcode]; --- Marco Bambini http://www.sqlabs.net <http://www.sqlabs.net/>=20 http://www.sqlabs.net/blog/ <http://www.sqlabs.net/blog/>=20 http://www.sqlabs.net/realsqlserver/ = <http://www.sqlabs.net/realsqlserver/>=20 _______________________________________________ sqlite-dev mailing list sqlite-dev@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev = <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>=20 ------_=_NextPart_001_01C8F630.0FC2EC1E Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable <HTML dir=3Dltr><HEAD><TITLE>[sqlite-dev] VM optimization inside = sqlite3VdbeExec=0A= =0A= =0A= =0A=
=0A=
Actually, = almost every C compiler will already do what you suggest: if the range = of case labels is compact, the switch will be compiled using a jump = table. Only if the range is limited and/or sparse other techniques will = be used, such as linear search and binary search.
=0A=
 
=0A=
I'm pretty sure, if you = perform the tests suggested by Mihai, that you will find zero = performance difference, neither better, nor worse.
=0A=
 
=0A=
Paul
=0A=
 
=0A=
=0A=
=0A=
=0A=
From: = sqlite-dev-bounces@sqlite.org on behalf of Marco Bambini
Sent: = Mon 8/4/2008 11:40 AM
To: = sqlite-dev@sqlite.org
Subject: [sqlite-dev] VM optimization = inside sqlite3VdbeExec

=0A=
=0A=

Inside sqlite3VdbeExec there is a very = big switch statement.
In order to increase performance with few = modifications to the 
original code, why not use this technique = ?
= http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html<= /FONT>

With a properly defined = "instructions" array, instead of the switch 
statement you can = use something like:
goto * = instructions[pOp->opcode];
---
Marco Bambini
http://www.sqlabs.net
http://www.sqlabs.net/blog/
http://www.sqlabs.net/realsqlserver/



<= FONT face=3DArial = size=3D2>_______________________________________________
sqlite-dev = mailing list
sqlite-dev@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev

------_=_NextPart_001_01C8F630.0FC2EC1E-- --===============1911358387== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ sqlite-dev mailing list sqlite-dev@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev --===============1911358387==-- mu-1.6.10/lib/testdir/tmp/000077500000000000000000000000001414367003600152505ustar00rootroot00000000000000mu-1.6.10/lib/testdir/tmp/1220863087.12663.ignore000066400000000000000000000101731414367003600202230ustar00rootroot00000000000000Return-Path: X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW, SPF_PASS autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id D68E769CB5 for ; Fri, 8 Aug 2008 20:56:25 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [72.14.221.111] by mindcrime with IMAP (fetchmail-6.3.8) for (single-drop); Fri, 08 Aug 2008 20:56:25 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs71287wfh; Fri, 8 Aug 2008 07:40:46 -0700 (PDT) Received: by 10.100.122.8 with SMTP id u8mr3824321anc.77.1218206446062; Fri, 08 Aug 2008 07:40:46 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id d35si2718351and.38.2008.08.08.07.40.45; Fri, 08 Aug 2008 07:40:46 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Received: from localhost ([127.0.0.1]:47349 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KRT93-0006Po-A3 for xxxx.klub@gmail.com; Fri, 08 Aug 2008 10:40:45 -0400 Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!news-out.readnews.com!news-xxxfer.readnews.com!panix!not-for-mail From: anon@example.com Newsgroups: gnu.emacs.help Date: Fri, 08 Aug 2008 10:07:30 -0400 Organization: PANIX Public Access Internet and UNIX, NYC Message-ID: References: <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com> <200808062238.15634.juanma_bellon@yahoo.es> NNTP-Posting-Host: panix5.panix.com Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Trace: reader1.panix.com 1218204439 22850 166.84.1.5 (8 Aug 2008 14:07:19 GMT) X-Complaints-To: abuse@panix.com NNTP-Posting-Date: Fri, 8 Aug 2008 14:07:19 +0000 (UTC) User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.2 (windows-nt) Cancel-Lock: sha1:Ckkp5oJPIMuAVgEHGnS/9MkZsEs= Xref: news.stanford.edu gnu.emacs.help:160963 To: help-gnu-emacs@gnu.org Subject: Re: basic question: going back to dired X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 710 Lines: 27 I seem to remember from my early school days it was a campaign slogan for someone nick-named Kinderhook that went something like Old Kinderhook is OK - Chris "Juanma Barranquero" writes: > On Wed, Aug 6, 2008 at 22:38, Juanma wrote: > >> For all I know, it comes from "0 Knock-outs" (from USA civil war times, >> IIRC), i.e., all went really well. > > See http://en.wikipedia.org/wiki/Okay#Etymology > > "0 knock-outs" is among the "Improbable or refuted etymologies". > > Juanma > > -- (. .) =ooO=(_)=Ooo===================================== Chris McMahan | first_initiallastname@one.dot.net ================================================= mu-1.6.10/lib/testdir2/000077500000000000000000000000001414367003600145325ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/000077500000000000000000000000001414367003600152555ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/cur/000077500000000000000000000000001414367003600160465ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/cur/arto.eml000066400000000000000000000561701414367003600175230ustar00rootroot00000000000000Return-Path: <> X-Original-To: f00f@localhost Delivered-To: f00f@localhost Received: from puppet (puppet [127.0.0.1]) by f00fmachines.nl (Postfix) with ESMTP id A534D39C7F1 for ; Mon, 23 May 2011 20:30:05 +0300 (EEST) Delivered-To: diggler@gmail.com Received: from ew-in-f109.1e100.net [174.15.27.101] by puppet with POP3 (fetchmail-6.3.18) for (single-drop); Mon, 23 May 2011 20:30:05 +0300 (EEST) Received: by 10.142.147.13 with SMTP id u13cs87252wfd; Mon, 23 May 2011 01:54:10 -0700 (PDT) Received: by 10.204.7.74 with SMTP id c10mr1984197bkc.104.1306140849326; Mon, 23 May 2011 01:54:09 -0700 (PDT) Received: from MTX4.mbn1.net (mtx4.mbn1.net [213.188.129.252]) by mx.google.com with ESMTP id e6si18117551bkw.39.2011.05.23.01.54.07; Mon, 23 May 2011 01:54:08 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of MTX4.mbn1.net designates 213.188.129.252 as permitted sender) client-ip=213.188.129.252; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of MTX4.mbn1.net designates 213.188.129.252 as permitted sender) smtp.mail= Resent-From: X-Default-Received-SPF: pass (skip=forwardok (res=PASS)) x-ip-name=192.168.10.123; From: ArtOlive To: "f00f@f00fmachines.nl" Reply-To: Date: Mon, 23 May 2011 10:53:45 +0200 Subject: NIEUWSBRIEF ART OLIVE | juni exposite in galerie ArtOlive MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d" X-Mailer: aspNetEmail ver 3.5.2.10 X-Sender: 87.93.13.24 X-RemoteIP: 87.93.13.24 Originating-IP: 87.93.13.24 X-MAILINGLIJST-ID: <10374608.109906.11909.2011523105345.MSGID@mailinglijst.nl> Message-ID: <10374608.109906.11909.2011523105345.MSGID@mailinglijst.nl> X-Authenticated-User: guest@mailinglijst.eu X-STA-Metric: 0 (engine=030) X-STA-NotSpam: geinformeerd vormen spec:usig:3.8.2 twee samen X-STA-Spam: 2011 &bull • e-mailing subject:juni X-BTI-AntiSpam: score:0,sta:0/030,dnsbl:passed,sw:passed,bsn:10/passed,spf:off,bsctr:passed/1,dk:off,pbmf:none,ipr:0/3,trusted:no,ts:no,bs:no,ubl:passed X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply Resent-Message-Id: <19740414233016.EB6835A132F5FCF4@MTX4.mbn1.net> Resent-Date: Mon, 23 May 2011 10:54:07 +0200 (CEST) --_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d Content-Type: text/plain; charset="iso-8859-15" Content-Transfer-Encoding: quoted-printable ART-O-NEWS; juni 2011 Westergasfabriekterrein Polonceaukade 17 10= 14 DA Amsterdam tel: 020-6758504 info@artolive.nl www.artolive.nlJuni= expositie bij ArtOlive: Peter van den Akker en Marinel Vieleers Zondag 5 juni Elke maand vindt er in de galerie van ArtOlive een expositie plaats. We = lichten enkele kunstenaars uit (die je misschien al kent van onze website= ), waarbij we een spannende mix van materiaal en techniek presenteren. Ti= jdens de expositie staan we klaar om elke vraag over ons kunstaanbod te b= eantwoorden.=20 De exposities zijn te bezoeken van maandag t/m vrijdag, tussen 10:00 en 1= 7:00 uur. De opening is altijd op de eerste zondag van de maand. Dit valt= samen met de Sunday Market die elke maand op het Cultuurpark Westergasfa= briek georganiseerd wordt. De Sunday Market is gratis te bezoeken en staa= t vol met kunst, design, mode en heerlijke hapjes, en er hangt altijd een= vrolijke sfeer. Een ideaal moment dus om in te haken en deze maand twee = kunstenaars te presenteren: Peter van den Akker en Marinel Vieleers. We verwelkomen je graag op zondag 5 juni 2011, van 12:00 t/m 17:00 uur op= de Polonceaukade 17 van het Cultuurpark Westergasfabriek in Amsterdam!=20= bekijk meer werk op www.artolive.nl... Peter van den Akker "In mijn beelden en schilderijen staat het mensbeeld centraal; niet als i= ndividu maar als universele gestalte, waarbij ik op transparante wijze ti= jdsbeelden en gelaagdheid in het menselijke handelen naar voren breng. Ve= rhoudingen tussen mensen, verschuivingen in wereldculturen en verandering= en in techniek, architectuur, natuur en mensbeeld vormen mijn inspiratieb= ronnen. Het zijn allemaal beelden en sferen die naast en met elkaar besta= an. Mijn werkwijze omvat vele technieken in verschillende materialen: sch= ilderijen, gemengde technieken op papier/collages, zeefdrukken, beelden i= n cortenstaal, keramische objecten." Peter van den Akker exposeert regelmatig in binnen- en buitenland bij gal= erie=EBn en musea en is in verschillende kunstinstellingen en bedrijfscol= lecties opgenomen. lees meer over Peter... Marinel Vieleers Marinel Vieleers probeert het menselijke in de bouwwerken - en ook vaak i= ets van het karakter van de bouwer of bewoner - te laten zien. Het zijn m= aar subtiele details die dat alles weergeven. De 'tand des tijds' of invloed van mensen op de gebouwen spelen vaak mee = in het werk. Koper, cement, lood en andere materialen worden in haar nieu= we werk gebruikt. Op deze manier kan ze gemakkelijker improviseren en nog= directer op haar gevoel afgaan. Marinel is gefascineerd door de schoonheid van het imperfecte. De gelaagd= heid van ouderdom, het verval. De imperfectie die ontstaat door toevallig= e omstandigheden maakt een huis, een muur, een schutting, hout of steen b= oeiend. Het is doorleefd en het toont een stukje van zijn geschiedenis. lees meer over Marinel... =20 ZONDAG 5 MEI - Juni expositie in de galerie van ArtOlive met Marinel Viel= eers en Peter van den Akker Opening op zondag 5 mei, tijdens de Sunday Market op het Cultuurpark West= ergasfabriek in Amsterdam. Je bent van harte uitgenodigd om tussen 12:00 = en 17:00 uur langs te komen! Daarna is de expositie te zien op werkdagen (ma - vrij) tussen 10:00 en 1= 7:00. De expositie duurt tot 24 juni 2011. wil je niet langer door artolive ge=EFnformeerd worden? Klik dan hier om= je af te melden.=20 kreeg je dit mailtje doorgestuurd en wil je voortaan zelf ook graag de n= ieuwsbrief ontvangen?=20 klik dan hier om je aan te melden.=20 Deze e-mailing is verzorgd met MailingLijst --_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d Content-Type: text/html; charset="iso-8859-15" Content-Transfer-Encoding: quoted-printable Artolive
3D"artolive"
ART-O-NEWS • juni 2011
Westergasfabriekterrein   Polonceau= kade 17 1014 DA Amsterdam   tel: 020-6758504  info@artolive.nl<= /a>  www.artolive.nl
Juni= expositie bij ArtOlive: Peter van den Akker en Marinel Vieleers

Zondag 5 juni
Elke maand vindt er in de galerie van Art= Olive een expositie plaats. We lichten enkele kunstenaars uit (die je mis= schien al kent van onze website), waarbij we een spannende mix van materi= aal en techniek presenteren. Tijdens de expositie staan we klaar om elke = vraag over ons kunstaanbod te beantwoorden.

De exposities zijn te bezoeken van maa= ndag t/m vrijdag, tussen 10:00 en 17:00 uur. De opening is altijd op de e= erste zondag van de maand. Dit valt samen met de Sunday Market die elke m= aand op het Cultuurpark Westergasfabriek georganiseerd wordt. De Sunday M= arket is gratis te bezoeken en staat vol met kunst, design, mode en heerl= ijke hapjes, en er hangt altijd een vrolijke sfeer. Een ideaal moment dus= om in te haken en deze maand twee kunstenaars te presenteren: Peter van = den Akker en Marinel Vieleers.

We verwelkomen je graag op zondag 5 ju= ni 2011, van 12:00 t/m 17:00 uur op de Polonceaukade 17 van het Cultuurpa= rk Westergasfabriek in Amsterdam!


3D""  = bekijk= meer werk op www.artolive.nl...   

=
3D""
<= a target=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D= 154043&a=3D10374608&t=3DH"> <= /td> Peter van den Akker

"In mijn beelden en schild= erijen staat het mensbeeld centraal; niet als individu maar als universel= e gestalte, waarbij ik op transparante wijze tijdsbeelden en gelaagdheid = in het menselijke handelen naar voren breng. Verhoudingen tussen mensen, = verschuivingen in wereldculturen en veranderingen in techniek, architectu= ur, natuur en mensbeeld vormen mijn inspiratiebronnen. Het zijn allemaal = beelden en sferen die naast en met elkaar bestaan. Mijn werkwijze omvat v= ele technieken in verschillende materialen: schilderijen, gemengde techni= eken op papier/collages, zeefdrukken, beelden in cortenstaal, keramische = objecten.”

Peter van den Akker expose= ert regelmatig in binnen- en buitenland bij galerieën en musea en is= in verschillende kunstinstellingen en bedrijfscollecties opgenomen.


3D""  = lees meer over Peter...   

= Marinel Vieleers

Marinel Vieleers probeert = het menselijke in de bouwwerken - en ook vaak iets van het karakter van d= e bouwer of bewoner - te laten zien. Het zijn maar subtiele details die d= at alles weergeven.

De ‘tand des tijds&r= squo; of invloed van mensen op de gebouwen spelen vaak mee in het werk. K= oper, cement, lood en andere materialen worden in haar nieuwe werk gebrui= kt. Op deze manier kan ze gemakkelijker improviseren en nog directer op h= aar gevoel afgaan.

Marinel is gefascineerd do= or de schoonheid van het imperfecte. De gelaagdheid van ouderdom, het ver= val. De imperfectie die ontstaat door toevallige omstandigheden maakt een= huis, een muur, een schutting, hout of steen boeiend. Het is doorleefd e= n het toont een stukje van zijn geschiedenis.


3D""  = lees meer ov= er Marinel...   

3D""
3D""

ZONDAG 5 MEI - Juni expositie in de galerie van ArtOlive met = Marinel Vieleers en Peter van den Akker

Opening op zondag 5 mei, = tijdens de Sunday Market op het Cultuurpark Westergasfabriek in Amsterdam= . Je bent van harte uitgenodigd om tussen 12:00 en 17:00 uur langs te kom= en!

Daarna is de expositie te zien op werkdagen (ma - vrij= ) tussen 10:00 en 17:00. De expositie duurt tot 24 juni 2011.
3D"Kunst wil je niet langer door artolive geïnformeerd worden? Klik= dan hier om je af te melden.
kreeg je dit mailtje doorgestuurd en wil je voortaan = zelf ook graag de nieuwsbrief ontvangen?
klik dan hier om= je aan te melden.

<= HR SIZE=3D1 STYLE=3D"COLOR:#d3d3d3" SIZE=3D1>Deze e-mailing is verzorgd m= et MailingLijst

--_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d-- mu-1.6.10/lib/testdir2/Foo/cur/fraiche.eml000066400000000000000000000005201414367003600201430ustar00rootroot00000000000000From: Sender To: Recip Subject: search accents Date: 2012-12-08 00:48 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit line 1: Глокая куздра штеко будланула бокра и курдячит бокрёнка line 2: crème fraîche mu-1.6.10/lib/testdir2/Foo/cur/mail5000066400000000000000000001323441414367003600170070ustar00rootroot00000000000000From: Sitting Bull To: George Custer Subject: pics for you Mail-Reply-To: djcb@djcbsoftware.nl User-Agent: Hunkpapa/2.15.9 (Almost Unreal) Message-Id: CAHSaMxZ9rk5ASjqsbXizjTQuSk583=M6TORHz=bfogtmbGGs5A@mail.gmail.com Fcc: .sent MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: multipart/mixed; boundary="Multipart_Sun_Oct_17_10:37:40_2010-1" --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: text/plain; charset=US-ASCII Dude! Here are some pics! --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: image/jpeg Content-Disposition: inline; filename="sittingbull.jpg" Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD/4QvoRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB AAAASAAAABsBCQABAAAASAAAACgBCQABAAAAAgAAADEBAgAOAAAAbgAAADIBAgAUAAAAfAAAABMC CQABAAAAAQAAAGmHBAABAAAAkAAAAN4AAABndGh1bWIgMi4xMS4zADIwMTA6MTA6MTcgMTA6MzM6 MzcABgAAkAcABAAAADAyMjEBkQcABAAAAAECAwAAoAcABAAAADAxMDABoAkAAQAAAAEAAAACoAkA AQAAAMgAAAADoAkAAQAAAGsBAAAAAAAABgADAQMAAQAAAAYAAAAaAQkAAQAAAEgAAAAbAQkAAQAA AEgAAAAoAQkAAQAAAAIAAAABAgQAAQAAACwBAAACAgQAAQAAALMKAAAAAAAA/9j/4AAQSkZJRgAB AQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwc KDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIy MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACAAEcDASIAAhEBAxEB/8QAHwAA AQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIh MUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpT VFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5 usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAA AAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEI FEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm Z2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK 0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDq77xdrX/CQ6laRXjRxQTF ECovA/EUg8Sa6W/5CUuP9xP8K5yWQnxjrw9Lwj9BWjkgZHFAG6mu6yV51OXP+4n/AMTUq61rBB/4 mU2f9xP/AImsJJTuAJFW0YDnfmgCTUPFGqWFq882p3G1eyqmT/47VfRfGGpawkgGp3CyIeg2cj1+ 7XK+O7zybCGMNjzHyR6gD/69ZvgG8zqU67vvRZH4EUAesJe6m/XVLv8ANf8A4mpf7Qvl/wCX+6b6 uP8ACs+ObKdaeh3Hg9aANTw/4gurjxTLpU7tIv2cTKzHpgkH+n5UVheHGI+KWzJwdNP/AKFRQBzD 7f8AhMfEDEHH24j/AMdWrs0oCkDrVKJs+NfEsZ+79u/9kWrd5GqKTmgCstwwkyT0p5uzu61mOzbj zSFn3DmgDB8ePLPe2MEQZyykhRzk9/5Va8D6Vd2Mz3d3CYxJHiPd16+la0hhMybkUvxhj1HWr5uM uB0wMYoA3YJARjvV+DBPasC2lYsOuK3LVunWgCLQRj4sIPXTGP8A4/RSaF/yV2P30tv/AEOigDmY QD408UE9ftw/9AFXpv3iFT9Kgs4t3jXxV6C+H/oAq5cxkMcCgDFltXVyMVVv7iGwtzNcNsQfiT+F a8jbAxdgFAzmuTZZfEV81vG+xTyX/uIPT3P9aAIBr9vdNHcQI/lxk5DDBrfsLuK+jE0MqupPOOx9 KzNY8L6fbaaYrdGXb3BOcnuT+FcpodzN4c8RRRyylrW4IDE9MdM/UUAes2wbOAK27PhRms6CJlwc VrWowRkUAV9CP/F3YffSm/8AQ6Kfo+P+FuWp9dLf/wBDooAxrH/kd/Ff/X8P/Ra1evUOcgVW01Qf G/izIz/py/8Aota2LqPK4xQBxniWc2mi3MxBGFA/Mgf1rmtEF/Z6HNqMNuzvPnY+7G1V6Hoe+T0r qfGmnT3Xhm8WNWJVQ+B/skH+lUPBt3d3PhuzXyBM6xBY0YfKDnALewxmgDE1BfEDaPaXNzMRJPIQ +TjCgDHb69u9ZGt2Us2lrdNDtMLAgq27Kng84Fd74qnaMwWB8qWRTnzUcfePGSOx4ziuf1kzT6S9 tuRHlVUG5sDJOMA+lAHofh5/tvh3T7k4ZnhXcfcDB/UVuRQEdqzvDelPo/hywsJGDSRRjeR0yeTj 2ya3I8/3aAMXSU2/FmzJ/wCgbJ/6FRUunf8AJV7H/sGy/wDoQooAyNJXf448XYPS+X/0Wtb8ynyj 0rm/DIll8W+KDKQ0pvF3FehPlr0rvINMzbfN8rsc7upH0oA5ie3mktZSI1ICn5W43e1ec6ZrDwax facIj9liUNtUcgE8j0IzXrHiqS20rQJbiadoyBsWQjc2T2HvXnvhbREuzeXTbvMlfILcsF6D6jFA GJr+pWE1ymFkwFzhlwo+i1xevazLd3Fva2+UiQhh7kdPyr0jVfA8t0BeXNybe35UK2EJAJwST/QG uS1Pw7HYalbKHUIxYxyDd8wHUnNAHsnhXVBrGhWkrBlmEYVww6sAATXQInA5rn/AOZtIa3mQHZI+ xwfvAnJ6d8n9a6yazEKhlzgUAc1YAr8WbH302X/0IUU6xBPxYsSe2my/+hUUAV/Bdj5fi3xWJJDJ JHeopY8bj5a5OK9AUArwARXEeFjjxh4xbub5f/RYrsIZgJhGTjcuQMGgDnfHiwnw1KJoVkUuB8yg hfeuZ+HemTLpjx3OCZNzKUbPy54/Sut8Z263OlJE1wYgzkkjvgH86yfBb+XYWuIGiEithWzn9aAN loTcO0ctuGjV9oMg5JGCSOOnp9K8/wDH1qH1iERrukRAqqB3Jzj9BXpsk6F+oyCuRjJ54rhNcg+3 Ge5XiUSL5ZGc87sdPagDQ+HlvJHoAdo9h85mUY7dK7WSRCoB6HiuV8IiW10JYs7yszDJ7fN/k1tG Rpb4xj7qpnj3Iwfx5oAwLMgfF+1UHI/suTH/AH3RTLJNnxltx2Olvj/vuigB3hgf8Vp4vH/T8v8A 6AK6aRWFk2CA2CPSua8M4/4T3xcp/wCftD/45XR6q32e1JjUySCRdqA4J3HH9aAKHiJTceH4mliK r5e5lDfMpx2Iqp4eQR6Zp75Y4jX7xyfTn8q29djjbS/LMqxYGFdugNZWlskOh2pKgYj2AqO4OB/M 0AW7+NLQ3Fwi/O6hsk5yRwOO3WuS1qGJtNuvN3iNJkX5e+EIxn8f1re1e4ubq8jSOMiBArZJ/wBY xOcfQcZ+tVNTsYh4dnjmG9PMJIP8XYUAQ20z2Hg6OeJGTYQzd+N3Le+RzXQ6TGwtjLLkuxAy3XAH f8Saw9Mlt7vwsI4yZI9m07xtyM/y5rqodqxIFAIx1oA5iDj4w2ZHfS3/APQjRSw8/GGzx20x/wD0 I0UAee+I/GV/4S+IXiAWlvFKJ7gM28njC+1Ubn4v6xclC1hbAq6vwzdjn+lXviB4X1O88b6lPBYX EkUkgZXWJiDwPQVzH/CH61zjSbwj1EDf4UAbV78YdZvYPJbT7UA+7HP61HbfFXXLW3SFdOtSqZ67 v8fesg+Ddbzn+yL3P/XBv8Kcvg3Xc5Oj3x/7YP8A4UAaY+KuvIQP7PtM5JXKt/jUF78Udcu7F7WS ytEVv4grZHPB61VPg/Ws/wDIGvs9v3T/AOFMPg7XcHOk32P+uD/4UAWLb4l6vb2zQJZ2m1gP4WGC FAz19q17f4va0sSobS04GB8rf41z3/CIayOuk3g/7d2/wqRfCWr8f8S27/78P/hQB33w78Q3fib4 jR3l3HHG6WTxgR5xjOe/1oq78JvCmo6dq8+qXUBhhETQqJAVYsSDkA8496KAP//ZAP/bAEMABQME BAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4k HB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh4eHh4eHh4eHh4eHv/AABEIAWsAyAMBIgACEQEDEQH/xAAdAAABBAMBAQAAAAAAAAAAAAAH AwQFBgACCAEJ/8QAVhAAAQMCBAIHAwYHCwoGAgMAAQIDEQAEBQYSITFBBwgTIlFhcRSBkRUjMqGx 0RYXQoKSssElJjNDRFJicpOi8CQ0NmNzo7PC0uEnNVNUVYMJRWR08f/EABQBAQAAAAAAAAAAAAAA AAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDqjNudMEyw6lvE3HEqUnUS kDSmeEkkCTB241Aq6X8mgSL5J/PT99DPrX3S232GwohPbp5/6v8A70PMsrSuwYKgkgtjiONB0Ovp lykFQLhJ8+0Fefjoyh/6/wDfFBctsJWkpt0wRzSK9ft7cwVMNmf6IoDOOmjJ5/lB/SFejpmyeT/n I/TFBdFqwACGGR6IFOWbRhZ3Ya349wUBgT0y5QP8qT+n/wBq3T0x5OPG8A/OFCy3tbZCRpt2j+YK cotLMjvWjM/1BQEs9MWTR/LUn0VWfjiyb/72hki1sw5q9kaHL6ApZDFspQHsze3DuCgI6umLJqUg +2Eg+AJ/ZSR6aMmj+UOfon7qozjTACUhlsCP5opo402AQWkeumgIK+mrKAA0uuK8e6R+ykj03ZU5 JePx+6h6gaTGlPHmkUwzDjlrhDKVvtIWXVaUIjj4+6gKKOm7KZUQrtU+YCj/AMteHpuyvvpbeV7l f9NAHEc3Ls8ed9iYZLTZIAI4+vpRAy1i7WKYS3dhtIDghQHIjiKC9npwy3O1rcH3K/6a1V034Ef4 OyfPqlf/AE1WG1KKxAAHhFO0LgTCfhQTf47cGPCwuJ/qr/6a9R004as7YXcn0bX/ANNRLbqhsAOF OWnVgEnh4UEm30wWbhhvBL5e3Jpz/ppwOlMLSC3lzElT4suD/kqHN642YT9tIu3T7h2UYHnQWW36 S9YleXcQA8Qhf7U05b6SMPXiFpYGwfZfu19myl9XZ6leEkR9dVBy7XEFXLcUN+mPEn7VnCrplZQ6 zclaFeCgAQfqoOqbO4L6VBxvsnURrRqConcbisqo9FOYE5mw1zGENdkLllpZQSCUkApO/wCbWUAY 63i9N0x3v5SkHy+aqj5QBXhFqRzbFWrrjrWm5a1ER7Unh/saqOSXP3Bst/4pNBbWhLY1nvCt1NyZ mRWtuoKESCOdKjSOB99BiEjYHYU4ZQpKpTwpArTxpVt8g7RQPmlQkpP2UoFE8VkU0buEkK4A+NbN rCzHDagfQCmN69TtsPjTRLoBA1b0oHFaZPwoH6IPODFJPwOG9asklJJ228a8egp9aBspcHhvQf6T b55eZHWVu6m2kgISDsnaT76Kt46hlpx1WyUJKj7q59xzEFXV9dXC1krccK9/M0CybtYUTrMnj50R uiPGLl69OHBTaWG2isgjdRkffQgFyoqO441eOiW4WM1W8D6aVBXpFAfGCVGQmnDZO07jmaYW74G0 yKfNuJUBB50D0ISAIAiNyK3UUkQIpDtgUhAAIApNToAKQACedAotaZgCfOvQrUCAIpsVAma31kEE bbUG60Dwn1oW9O6S3h2HqnYvK291E1SlEkzvQr6f1qRg1gVHc3B4+lAaeq2rXkFhcz3NPwcXWU06 pzqnchtQ4CkBQjT/AKxVZQDXrpOEXLJO0XYj+xFVDI6lHAbEeLSfsq1dd2UPWyxMG7HHhPZCqtkX /wAhw9Svo9gj7KC4Wx2MCKdt7ncTTaxbGmBz3p4hISNzHhQJrQSYnjXiUkQOYpZzupJJpFK5g7n3 0ChQSkbxSluSFDfam5WACokj1NetPAqA29KCXb0HdQ9TSwQlSduVR6XBpJnetkXWgCTQSTQCRpMk Uk8djzpsm9ExNaOP6pOqBQRWcblNtly/eI3SwoTPiIrnbEXAl7j4UaulrEHLbJ9z2ZEuKS2fQneg Fevy62ASQYoHCHkqXsqrl0U3aWs52mpelLgU3v5jah20/C9zO/hVmyc8Bj+HjfvXCB9dB09bq0mD zqQZUkAVDtriOdO7ZzSYO8UEoHBw3r0KB3mm6XUxERWdpO0UDhJBVvSulKh9kU2bUdzvvSzKu9tI mgXShOjfaTQk6xZ/cOw4GLk7/m0XlqlGxEcCDzoP9Y1KhgNhI/lR/VNAWuqGpJyG3pBHdVPn84qs rTqgqJyKyCI+bXHp2qqygH/XoQEW9i5M6r0CJ/1VVnJKP3Aw8QP4BvYf1RVh69K1lm0BMgXyQPL5 moPJ50YDY+TCP1RQW+yRoaC44HhSygNXe99NbW7lCUKA48K3cdgbk8KDa5IAMcKj3XiklIMTSrj0 zCppi8STwNAo5cEo4RXtq53pn1pke8NIUR5UoydWoA+lBMdrCdjSLr5G8yfCmJdWE6ZpNx2NiZoH 4uZjlThKitJ35TUOl4DntUlZOgoiaAe9Nl+tnDbS1QAULdlzxHgftoMLdm4VzAmKKvTsvsrywUqS y8y42seBBBB9xoSWTa7hVwocGmyrh7qDZpUKFXzoptG77NdoFkwiXD7uFUvCMOu7+5SxbNLccO8A TRd6Nsq3eB5mbVezq9l7ThsCTEUBcQvhtuKdWy0qVJ40wS4NqcNOTvA+NBKpMokRSiFKnx8opo07 KAAYNOGwrko0DlB/JO005Y0gTNNW9pkzTm3SDJJoHIQhIlKZ50IesgFDLdgYgC74/mmjC03KaEXW WBTlmykH/Ox+qqgJnVAV+8S3hMbOA78fnVVlZ1Oio5BbnkHI/tVVlAOOvSIbtlSN8QSD/Y0yyayh 3Ldg4BBNs37+6KedfERaWqo2+UE/8E1FdGdwF5Yw0kgzatmPzRQWdhnRClCTWPAEHeKkENodbSpO xpBy3IJJjegjFwjh6U1eWSoxw50+uGQHPE86j7qEqiY9KBstYSe6B61gfLapB5QaaPOQs77V4twR wNA8TcFyPKsdd25UyS6RBAivXXJSDQOm1jyinlq/CoSY22qDDh1AVI22mUqmNqCpdI+GXOZb9Ng1 I9jZL+qNiVSAPiKiMhZQFixcHGGgkvJPaJJ4JBolsKaTdO9zdTcKV4CdqalLSrtevcQEkeVA7ytl /CcOm7sLVKFugb+VSt06k3WgfSSO9HKss30NsagAkAd0GminCXSr8onegfNKSDO/nJp22tCSDUYF xEJpVDkkwDPrQTTCoGxBmnjDp4KG9QVu4rVxjepS3WY1KVPpQSbSiT508tBvBImo9hJICt96f2yS TwIigkWht6UHes8ojK9mJE+2Dj/UVRhaEpn7aDnWhH71LP8A/uj9VVATupmZyCiePzn/ABVVledT OfxfszG6XYj/AGyqygHfX3A+TrPmTiKZ3/1JqvdFX+h+EqPO2TU91+NrK1Mf/sER/YmoLopSpWR8 II/9sPtNARbNSdAKdzHCtnY0Eq4zypnbFaEAqImnRUFt6p93OgjbyEEn6qhrxQUo+FTGIp7m23rU HeGKBi6E6orRcRtWyiSoxWp4ERQeoAI3NeLSnnW7Q2341s5skcOMUCSEpMk08to0gA/Gmu4HCvW1 qoPMQWtOJIDR/I3HiKyycacfU4o86Y4qpxBcutiUphIpLLy1LGtSN1RsTwoLS0SQpx3uoG4HlSHa gq1JMSa9ullVulJAA5+dN0wYiKCQbcJ+lNLoVzmmjKoEmndudSoVAHpQOrbVq3mIqWtYME7Co9oE xHAeFPWVqCQY250EsyuBsSPWn7C1CI4+tRdq4F7gjzFSduoCBHoKCRYUYAG1CHrQJjKFqqB/nqf1 FUWbck96NO3jNCXrPmMm23Ha9R+oqgJPUvM9H6PLtR/vTWVt1MkoT0eMaQAVpdUYPPtSPurKAb9f ZQNkwnf/AMwR/wAA1GdD7QOQMHUeduPtNSfX3H7n2y4mcSSPT5g1G9D69XR9gxCeFvHHzNBczGkR tWqXIBjlW4HdO0U1XqQsGOe9ApcAKTHEGoe7tyJqWWSBv61roQ6nvCgrD7cEyPqpNCYn0qavrMhW w5U1NsYAAoGLAEmaUcAinItSZ8R4VjjJ0Qo+tBHK+NeoI0kVXs3ZptMIS42wk3FwB9EGAPU0KMWz zma6uCtm67BoHZLSQEx6nc0BdzRfBq0UhKu8BwFI4BchttK1EyrlPCqjhmJOXlrbquHUPOrbBJJ5 8+FaXC32NTguLi2MmCYU2fLjPxoCel0vDUFFQPCnSAQaH+Vc625fRhuKpbtX+CHAruL+6iC0QtKV JUFA8CKByyFE8KeI2CTzpG2E8h8acpQSRCRH20DxlRMGPqp2zOkJB2prZtlRIg/Gn7bSUwNW9A+t BAO4B8Kk7UeJmo20SUkmeHlUnbK2G1A+aiZjfxoS9Z8k5Ltwrleo4f1VUV0d6IG/OhR1mkg5HZkz /lqP1VUBI6l5H4vmRJOzvHl86aykOpOf3iEb/Sdj+0rKCgdfUj5NtxzGJI9/zBqP6Gkj8XuDDn2H /Malev6lHybakQD8oNg7f6hVMehVIV0dYMqJhj/mNBcm29Q24U3u2gDtUohvS2dtzTO6H+IoI93d PmBTcq0xBmnDoM7b7U1KZUQCBvQLKcQtuFRSCwAmBFIvBxJ2MGaVAUUhQIngaBNUg7kVV81YoUhV rbuweCiOPpUxmC9Rh9it5Su+dkDmTQ9Nz2q1PrO5JCd5JNAxxyztBaOOXyktsNp1rUefqaH7dpc5 hvlDCLJabVJgOLG5H2D0q7YhhV5nDFrfBLYrTbA9reOAwAkHYep/YKL+X8qWGHYe1aWjCUBtMDQn c+cmgDtvlq7scEQ2grTdtnVqKdhVXx3EscYTpumoIkdogbKHmDXRWJ2NotlxpbjRUOYVJ9N6G+bc Mt3EdghaFx9IAUAXfxB25bIe0haOHd+qiR0QZ1dRdIwXFXdbayEsOqP0T/NNU/MuXH7Qret0KKUn dIE/CoCxUW3UnfcyCOINB2BaiRI8Nop+0gxP21R+iPHF45gCUPq1XVsezd347bGr8wlQERQLWp0r Gw41IoAKp2k0xQkg/Qnwp9bSYJSZFA7YSfjUhbN6uMAimTBIUCakbZW44UD1hkJAoUdZ9pKMgNqH H25H6qqLTR7oPOhZ1ngD0dSOIvGz9SqC4dSZJGQyeRU7H6YrK36lBH4vk7flvQfzxWUFE6/oBwm1 Vz+UWh7+wVTboPSR0cYKkCf8nk/pGpHr/o/e9aLjf5SbH+4XTPoOUk9G2CyCT7PwA/pGgvaR3eAN M71E8qkkJBRsDvTa7QNBFBCuoHGKaqbGuZEU/udIERvTbUmSNO9A0ukjSTG9Nm1BC5PDnUg8pIHC ozEn0tMLcCeCSfqoB9nrElXOLi2bJ0IOkeE86rVzcBDKnAoBLRgCNyfL/HjXmO4koPaoPaOEmf5s 86aYHbPY7mmywdk6mmyHbjwKZkz60Bl6NcGRhWX0XDrWm6vPnnIHeAPAe4VZx2iEFSVrKYgpI0/X UJeYuzhrae0Qt9SANmwdIH7ajh0j4WrW204dYEEE8NqCXW6+7dP2yUMNnTKdZJ24cao2ZGCVO9ol tzcmUnh/j7qXTm3tbC8xdcKQnsWRJ7pIUoq+qKqeMZ3YUp9xxxsJcUCQngOJ2+NAmptDxKHEqSCN vGhnnCxOHY8sISEpe76YECecfb76J2GXDV8wLj2a4DDhlDikEA+h8Kr/AEk4St7Chdtd4sHUI4xz Hw391At0JY38mZntmnHdLV38y4nlP5J/x4102ywVAEbiuLMDfcYeauGjpUlQUDHMGu1MpXPyll+x vkbh5hKifOKBZtuCRoiKWbQoGYpx2KkmYO/lSiGjI2EeYoMaQSSDTplJA2rxtJG1OmUgD/tQKM6h 50MOsv3ujlwgzF02ftopJMAAxt5ULeslKujq5I/9w2frNBb+pM6Dkfsuep8n9NH31lJ9SUfvOBgb F/cf10VlBVuv7q/B61g7fKTW3/0Lpn0IjT0d4IN/82HPzNO+v8Vfg/apkR8osn/crpr0HEno5wUE cLeJnzNARGSCmCD8ab3IBkAcqctjbhwrV1oqTMAUEBdpABnemRAHvqYu7WASQDNMOw7xAKfHjQMn YjvDaq/m64RaYFevcNLSj9VWp+3IEyKo3S8U2+RcSXIkthI9SQKAKXV4u5ue21BLY3JngImrb0S2 TrmCXuYVuKb9pfUkr0nZpA4Ty3maHDSXsQLOHW/8PdOJZbHLvH7AAfjXUOW8rWWGZbssMTZtutMt BGpbQMniVRE7mTQDzMfSJctYXcpwbLq30MoSFP3KinXO3cQBJ9SRQpu/lvE8WS+jCQXXVAyylaUu T68TXQubsNNpZK1YwLVsDV3mSSPSFCoPJeTH3rv5dvO2UiYtu1RpKxH0o4xvzNBHXuX0WnRpcGzQ supWlSyqZMjccfGN6FGG2+Iv4u5cW+ALxNLSoaZUfmwoc1Abq9Jrq3GsPtrbI12gcFNHaI3oJW9q 7Z9qbEpSpapg+NA2ezHnJ4rZxLCrZLVshHYWzdutBXPECCoAjzqRtlKxNpxp22U0tSTLS4kCPXen +XLW5xR2EN3iXE/SCmwUj3wD9tTSsHLPdUtK1cYiaDnq1bXa4jcWLuy23FAe7b7q616v1yb/ACFa JMyySg78INc19IViLHNAuG0hPa94jzTxo79Vq/DtjiWHDcJWHk+iuXxFAXrls7xO1etJkCCaevNy NXOvGGxG6QDQaIbM8AactohMRWyWyD605aSInwoGwTIgCD40MOsa3/4cXh8Hm/1qLSkhMqNDHrHN T0Y38Dg43+sKCd6k4H4EHxDj36yKyvOpTIyZpJPF79dFZQVLr/T+Dtt4DEmf+Cuk+g1uejfAzEk2 wn4ml+v8EjK9sYknE2Y8vmXKT6BQ4OjTAyT/ACfh+caAiBoDYCaxxOlAGnjSralQJg7V4+sdmBtQ Rd2gaZM7nwqLdShCzE1NXS2wmJmot5CSdSQregY3KthE+kUNOnjtF5Hf7PVHatlfoFCim+hOkwDI 86p3SRhyr/Kt/apaKipklIHGRuKDmjKV81ZZuwi6cWSlNzpE8BtE/EiuqLbMPtFi2WVDVA2rjXEy 5bX1skApW2AQPA6ia6awJQNvavpSZWgKI5EEUBLwmwbxFSHMQYZd098FSQQD761GMWuK45eYPhYT cKsUJNw4ncIKphI84BJqs5hzKcPy4+q0lVypISNIkk8gPEkmKnsuZCvMHyEi3sMQFnmC6V7Re3Rb 16nFcUnyTwHp50Epmxm3ssnrYeUe0uEHSIoIYg0cKS9cOW5Uw1ClqHEJ5n1q59JWKYhh7tthN0+u 7et2NetKY1pHExyoft4lfYnir1u6sPWL+mUqT9HxE+HxoLhaXS2sND9k5rbdROpJ2INa212OzK1L nn3qhMtMPYU67hThKmEElon+YeHw4e6nFxPblQBKPyY/x5UAt6VLwPZoba3DYbn3knf6qLXVGbcV e4u8QezS02kEjmSTFDHNuXcRxnNDKrG3U4SnRJMSZ/711B0I5OcyhktuxvA37a4suPqRuJ5CecCg ujoEQfCtmOI2rZ0d2vEpEbcKBy2AVcvfS6UgbDnTNJPnSrRK5UmRQKqC9ESDvyoY9Y4q/FlfiQfn G/1hRPWogaYihh1joPRhfEj+Mb/WFBMdSon8DgDyL8emtFZWvUnCRlFYSRIL0+utP7IrKCr9f3/R q2PH90mR/uV170EKKejLAxAP+TbfpGvOv+CMsWpPPEmY/sV1r0Bk/izwPcx7Od/zjQEhlRUNk+6l XEymY5UmyDp4x50uggpHemgj3mZSSRA8aadmEqAVFS9yfmzHhUU8UzvyNB4tpBG4NMLy2YeZWFN8 QeNPA4QdMkT9VIvOHSUhQ3oORulXJl7a5tvbq2tyq3U4FIQBvBAO310bMBsQcKw+4ZIIS2kKSR5V KZ9sPaWm1WqW37i2lSxH0gBBFUTLeamLW+Tg97cBt9StSEcAB/N9aAws4LgLCbTEbrSi3aWLo61d 1Kk7ifQ7+6vXeke2uLdCsvYLf46tThSk2zKuzBH85wjSBVeTeu3rCMPNom/tnFauyUdKfzvETyqb fubpFs2wthtCUCENsSAPACKCk50xPMvt72L4nkt4Xa2SygtuIUkIIjeFHkfKh5huMWdk+W73Bb2y UDu6WSpER4iYq45rwS9duV3Nz7Ssap+ceJ5cAJquM9o2QEIJ7wkKPCgnsHxCzxa1F0w6haSkplJ5 UjdH5tdwe4gCRJj/ABzpo0lq3X2rbKGVLBBKe7J9OdUnpQzULWx+R7R3/KHk6VkH6CefvNBMdEmc 13PSMm1u3W12C3VBgFIlJ8Z84+uuvLIzaoKtyRXzpy7fXFjcpWypTa5ACk7Eedd99HmJt4zlHDbx D6XlKYSFrTwKgN/roJ1xHdFY2J3mt3EmIrUCO7FAqhAVsRtThIAiCD5UigGCZO1bpSqNiaBR3SpH CCKFvWNI/Fne7TLjY/vUTl6tJoXdY0KPRnfGP41sf3qCb6lP+h7h0gDW8J/PTWVp1Ilzk59E8HXf tRWUFX6/5P4NWoKhHyizt/8AS5SnV+bDnRpgaU7xb8PzjSH/AOQNUZfsk+OINH/dOUr1eFhHRlgg 3nsJHl3lUBONvGxTBitUthPGZpVLx3kztz51p2xJ2igTuWu6ajLhvjvUs8XltEttqWI3IrZGFJWl K7l4hSk6uz578KCtLQrXEz4RUknAHT2a1uaFkayI29KnWcIsrZ/tN1rSJgqmo1u5vLpt1p1pTUPE srk7jjvQBvH04hhuarxzELlaGn0Q2hHBJ3gTXOefrwt5oadUl1p9h4HQvYxqkGuoOmF+1U0bJ25Z aeWsKMqEq8QJrm7F8PcxfPTLKy12LSzD6mtaFJBkBQHHwoDBaYld4c004SpbRAKVDiAeVWFrPNi1 aAugrlOgEHcbc6jsv263sBZtbhse0sICXE6eGw5elRSsJtA4/rag8Y+6gaZqzheXlyvsSWrdHdSh KREcKgm8bt0IlxYTGxkQaZZvtW14u0D80yGwVRtJBqCxRy2ClOaU7bJkzvQSmI5mff2YgNjYeJNC /MTy38auHnFd7VzNWW9uDDVtbgLed2QmOE8SagMesfZr0NKJU5pBVvxJoNMLt3HvyoI70zw3/wC9 dP8AQRnVzD7K3wm5uGlWSTpSeaT4ek0CMm4GL68asw65LpCNCB4kcffRz6JMAbX0iXOEBxsJsWUO IhI0qBH+PfQdDsrQ4RpcSraYml9AUkq2EVDW2GO2OKe0hsr1JIUUq2I9KmEPs6ezSpJWRtNAow2C jma2TqA7vCvGFOCQpPpW5PmRHGg8UCUgn30LusgI6Mr8CZ7Rs/3hRWOyBznwoXdZRMdGV/wMuNfr UDvqQz+CNwTw7Z0Ae9FZXvUiH70LiOAedjb/AGdZQC/riY9cZgybaXdx2QWnEGUKDaFIAUGV6tlC eYq1dX1BPRrgRHO3/wCY1WuubhNnhOUrC1sX3LhtF61LjiYUo9k5JMVaur4kDoxwLf8Ak5P95VAT A0rmB8a2w+1L9ylomATvHhSrTetMJBJPKp7B8P7BsFMdqrdRPKg8xK0Qzh6bdlYbkiSBvE71AnCc TxHGRcqAtrdteyie84ANtuQq6KZSmCoaz4mtVpII22oIe2wq2t2tMOOL3lajJ3qDzc37Jhb2lSkF YgL/AJm3GrhpUkn3VSOljMWDZawBy5xd4gOgpbQlGpSj5Cg59zRaP67w35beeZAUwp8gl1JO59QK p/RVgS2XL/GL5am+3LyWg2jVI4QBB47/AAqbxvNb2a1vMYQ+i11KSEQ0QVeEq4/DzqwdGjLlvlkL unHEPquH9bkAqRGqVAcyDvHlQXHEMETbu219aJKA+yhtxB5EDYn7KrGYLY21yEOoLayCFBSYNF+8 w4sXFu+EqubNTRVp4qVpbBA8NzJk1TukZeMu4fdN4ZgaMTcaLIDIZDim9X0zMgwPf6UHP2e03Tt/ bN2rS3HFpACW06lKPkBxppYdGmasQR7RiCRhtvpK4cV84QP6PL311ecm2jFs0xgybSxu1sIcDxZk lJ4jYgmD9tVvFsoWiQ45mDHVvW8QvfsEQDChtufpJI35Gg57Tl/C7ZDjdtcsWz7SE9s65K3EhXPh z4eFVvNtrg15b9ph63W32nEMtoKCS8Oa1K4DyFdE57s8k4060zqQbi2SG0sNgw547jYgcffQr6RL Sywu6ZFtbN2zCXO0LaR/CECdz/jlQVnLWH3RxRoWoLEaVLUtUwf5225H30XeifGlYJnO0tn0ds3f /MG6UgJgkyD6HhVUyW3b3l+wEpSxcusjSDJC522+HD0qy4Thq8VzrY2QcKmRqaeCO4oECAoBQmAR QdTWTYCAVLCieFe3WFWV4pKnWh2iFSlSTBFQfR85eHD3MOxLvXlkoNqWf4xP5KvfVwZbIIOxmgi1 4elgSkrIPDypqtlQE1ZnW0qbg7yKjLxlTKVAJGmOJoItepKR3gDFC/rJKJ6Mr2AdJcb3P9aig4VE bDj4UMOsekp6ML2SILrUfpUEj1JmyjJrytiFOOnj5oH7KyvepPIyfcTzccP95IrKAb9dN9p7J1gb a+urtkXjPefIKpDTgIMbE93iKtHV1LKujjL5fC+z9mIMeSlVS+tZh2L2HRrZt47Zi1vTiTQKO11w OzcgzJnh4mip1YcNb/FTgDrqQrVbE7jh31UBOsLcdhrbZ0JiEDnUvbM9miIk86SsdK3VqaVqbTtE bTzp4J5UGq0kxArRxsngDTkkgbcBWizwmRQNXGjvFBHrLZYxXHUYabJhbzDetC0gx3jEb8uHOjq4 ElMzNUHpcfScEFmq5RbofVC1qXpgDfY+M0HI/YYtl1a3bmyt7C3LnZ26zHarIMaoEmOfhRKyM8nE 7Fm4tihLaFKWHNMBKtCiVkb89yKHXSo7a2+IlouqKWkKTDjsjURsB5wZnzq+9ByG2cCtmlKI1rB4 mSSOBI5GfhQG51SMLwhd9d3wWtNqhaU6YTAG+mPEmn2AN2tvZm9cCW37sh1ZPiYAHwgU6Yw9rEcv 27ToAOgBJT4D9hpZGHNuhDK4T2YiOQoEsVtnlLtvZ7QOuIUdLmqNCSNxHP0oZ490dXl5jryPlJ9F m6kOOslZWFKPE7kxwG1EZnGGnMSvLNKoatEEvOzEE8BNKNOYa7iSk2zrbtwWwpxSSVGOUnnQU+wy Rg+HIQpNqhSkc1JGxoE9OeErv82Iw9huO9Ko2CUwkn7a6rv7VTiDpMeBP21zn0j26lZuvLy3e1Ia CVSonhI24eCaCjWtk0xcW5xC8RbtWoCmm0nvEJMgGOG9WHo4dxRzOthdXGHutoQ+oF7V3QF8IJ3P ED31EvM/InbEqZccvHNRURqCWwIjf3mpfJ79xdZywtCriXC+lxSNR7yAdyYMAbbCg6WbZXa4na4m 2SEqhm4HLSfon3GPjVub2G1QVtbC6wxbau6FoInwqTwl0uWqAsy433HPUUEnJg7CKbvoC0qB4U4k cfqpFwlSjHKgg7pstrIKNPhAoSdZhSm+jW4kDvXLSfrP3Uar5jtGpmKB3WkXp6O3GuZu2p+ugn+p aQrJTp0gaXHUkxx7yT+2sr3qWQMjL8S69z/pJrKCqdfhR/B21bIgG8ZIMcfm3Pvq79WMA9DeXdWw 9nV8A4qqf19Qn8F7NYBKxetD3FDn3Vaeq68X+iDA0L/ikKSNuI1E/toDCNKW+7tXjbknTMKB3pNR BRusSRwpG1bQlZdTqle5M0D8pIRIMzWi0k7kVs26VARuOHpWy9kydtqBvyAoR9PDdy6W0NBOlLJV KuAMnf3UXlQTx40JOmrHLe1vfYLllLluGPnSeRPn8KAA4sxhGY0IwXEbdAcaKSxfJRpVpJAjVzTx MHwFGPLGXLXA+yw6zh1lgobSswZKYG9B3GUWzCEXdmXHGFPoShKR/Bq8FetHnJTbjlip1/6Yuz9L YjvJP7aC9ZYYPyUyoOiQkApSQUp8hG23Davc2JxC3wi7fwlkvXvZHs0AgFR8idp8JpbKaAjCGwmS EqKQeaoP0jtxPH31LPJ7VlSRsY29aClZTwFdnkq3axBhab19IuL8FYUpxziQo8Dvy4VL4Szh7XaN WduGlIjVCCBvy4fZUi2T2imjsJmDSVuy6h59bjoUCrupH5I8KBtjTybTCLl8qCSlohJP84jb6656 zk0FX+IS0pbhW2lKUnc7EzRw6QHpwL2clZ7d1KShIOpQG8SOA2Ek+nOg3iuFrxBV+Ge0S4HHXAAd +4Dtz5GgodlhTuLOXV9blt+3t1qTcIKtK2FA8DOxnl76neizALh3NTd4ppRJCVIIJ+jJ+HCvcq4h 8nZdRamwLpecU4tLg1BaCd9W3Ab8TPCp/oVxC3s80Iw95d2lVwSlhLoEJgcoHODQdA2Dei1QmDJG 9LJZ7N4vN7H8pP8AOrGNSedOAQU7+NB6l1KyIO8SKb3L4QstEwTBn1P214NFvreIJK+fgPCoyzWi /eXfPhaQh5SG0qVACdoMefH30Ek+4lCi2dwQTx50Detesfi6J4TeND6lUYbgqf7VsK0KSe799BDr VOLPR8pCjwvWgY9FUFr6k8fgSuP/AFHj/eR91ZXvUm/0GV/Xe/XTWUFc695P4NtJO6ResHj/AKty rb1YRo6IMvAAd5lZ9/aKqqdexKjgCIE/5Vbn+45Vs6ti9PRLl0Dj7Or/AIi6AqO6QtC1qjkBHE1o HCEKCdlQSJ4DwmtL3UGwtKgkpM1s22OzCn1pJMcOFBtgq1rtRJKlT3jBAJ8p4ipIokAz7qq+FYnj D+OutrtbVOFto2eQ4dQVJATEQdoPvqypdA3kEcaDCxJ5jehL0k4JbX+aF2D4Kn7tslsaZ2jaD4yf qovJeBA9aGGbcYUxmS4XcLW2GwopMDToHL12nagDeMWrOQ8Ut38Te+bfeShTDYSrWUwZUOW870UM MxBD2EX17bgFKrp4twdjCEkfZQn6VMHxDMuZrB/CXbd3tnA0u2dX88gkzqG26efjtRiwWy7DLhY0 gr9ofQTp3nSdx+jQXTLLxUxdJVGpL6jtMQrcRPkR75qQQ+kKUkq3mq1ktSoeSlBQkttqEj6RKASf MST7wamXwWX+2EQdlDwoFApS7wmBoB2ptbG6TdvOOvsG3OzTSEFOnfz4+tN7fEW3seXZNmS0NawP yR51o9d3Vzj6mBZhq3aSfnCoFThkbwOAoIHON2HsXtbALWgBQVISd1KJgTwiEmR6VV8rgqexG9AB 0WjzgJG0mpa5Wm4zViN8hQcRaoXJ0gAdmgAAEHeFFcnjO3KkujlTSbK+uX/4MMLCpH5Ox4e80Alv 7O89rtLK0tlXK1EgISqEK27yp2HAbDyqU6MsLXhucEqWyHHGnNTbhSJIVPCCY9Kl8CtsYvbxzFuz bu7dLmi3t1PFGpO41pjhyEceNPMpl+zzkwzeKLr2IK+ilOlCAlJIgevPnQGFNwrsCofSArXA7x29 Sokd1KiJ9K2QzpSNUwRxpLB22MPs3xqVHaqWeZMkmBQa4u+3fONWdqVrLdylL+gwWiBqTPiOHuNM ct3D14L9zswi1RcuIb1cVadp9Nqe4241h6HMQbMLQytao4KhOxPjwpnktl1rKNqHge2cRrcP9JW5 +2gmrRsItgTBJ8KA3WzRoyUFJ4KvG5+CqP6GyGUgAcKBnWzQT0epIH8ub+xVBYepbIyQ2AQR8/Pl 84Kyk+pPq/A10HcBbo9O8jasoILr1ScBRBgh63j4O1YerOo/ilwHURs0sbf7VdV/r0j9wkqn8u3+ 1yrB1Z5X0TYFwjslgf2q6Aq3X+bSPCeNIYchNywQ5JTBETtTi4SVWy4HLamuEhSElJM78qB1CGG2 7VpCUpnvnkkePrUQ/cXjOYmbFAUbd4FSf6McR9dSmINLUJCCptB1KTzX4D0mqhmPH3MvZgZvbxtR sy+1bKITMLcCt/iAKAjhsJQIiYoW9Ilp+6V44yg3DhQIQ2qTJ2I08440TWHw/bJdSkiUzvQ2xxdy xib7rDalvF8nYctXCaAMYfjWJp6Q8Js8SY1D2xBCwkoIIOwE8eKZo34OV+zW0pK+1vS4QFcAttdD XGMYwu66SLG4xKzadvWLhDbLikFKgeB9QJPwotYez2YwxI4FDK9/zx+2gTynCb20WVhRfsQAmBKA hRmY33Kvqqw4mlCLRxxwEpSJgbk1XsHhl7D5b0Bu5fZU6Y4EylBnfcmdvDzq4ONpXG21AN8i4ZmH D845gfxLsBZ3LiV2znaa3FbniD9EAbRw22qw2bTFniV865dXLj4BUpK1Hs+E90UviDqm8UJgCFAH zBH3iorPVwm1wdy5ceLZdSGUKClJAUo7GR/jhQVxntG8v4ze3LTSHnWtB0DYFaiYnme9x58aQwNt bWT8XdSrQPZwkq8CZn6op1i/zOT20JVPbuBSTxkAEj7BS+VOweyhiDlwvTbuFxKlEcEgQf20AJuF Y1eW9vb2jtxpYK3FqZdG0ERPDxiiV0WYWu8xNGP3ilpdb7gSsGCqIkVBXgwDCMFQ40t5Srx5QZd1 hSVRAkDkB4bVa+iS+YcuHMMR2kBHtACySdzvx5cKAj3t8pi1WUtqWoJkAVC/L9naWKbh1Sbi8WUM 9i2QopWQYkDcDjvUvi94zZYY/dOphttsqJAnYChDlN22ds14xiS/ZXrm+cuWme00rUClPZpUBvJA n86gvrz1zieBui4T2NzeOIY7IHZvUdwPQA1dLVkW7DbKYAQkCqlhlstzMOCWOgDskru3gTMHTpAJ 9VH4VeFp+cMAbeNBouNEBRG24oJdbFH/AIcpM8b5refJVG90d0bbmgp1sEk9Hze8RfN7fmqoJTqU 6fwKfE97tndp80VlJdSrbLFyJnvu/aisoIrrzpP4OoVy7S3+1ypjqvuT0SYIkk7B0f71dRnXlSDl hKo4Lt/f3nKfdV0a+inByPyQ6D69qugM6BLShHEUyw7a4dQDMHantuklBHKmjbfY3ijBAO9BviT7 bPdeMJcQQY/Z9dQV6S4n2e+CXgp5tbIUmSUpgyfMGneeGFuYCt5DikKZUlyQJ2Bk+u01st1tWYbS zCNZuGlKkD+DSIG/hMigstuEhlO0gjaqRmppJvXvY3GW7pSiYdMJkDjPjV8Q2EtgDkIqi52tmlG4 L3dE90zxJHDyoAnjDdwzm21uMX7uKXVwA2yIKUJTxWFDZRO3186NzjqWbXCVE8EIT8Ck/fQlunm3 834azcWVtqt3VJQtLkqEjf7d/Si9i7KU4HaPJ4MupmPAgj7qBtdtqRbX51hPs16h8o2GuTATJ9QR 5gVamHSS2VRCk+FRl9bj2m7HY9qLi2JCAY1KA2G+wM86c2F12+CW7y161pQNStpkcZjnQNcetwhZ dA7y1JHHjVTz5cLdcw2wYQHpd1OTuECISvbeZkDx34xVhz1iAscKNyYKUd4jVHI0PsOujfZgaxC5 uVIbZSFFtPNOkOBRj1IA8vSgks4OMpNtZtAFthWohJHArA4egVTjCbFf4txag6XHWCtfA7rOoj6z UFfF66XcXUavablQSTP0Q2qOJ/pD3ir4y2G8Hbti1rBbCVDgIAoOfsaxFOG481hC3LZ21QrtWm1t hayrgrSeRIMRwgUR+ibCWkdpj3cC32g2hCTPZpHI+e31VRs2ZYtHLxePKJUwpxTSWUcdYPDUOCSI 38iKu/RG4ub+xDehhhSVISeI1DfhtxFARnG27izW28gKQRBBHEUEsFwkXnTJcYeo6rSwWl9CI4bC J8d/so3gAtQKpOG4a3adIuL4i0nvOWaJ24qkxQWzKzYdxbEsVIOnULdonwTx+s/VU8rvKK5nwApD CrX2TDmrfYECVkCJJ3P106aSJ8qBEqIAEDhQZ61xB6PmduF83+qujY62nwoJ9a8j8XzQif8ALkfq roH3UsP72LoT/GO7e9FZWvUtIGWn0wZK3if0kVlAy68IJyxO8Tb/AK6/vpfqquBPRfhQJjvuiDz+ cVSXXg3yuE+HYH++uvOqopA6M7DWB3XHY/TNAemBKCBSdy2lDqSY350lZ3KdxIn7a0xJ0ltCkmCn eg0x1CXMJfbIiUECee1UToGzLe461jFtibRVcWF4WUPkfwiOQnxEVaMRxGbRYWZEGo7oktLS0yym 5YA1XTzjyz4kqNBeytU+NVvMFlbYip5t5TqVEBIUhUaTyPhU4t4p4c6qGbHL9DinLI/OIlQSTHLj QDfOeCsYNmBDlpZEvKR2i7kypSjqg78Ad55UUFLL2WlIUkmWwRtPDehPjeM3t04W7nuBtxsFA2JK l/ZANF7C1JVh7bSu8kpjc0Gl1iLbVrh12tQRKkoJJ2hXdH1kVtgbgFvdWyne1LbihqkHUPHbn4+d QePYbcYng7Fqw5p7J4FSvNCpA+IFLNXTeFIxS+dSUKTal5QJ2UUpMke/9njQD7NmNX+dM7KyvhyU 2+HWw7R+4c37VKFaVCBwEggTE0/dbfQWbewabsy6pWs9nJDadjII2Jnlt4VZzY2eH4cvFbawtkYl fNN+0PBAG8TJ99Nra0Q7jTSyhLa3GktrK3ZEDcpTzJkn4cqDW8swi5wu23AbaU85pHFS1AD6pq2L HzACRAAqKdQh7FiscilA9E//AO1KuL1IMcAKATZrFthgdwnEb1NjhhJdU+133DqV3U6dJj135VNZ CxrK7PZ4Xg945erMBb3YmSTJ75gR68KpvSFdtXV3ibz60e0W1ou5LSRsEAqS2k+oMnwIqMyLauYM 3heI3lk9bKfulWt20pWkgLIgn3EEUHQEd07kCq1lwPO5qxRTgGhCkBJ8QBP2mpe2eXZdnaP6ltaQ lp8qmT/NV4Hz5+tQVveLtMcu9AkuOoB9KAgNqUobEA1oHFa9JFJ27mod0+tK7SFzvzoN1lWmZ4UE +taf3gsTzvkAforo0uujSQN6B/WqcKskWyRzvk/qroJfqYT+DTuw2U9+sisrfqZD97D3kp2fXUis oGHXc3y3Eb6WD/vF1TOr7mvCMJyFbWt3i1nbPJdc1IceCSJVI2NXTrrtzgIUebbH1OL++uNnEkHa RvQd42PSFlkAFWP4bI//AJKPvp09n/La298cw0+l0j764C7yTxPrXhKualUHcGN51wJVo6pvGcOJ 0HhcI++k+jDOGCYZlJi1uscw8LC1qANygEBSiY4+dcSHUocTvXg17gK4UH0Jts/ZXUolzMOGJA8b pG/11Vcx59y6rGJbx2xcbKIMXSAkHj41xBqXEaj8a1UTO5NB1FjucsuqddGH3tj3HWe0V24AUe/M c1RIPGN6I+HdImVm7VtCswYYCEj+Up++uFIJ8ayFfzj4bUHdFp0j5TQ++hWYcNSgr1g+0p58ai84 dIeUVWi1s47h90lbKmXbcXAhaVbevjw8fSuLAFR9I153juSZoO2nek3J5Nuk5isAlpIIT2oIGxHv 2Net9KOUWFBacdwwlcqWQ6JnhwiuJAFExJisg8N6Dta16UMn9sFuZhsATqO7nifup4elTJn5OZLD +1rhzSrzrIJTtsaDoPpMz1gb+NG7sL21uBd4Y7au6FA7laon0mansdz9lrEsh2S14tZ+2pQytbYW NWtIAPv2rl7SRsTNbBJ8aDtW36UMn3Fg2HcespU2nUC5wMVBPdIWWk4s1+7to4nWklwrHAHnXJAC toJrfccKDuWz6UsohO+YLEf/AGU5b6UMnkE/hDYE/wC0rhi1WG7hC3UFxCFAlMxqHhNSPttq46Sq 1S2kuqXAPAEyEjyHCg7Vd6T8omNOYLDf/WULOsDmzBMeyqzbYdidtdOi7SvQ2qSBpUJ+uufFLK3V rQAlKjIA5DwpdqeZJoOu+pjtlh8Rtqd/WTWUr1N2+zyo54rLiuP9NI/ZWUDLrop1YAgaTu21B5fw iv8AtXHrzMmNJ25iuzuuIytzLgUB3UtN/Hta5DW1J+jQRBZAMwTWBid441KlqTFZ2IkkpE0EZ7OJ 3FeKY7xhMVK9l4CvOy24cPKgiCx5D41qWoMQRUuWSREDjNeFkapIoEEi0Fro1Q4EJAIRzBnf69/S lXrlpSrwtdnp1ksgtjgVSeXhtXpYT/NE16GeGw28qDxm4bLlmXez0hep8dn4K25eG1aBVsm00KIL obUmQjiZOx90QeVO2WmFFKFMjUTx1QPftTj2FggkBgDURBe3+ygi7z2Zxp/sVJSlTiS03o3QnfaY 9PXjSeG+zNsuJfSky82RIJOkTq5c5G3OpP2JorCAlkd0GS5sd/t+6tnbW3aX2iW0KSFboS7Mj4UE Y8m2DKezUndrTu3BCtczw8K2U1a+03L6XG9Dpc7NGg93+by293hSzrLanCW29KTwSTMV4liOVBHP WiUL0ocS5wkpBiffSaWNztvUr2G86a9DMDYR40EX2BE6RNbJYPGNvGpMMA8q9LJ8B8KBghnfhSoa QdwmDTzsiTukVuGjEQAaBu22EwIpw0gTsK3S0rbu07tmVTPCg606nrShk5bihA1OJHn3xWVKdUtC R0bJUEgEPLB24nWr9kfCsoNOtVaOXeUEttNqWsplISkknStBP1Sa5NXhS9KdVncSAdXcO/hX0PxG wssRt/Z7+0ZuWpnS6gKE+O9RRyblgmfka2HpI/bQcDDDGAynVZ3Xac1Rtz/7Ui9hqVbtMOpE7yJr 6ADKGWxwwlj4q++tGcmZYZWtbWEMoKzKgFqgnxiYoPn/APJrv/oufCvBhywf4Je3ka+hP4L4BEfJ jUep++vBlfAAZGGNT6q++g+e3yavj2S/gaw4a4Y+Zc/RNfQo5XwAmThjU+p++vPwWy/M/JjXxV99 B89Pkp4meycj+qa2GEvT/Aun8019ChlfAASRhrUnzV99bHLeBnjhrJ+P30Hz2ThL+/zDv6Ne/JFw Zlh39E19Ck5dwVPDDmR8aw5dwX/49r6/voPnl8kvD+Idn+qa1+S3Z/gnI80mvoactYESScOaJPmf vrwZZwEGRhjIPv8AvoPnmMLdH8W5+ia9+TF7y0ufSvoYct4EeOGMH1BNe/g7gn/xzP1/fQfPA4cs H6Cx5RXhw9QH0VV9DXcsYAv6WGMn4/fSasqZdJk4Wz8VffQfPgWPPQfSsNlvOk719Al5Qy2RvhLP xV99aLydlk8cIY+KvvoOChZ4fKtSbgbbcONYLO030h7yMV3j+BuWNR/ce34+f316jKGWwSBhLIB2 O6vvoOE7e0swiXRcSBtoAiYP7Yq69FHRxdZ9xt2zs1qtLW3QHLi5WmQkEwEj+keXoTXXyMpZcSNK cJYA4wCfvp5Z4DhNqoqtrQNEiDoWoftoGeRMrYdlDBk4PhfaezoMhThBUSSSSSAPGsqfSAlISOAr KD//2Q== --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: image/jpeg Content-Disposition: inline; filename="custer.jpg" Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD/4Q1kRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB AAAAyAAAABsBBQABAAAAbgAAACgBAwABAAAAAgAAADEBAgAOAAAAdgAAADIBAgAUAAAAhAAAABMC CQABAAAAAQAAAGmHBAABAAAAmAAAAOYAAADIAAAAAQAAAGd0aHVtYiAyLjExLjMAMjAwNTowMTox MCAwMDo1NzowMwAGAACQBwAEAAAAMDIyMQGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA //8AAAKgCQABAAAAyAAAAAOgCQABAAAA9gAAAAAAAAAGAAMBAwABAAAABgAAABoBCQABAAAASAAA ABsBCQABAAAASAAAACgBCQABAAAAAgAAAAECBAABAAAANAEAAAICBAABAAAAJwwAAAAAAAD/2P/g ABBKRklGAAEBAAABAAEAAP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAk LicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIy MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAIAAaAMBIgACEQED EQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0B AgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpD REVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEB AQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFR B2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVW V1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrC w8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANNRzUiIfSlR DmrUUfrXOajEiz2qZYPap0jqlrOr2+i2RkkZDMR+7jLYJPqfanYCd4OOlQGe2ibbLcQo/ozgGvPL vxRqE6kSXsj+yfu1/JeT+dYsl55m7JUZ6kD+vWiwrnsAlgfG2eI/RxUhjrxlLhgMLKcdua0YPE2q 2mDHfSMB2c7h+tFgueqeVimmPiuO034hKWEepW4APHmw9vqK7G3ube9tlntZUliboymiwXECCgpU oSnGPNIZEqgUVKIz3opAVUXDVajFV4/vVT8R6yNC0SW6THnsfLhB/vHv+AyaaApeKPF0WjK1nZ7Z L0jk9RF9ff2rzC71Ge7naaeZpZWOSzHNVZriSaRndyzsdzMTkk1GFLHABP0qhEhnc5xWjpmg32q5 8oY9M96hs7DzHG4MBjJJGK9j8HW1ta6ckqqHVunHQ0BY8wl8H6hDGWOcjttNYtxbzWxIcHivoXWN X0nS7YzXh5I+WMDJNeY+KTb3umHUotOlhgkfYjkfqaLhY4ESZrb8Oa/caLqCMrFrZyBLH2I9fqKx Y4mkcIilmJwABkk16R4V8BmDy9Q1dfnGGjtj292/woCx2qjIB9aftp+MUvFSMYFopxopDKCDDdK5 L4i2V9dWdlJb28kkEJcylBnaTjBP68116ctWjanFUhHzpit7SLa0t1W7u5lCk4AxmvZbzwvoGoOZ rnS7dpDyXUbCfrjFeZ3WkW0OuXEcMMbQK52RljtA9jTESWNvDqExntAJDu2YEf3R6/413Xhq7itb IW88MgKMct2zmvN47h9C1WCS1uzGGbbIqnPynsexFelLMloUMjF4pVBEh75oAvX50q7+RIXknkwG Pt6c9q0bu00e/wBEk0m/kht4pE2KrOAwPYjPcVmWlza2TSXTbWwMgep9K5zWdavNSmISzS4bOMjA x6AUhm3pHhfRNFVZLG3EkmOLiQ72P0PQfhWnITUVjJNLYQPOmyUoN6+hxUjZzQAwr7UgWpKCMUgG 7RRRzmikxlGNavwDiqiDB6VegGQOKpCJygkhaM9GBBrzHxFp50+5la8tnO4DbIg6LnqP0r0S/v5L K1MtvaS3bZKbYcHDDqD7+3WvONT1u+1W/jbUSVtY3yYEGAB396YjCtLQXt1mOBhAjZDPyT7V3GkX bJbtp92pe1I+V8ZMf19qzta8T6dounx6dplhbySkbi/U4PIJP9K4y88Q6reYMk2yPOQiKFU/h3oA 73VLC6trFrmCVJ7dTn5X6fhWLo3ih31i2N3Bi1iYqWjHTPc+uKg0zxCb+2NrNaHj75hOFI9wKs6j FF9hf7CotmjAZWTj659aAPUQVdA6EMpGQR0IpjCuJ8GeKVNtJYXshd4xuiKLyw7jH9K7SK4guGdI nBePG9Dwy5GRkHkUDFA5HWnMBikIwaKkBo64FFAPNFIZVQVi+IvEk+mSJa2OVnxueQKDt9AM1uov NedeIJkufENyySEAEIMHjgY/mKpCZaOri/hjg1G3jnhRtylB5LofVWXHP1zWpqF1pt1pg8+4jmlS PbA6o32lj/02ydpGOMjr+lcsXliHOJF7jHNQSyAYmiPyA5I9DTEQnTVimMwywHOD6VoafJHp13Hc pZW92gJIhnGUORiljcSJnqDUMTBQ0fdf5UAaFhANDj1STUbP7PJN+9EQHHlnJUKPTJNSW4G4jqCB 1qKKzu0EeoXV0k0N0CqiSXc4C5HI9OaSNsXCIvCg4oAzNbj1O01iDVXhS3km+dPKUKpHTOB61b0j xNLYayl3JGBE6COWNOAQOhFMv7QGCa7N5E489kFuZCXTHOcdhWMGBJJ6CgD3BJEliSVTlXAZT7Gn AA1xvgnXJrxX0+5fd5SAxEjnb0wf0rshjFSMTABopwxRSGVgDn615TNCTNMrctHIwP516xzuGK8z 1FBa+I7yJgNrynH48/1qkJleOZo0GMstMlDy5MKxsx6g8GpGUxSnb9084p5RZUyuFb06UxGfazPE WicFWB4B9Kk3fv8Ad68VDOri4BaNt443D+tEchKsfegDQsVQy3DjJcR/d7dR+Va9lDGLR/NkLOzE gAenTn6iudsJtmoMeoaMg+3IrbtpJVjXa+O/NAGK4Q3M5bG7ewz+NZqAZcdlY5q3bxXOrasba0QN LNIxUFgo7nqa1PCPhttZ1a6S7Vha2rnz9p5Yj+EGgDLsZ7i0nS6t5WikU5BHp6V61o2pxatp8c6M vmYxIgP3W9KI/Bukyu0stmgZkwkSsVWMepx1NcOss/h3Wp47K4WRUfa+4cSD6UmM9IGM0VQ0zVrf VLbzImxIB88Z6r/9aipAn3ZNcB41tGj1gzKMGVFcH3HH9K7pDzXJeNCrTRuzHem1AueMNuOfzX9K pAznLa7juUCSEJMvr3qZwydRx6isi4ty3zJw1TWksjrs3kOOoJpiL0twwXlCwHoKxri6WFj8pG/k A1fe6vIODtKmu1+Gc9vcazNHeRxyZh+RXjVuc8nJ5oA4zwxp91rmpSW1nGGnZMZbgAZ5JP5V6Cvw 213Jb7TYgbcKPMbr/wB816pGsESARpGi+iqAKT7VCPlMi5+tAHisPwc8SLJk39hH/tpI+R/47XWe FvBeqeGtHnhlENxO8hlYRSH5vQDIHP1xXoSzow4ZT9DSvIO1AHiepeMNUWaWCBPsgDFWDrl8jsc9 PpXMajqdxe3BuLt90zAAsABkDp0rq/ixpTWeuwanbDC3kZDr/trwT+IIrziS6uUcJcqGWgDpPDOo pb+ILctJxITGQffp+uKKyNFtjc67YxqdyNMrfgDk/wAqKljR64vB5rK8Q6DaapCtxI0qXC7I0Kth eXAGR3+8fzrTDYNQavKRol465DpEZFI6gryP1FJMbPMEcqxRhytNkwrLInDDriqST/ay3z4lB3A+ tSrIWQq3DjqDVEl9h9oiDIC2e1V4H1KxvEuIGe2KHPmZxtqkbuazYtC5FVJ767vG/eyEr6dKAPQr P4k6tZRHcy3KKeGlXJI98VuQ+OdM1EB3nubN3IASWM4Y+gIzXksN4YcB49yjsank1L7Q4ZvlVfuj 3oC57To2vQX8jJBdJujO0hnCnP0ODXWxC6dBuLL6ZFeP2/izwp4hsIodejmtNREflvdRICjnszY5 OepGOtcvc6vqeh3Ij0jXbnyifl8mZth+maLDPU/i9Cf+EOt53YB4rtcHvypBx+leILdgDbICwrqL 7TvHPiVo7fUTdTxp8y+dIAg9+uM/rXS6D8PbDTQs+pst5cjkJ/yzU/T+L8fyp3AyfAmiXHn/ANrz I0duqkQhurk8Z+mM0V6BI+FCqAFHAA6CiobA/9kA/+EMRWh0dHA6Ly9ucy5hZG9iZS5jb20veGFw LzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQi Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUg NC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5 LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgog ICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6dGlm Zj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8v bnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50 cy8xLjEvIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAwNS0wMS0xMFQwMDowNzoyNyswMTowMCIKICAg eG1wOk1vZGlmeURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpNZXRhZGF0 YURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpDcmVhdG9yVG9vbD0iQWRv YmUgUGhvdG9zaG9wIENTIFdpbmRvd3MiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHRpZmY6 WFJlc29sdXRpb249IjIwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIyMDAvMSIKICAgdGlmZjpS ZXNvbHV0aW9uVW5pdD0iMiIKICAgZXhpZjpDb2xvclNwYWNlPSI0Mjk0OTY3Mjk1IgogICBleGlm OlBpeGVsWERpbWVuc2lvbj0iNzU1IgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iOTMwIgogICB4 bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6Zjg2ZTcwZTQtNjI5OC0xMWQ5 LTllM2YtZDQyZjM0NjM5ZGJiIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ1dWlkOmY4NmU3MGU1LTYy OTgtMTFkOS05ZTNmLWQ0MmYzNDYzOWRiYiIKICAgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIi8+CiA8 L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg ICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/2wBDAAUDBAQEAwUEBAQFBQUG BwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUF BQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh7/wAARCAD2AMgDASIAAhEBAxEB/8QAHQAAAAcBAQEAAAAAAAAAAAAAAAIDBAUGBwEICf/E AEIQAAIBAwIEBAQDBgUBCAMBAAECAwAEEQUhBhIxQQcTUWEicYGRFDKhI0JSscHRCBVicoLwFiQl M5KissJDU9Lh/8QAGQEBAQEBAQEAAAAAAAAAAAAAAAECAwQF/8QAGhEBAQEBAQEBAAAAAAAAAAAA AAERAjEhEv/aAAwDAQACEQMRAD8Am8Hl60dEGN+tAbnalEBIr5+PUIoGcmllXPSgkW9LxxntVBET 2pVY/Y0tFEe+KcpFRDYRDHSjrD/pp4sdHWIUwMHhwOlJcpXr0qVeEEd6aXCxxRtLK6xou7MxwB8z TDSSpjelAtQFxxvwjaSmGXW7dnH/AOtWkH3UEUeLjjhGXATV0z7wyD/61cNTTDbG1AKcdKjBxXww RznWbUA9Mkj+lSem3+n6lE0mnXtvdopwzQyBgp9DjpTDXGQ0mY6fFD6Vzy8DpTDTMR7UOT3p0U9q KF3oaasm1F5PSnTr3pMAb1FJrGwGRRuX1o64xvRtqYG5X2oyp7UpgV1agCKAQSKFKZHLQozUEoNL Rj3oBd+lLxJsKrTsaEmnccW+d65Ehz0pzGp9KuI5HHv0pdU7Yrsa+1LqmSBitSAsabdKrXiLxfb8 IabG6xR3V/O4EVsXweXuxx2/maX4w410PhmJknlWe7A2gRhkf7j2/nWCaxxK9/qs+ohea6mbJuJd 2A7BR0UDoKuJaud54gcYX0Jk/wC46Lbno5T4sf8ALJ/Sqlq+sx3j/wDiOoahq7A5HmSFIwfYf2qA uLxpn55ZGkf1Y5ptLPynAxVxEsdQXcwWNrCvuvM33phM8jS+Z5rZBz1pn+JxuWxRGvxzbDI9aYbE rHfXgGFuX/T+1OrPXdZs3LWt/NCx6lTjP2xUGl3EO9HN3Gds/ah8XnTvEjiiywZLsXKj92UBs/ff 9at+heL9jNiPV7FoG7vCdvsf71izzAocGkWkyD1phuPVuh63o+uRc+mX8NwQMlAcOPmp3p8UA7V5 Gs725sp0mtLiSGRDlWRiCD7EVq3Afi5KrR2HFA8yPoLxB8S/7x+8Pcb/ADqYNfkA3pLlpWKWC6t4 7m1lSaGReZJEbKsD3BoKtYWEwvSjhQe1GA9aOoFA2K4JoAb9Kdcq5zQEagnYVKpALkHrQpyAB0Az 6UKioTlIpxCMikyMiloU2qwLRfMU6iGe4puin0p3Ah6kYHf2rUQsgVFLuyqoGSScACsj8TfFURPJ pXDEo2+GW8Hf2T29/t61D+MHiM+pTy6BoMpFhGeWedT/AOeR2B/h/n8qyiSQjuc961ImndzdzTyN NPK0sjHJLHNNZLk5601klY96SZqrOnRuipJApKWdn70iuTTqztWnkUAEg+gqp9JRrJIQEVmPsM0s LG9dsC2lyenwmtO4D4PZ3jmKeYp64GQPnWoDw8WeBZIrZUbqMj+dTVx5l/yvUACfw0gx1yKQaOaM kNGy/MV6UuuALhIyGRDj2qj8W8E3do3mCDIJ3wOgppjICzD2owmPepnWdKaAtlSCO2KgGBBwe1VM OBJnYUA2BTdTR1zQaD4UceXHDd+thfyNJpE74dTv5JP76+3qO/zr0PHiQB42DKwyrA5BHrXjtc16 l8Lo75OBNIF+GE34cYDdeTJ5P/bisdRqLGq980cLjpXQpB60dQc1lonymuBd8UsFocveoogXahSm 2+1CsqglI6E05twu1NU69KdW/XFaiHcYXOKznx44xOi6QvD2ny8t7fITOyneOE7Y+bbj5A+orRlK Rq0kjBUQFmJ6ADqa8l8ba5LxDxTf6vITi4lJjH8KDZR9ABW+WUY03KMCkC5cnJorHJoA4rQ6elBI 2kOFGTjNGUcx32qZsdAubiESoC+eyjcUQwgspVTmZod+ilxk1a+EtC8/UIeZ+WOVSSmNw3/WKjG0 60tpbeCZJTLIQVKHIO/Tb5Vb7Vbhbxr+1jES82Vjkckuo/d6bHHr7VBtXhrp8ttaxhreGKFfzcw/ X1rTbdomQBQCpGxA61ROFW/EaBDcsQrcgyo3GcdKnLNTHmWV0VTjdTjb5f8A+UaieFjHK/mHBQZJ J6Yqr8XXPD8UDie9tkC5BzKPsKrfGnFGu6jM2k8OlxynlaY/EgPYbd/sKyjTOFeK+JeJmtdUSRgk gDM/wDrjI9fuaiadcY22k3EL3MbRgcpCuB136VjOoIou2ZNlJyK9NeKfhUR4fwS6aZDLYEtcIDu4 I3b6YrzJdczTNzdtvtViEQoNKwxs7qiKWZjgADJJqS4b4e1fiC/Wy0ewmu5m68i/Co9WPQD3NejP C7wtseEuTU9UaK+1gj4SBmO3/wBuerf6vtVXFO8K/CR08nXOKoimCHgsGG59DJ//AD9/Sti5eu1O 5zzHem/b3rPqyCgHNGUUXau5qK6a4a4DXcZqApOxwDQoxWhWK0gsdDinEGAelJMgxS0CgVqMmHHM 7wcDa3LHs4sJsEdvgIryU29ez0tobmNre4iSWGVSkiMMhlIwQazXjHwGtLsyXfCt8LWQ7i0uCSny V+o+ufnXSMvPGN66BVl4m4H4p4clZdW0W7hQHaUJzxn5OuR+tV4Ieb8pG/pVE5w/odxcSiaWNhGm CB6g9/lWn6XprWmnk5jifPQ9MHPX5AVQtK1trTTrlDkyOnlg+m2/9BTF77W7iN3Ms5UAFiW6elRF 6gsNQZ31aOS3gnjCrIzDnJU9cdh9KXKZto52hYWkcgAxj2OGx03qF4D1qOyv4oNSvVCznq+8cfpz fP8AStj/AOxtla6VJDaXLXMDp580gwU5zvzAg7/agsXBN7pqWqWYYK4QOQTsexq4wjTrpArMvKw+ 1ZXw1oyxW8byOfPyc4bOKmJWvLRSPMbH5gcYxmoq5axacP2ulSAzx28e55om5Wyfl3qJ4Jt9Gtbt ruCGeaRsBGlkJyM5O9VaOG8v7oJzFhnY9Rg1oGh6BIlp5WQVC9em+KKstmq3isvLEI5MgqNwfXtW O6j4EcH2/Fd7qF811PBNKZorQNyRIDuVyNzv7jtU5xBxbxhoDfh7Hh6wEEPWV7gF3/2ov9a5oPE2 rcUomoXUFvBaRgoAHLSM/cEY+ED077VUSem2NhpNmLLS7K3srdOkcKBR+nU+9CQk96OxBpN8YqVT dzSecjpSjgE7UFUBTtvQJMoAz61xRntSpAoBRisqIF36Cjcu3SukUBt1oOcu9CjKAaFYq6r2AcZI pxEvTem4SnMKYrUZPrUbipa2bG9RNqN6koelbglElBXBGR6Go/VLezNlPKmnW0kgQkc0S7n7UvET iofjLVrjS9PDWyKZHzueigdT+o+9VHmziHSiuu3d6UjJmdZ1B/KBJvnHrnI9iDUqAlxZLplxp6lF AdnXClhk7j164ovF/Pa6v5v4ZppJCWl5JCNic8uOg3Pak5+JntoRaS2DRyQrkCQbgEk9vcn70ZV/ XtFktLCS4ihcRscDKbjpt/Or34IW8eraeLS4vrkJbyeYsAlbkLDcZXoaoGtahf3/ADT80nk8u4J+ EbdKmPCrU7jh/VI71gTaSEeZj933+VFbglytnfGKdVUMxOMGpaSS3uYEBQZGQDmm2o2FvxBpy3tn MAWXmSRNwfqKqY1HUdKumt7wFcbFlG30qYrQOHohBdBlwA24wOm+f+vrSfGfF8nOdN0+8MKQjMrq uSx9B/aqmeJkS2llRsyKhKgnHNjNQcU97qzNGzRKZy3MScg/P5bUDbiHjp7GZFt9OlupWBBeQkkD tnHqMnFO/Crij8fxBLbRwGOKaIyEZ3LZzzH7kU2nsI9Jljt5ktnuZRhZMZ39fUbZH2rQ+H+GtN0x zeW9nDFdSKFkdR1wP0+lBNk5FEkyRSpGBik2HzqBHFGA964QAcZoZHTNWtDADHWuMpxkGuLgnFK4 GOlZoR5cbmisTncUs4xtSRHM1AF+VCjqu2TQrNVAxg9KdQj1pJBvTmMHpWoyc2y5bYU+jUimtuu1 PIx9a0HMewFR/E1g2oaXJChxJjCt6Zp+uwBpxFvjeqjzxfF9O1r8XeIwYebHKh/ccMcbf7Sp981A Xer8P3Mt3eXrPJMIisUUSgZboC3etw8W4OFE0sXWs31tYXiDML8vNJJ/p5Ruw/lXn6/01dXuGvtO tMO8uJQgPIpYZH3wftREUbt7pvwFkk0dtLgSK+Pi9/b9auOkaekdqEIxt27Va+A/CfWbuwTU4tPE kL5BmdwoGOu27Y+QNPf8hcSyxxhAiNy87HkUn5tj+9Ax4M1274duxDcRmfT2OSq9Y/cf2q7cQaVb a1apcWEsTLIAUYbA59ao2qXXDehoX1PU43cDaKE4P1JGfsPrTXhjiXUtUMknDn+W21ssn/kTO3O5 9cbkfeinXEXDuraYhafSJJ0XOJIGz+lUK/4ku9PZ1tw6GPJIdcEe1bXJxWLfTHg1WExl15SVBIBP p9axbibSdT4g1aX/ACyyldJGCmRl5VC+pJoENB41upOIrK/1W1W8tYHBeHJGR6/Mdd69P6Ve2Wp6 bBfafKJLaZOZGH9fevMs3DtnoFsJNTW6ZCcGeJQVQ+46/fFX3wr4ls9Im/Cxail1pc7ZYdHgb+Ir 6euD70I2Vl+HNJkUvsyBlIKkZBBpJhRSLYwaKuD1FKkY61zAA6Vm0EQKW2pZeXuKSU4pQnbaoorh TSZ5RR96IwqDqnOcChRosAdaFZqocYB6UrG2/Sk1BzS8Y3rcQ7tj7U9i3ppboaF1r3D+l+bFf6xY 296q/s4ZSTgnozKu+PatodalfWmm2Zu76dIYV/ebv7AdSfYVmPFnirO4ktOHoDD+7+JlALf8V7fX 7VfOEtW1K0sNSng4j0Xi7VrlwbeGS9NmkSY/IkfKwXt8+5rJ9S401nT9TOleLPAyzo7HlvIIBDOo /iR1+GQD2OKuMqNftPqd1JPqM81xNJ+aSVizH61evCS+s40l0q5jhWYDy/2oGCMgxsc9gwAPtUzx F4ZTDQouJeGmm1TRp4xMvNHyzRoRnJHRh7j7Vmmr+ZYTW+q2p/aQNhwD+de6mojWPETxMvdE0KG1 1LmFxFzJDaKRGWYE/E6rsFHQD296wHiDjbiTXJna71KZUb/8UR5Fx6bdfrTXiC9u9b1iW/uA3NId lySFHTApyugO9mLiHJI3xVVXzzN8TMSfc0406/u9OnE9pO8TjuO9WCLR47q3DACGdR3GVb2P96tX iN4K8Q8G8LR8QXV5YzxYX8RBEx5oS3pnZgDtQJaD4j3z2fk6laC5jGzSR/mX3q26JxClzah47kTI 35SVwe+29UfwY0NNQur2/uA4ihVY0IPVzv8AI7Dv61aNStX03U5YT5fKeZ4+VcdOQ7j1xn7VApqL /wCZWNzbSr+zmjZcHsazC202WwnVortlukJ/J0BHbPetK06QvEzEHIkI+hFI3fh1+O4e1Pia31uO G5t5ARZsm7DAyQc5/Sg0Dwd4ztdU4Y/C6jMyX1rJycnLn4NsfYnGPf2q6aVq2m6tC02n3ccyI5jf BwUYdQwO4Psa8kcLa3LpGssWeTyXfEgU4Jwa2vWLxdQ4e1oWGnabc6Zcwfio721YJcQuF5v2i7MQ G2BGwzvRZWsOuKTIIqgeC/FjaxpB0q+maS+tF+FnOWkj9fmOn2rQGO9ZsUl0augkVwjfOKAB9qiu 52pNmzttRiDRGU5xQGQ9xQrqKcbihWaIxF70snWklB74paJcmtQRXHHEq8NaA90ih7uU8lup9cbs fYf2rDLvWJ9QvGur6WWWd2y7Mc5q9eOV60Op6XbqhYLA8jHtu2P6VQYruAkeZFsepxW2KfQTI35G Gau/DvG99aWo0zW401vR2xz211livujHdSO3p7VSLdrGX9/lPTI9KXaCXk/ZOjemaDW9M02NpbTi LT/FjVrPhaxbzf8AL7mTnmgYDaH4iQVxtuDt69aoviLrXD+u67LcaLpE1pBJnzWcgLMf4gmAVPr2 /nVMnlBcRXCBJ13QkbA06065FxCWcYdG5WT0IoGI0eISMgUe2RR9OJt5Ws3xjtt1qUB518zG6HtU frScskVygIwd8UHZ7copUCjaxe8bcR6Yuj3V7NeW5OUiCc0jY3+ZG2aXibmUSb4YdRU3wbxqvA2q zak1i9z5kBjDkcxjOf5f2FTQXwqnWz4eOmvGY5orhxICuG5tuvv2ol9rcOs69d+UcpZOsRYfvFld f54qNtOJ5+Jtf1XVpMwySzI/L3/Ly/8A1FHg0aLS9aklslKw3flM8Z3AbzR09tzVEjYcoeULsGAc CmvEOnarqt1aWelreSySK3PBbgnnUYO4HYUdJkSWAKd+XlOPepJ+J9S4VhttZ01Od1k5JEJ/OpBy PuB9hQZLxHpk+n6sySQvE2fiR1wVYdQQad2F5eWkb/h5XiJjaNgDsVbZhj3qa4o1W64nu5tVvIDF M7c2CcmohlBJ2xlaB9wzrl1w/r8Go2ZUyxDHK35WB6g16R4T1634i0SHUYF8st8MkZOeRh1FeW/J WRw2enU1qvgnxTp+nR3Gj6lMtuZ5Q8EjbKSRjlJ7dBUpK2QkD0rg3zRGIJ2O1GSstlFwAc0VsA7V 35UXBzUo6p2oV0KaFZVGKm9LxKM9M0T0NdklENvLLjPIhYD5CukRjfi1qCXHF88eQY7ZFgH2yf1J +1VyGOORMYGKbNK97JPLcMWkkcyOT3J60haieGQqmeXO2a0wkJ7FGHwkKexBpBLqeycJOTy9m7U/ tJo5sebGMinM8VpLGedMqN996COvF/Hw+ZFyyOo6A9fl71H2NwEvg2cGQcrjpuOmf+u1PxZRK7SW NyFx+5UfqyK3/eEBW4jPM4/jH96Im7d/jOGG/auXo57RlO/ptTHS7xJ4lIOT0p60mQ4GxopppdwG QwOfy7UtIshlFuInmD4AULnOegqOGI7wMo2JwfSn8mXQgNysR8LZ70Fp4h8ML/hWK01C51Czb8eq q0ETEtGx3HsR7im96wjWJFlVmjKq3fmxlz/8ahtK1TXdc4jjt9cvGljit2EQXbmIAAz9KXnYWVq8 apjCSSDfJGwTr/yNAiMlFy/7oORvjepDihUk0CGFZFjDToDI/wCUZOMn7060vT7OXQVvpLadCwZY wzBEkPL8JJPQBs5OcHp61E8Xs3+TwW867mQcy47gGgl+P+CtL4d0qyv9L1w6glwgLq6gEZHUY/lv Wb3MmG5c4PKR9akYbaRinPczyRpuiO2QufTNRuqxNHdKvYsD/wBfagPFtEAegFFkbzcKq7A9aDDK igx8qHb8zHCiiPQfhJrX+bcJQiWQyT2rGFyeu3Q/aropX0rzp4e8YS8JXqxPCJ7O5I89R+ZcfvCv QOnXttqFlFeWkgkglUMjDuKzY1DvPegGoLRWqNFFbI7UK4g2O1CshgAcelGRFkUxt0YEH610jAo0 LKD71qDzaI1g1Ke1JB5XZM/I4o1xGY2Db4FLcWRfgOML9OgS8kH0LH+4p75AmgDcwwa2wYYLplNi BtiixzzRH4gWA7EVyM/h5miz8NPIRHMOUAADvnFENpYrO7X87wSnoU/tTC80bUoR5kEgvUG+xww+ h/pUxJawr8cec02FxJE27HlHrQQOkTG2lliZCuGyA2xHtUxFKzfER9qYaxBLPci9tD5g5cSRj8w9 x6ijW8+YsZHTegPLvuMDBp2JAIlbbcY6U2tpPMV8/LOKAceXjB2oJ3hCeN9Z3UMywvjbp0rt9bmS OeeQZeQQrGN8AkFjt26ioLhed4uKIU5iOdXBH/GrLqDfEkaDc3BJ/wCKqv8ASirgbmxtLENZ6W7x ae37cMQFYc+Qq8wOSAASfXvvVP8AEdndhLJzeZ+LfmBbJyc7VJPdW0ziW6hilkAzzFFBHyqJ8RLt brTLec8gkacFiFwT8JoIK2lXlBYbVE8RMFaKT19/ejG5wAq0x1uVnhiB3IbagdwqHVc9MUSP9vdF v3E+FaPptvdX7Q2NhDJPdzEJHGgySTWq2/hOukcMvd6teSC/EZZUhwY1OM4JxknttjrQZqYlDGRx sOgNaX4M8Sw2Usmi6hOscUp5rcucAN3X60Xh3wf4k1e1ju7ua105GHMsUuWk/wCQH5flnNUnUrFo NVms5MB7VzGeX+JTg4+1QemVwRkYxRGJzWd+H3HtrJDBo+sytDdKOSOdz8MnoCexrRRg4IIIPTFZ aHRtutCiEkChWappJkLjJrkaEnJo8rCgjYxvW4MN8XrNYeNr3GR5ipJ09VFRWg3XnQlMnmTYg1dP G+z/APGbO8HSa25D81J/oRWXQztYXvm78j7PVYqw6hFGSJOXp1OKbpzKOZExipGIxXduGT4sjam3 lmJzG+du4NULW84YBZCPfaiXtosykxt13xXFCEEDOR39aNDKUk/KceuelEVq/gu4JcRrICD13okr XBhE8x+IHDEdSOxNW55oHT4lXPqKjr+2je0kjHLlxjtRURZNiPYjfvXWbAIYCkbZuVeQnp1HvSU8 yDIBG+2KIW055DxDp7W7AP5uCcA/Dj4v0zVnhdpZIWOxMZkP/Ni230IqrcMRo2tyTqCTDbyMBnbJ HKP/AJVaLGRBcXDkhkiYRqADsFGB/KosSyxQOg50JPuKqvH7CJrG2RsjDyN+gFS9veTT3W4+HPrV X48uObWljBwI4gMDsTRTbhvTbjXtfstHtZooprqURh5Gwq+5qa8ZeCLjgi6s7d9VttRSUkiSIcpV h1BXP61VrW4ltikttIYZo2DI69Qw70e9utS1/VrSPULlp5ZZVXJwOpAJrSN7/wAO3B6Wul/9qL5R +JuU5bcMu6Reo92/lWo2GmS6hqBv78AJGSLe3JyFH8Te/f2+dNOHEkt+FtOtRcRmNY1AePBGw2pT U9RFoHSW9htI+txcu3KIl74z1JOwHr8qjUTkY/Fo0Vs7RW6bM46k98e9Y54+No6alYWtjbRw3MMZ 82RVwFQ9Ax9Sd9/ep3WvFzQ7G3FhollcXap8IkJ8tSPXfcn3xVP408QdP4k4Yk0M6GLbmkEvmGfn JcHOTsPehqhpb2kjiTl53H72dqv/AADxs9hyaZquTaDaObmyY/Y+1Z000iAsRGF6DkJOPnSE1xME ODzKR2rNiPTqyxTQrLDIskbDKspyCKFZ14IX7XGgXFm0vP5MpIBOcA9PpQrNaXlmwBtXRkkGkQwz SqZyKSireMVh+I4TivAPitJ1JP8ApbY/ry1h+pW3OTgnHbevQPHsN3qOjwaDYcguNTnEILdAoBcn /wBtYMRzKFIww2atxmmGl6nPps3lOeaPO4qz2t5aXyghwD71WLy18zJC71FwzSW8uCSKrK+XFpyn nifJPXemp8xdgjZ+RqOtLucqG5+YEbUs2pyoCWJoHEqyn8sbkkdlNRV4uoKwPkuvtykVIwcQRK2G yKPNrEEzpySFGXpk/Cw9DRVT1qeW1lWQoyCUZx03HWo1LtpG3JrdvBySz1fxAtbDUeG7PUomVjI8 8SukAxs45ts5GPXrXp+z4b4chAaHQ9MjI6FbVB/SkMeG/CzTdS1jiRNOtdPu5TduiGZISyRKrcxZ j6bV6l4a8FuDLSxWO7hvb+U7vJNM0ZLHr8KYx+taqkcUIVI41VewUYArsigSZGKuKoKeEHAMJDpo rhvX8VKf5tTW78DfDW+na5udDleV8Zb8ZMOnyatLyCu/pQQgDqKDNbPwJ8M7W6juU4e8xozkLLcy un1Utg/WlX8EfDsasurW+hm3u0bmUxXEiqp9lzj9K0gkAda7nK7VRVZeDdIeJwqzwzH8s6vzPGfU Bsr+lecPGPh/ifh/iBbLW9Sl1GxkzJY3LfCGGdwR0DDv8/evWb7HOdqpvjLwsvGHh9qGnxIDf26G 4sX7iVRnl/5br9amFeQiOQ4yCPbrTeSLLl4zv1371DQX8yA+YCGHX2NBuJCshR0UgbdKiHdxd8jl dwfSm348xnm5Oai/ibe/bflRj0P96ZXHNBLyMpA96GtG8G9cS34rWDIWO9Qxuv8AqG6n+n1oVV/D S3a7490qNCwVZfNbHooJ/pQrnZjUejBjPSl12IG1Nhkt0pxGD1NSVcQ3F17HpeqcOalNJyRRaj5b tnAAdGWsIlYJql7CSMpcOvX0Y1vvG3DZ4q4Yn0qO4W3nLLJDKwyEZTnt7ZFYxxfwJq3DV9LNNfR6 j+xWeeRFK8nM3IOvXfG9bjNRbJzHAxnGMUwurJJVbb608t5gFywJJ9KcZXBBGRVREabM1rP5E26n cGp0Q288PMNjTC/tkki5kwCB2613SblsCF2APT50AvNMj5TIuB3z3qNSMBvi7Hap65TbP9dqirhV QFsYOelNMW7wn1ltC4uspjPy28kgSTmOwB6H23r2Fpl+rwhieY8ucj+9fP1794mJBAxWhcEeJHF1 vYCCDU3Fra/xgNk9lyRnHtRXtA3S8iuCppvd38ceDg7HtWD6B40W7Wwe9tQZlHxRBsBv9p7VcND4 80ni+ynbTGkt7222ntJtnXPQjsw9xV1V8XX7U8o5XO5H8v70dNcsOflZ+VvQmszvG1Yc7RJzxk5y N+tIQX8sbjmkhBO551II+9T9GNfTUbaTHJMjfI04S4UjGay+zvp5CpJBP+kbCp2y1GRByFmPfc9K foxb2lDE7/ShDIomUZ2OxHzquG8nZQyinNpdyNgEYYGmmPH3jLoFpo3iVrulEGBBcmaEqNuST4wP pzY+lUu60i2EBImVpPbvWwf4z7Q23iBpeoRLgXenAE+pR2/owrCo7iUOAT9KrIvJJbyfDkYNSkFx Hd24jlxzr+U0QeVdDHMFIG5NMJk8p/gl+1X0aj4Eaf5vFNzeFdrW2IB92IA/QGhVn8BdNntOF59U uYyn46UeVnqyLtn5Ek/ahXLq/Wo0LlAbY0dAc9TSJOD70ojjY1iNU7iyM4yKqfF8IvNa1KyfcT8N 3DKD3aN1dftirVDKp9Kz/wAbLq60pdO1ywmWN+SazfftIvp8s1uJWNluQq/NsRnrTtJkbBUjGKj7 OSOeE2rN8QGVNNj51jNljzRnvW2U7JKrKQoyR1phM/lSK64z1NKQOsq8/MObrmkJjzty8h9M1BOW E63UIQMOalZdBkul+J8DtvVTE01pLzwyMuPQU4bi3UUQJkEjbJFMNSl1wzbwjnurxQvcLTC61OGO FdO08IkS+nf5moDUtbvr5irynHtSNoxiIbO9WRFnVWEBfm3A9abadJxE16l3pN7cWk8eyyxyFDj0 yO3tSNpqSD4ZcYPWp231e0t7blgYA43FBYLbxG474Zt+eW/ttULYDrOhJP1BGftVguOK+MNV4cte I7LheG4sp8q01rO+Y5FPxKy9j36dMVlVzfSX1zsMhdzWm/4fePLPhniSTh7XJUj0bWCFLufggnH5 XPoD+Un5HtRZVq4J8Q+GDpbza/FrMFzAc3Kxwq/kL/ER1K57gbbVpPDHFPAuuMo0jjbT5XbcQzv5 T/8Apf8AtTTirwj0niC9N7a6oulXirmKSABiT/qGd1x2715q8SOAZ9D4gm0y4jWw1INlMHFtdL2a Mn8pP8JqZFe1raxkKjlaGZT3RhinSWLKdosZr532mu8UcPXLQ2uralp8sZwViuHT+Rqaj8XPEeKE xJxfqnKRjeXJ+53rWJrYv8cd9YC94ask5Wv44pXch90jJUAEe5B+1eaDKSMgkGlNV1HUNVvZL3Ur ye7uZDl5ZnLMfqaluDODeIeLLvydHsHkjU4kuH+GKP5sf5DJ9qvgghI4YkM2/vWreEXhhfcQSxaz r8Ulto6kMkbfC917DuF9+/b1GkcB+EPDvDix3mphdX1Jfi5pF/Yxn/Snf5tn5Cr9PP22A6Vi9Lhp NHFEkcEEaxRRqFRFGAoHQAUKTnkHPmhXOqTcnNBGNChWI0VjbvgVnX+IiMtwbZzKQDHeA/dWoUK6 c+s1g0N2/mrMuzKas0YS/tQ7LguvN8qFCt1kwV2tJ/KU5XNO2cqSQBkj0oUKBheFmzvudyah51Oe tChViVyOFQfenCxKRvQoVUJSRgHrSTMQ2ATQoVYsOIZ3gBAJpve3Ek5BJ70KFIJXh/i/ibQZEl0r Wbu2KkEKJCV/9J2q73Pi3xPxRZ/5JqENhNPe8tsLqSLLICcbD60KFLCM+4gDw63cWUkjTpaSNApb qQpIrWPD/wAMeG+IuDLW/vWvYbqUNl4ZRjrtsQaFCs9XIs9XHhvwa4M04CS9hudVl5sg3EnKg/4r jP1zWhW8dvY2kdpZW8NtbxjCRRIFVR7AUKFc9tawSSViDTSRsnehQqKbyjLbUKFCs0j/2Q== Cheerio! --Multipart_Sun_Oct_17_10:37:40_2010-1-- mu-1.6.10/lib/testdir2/Foo/new/000077500000000000000000000000001414367003600160465ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/new/.noindex000066400000000000000000000000001414367003600175010ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/tmp/000077500000000000000000000000001414367003600160555ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/Foo/tmp/.noindex000066400000000000000000000000001414367003600175100ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/000077500000000000000000000000001414367003600152765ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/cur/000077500000000000000000000000001414367003600160675ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/cur/181736.eml000066400000000000000000000040741414367003600173440ustar00rootroot00000000000000Path: uutiset.elisa.fi!feeder2.news.elisa.fi!feeder.erje.net!newsfeed.kamp.net!newsfeed0.kamp.net!nx02.iad01.newshosting.com!newshosting.com!post01.iad!not-for-mail X-newsreader: xrn 9.03-beta-14-64bit Sender: jimbo@lews (Jimbo Foobarcuux) From: jimbo@slp53.sl.home (Jimbo Foobarcuux) Reply-To: slp53@pacbell.net Subject: Re: Are writes "atomic" to readers of the file? Newsgroups: comp.unix.programmer References: <87hbblwelr.fsf@sapphire.mobileactivedefense.com> <8762s0jreh.fsf@sapphire.mobileactivedefense.com> <87hbbjc5jt.fsf@sapphire.mobileactivedefense.com> <8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk> Organization: UseNetServer - www.usenetserver.com X-Complaints-To: abuse@usenetserver.com Message-ID: Date: 08 Mar 2011 17:04:20 GMT Lines: 27 Xref: uutiset.elisa.fi comp.unix.programmer:181736 John Denver writes: >Eric the Red wrote: > >>> There _IS_ a requirement that all reads and writes to regular files >>> be atomic. There is also an ordering guarantee. Any implementation >>> that doesn't provide both atomicity and ordering guarantees is broken. >> >> But where is it specified? > >The place where it is stated most explicitly is in XSH7 2.9.7 >Thread Interactions with Regular File Operations: > > All of the following functions shall be atomic with respect to each > other in the effects specified in POSIX.1-2008 when they operate on > regular files or symbolic links: > > [List of functions that includes read() and write()] > > If two threads each call one of these functions, each call shall > either see all of the specified effects of the other call, or none > of them. > And, for the purposes of this paragraph, the two threads need not be part of the same process. jimbo mu-1.6.10/lib/testdir2/bar/cur/mail1000066400000000000000000000027721414367003600170250ustar00rootroot00000000000000Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "John Milton" Subject: Fere libenter homines id quod volunt credunt To: "Julius Caesar" Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com> MIME-version: 1.0 x-label: Paradise losT X-Keywords: milton,john Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Precedence: high OF Mans First Disobedience, and the Fruit Of that Forbidden Tree, whose mortal tast Brought Death into the World, and all our woe, With loss of Eden, till one greater Man Restore us, and regain the blissful Seat, [ 5 ] Sing Heav'nly Muse,that on the secret top Of Oreb, or of Sinai, didst inspire That Shepherd, who first taught the chosen Seed, In the Beginning how the Heav'ns and Earth Rose out of Chaos: Or if Sion Hill [ 10 ] Delight thee more, and Siloa's Brook that flow'd Fast by the Oracle of God; I thence Invoke thy aid to my adventrous Song, That with no middle flight intends to soar Above th' Aonian Mount, while it pursues [ 15 ] Things unattempted yet in Prose or Rhime. And chiefly Thou O Spirit, that dost prefer Before all Temples th' upright heart and pure, Instruct me, for Thou know'st; Thou from the first Wast present, and with mighty wings outspread [ 20 ] Dove-like satst brooding on the vast Abyss And mad'st it pregnant: What in me is dark Illumin, what is low raise and support; That to the highth of this great Argument I may assert Eternal Providence, [ 25 ] And justifie the wayes of God to men. mu-1.6.10/lib/testdir2/bar/cur/mail2000066400000000000000000000006461414367003600170240ustar00rootroot00000000000000Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "Socrates" Subject: cool stuff To: "Alcibiades" Message-id: <3BE9E6535E0D852173@emss35m06.us.lmco.com> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Precedence: high The hour of departure has arrived, and we go our ways—I to die, and you to live. Which is better God only knows. http-emacs mu-1.6.10/lib/testdir2/bar/cur/mail3000066400000000000000000000036131414367003600170220ustar00rootroot00000000000000From: Napoleon Bonaparte To: Edmond =?UTF-8?B?RGFudMOocw==?= Subject: rock on dude User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO) Fcc: .sent MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le 24 février 1815, la vigie de Notre-Dame de la Garde signala le trois-mâts le Pharaon, venant de Smyrne, Trieste et Naples. Comme d'habitude, un pilote côtier partit aussitôt du port, rasa le château d'If, et alla aborder le navire entre le cap de Morgion et l'île de Rion. Aussitôt, comme d'habitude encore, la plate-forme du fort Saint-Jean s'était couverte de curieux; car c'est toujours une grande affaire à Marseille que l'arrivée d'un bâtiment, surtout quand ce bâtiment, comme le Pharaon, a été construit, gréé, arrimé sur les chantiers de la vieille Phocée, et appartient à un armateur de la ville. Cependant ce bâtiment s'avançait; il avait heureusement franchi le détroit que quelque secousse volcanique a creusé entre l'île de Calasareigne et l'île de Jaros; il avait doublé Pomègue, et il s'avançait sous ses trois huniers, son grand foc et sa brigantine, mais si lentement et d'une allure si triste, que les curieux, avec cet instinct qui pressent un malheur, se demandaient quel accident pouvait être arrivé à bord. Néanmoins les experts en navigation reconnaissaient que si un accident était arrivé, ce ne pouvait être au bâtiment lui-même; car il s'avançait dans toutes les conditions d'un navire parfaitement gouverné: son ancre était en mouillage, ses haubans de beaupré décrochés; et près du pilote, qui s'apprêtait à diriger le Pharaon par l'étroite entrée du port de Marseille, était un jeune homme au geste rapide et à l'œil actif, qui surveillait chaque mouvement du navire et répétait chaque ordre du pilote. mu-1.6.10/lib/testdir2/bar/cur/mail4000066400000000000000000000020561414367003600170230ustar00rootroot00000000000000Return-Path: Delivered-To: foo@example.com Received: from [128.88.204.56] by freemailng0304.web.de with HTTP; Mon, 07 May 2005 00:27:52 +0200 Date: Mon, 07 May 2005 00:27:52 +0200 Message-Id: <293847329847@web.de> MIME-Version: 1.0 From: =?iso-8859-1?Q? "=F6tzi" ?= To: foo@example.com Subject: =?iso-8859-1?Q?Re:=20der=20b=E4r=20und=20das=20m=E4dchen?= Precedence: fm-user Organization: http://freemail.web.de/ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from quoted-printable to 8bit by mailhost6.ladot.com id j48MScQ30791 X-Label: \backslash X-UIDL: 93h!!\i 123 mu-1.6.10/lib/testdir2/bar/cur/mail6000066400000000000000000000010621414367003600170210ustar00rootroot00000000000000Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "Geoff Tate" Subject: eyes of a stranger To: "Enrico Fermi" Message-id: <3BE9E6535E302944823E7A1A20D852173@msg.id> MIME-version: 1.0 X-label: @NextActions operation:mindcrime Queensrÿche Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Precedence: high And I raise my head and stare Into the eyes of a stranger I've always known that the mirror never lies People always turn away From the eyes of a stranger Afraid to know what Lies behind the stare mu-1.6.10/lib/testdir2/bar/new/000077500000000000000000000000001414367003600160675ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/new/.noindex000066400000000000000000000000001414367003600175220ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/tmp/000077500000000000000000000000001414367003600160765ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/bar/tmp/.noindex000066400000000000000000000000001414367003600175310ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/wom_bat/000077500000000000000000000000001414367003600161625ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/wom_bat/cur/000077500000000000000000000000001414367003600167535ustar00rootroot00000000000000mu-1.6.10/lib/testdir2/wom_bat/cur/atomic000066400000000000000000000016601414367003600201550ustar00rootroot00000000000000Date: Sat, 12 Nov 2011 12:06:23 -0400 From: "Richard P. Feynman" Subject: atoms To: "Democritus" Message-id: <3BE9E6535E302944823E7A1A20D852173@msg.id> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Precedence: high If, in some cataclysm, all scientific knowledge were to be destroyed, and only one sentence passed on to the next generation of creatures, what statement would contain the most information in the fewest words? I believe it is the atomic hypothesis (or atomic fact, or whatever you wish to call it) that all things are made of atoms — little particles that move around in perpetual motion, attracting each other when they are a little distance apart, but repelling upon being squeezed into one another. In that one sentence you will see an enormous amount of information about the world, if just a little imagination and thinking are applied. mu-1.6.10/lib/testdir2/wom_bat/cur/rfc822.1000066400000000000000000000032451414367003600200470ustar00rootroot00000000000000Return-Path: Subject: Fwd: rfc822 From: foobar To: martin Content-Type: multipart/mixed; boundary="=-XHhVx/BCC6tJB87HLPqF" Message-Id: <1077300332.871.27.camel@example.com> Mime-Version: 1.0 X-Mailer: Ximian Evolution 1.4.5 Date: Fri, 20 Feb 2004 19:05:33 +0100 --=-XHhVx/BCC6tJB87HLPqF Content-Type: text/plain Content-Transfer-Encoding: 7bit Hello world, forwarding some RFC822 message --=-XHhVx/BCC6tJB87HLPqF Content-Disposition: inline Content-Type: message/rfc822 Return-Path: Message-ID: <9A01B19D0D605D478E8B72E1367C66340141B9C5@example.com> From: frob@example.com To: foo@example.com Subject: hopjesvla Date: Sat, 13 Dec 2003 19:35:56 +0100 MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 7bit The ship drew on and had safely passed the strait, which some volcanic shock has made between the Calasareigne and Jaros islands; had doubled Pomegue, and approached the harbor under topsails, jib, and spanker, but so slowly and sedately that the idlers, with that instinct which is the forerunner of evil, asked one another what misfortune could have happened on board. However, those experienced in navigation saw plainly that if any accident had occurred, it was not to the vessel herself, for she bore down with all the evidence of being skilfully handled, the anchor a-cockbill, the jib-boom guys already eased off, and standing by the side of the pilot, who was steering the Pharaon towards the narrow entrance of the inner port, was a young man, who, with activity and vigilant eye, watched every motion of the ship, and repeated each direction of the pilot. --=-XHhVx/BCC6tJB87HLPqF-- mu-1.6.10/lib/testdir2/wom_bat/cur/rfc822.2000066400000000000000000000026611414367003600200510ustar00rootroot00000000000000From: dwarf@siblings.net To: root@eruditorum.org Subject: Fwd: test abc References: <8639ddr9wu.fsf@cthulhu.djcbsoftware> User-agent: mu 0.98pre; emacs 24.0.91.9 Date: Thu, 24 Nov 2011 14:24:00 +0200 Message-ID: <861usxr9nj.fsf@cthulhu.djcbsoftware> Content-Type: multipart/mixed; boundary="=-=-=" MIME-Version: 1.0 --=-=-= Content-Type: text/plain Saw the website. Am willing to stipulate that you are not RIST 9E03. Suspect that you are the Dentist, who yearns for honest exchange of views. Anonymous, digitally signed e-mail is the only safe vehicle for same. If you want me to believe you are not the Dentist, provide plausible explanation for your question regarding why we are building the Crypt. Yours truly, --=-=-= Content-Type: message/rfc822 Content-Disposition: inline; filename= "1322137188_3.11919.foo:2,S" Content-Description: rfc822 From: dwarf@siblings.net To: root@eruditorum.org Subject: test abc User-agent: mu 0.98pre; emacs 24.0.91.9 Date: Thu, 24 Nov 2011 14:18:25 +0200 Message-ID: <8639ddr9wu.fsf@cthulhu.djcbsoftware> Content-Type: text/plain MIME-Version: 1.0 As I stepped on this unknown middle-aged Filipina's feet during an ill-advised ballroom dancing foray, she leaned close to me and uttered some latitude and longitude figures with a conspicuously large number of significant digits of precision, implying a maximum positional error on the order of the size of a dinner plate. Gosh, was I ever curious! --=-=-=-- mu-1.6.10/lib/testdir4/000077500000000000000000000000001414367003600145345ustar00rootroot00000000000000mu-1.6.10/lib/testdir4/1220863042.12663_1.mindcrime!2,S000066400000000000000000000143251414367003600207270ustar00rootroot00000000000000Return-Path: X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-4.9 required=3.0 tests=BAYES_00,DATE_IN_PAST_96_XX, RCVD_IN_DNSWL_MED autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id 5123469CB3 for ; Thu, 7 Aug 2008 08:10:19 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for (single-drop); Thu, 07 Aug 2008 08:10:19 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs39272wfh; Wed, 6 Aug 2008 20:15:17 -0700 (PDT) Received: by 10.65.133.8 with SMTP id k8mr2071878qbn.7.1218078916289; Wed, 06 Aug 2008 20:15:16 -0700 (PDT) Received: from sourceware.org (sourceware.org [209.132.176.174]) by mx.google.com with SMTP id 28si7904461qbw.0.2008.08.06.20.15.15; Wed, 06 Aug 2008 20:15:16 -0700 (PDT) Received-SPF: neutral (google.com: 209.132.176.174 is neither permitted nor denied by domain of gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org) client-ip=209.132.176.174; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.132.176.174 is neither permitted nor denied by domain of gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org) smtp.mail=gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org Received: (qmail 13493 invoked by alias); 7 Aug 2008 03:15:13 -0000 Received: (qmail 13485 invoked by uid 22791); 7 Aug 2008 03:15:12 -0000 Received: from mailgw1a.lmco.com (HELO mailgw1a.lmco.com) (192.31.106.7) by sourceware.org (qpsmtpd/0.31) with ESMTP; Thu, 07 Aug 2008 03:14:27 +0000 Received: from emss07g01.ems.lmco.com (relay5.ems.lmco.com [166.29.2.16])by mailgw1a.lmco.com (LM-6) with ESMTP id m773EPZH014730for ; Wed, 6 Aug 2008 21:14:25 -0600 (MDT) Received: from CONVERSION2-DAEMON.lmco.com by lmco.com (PMDF V6.3-x14 #31428) id <0K5700601NO18J@lmco.com> for gcc-help@gcc.gnu.org; Wed, 06 Aug 2008 21:14:25 -0600 (MDT) Received: from EMSS04I00.us.lmco.com ([166.17.13.135]) by lmco.com (PMDF V6.3-x14 #31428) with ESMTP id <0K5700H5MNNWGX@lmco.com> for gcc-help@gcc.gnu.org; Wed, 06 Aug 2008 21:14:20 -0600 (MDT) Received: from EMSS35M06.us.lmco.com ([158.187.107.143]) by EMSS04I00.us.lmco.com with Microsoft SMTPSVC(5.0.2195.6713); Wed, 06 Aug 2008 23:14:20 -0400 Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "Mickey Mouse" Subject: gcc include search order To: "Donald Duck" Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Content-class: urn:content-classes:message Mailing-List: contact gcc-help-help@gcc.gnu.org; run by ezmlm Precedence: klub List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-help-owner@gcc.gnu.org Delivered-To: mailing list gcc-help@gcc.gnu.org Content-Length: 3024 Hi. In my unit testing I need to change some header files (target is vxWorks, which supports some things that the sun does not). So, what I do is fetch the development tree, and then in a new unit test directory I attempt to compile the unit under test. Since this is NOT vxworks, I use sed to change some of the .h files and put them in a ./changed directory. When I try to compile the file, it is still using the .h file from the original location, even though I have listed the include path for ./changed before the include path for the development tree. Here is a partial output from gcc using the -v option GNU CPP version 3.1 (cpplib) (sparc ELF) GNU C++ version 3.1 (sparc-sun-solaris2.8) compiled by GNU C version 3.1. ignoring nonexistent directory "NONE/include" #include "..." search starts here: #include <...> search starts here: . changed /export/home4/xxx/yyyy/builds/int_rel5_latest/src/mp/interface /export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/interface /usr/local/include/g++-v3 /usr/local/include/g++-v3/sparc-sun-solaris2.8 /usr/local/include/g++-v3/backward /usr/local/include /usr/local/lib/gcc-lib/sparc-sun-solaris2.8/3.1/include /usr/local/sparc-sun-solaris2.8/include /usr/include End of search list. I know the changed file is correct and that the include is not working as expected, because when I copy the file from ./changed, back into the development tree, the compilation works as expected. One more bit of information. The source that I cam compiling is in /export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app And it is including files from /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common These include files should be including the files from ./changed (when they exist) but they are ignoring the .h files in the ./changed directory and are instead using other, unchanged files in the /export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common directory. The gcc command line is something like TEST_DIR="." CHANGED_DIR_NAME=changed CHANGED_FILES_DIR=${TEST_DIR}/${CHANGED_DIR_NAME} CICU_HEADER_FILES="-I ${AP_INTERFACE_FILES} -I ${AP_APP_FILES} -I ${SHARED_COMMON_FILES} -I ${SHARED_INTERFACE_FILES}" HEADERS="-I ./ -I ${CHANGED_FILES_DIR} ${CICU_HEADER_FILES}" DEFINES="-DSUNRUN -DA10_DEBUG -DJOETEST" CFLAGS="-v -c -g -O1 -pipe -Wformat -Wunused -Wuninitialized -Wshadow -Wmissing-prototypes -Wmissing-declarations" printf "Compiling the UUT File\n" gcc -fprofile-arcs -ftest-coverage ${CFLAGS} ${HEADERS} ${DEFINES} ${AP_APP_FILES}/unitUnderTest.cpp I hope this explanation is clear. If anyone knows how to fix the command line so that it gets the .h files in the "changed" directory are used instead of files in the other include directories. Thanks Joe ---------------------------------------------------- Time Flies like an Arrow. Fruit Flies like a Banana mu-1.6.10/lib/testdir4/1220863087.12663_19.mindcrime!2,S000066400000000000000000000071361414367003600210330ustar00rootroot00000000000000Return-Path: X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime X-Spam-Level: X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham version=3.2.5 X-Original-To: xxxx@localhost Delivered-To: xxxx@localhost Received: from mindcrime (localhost [127.0.0.1]) by mail.xxxxsoftware.nl (Postfix) with ESMTP id C4D6569CB3 for ; Thu, 7 Aug 2008 08:10:08 +0300 (EEST) Delivered-To: xxxx.klub@gmail.com Received: from gmail-imap.l.google.com [66.249.91.109] by mindcrime with IMAP (fetchmail-6.3.8) for (single-drop); Thu, 07 Aug 2008 08:10:08 +0300 (EEST) Received: by 10.142.237.21 with SMTP id k21cs34794wfh; Wed, 6 Aug 2008 13:40:29 -0700 (PDT) Received: by 10.100.33.13 with SMTP id g13mr1093301ang.79.1218055228418; Wed, 06 Aug 2008 13:40:28 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com with ESMTP id d19si15908789and.17.2008.08.06.13.40.27; Wed, 06 Aug 2008 13:40:28 -0700 (PDT) Received-SPF: pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) client-ip=199.232.76.165; Authentication-Results: mx.google.com; spf=pass (google.com: domain of help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165 as permitted sender) smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Received: from localhost ([127.0.0.1]:56316 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1KQpo3-0007Pc-Qk for xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:40:27 -0400 From: anon@example.com Newsgroups: gnu.emacs.help Date: Wed, 6 Aug 2008 20:38:35 +0100 Message-ID: References: <55dbm5-qcl.ln1@news.ducksburg.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Trace: individual.net bABVU1hcJwWAuRwe/097AAoOXnGGeYR8G1In635iFGIyfDLPUv X-Orig-Path: news.ducksburg.com!news Cancel-Lock: sha1:wK7dsPRpNiVxpL/SfvmNzlvUR94= sha1:oepBoM0tJBLN52DotWmBBvW5wbg= User-Agent: slrn/pre0.9.9-120/mm/ao (Ubuntu Hardy) Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!feeder.erje.net!proxad.net!feeder1-2.proxad.net!feed.ac-versailles.fr!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail Xref: news.stanford.edu gnu.emacs.help:160868 To: help-gnu-emacs@gnu.org Subject: Re: Learning LISP; Scheme vs elisp. X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Users list for the GNU Emacs text editor List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org Content-Length: 417 Lines: 11 On 2008-08-01, Thien-Thi Nguyen wrote: > warriors attack, felling foe after foe, > few growing old til they realize: to know > what deceit is worth deflection; > such receipt reversed rejection! > then their heavy arms, e'er transformed to shields: > balanced hooked charms, ploughed deep, rich yields. Aha: the exercise for the reader is to place the parens correctly. Might take me a while to solve this puzzle. mu-1.6.10/lib/testdir4/1252168370_3.14675.cthulhu!2,S000066400000000000000000000016331414367003600204500ustar00rootroot00000000000000Return-Path: X-Spam-Checker-Version: SpamAssassin 3.1.0 (2005-09-13) on mindcrime X-Spam-Level: Delivered-To: dfgh@floppydisk.nl Message-ID: <43A09C49.9040902@euler.org> Date: Wed, 14 Dec 2005 23:27:21 +0100 From: Fred Flintstone User-Agent: Mozilla Thunderbird 1.0.7 (X11/20051010) X-Accept-Language: nl-NL, nl, en MIME-Version: 1.0 To: dfgh@floppydisk.nl List-Id: =?utf-8?q?Example_of_List_Id?= Subject: Re: xyz References: <439C1136.90504@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439B41ED.2080402@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439A1E03.3090604@euler.org> <20051211184308.GB13513@gauss.org> In-Reply-To: <20051211184308.GB13513@gauss.org> X-Enigmail-Version: 0.92.0.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-UIDL: T To: Bilbo Baggins Subject: Greetings from =?UTF-8?B?TG90aGzDs3JpZW4=?= User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO) Fcc: .sent Organization: The Fellowship of the Ring MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's write some fünkÿ text using umlauts. Foo. mu-1.6.10/lib/testdir4/1305664394.2171_402.cthulhu!2,000066400000000000000000000011561414367003600204020ustar00rootroot00000000000000From: =?UTF-8?B?TcO8?= To: Helmut =?UTF-8?B?S3LDtmdlcg==?= Subject: =?UTF-8?B?TW90w7ZyaGVhZA==?= User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO) References: 1n-Reply-To: MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test for issue #38, where apparently searching for accented words in subject, to etc. fails. What about here? Queensrÿche. Mötley Crüe. mu-1.6.10/lib/testdir4/181736.eml000066400000000000000000000040741414367003600160110ustar00rootroot00000000000000Path: uutiset.elisa.fi!feeder2.news.elisa.fi!feeder.erje.net!newsfeed.kamp.net!newsfeed0.kamp.net!nx02.iad01.newshosting.com!newshosting.com!post01.iad!not-for-mail X-newsreader: xrn 9.03-beta-14-64bit Sender: jimbo@lews (Jimbo Foobarcuux) From: jimbo@slp53.sl.home (Jimbo Foobarcuux) Reply-To: slp53@pacbell.net Subject: Re: Are writes "atomic" to readers of the file? Newsgroups: comp.unix.programmer References: <87hbblwelr.fsf@sapphire.mobileactivedefense.com> <8762s0jreh.fsf@sapphire.mobileactivedefense.com> <87hbbjc5jt.fsf@sapphire.mobileactivedefense.com> <8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk> Organization: UseNetServer - www.usenetserver.com X-Complaints-To: abuse@usenetserver.com Message-ID: Date: 08 Mar 2011 17:04:20 GMT Lines: 27 Xref: uutiset.elisa.fi comp.unix.programmer:181736 John Denver writes: >Eric the Red wrote: > >>> There _IS_ a requirement that all reads and writes to regular files >>> be atomic. There is also an ordering guarantee. Any implementation >>> that doesn't provide both atomicity and ordering guarantees is broken. >> >> But where is it specified? > >The place where it is stated most explicitly is in XSH7 2.9.7 >Thread Interactions with Regular File Operations: > > All of the following functions shall be atomic with respect to each > other in the effects specified in POSIX.1-2008 when they operate on > regular files or symbolic links: > > [List of functions that includes read() and write()] > > If two threads each call one of these functions, each call shall > either see all of the specified effects of the other call, or none > of them. > And, for the purposes of this paragraph, the two threads need not be part of the same process. jimbo mu-1.6.10/lib/testdir4/encrypted!2,S000066400000000000000000000045231414367003600170020ustar00rootroot00000000000000Return-path: <> Envelope-to: peter@example.com Delivery-date: Fri, 11 May 2012 16:22:03 +0300 Received: from localhost.example.com ([127.0.0.1] helo=borealis) by borealis with esmtp (Exim 4.77) id 1SSpnB-00038a-Ux for djcb@localhost; Fri, 11 May 2012 16:21:58 +0300 Delivered-To: peter@example.com From: Brian To: Peter Subject: encrypted User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:21:42 +0300 Message-ID: <877gwi97kp.fsf@example.com> MIME-Version: 1.0 Content-Type: multipart/encrypted; boundary="=-=-="; protocol="application/pgp-encrypted" --=-=-= Content-Type: application/pgp-encrypted Version: 1 --=-=-= Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.12 (GNU/Linux) hQQOA1T38TPQrHD6EA//YXkUB4Dy09ngCRyHWbXmV3XBjuKTr8xrak5ML1kwurav gyagOHKLMU+5CKvObChiKtXhtgU0od7IC8o+ALlHevQ0XXcqNYA2KUfX8R7akq7d Xx9mA6D8P7Y/P8juUCLBpfrCi2GC42DtvPZSUu3bL/ctUJ3InPHIfHibKF2HMm7/ gUHAKY8VPJF39dLP8GLcfki6qFdeWbxgtzmuyzHfCBCLnDL0J9vpEQBpGDFMcc4v cCbmMJaiPOmRb6U4WOuRVnuXuTztLiIn0jMslzOSFDcLTVBAsrC01r71O+XZKfN4 mIfcpcWJYKM2NQW8Jwf+8Hr84uznBqs8uTTlrmppjkAHZGqGMjiQDxLhDVaCQzMy O8PSV4xT6HPlKXOwV1OLc+vm0A0RAdSBctgZg40oFn4XdB1ur8edwAkLvc0hJKaz gyTQiPaXm2Uh2cDeEx4xNgXmwCKasqc9jAlnDC2QwA33+pw3OqgZT5h1obn0fAeR mgB+iW1503DIi/96p8HLZcr2EswLEH9ViHIEaFj/vlR5BaOncsLB0SsNV/MHRvym Xg5GUjzPIiyBZ3KaR9OIBiZ5eXw+bSrPAo/CAs0Zwxag7W3CH//oK39Qo1GnkYpc 4IQxhx4IwkzqtCnripltV/kfpGu0yA/OdK8lOjkUqCwvL97o73utXIxm21Zd3mEP /iLNrduZjMCq+goz1pDAQa9Dez6VjwRuRPTqeAac8Fx/nzrVzIoIEAt36hpuaH1l KpbmHpKgsUWcrE5iYT0RRlRRtRF4PfJg8PUmP1hvw8TaEmNfT+0HgzcJB/gRsVdy gTzkzUDzGZLhRcpmM5eW4BkuUmIO7625pM6Jd3HOGyfCGSXyEZGYYeVKzv8xbzYf QM6YYKooRN9Ya2jdcWguW0sCSJO/RZ9eaORpTeOba2+Fp6w5L7lga+XM9GLfgref Cf39XX1RsmRBsrJTw0z5COf4bT8G3/IfQP0QyKWIFITiFjGmpZhLsKQ3KT4vSe/d gTY1xViVhkjvMFn3cgSOSrvktQpAhsXx0IRazN0T7pTU33a5K0SrZajY9ynFDIw9 we7XYyVwZzYEXjGih5mTH1PhWYK5fZZEKKqaz5TyYv9SeWJ+8FrHeXUKD38SQEHM qkpl9Iv17RF4Qy9uASWwRoobhKO+GykTaBSTyw8R8ctG/hfAlnaZxQ3TwNyHWyvU 9SVJsp27ulv/W9MLZtGpEMK0ckAR164Vyou1KOn200BqxbC2tJpegNeD2TP5ZtdY HIcxkgKr0haYcDnVEf1ulSxv23pZWIexbgvVCG7dRL0eB+6O28f9CWehle10MDyM 0AYyw8Da2cu7PONMovqt4nayScyGTacFBp7c2KXR9DGZ0mcBwOjL/mGRKcVWN3MG 2auCrwn2KVWmKZI3Jp0T8KhfGBnFs9lUElpDTOiED1/2bKz6Yoc385QtWx99DFMZ IWiH5wMxkWFpzjE+GHiJ09vSbTTL4JY9eu2n5nxQmtjYMBVxQm7S7qwH =0Paa -----END PGP MESSAGE----- --=-=-=-- mu-1.6.10/lib/testdir4/mail1000066400000000000000000000027721414367003600154720ustar00rootroot00000000000000Date: Thu, 31 Jul 2008 14:57:25 -0400 From: "John Milton" Subject: Fere libenter homines id quod volunt credunt To: "Julius Caesar" Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com> MIME-version: 1.0 x-label: Paradise losT X-keywords: john, milton Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Precedence: high OF Mans First Disobedience, and the Fruit Of that Forbidden Tree, whose mortal tast Brought Death into the World, and all our woe, With loss of Eden, till one greater Man Restore us, and regain the blissful Seat, [ 5 ] Sing Heav'nly Muse,that on the secret top Of Oreb, or of Sinai, didst inspire That Shepherd, who first taught the chosen Seed, In the Beginning how the Heav'ns and Earth Rose out of Chaos: Or if Sion Hill [ 10 ] Delight thee more, and Siloa's Brook that flow'd Fast by the Oracle of God; I thence Invoke thy aid to my adventrous Song, That with no middle flight intends to soar Above th' Aonian Mount, while it pursues [ 15 ] Things unattempted yet in Prose or Rhime. And chiefly Thou O Spirit, that dost prefer Before all Temples th' upright heart and pure, Instruct me, for Thou know'st; Thou from the first Wast present, and with mighty wings outspread [ 20 ] Dove-like satst brooding on the vast Abyss And mad'st it pregnant: What in me is dark Illumin, what is low raise and support; That to the highth of this great Argument I may assert Eternal Providence, [ 25 ] And justifie the wayes of God to men. mu-1.6.10/lib/testdir4/mail5000066400000000000000000001322261414367003600154740ustar00rootroot00000000000000From: Sitting Bull To: George Custer Subject: pics for you Mail-Reply-To: djcb@djcbsoftware.nl User-Agent: Hunkpapa/2.15.9 (Almost Unreal) Fcc: .sent MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: multipart/mixed; boundary="Multipart_Sun_Oct_17_10:37:40_2010-1" --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: text/plain; charset=US-ASCII Dude! Here are some pics! --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: image/jpeg Content-Disposition: inline; filename="sittingbull.jpg" Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD/4QvoRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB AAAASAAAABsBCQABAAAASAAAACgBCQABAAAAAgAAADEBAgAOAAAAbgAAADIBAgAUAAAAfAAAABMC CQABAAAAAQAAAGmHBAABAAAAkAAAAN4AAABndGh1bWIgMi4xMS4zADIwMTA6MTA6MTcgMTA6MzM6 MzcABgAAkAcABAAAADAyMjEBkQcABAAAAAECAwAAoAcABAAAADAxMDABoAkAAQAAAAEAAAACoAkA AQAAAMgAAAADoAkAAQAAAGsBAAAAAAAABgADAQMAAQAAAAYAAAAaAQkAAQAAAEgAAAAbAQkAAQAA AEgAAAAoAQkAAQAAAAIAAAABAgQAAQAAACwBAAACAgQAAQAAALMKAAAAAAAA/9j/4AAQSkZJRgAB AQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwc KDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIy MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACAAEcDASIAAhEBAxEB/8QAHwAA AQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIh MUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpT VFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5 usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAA AAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEI FEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm Z2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK 0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDq77xdrX/CQ6laRXjRxQTF ECovA/EUg8Sa6W/5CUuP9xP8K5yWQnxjrw9Lwj9BWjkgZHFAG6mu6yV51OXP+4n/AMTUq61rBB/4 mU2f9xP/AImsJJTuAJFW0YDnfmgCTUPFGqWFq882p3G1eyqmT/47VfRfGGpawkgGp3CyIeg2cj1+ 7XK+O7zybCGMNjzHyR6gD/69ZvgG8zqU67vvRZH4EUAesJe6m/XVLv8ANf8A4mpf7Qvl/wCX+6b6 uP8ACs+ObKdaeh3Hg9aANTw/4gurjxTLpU7tIv2cTKzHpgkH+n5UVheHGI+KWzJwdNP/AKFRQBzD 7f8AhMfEDEHH24j/AMdWrs0oCkDrVKJs+NfEsZ+79u/9kWrd5GqKTmgCstwwkyT0p5uzu61mOzbj zSFn3DmgDB8ePLPe2MEQZyykhRzk9/5Va8D6Vd2Mz3d3CYxJHiPd16+la0hhMybkUvxhj1HWr5uM uB0wMYoA3YJARjvV+DBPasC2lYsOuK3LVunWgCLQRj4sIPXTGP8A4/RSaF/yV2P30tv/AEOigDmY QD408UE9ftw/9AFXpv3iFT9Kgs4t3jXxV6C+H/oAq5cxkMcCgDFltXVyMVVv7iGwtzNcNsQfiT+F a8jbAxdgFAzmuTZZfEV81vG+xTyX/uIPT3P9aAIBr9vdNHcQI/lxk5DDBrfsLuK+jE0MqupPOOx9 KzNY8L6fbaaYrdGXb3BOcnuT+FcpodzN4c8RRRyylrW4IDE9MdM/UUAes2wbOAK27PhRms6CJlwc VrWowRkUAV9CP/F3YffSm/8AQ6Kfo+P+FuWp9dLf/wBDooAxrH/kd/Ff/X8P/Ra1evUOcgVW01Qf G/izIz/py/8Aota2LqPK4xQBxniWc2mi3MxBGFA/Mgf1rmtEF/Z6HNqMNuzvPnY+7G1V6Hoe+T0r qfGmnT3Xhm8WNWJVQ+B/skH+lUPBt3d3PhuzXyBM6xBY0YfKDnALewxmgDE1BfEDaPaXNzMRJPIQ +TjCgDHb69u9ZGt2Us2lrdNDtMLAgq27Kng84Fd74qnaMwWB8qWRTnzUcfePGSOx4ziuf1kzT6S9 tuRHlVUG5sDJOMA+lAHofh5/tvh3T7k4ZnhXcfcDB/UVuRQEdqzvDelPo/hywsJGDSRRjeR0yeTj 2ya3I8/3aAMXSU2/FmzJ/wCgbJ/6FRUunf8AJV7H/sGy/wDoQooAyNJXf448XYPS+X/0Wtb8ynyj 0rm/DIll8W+KDKQ0pvF3FehPlr0rvINMzbfN8rsc7upH0oA5ie3mktZSI1ICn5W43e1ec6ZrDwax facIj9liUNtUcgE8j0IzXrHiqS20rQJbiadoyBsWQjc2T2HvXnvhbREuzeXTbvMlfILcsF6D6jFA GJr+pWE1ymFkwFzhlwo+i1xevazLd3Fva2+UiQhh7kdPyr0jVfA8t0BeXNybe35UK2EJAJwST/QG uS1Pw7HYalbKHUIxYxyDd8wHUnNAHsnhXVBrGhWkrBlmEYVww6sAATXQInA5rn/AOZtIa3mQHZI+ xwfvAnJ6d8n9a6yazEKhlzgUAc1YAr8WbH302X/0IUU6xBPxYsSe2my/+hUUAV/Bdj5fi3xWJJDJ JHeopY8bj5a5OK9AUArwARXEeFjjxh4xbub5f/RYrsIZgJhGTjcuQMGgDnfHiwnw1KJoVkUuB8yg hfeuZ+HemTLpjx3OCZNzKUbPy54/Sut8Z263OlJE1wYgzkkjvgH86yfBb+XYWuIGiEithWzn9aAN loTcO0ctuGjV9oMg5JGCSOOnp9K8/wDH1qH1iERrukRAqqB3Jzj9BXpsk6F+oyCuRjJ54rhNcg+3 Ge5XiUSL5ZGc87sdPagDQ+HlvJHoAdo9h85mUY7dK7WSRCoB6HiuV8IiW10JYs7yszDJ7fN/k1tG Rpb4xj7qpnj3Iwfx5oAwLMgfF+1UHI/suTH/AH3RTLJNnxltx2Olvj/vuigB3hgf8Vp4vH/T8v8A 6AK6aRWFk2CA2CPSua8M4/4T3xcp/wCftD/45XR6q32e1JjUySCRdqA4J3HH9aAKHiJTceH4mliK r5e5lDfMpx2Iqp4eQR6Zp75Y4jX7xyfTn8q29djjbS/LMqxYGFdugNZWlskOh2pKgYj2AqO4OB/M 0AW7+NLQ3Fwi/O6hsk5yRwOO3WuS1qGJtNuvN3iNJkX5e+EIxn8f1re1e4ubq8jSOMiBArZJ/wBY xOcfQcZ+tVNTsYh4dnjmG9PMJIP8XYUAQ20z2Hg6OeJGTYQzd+N3Le+RzXQ6TGwtjLLkuxAy3XAH f8Saw9Mlt7vwsI4yZI9m07xtyM/y5rqodqxIFAIx1oA5iDj4w2ZHfS3/APQjRSw8/GGzx20x/wD0 I0UAee+I/GV/4S+IXiAWlvFKJ7gM28njC+1Ubn4v6xclC1hbAq6vwzdjn+lXviB4X1O88b6lPBYX EkUkgZXWJiDwPQVzH/CH61zjSbwj1EDf4UAbV78YdZvYPJbT7UA+7HP61HbfFXXLW3SFdOtSqZ67 v8fesg+Ddbzn+yL3P/XBv8Kcvg3Xc5Oj3x/7YP8A4UAaY+KuvIQP7PtM5JXKt/jUF78Udcu7F7WS ytEVv4grZHPB61VPg/Ws/wDIGvs9v3T/AOFMPg7XcHOk32P+uD/4UAWLb4l6vb2zQJZ2m1gP4WGC FAz19q17f4va0sSobS04GB8rf41z3/CIayOuk3g/7d2/wqRfCWr8f8S27/78P/hQB33w78Q3fib4 jR3l3HHG6WTxgR5xjOe/1oq78JvCmo6dq8+qXUBhhETQqJAVYsSDkA8496KAP//ZAP/bAEMABQME BAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4k HB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh4eHh4eHh4eHh4eHv/AABEIAWsAyAMBIgACEQEDEQH/xAAdAAABBAMBAQAAAAAAAAAAAAAH AwQFBgACCAEJ/8QAVhAAAQMCBAIHAwYHCwoGAgMAAQIDEQAEBQYSITFBBwgTIlFhcRSBkRUjMqGx 0RYXQoKSssElJjNDRFJicpOi8CQ0NmNzo7PC0uEnNVNUVYMJRWR08f/EABQBAQAAAAAAAAAAAAAA AAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDqjNudMEyw6lvE3HEqUnUS kDSmeEkkCTB241Aq6X8mgSL5J/PT99DPrX3S232GwohPbp5/6v8A70PMsrSuwYKgkgtjiONB0Ovp lykFQLhJ8+0Fefjoyh/6/wDfFBctsJWkpt0wRzSK9ft7cwVMNmf6IoDOOmjJ5/lB/SFejpmyeT/n I/TFBdFqwACGGR6IFOWbRhZ3Ya349wUBgT0y5QP8qT+n/wBq3T0x5OPG8A/OFCy3tbZCRpt2j+YK cotLMjvWjM/1BQEs9MWTR/LUn0VWfjiyb/72hki1sw5q9kaHL6ApZDFspQHsze3DuCgI6umLJqUg +2Eg+AJ/ZSR6aMmj+UOfon7qozjTACUhlsCP5opo402AQWkeumgIK+mrKAA0uuK8e6R+ykj03ZU5 JePx+6h6gaTGlPHmkUwzDjlrhDKVvtIWXVaUIjj4+6gKKOm7KZUQrtU+YCj/AMteHpuyvvpbeV7l f9NAHEc3Ls8ed9iYZLTZIAI4+vpRAy1i7WKYS3dhtIDghQHIjiKC9npwy3O1rcH3K/6a1V034Ef4 OyfPqlf/AE1WG1KKxAAHhFO0LgTCfhQTf47cGPCwuJ/qr/6a9R004as7YXcn0bX/ANNRLbqhsAOF OWnVgEnh4UEm30wWbhhvBL5e3Jpz/ppwOlMLSC3lzElT4suD/kqHN642YT9tIu3T7h2UYHnQWW36 S9YleXcQA8Qhf7U05b6SMPXiFpYGwfZfu19myl9XZ6leEkR9dVBy7XEFXLcUN+mPEn7VnCrplZQ6 zclaFeCgAQfqoOqbO4L6VBxvsnURrRqConcbisqo9FOYE5mw1zGENdkLllpZQSCUkApO/wCbWUAY 63i9N0x3v5SkHy+aqj5QBXhFqRzbFWrrjrWm5a1ER7Unh/saqOSXP3Bst/4pNBbWhLY1nvCt1NyZ mRWtuoKESCOdKjSOB99BiEjYHYU4ZQpKpTwpArTxpVt8g7RQPmlQkpP2UoFE8VkU0buEkK4A+NbN rCzHDagfQCmN69TtsPjTRLoBA1b0oHFaZPwoH6IPODFJPwOG9asklJJ228a8egp9aBspcHhvQf6T b55eZHWVu6m2kgISDsnaT76Kt46hlpx1WyUJKj7q59xzEFXV9dXC1krccK9/M0CybtYUTrMnj50R uiPGLl69OHBTaWG2isgjdRkffQgFyoqO441eOiW4WM1W8D6aVBXpFAfGCVGQmnDZO07jmaYW74G0 yKfNuJUBB50D0ISAIAiNyK3UUkQIpDtgUhAAIApNToAKQACedAotaZgCfOvQrUCAIpsVAma31kEE bbUG60Dwn1oW9O6S3h2HqnYvK291E1SlEkzvQr6f1qRg1gVHc3B4+lAaeq2rXkFhcz3NPwcXWU06 pzqnchtQ4CkBQjT/AKxVZQDXrpOEXLJO0XYj+xFVDI6lHAbEeLSfsq1dd2UPWyxMG7HHhPZCqtkX /wAhw9Svo9gj7KC4Wx2MCKdt7ncTTaxbGmBz3p4hISNzHhQJrQSYnjXiUkQOYpZzupJJpFK5g7n3 0ChQSkbxSluSFDfam5WACokj1NetPAqA29KCXb0HdQ9TSwQlSduVR6XBpJnetkXWgCTQSTQCRpMk Uk8djzpsm9ExNaOP6pOqBQRWcblNtly/eI3SwoTPiIrnbEXAl7j4UaulrEHLbJ9z2ZEuKS2fQneg Fevy62ASQYoHCHkqXsqrl0U3aWs52mpelLgU3v5jah20/C9zO/hVmyc8Bj+HjfvXCB9dB09bq0mD zqQZUkAVDtriOdO7ZzSYO8UEoHBw3r0KB3mm6XUxERWdpO0UDhJBVvSulKh9kU2bUdzvvSzKu9tI mgXShOjfaTQk6xZ/cOw4GLk7/m0XlqlGxEcCDzoP9Y1KhgNhI/lR/VNAWuqGpJyG3pBHdVPn84qs rTqgqJyKyCI+bXHp2qqygH/XoQEW9i5M6r0CJ/1VVnJKP3Aw8QP4BvYf1RVh69K1lm0BMgXyQPL5 moPJ50YDY+TCP1RQW+yRoaC44HhSygNXe99NbW7lCUKA48K3cdgbk8KDa5IAMcKj3XiklIMTSrj0 zCppi8STwNAo5cEo4RXtq53pn1pke8NIUR5UoydWoA+lBMdrCdjSLr5G8yfCmJdWE6ZpNx2NiZoH 4uZjlThKitJ35TUOl4DntUlZOgoiaAe9Nl+tnDbS1QAULdlzxHgftoMLdm4VzAmKKvTsvsrywUqS y8y42seBBBB9xoSWTa7hVwocGmyrh7qDZpUKFXzoptG77NdoFkwiXD7uFUvCMOu7+5SxbNLccO8A TRd6Nsq3eB5mbVezq9l7ThsCTEUBcQvhtuKdWy0qVJ40wS4NqcNOTvA+NBKpMokRSiFKnx8opo07 KAAYNOGwrko0DlB/JO005Y0gTNNW9pkzTm3SDJJoHIQhIlKZ50IesgFDLdgYgC74/mmjC03KaEXW WBTlmykH/Ox+qqgJnVAV+8S3hMbOA78fnVVlZ1Oio5BbnkHI/tVVlAOOvSIbtlSN8QSD/Y0yyayh 3Ldg4BBNs37+6KedfERaWqo2+UE/8E1FdGdwF5Yw0kgzatmPzRQWdhnRClCTWPAEHeKkENodbSpO xpBy3IJJjegjFwjh6U1eWSoxw50+uGQHPE86j7qEqiY9KBstYSe6B61gfLapB5QaaPOQs77V4twR wNA8TcFyPKsdd25UyS6RBAivXXJSDQOm1jyinlq/CoSY22qDDh1AVI22mUqmNqCpdI+GXOZb9Ng1 I9jZL+qNiVSAPiKiMhZQFixcHGGgkvJPaJJ4JBolsKaTdO9zdTcKV4CdqalLSrtevcQEkeVA7ytl /CcOm7sLVKFugb+VSt06k3WgfSSO9HKss30NsagAkAd0GminCXSr8onegfNKSDO/nJp22tCSDUYF xEJpVDkkwDPrQTTCoGxBmnjDp4KG9QVu4rVxjepS3WY1KVPpQSbSiT508tBvBImo9hJICt96f2yS TwIigkWht6UHes8ojK9mJE+2Dj/UVRhaEpn7aDnWhH71LP8A/uj9VVATupmZyCiePzn/ABVVledT OfxfszG6XYj/AGyqygHfX3A+TrPmTiKZ3/1JqvdFX+h+EqPO2TU91+NrK1Mf/sER/YmoLopSpWR8 II/9sPtNARbNSdAKdzHCtnY0Eq4zypnbFaEAqImnRUFt6p93OgjbyEEn6qhrxQUo+FTGIp7m23rU HeGKBi6E6orRcRtWyiSoxWp4ERQeoAI3NeLSnnW7Q2341s5skcOMUCSEpMk08to0gA/Gmu4HCvW1 qoPMQWtOJIDR/I3HiKyycacfU4o86Y4qpxBcutiUphIpLLy1LGtSN1RsTwoLS0SQpx3uoG4HlSHa gq1JMSa9ullVulJAA5+dN0wYiKCQbcJ+lNLoVzmmjKoEmndudSoVAHpQOrbVq3mIqWtYME7Co9oE xHAeFPWVqCQY250EsyuBsSPWn7C1CI4+tRdq4F7gjzFSduoCBHoKCRYUYAG1CHrQJjKFqqB/nqf1 FUWbck96NO3jNCXrPmMm23Ha9R+oqgJPUvM9H6PLtR/vTWVt1MkoT0eMaQAVpdUYPPtSPurKAb9f ZQNkwnf/AMwR/wAA1GdD7QOQMHUeduPtNSfX3H7n2y4mcSSPT5g1G9D69XR9gxCeFvHHzNBczGkR tWqXIBjlW4HdO0U1XqQsGOe9ApcAKTHEGoe7tyJqWWSBv61roQ6nvCgrD7cEyPqpNCYn0qavrMhW w5U1NsYAAoGLAEmaUcAinItSZ8R4VjjJ0Qo+tBHK+NeoI0kVXs3ZptMIS42wk3FwB9EGAPU0KMWz zma6uCtm67BoHZLSQEx6nc0BdzRfBq0UhKu8BwFI4BchttK1EyrlPCqjhmJOXlrbquHUPOrbBJJ5 8+FaXC32NTguLi2MmCYU2fLjPxoCel0vDUFFQPCnSAQaH+Vc625fRhuKpbtX+CHAruL+6iC0QtKV JUFA8CKByyFE8KeI2CTzpG2E8h8acpQSRCRH20DxlRMGPqp2zOkJB2prZtlRIg/Gn7bSUwNW9A+t BAO4B8Kk7UeJmo20SUkmeHlUnbK2G1A+aiZjfxoS9Z8k5Ltwrleo4f1VUV0d6IG/OhR1mkg5HZkz /lqP1VUBI6l5H4vmRJOzvHl86aykOpOf3iEb/Sdj+0rKCgdfUj5NtxzGJI9/zBqP6Gkj8XuDDn2H /Malev6lHybakQD8oNg7f6hVMehVIV0dYMqJhj/mNBcm29Q24U3u2gDtUohvS2dtzTO6H+IoI93d PmBTcq0xBmnDoM7b7U1KZUQCBvQLKcQtuFRSCwAmBFIvBxJ2MGaVAUUhQIngaBNUg7kVV81YoUhV rbuweCiOPpUxmC9Rh9it5Su+dkDmTQ9Nz2q1PrO5JCd5JNAxxyztBaOOXyktsNp1rUefqaH7dpc5 hvlDCLJabVJgOLG5H2D0q7YhhV5nDFrfBLYrTbA9reOAwAkHYep/YKL+X8qWGHYe1aWjCUBtMDQn c+cmgDtvlq7scEQ2grTdtnVqKdhVXx3EscYTpumoIkdogbKHmDXRWJ2NotlxpbjRUOYVJ9N6G+bc Mt3EdghaFx9IAUAXfxB25bIe0haOHd+qiR0QZ1dRdIwXFXdbayEsOqP0T/NNU/MuXH7Qret0KKUn dIE/CoCxUW3UnfcyCOINB2BaiRI8Nop+0gxP21R+iPHF45gCUPq1XVsezd347bGr8wlQERQLWp0r Gw41IoAKp2k0xQkg/Qnwp9bSYJSZFA7YSfjUhbN6uMAimTBIUCakbZW44UD1hkJAoUdZ9pKMgNqH H25H6qqLTR7oPOhZ1ngD0dSOIvGz9SqC4dSZJGQyeRU7H6YrK36lBH4vk7flvQfzxWUFE6/oBwm1 Vz+UWh7+wVTboPSR0cYKkCf8nk/pGpHr/o/e9aLjf5SbH+4XTPoOUk9G2CyCT7PwA/pGgvaR3eAN M71E8qkkJBRsDvTa7QNBFBCuoHGKaqbGuZEU/udIERvTbUmSNO9A0ukjSTG9Nm1BC5PDnUg8pIHC ozEn0tMLcCeCSfqoB9nrElXOLi2bJ0IOkeE86rVzcBDKnAoBLRgCNyfL/HjXmO4koPaoPaOEmf5s 86aYHbPY7mmywdk6mmyHbjwKZkz60Bl6NcGRhWX0XDrWm6vPnnIHeAPAe4VZx2iEFSVrKYgpI0/X UJeYuzhrae0Qt9SANmwdIH7ajh0j4WrW204dYEEE8NqCXW6+7dP2yUMNnTKdZJ24cao2ZGCVO9ol tzcmUnh/j7qXTm3tbC8xdcKQnsWRJ7pIUoq+qKqeMZ3YUp9xxxsJcUCQngOJ2+NAmptDxKHEqSCN vGhnnCxOHY8sISEpe76YECecfb76J2GXDV8wLj2a4DDhlDikEA+h8Kr/AEk4St7Chdtd4sHUI4xz Hw391At0JY38mZntmnHdLV38y4nlP5J/x4102ywVAEbiuLMDfcYeauGjpUlQUDHMGu1MpXPyll+x vkbh5hKifOKBZtuCRoiKWbQoGYpx2KkmYO/lSiGjI2EeYoMaQSSDTplJA2rxtJG1OmUgD/tQKM6h 50MOsv3ujlwgzF02ftopJMAAxt5ULeslKujq5I/9w2frNBb+pM6Dkfsuep8n9NH31lJ9SUfvOBgb F/cf10VlBVuv7q/B61g7fKTW3/0Lpn0IjT0d4IN/82HPzNO+v8Vfg/apkR8osn/crpr0HEno5wUE cLeJnzNARGSCmCD8ab3IBkAcqctjbhwrV1oqTMAUEBdpABnemRAHvqYu7WASQDNMOw7xAKfHjQMn YjvDaq/m64RaYFevcNLSj9VWp+3IEyKo3S8U2+RcSXIkthI9SQKAKXV4u5ue21BLY3JngImrb0S2 TrmCXuYVuKb9pfUkr0nZpA4Ty3maHDSXsQLOHW/8PdOJZbHLvH7AAfjXUOW8rWWGZbssMTZtutMt BGpbQMniVRE7mTQDzMfSJctYXcpwbLq30MoSFP3KinXO3cQBJ9SRQpu/lvE8WS+jCQXXVAyylaUu T68TXQubsNNpZK1YwLVsDV3mSSPSFCoPJeTH3rv5dvO2UiYtu1RpKxH0o4xvzNBHXuX0WnRpcGzQ supWlSyqZMjccfGN6FGG2+Iv4u5cW+ALxNLSoaZUfmwoc1Abq9Jrq3GsPtrbI12gcFNHaI3oJW9q 7Z9qbEpSpapg+NA2ezHnJ4rZxLCrZLVshHYWzdutBXPECCoAjzqRtlKxNpxp22U0tSTLS4kCPXen +XLW5xR2EN3iXE/SCmwUj3wD9tTSsHLPdUtK1cYiaDnq1bXa4jcWLuy23FAe7b7q616v1yb/ACFa JMyySg78INc19IViLHNAuG0hPa94jzTxo79Vq/DtjiWHDcJWHk+iuXxFAXrls7xO1etJkCCaevNy NXOvGGxG6QDQaIbM8AactohMRWyWyD605aSInwoGwTIgCD40MOsa3/4cXh8Hm/1qLSkhMqNDHrHN T0Y38Dg43+sKCd6k4H4EHxDj36yKyvOpTIyZpJPF79dFZQVLr/T+Dtt4DEmf+Cuk+g1uejfAzEk2 wn4ml+v8EjK9sYknE2Y8vmXKT6BQ4OjTAyT/ACfh+caAiBoDYCaxxOlAGnjSralQJg7V4+sdmBtQ Rd2gaZM7nwqLdShCzE1NXS2wmJmot5CSdSQregY3KthE+kUNOnjtF5Hf7PVHatlfoFCim+hOkwDI 86p3SRhyr/Kt/apaKipklIHGRuKDmjKV81ZZuwi6cWSlNzpE8BtE/EiuqLbMPtFi2WVDVA2rjXEy 5bX1skApW2AQPA6ia6awJQNvavpSZWgKI5EEUBLwmwbxFSHMQYZd098FSQQD761GMWuK45eYPhYT cKsUJNw4ncIKphI84BJqs5hzKcPy4+q0lVypISNIkk8gPEkmKnsuZCvMHyEi3sMQFnmC6V7Re3Rb 16nFcUnyTwHp50Epmxm3ssnrYeUe0uEHSIoIYg0cKS9cOW5Uw1ClqHEJ5n1q59JWKYhh7tthN0+u 7et2NetKY1pHExyoft4lfYnir1u6sPWL+mUqT9HxE+HxoLhaXS2sND9k5rbdROpJ2INa212OzK1L nn3qhMtMPYU67hThKmEElon+YeHw4e6nFxPblQBKPyY/x5UAt6VLwPZoba3DYbn3knf6qLXVGbcV e4u8QezS02kEjmSTFDHNuXcRxnNDKrG3U4SnRJMSZ/711B0I5OcyhktuxvA37a4suPqRuJ5CecCg ujoEQfCtmOI2rZ0d2vEpEbcKBy2AVcvfS6UgbDnTNJPnSrRK5UmRQKqC9ESDvyoY9Y4q/FlfiQfn G/1hRPWogaYihh1joPRhfEj+Mb/WFBMdSon8DgDyL8emtFZWvUnCRlFYSRIL0+utP7IrKCr9f3/R q2PH90mR/uV170EKKejLAxAP+TbfpGvOv+CMsWpPPEmY/sV1r0Bk/izwPcx7Od/zjQEhlRUNk+6l XEymY5UmyDp4x50uggpHemgj3mZSSRA8aadmEqAVFS9yfmzHhUU8UzvyNB4tpBG4NMLy2YeZWFN8 QeNPA4QdMkT9VIvOHSUhQ3oORulXJl7a5tvbq2tyq3U4FIQBvBAO310bMBsQcKw+4ZIIS2kKSR5V KZ9sPaWm1WqW37i2lSxH0gBBFUTLeamLW+Tg97cBt9StSEcAB/N9aAws4LgLCbTEbrSi3aWLo61d 1Kk7ifQ7+6vXeke2uLdCsvYLf46tThSk2zKuzBH85wjSBVeTeu3rCMPNom/tnFauyUdKfzvETyqb fubpFs2wthtCUCENsSAPACKCk50xPMvt72L4nkt4Xa2SygtuIUkIIjeFHkfKh5huMWdk+W73Bb2y UDu6WSpER4iYq45rwS9duV3Nz7Ssap+ceJ5cAJquM9o2QEIJ7wkKPCgnsHxCzxa1F0w6haSkplJ5 UjdH5tdwe4gCRJj/ABzpo0lq3X2rbKGVLBBKe7J9OdUnpQzULWx+R7R3/KHk6VkH6CefvNBMdEmc 13PSMm1u3W12C3VBgFIlJ8Z84+uuvLIzaoKtyRXzpy7fXFjcpWypTa5ACk7Eedd99HmJt4zlHDbx D6XlKYSFrTwKgN/roJ1xHdFY2J3mt3EmIrUCO7FAqhAVsRtThIAiCD5UigGCZO1bpSqNiaBR3SpH CCKFvWNI/Fne7TLjY/vUTl6tJoXdY0KPRnfGP41sf3qCb6lP+h7h0gDW8J/PTWVp1Ilzk59E8HXf tRWUFX6/5P4NWoKhHyizt/8AS5SnV+bDnRpgaU7xb8PzjSH/AOQNUZfsk+OINH/dOUr1eFhHRlgg 3nsJHl3lUBONvGxTBitUthPGZpVLx3kztz51p2xJ2igTuWu6ajLhvjvUs8XltEttqWI3IrZGFJWl K7l4hSk6uz578KCtLQrXEz4RUknAHT2a1uaFkayI29KnWcIsrZ/tN1rSJgqmo1u5vLpt1p1pTUPE srk7jjvQBvH04hhuarxzELlaGn0Q2hHBJ3gTXOefrwt5oadUl1p9h4HQvYxqkGuoOmF+1U0bJ25Z aeWsKMqEq8QJrm7F8PcxfPTLKy12LSzD6mtaFJBkBQHHwoDBaYld4c004SpbRAKVDiAeVWFrPNi1 aAugrlOgEHcbc6jsv263sBZtbhse0sICXE6eGw5elRSsJtA4/rag8Y+6gaZqzheXlyvsSWrdHdSh KREcKgm8bt0IlxYTGxkQaZZvtW14u0D80yGwVRtJBqCxRy2ClOaU7bJkzvQSmI5mff2YgNjYeJNC /MTy38auHnFd7VzNWW9uDDVtbgLed2QmOE8SagMesfZr0NKJU5pBVvxJoNMLt3HvyoI70zw3/wC9 dP8AQRnVzD7K3wm5uGlWSTpSeaT4ek0CMm4GL68asw65LpCNCB4kcffRz6JMAbX0iXOEBxsJsWUO IhI0qBH+PfQdDsrQ4RpcSraYml9AUkq2EVDW2GO2OKe0hsr1JIUUq2I9KmEPs6ezSpJWRtNAow2C jma2TqA7vCvGFOCQpPpW5PmRHGg8UCUgn30LusgI6Mr8CZ7Rs/3hRWOyBznwoXdZRMdGV/wMuNfr UDvqQz+CNwTw7Z0Ae9FZXvUiH70LiOAedjb/AGdZQC/riY9cZgybaXdx2QWnEGUKDaFIAUGV6tlC eYq1dX1BPRrgRHO3/wCY1WuubhNnhOUrC1sX3LhtF61LjiYUo9k5JMVaur4kDoxwLf8Ak5P95VAT A0rmB8a2w+1L9ylomATvHhSrTetMJBJPKp7B8P7BsFMdqrdRPKg8xK0Qzh6bdlYbkiSBvE71AnCc TxHGRcqAtrdteyie84ANtuQq6KZSmCoaz4mtVpII22oIe2wq2t2tMOOL3lajJ3qDzc37Jhb2lSkF YgL/AJm3GrhpUkn3VSOljMWDZawBy5xd4gOgpbQlGpSj5Cg59zRaP67w35beeZAUwp8gl1JO59QK p/RVgS2XL/GL5am+3LyWg2jVI4QBB47/AAqbxvNb2a1vMYQ+i11KSEQ0QVeEq4/DzqwdGjLlvlkL unHEPquH9bkAqRGqVAcyDvHlQXHEMETbu219aJKA+yhtxB5EDYn7KrGYLY21yEOoLayCFBSYNF+8 w4sXFu+EqubNTRVp4qVpbBA8NzJk1TukZeMu4fdN4ZgaMTcaLIDIZDim9X0zMgwPf6UHP2e03Tt/ bN2rS3HFpACW06lKPkBxppYdGmasQR7RiCRhtvpK4cV84QP6PL311ecm2jFs0xgybSxu1sIcDxZk lJ4jYgmD9tVvFsoWiQ45mDHVvW8QvfsEQDChtufpJI35Gg57Tl/C7ZDjdtcsWz7SE9s65K3EhXPh z4eFVvNtrg15b9ph63W32nEMtoKCS8Oa1K4DyFdE57s8k4060zqQbi2SG0sNgw547jYgcffQr6RL Sywu6ZFtbN2zCXO0LaR/CECdz/jlQVnLWH3RxRoWoLEaVLUtUwf5225H30XeifGlYJnO0tn0ds3f /MG6UgJgkyD6HhVUyW3b3l+wEpSxcusjSDJC522+HD0qy4Thq8VzrY2QcKmRqaeCO4oECAoBQmAR QdTWTYCAVLCieFe3WFWV4pKnWh2iFSlSTBFQfR85eHD3MOxLvXlkoNqWf4xP5KvfVwZbIIOxmgi1 4elgSkrIPDypqtlQE1ZnW0qbg7yKjLxlTKVAJGmOJoItepKR3gDFC/rJKJ6Mr2AdJcb3P9aig4VE bDj4UMOsekp6ML2SILrUfpUEj1JmyjJrytiFOOnj5oH7KyvepPIyfcTzccP95IrKAb9dN9p7J1gb a+urtkXjPefIKpDTgIMbE93iKtHV1LKujjL5fC+z9mIMeSlVS+tZh2L2HRrZt47Zi1vTiTQKO11w OzcgzJnh4mip1YcNb/FTgDrqQrVbE7jh31UBOsLcdhrbZ0JiEDnUvbM9miIk86SsdK3VqaVqbTtE bTzp4J5UGq0kxArRxsngDTkkgbcBWizwmRQNXGjvFBHrLZYxXHUYabJhbzDetC0gx3jEb8uHOjq4 ElMzNUHpcfScEFmq5RbofVC1qXpgDfY+M0HI/YYtl1a3bmyt7C3LnZ26zHarIMaoEmOfhRKyM8nE 7Fm4tihLaFKWHNMBKtCiVkb89yKHXSo7a2+IlouqKWkKTDjsjURsB5wZnzq+9ByG2cCtmlKI1rB4 mSSOBI5GfhQG51SMLwhd9d3wWtNqhaU6YTAG+mPEmn2AN2tvZm9cCW37sh1ZPiYAHwgU6Yw9rEcv 27ToAOgBJT4D9hpZGHNuhDK4T2YiOQoEsVtnlLtvZ7QOuIUdLmqNCSNxHP0oZ490dXl5jryPlJ9F m6kOOslZWFKPE7kxwG1EZnGGnMSvLNKoatEEvOzEE8BNKNOYa7iSk2zrbtwWwpxSSVGOUnnQU+wy Rg+HIQpNqhSkc1JGxoE9OeErv82Iw9huO9Ko2CUwkn7a6rv7VTiDpMeBP21zn0j26lZuvLy3e1Ia CVSonhI24eCaCjWtk0xcW5xC8RbtWoCmm0nvEJMgGOG9WHo4dxRzOthdXGHutoQ+oF7V3QF8IJ3P ED31EvM/InbEqZccvHNRURqCWwIjf3mpfJ79xdZywtCriXC+lxSNR7yAdyYMAbbCg6WbZXa4na4m 2SEqhm4HLSfon3GPjVub2G1QVtbC6wxbau6FoInwqTwl0uWqAsy433HPUUEnJg7CKbvoC0qB4U4k cfqpFwlSjHKgg7pstrIKNPhAoSdZhSm+jW4kDvXLSfrP3Uar5jtGpmKB3WkXp6O3GuZu2p+ugn+p aQrJTp0gaXHUkxx7yT+2sr3qWQMjL8S69z/pJrKCqdfhR/B21bIgG8ZIMcfm3Pvq79WMA9DeXdWw 9nV8A4qqf19Qn8F7NYBKxetD3FDn3Vaeq68X+iDA0L/ikKSNuI1E/toDCNKW+7tXjbknTMKB3pNR BRusSRwpG1bQlZdTqle5M0D8pIRIMzWi0k7kVs26VARuOHpWy9kydtqBvyAoR9PDdy6W0NBOlLJV KuAMnf3UXlQTx40JOmrHLe1vfYLllLluGPnSeRPn8KAA4sxhGY0IwXEbdAcaKSxfJRpVpJAjVzTx MHwFGPLGXLXA+yw6zh1lgobSswZKYG9B3GUWzCEXdmXHGFPoShKR/Bq8FetHnJTbjlip1/6Yuz9L YjvJP7aC9ZYYPyUyoOiQkApSQUp8hG23Davc2JxC3wi7fwlkvXvZHs0AgFR8idp8JpbKaAjCGwmS EqKQeaoP0jtxPH31LPJ7VlSRsY29aClZTwFdnkq3axBhab19IuL8FYUpxziQo8Dvy4VL4Szh7XaN WduGlIjVCCBvy4fZUi2T2imjsJmDSVuy6h59bjoUCrupH5I8KBtjTybTCLl8qCSlohJP84jb6656 zk0FX+IS0pbhW2lKUnc7EzRw6QHpwL2clZ7d1KShIOpQG8SOA2Ek+nOg3iuFrxBV+Ge0S4HHXAAd +4Dtz5GgodlhTuLOXV9blt+3t1qTcIKtK2FA8DOxnl76neizALh3NTd4ppRJCVIIJ+jJ+HCvcq4h 8nZdRamwLpecU4tLg1BaCd9W3Ab8TPCp/oVxC3s80Iw95d2lVwSlhLoEJgcoHODQdA2Dei1QmDJG 9LJZ7N4vN7H8pP8AOrGNSedOAQU7+NB6l1KyIO8SKb3L4QstEwTBn1P214NFvreIJK+fgPCoyzWi /eXfPhaQh5SG0qVACdoMefH30Ek+4lCi2dwQTx50Detesfi6J4TeND6lUYbgqf7VsK0KSe799BDr VOLPR8pCjwvWgY9FUFr6k8fgSuP/AFHj/eR91ZXvUm/0GV/Xe/XTWUFc695P4NtJO6ResHj/AKty rb1YRo6IMvAAd5lZ9/aKqqdexKjgCIE/5Vbn+45Vs6ti9PRLl0Dj7Or/AIi6AqO6QtC1qjkBHE1o HCEKCdlQSJ4DwmtL3UGwtKgkpM1s22OzCn1pJMcOFBtgq1rtRJKlT3jBAJ8p4ipIokAz7qq+FYnj D+OutrtbVOFto2eQ4dQVJATEQdoPvqypdA3kEcaDCxJ5jehL0k4JbX+aF2D4Kn7tslsaZ2jaD4yf qovJeBA9aGGbcYUxmS4XcLW2GwopMDToHL12nagDeMWrOQ8Ut38Te+bfeShTDYSrWUwZUOW870UM MxBD2EX17bgFKrp4twdjCEkfZQn6VMHxDMuZrB/CXbd3tnA0u2dX88gkzqG26efjtRiwWy7DLhY0 gr9ofQTp3nSdx+jQXTLLxUxdJVGpL6jtMQrcRPkR75qQQ+kKUkq3mq1ktSoeSlBQkttqEj6RKASf MST7wamXwWX+2EQdlDwoFApS7wmBoB2ptbG6TdvOOvsG3OzTSEFOnfz4+tN7fEW3seXZNmS0NawP yR51o9d3Vzj6mBZhq3aSfnCoFThkbwOAoIHON2HsXtbALWgBQVISd1KJgTwiEmR6VV8rgqexG9AB 0WjzgJG0mpa5Wm4zViN8hQcRaoXJ0gAdmgAAEHeFFcnjO3KkujlTSbK+uX/4MMLCpH5Ox4e80Alv 7O89rtLK0tlXK1EgISqEK27yp2HAbDyqU6MsLXhucEqWyHHGnNTbhSJIVPCCY9Kl8CtsYvbxzFuz bu7dLmi3t1PFGpO41pjhyEceNPMpl+zzkwzeKLr2IK+ilOlCAlJIgevPnQGFNwrsCofSArXA7x29 Sokd1KiJ9K2QzpSNUwRxpLB22MPs3xqVHaqWeZMkmBQa4u+3fONWdqVrLdylL+gwWiBqTPiOHuNM ct3D14L9zswi1RcuIb1cVadp9Nqe4241h6HMQbMLQytao4KhOxPjwpnktl1rKNqHge2cRrcP9JW5 +2gmrRsItgTBJ8KA3WzRoyUFJ4KvG5+CqP6GyGUgAcKBnWzQT0epIH8ub+xVBYepbIyQ2AQR8/Pl 84Kyk+pPq/A10HcBbo9O8jasoILr1ScBRBgh63j4O1YerOo/ilwHURs0sbf7VdV/r0j9wkqn8u3+ 1yrB1Z5X0TYFwjslgf2q6Aq3X+bSPCeNIYchNywQ5JTBETtTi4SVWy4HLamuEhSElJM78qB1CGG2 7VpCUpnvnkkePrUQ/cXjOYmbFAUbd4FSf6McR9dSmINLUJCCptB1KTzX4D0mqhmPH3MvZgZvbxtR sy+1bKITMLcCt/iAKAjhsJQIiYoW9Ilp+6V44yg3DhQIQ2qTJ2I08440TWHw/bJdSkiUzvQ2xxdy xib7rDalvF8nYctXCaAMYfjWJp6Q8Js8SY1D2xBCwkoIIOwE8eKZo34OV+zW0pK+1vS4QFcAttdD XGMYwu66SLG4xKzadvWLhDbLikFKgeB9QJPwotYez2YwxI4FDK9/zx+2gTynCb20WVhRfsQAmBKA hRmY33Kvqqw4mlCLRxxwEpSJgbk1XsHhl7D5b0Bu5fZU6Y4EylBnfcmdvDzq4ONpXG21AN8i4ZmH D845gfxLsBZ3LiV2znaa3FbniD9EAbRw22qw2bTFniV865dXLj4BUpK1Hs+E90UviDqm8UJgCFAH zBH3iorPVwm1wdy5ceLZdSGUKClJAUo7GR/jhQVxntG8v4ze3LTSHnWtB0DYFaiYnme9x58aQwNt bWT8XdSrQPZwkq8CZn6op1i/zOT20JVPbuBSTxkAEj7BS+VOweyhiDlwvTbuFxKlEcEgQf20AJuF Y1eW9vb2jtxpYK3FqZdG0ERPDxiiV0WYWu8xNGP3ilpdb7gSsGCqIkVBXgwDCMFQ40t5Srx5QZd1 hSVRAkDkB4bVa+iS+YcuHMMR2kBHtACySdzvx5cKAj3t8pi1WUtqWoJkAVC/L9naWKbh1Sbi8WUM 9i2QopWQYkDcDjvUvi94zZYY/dOphttsqJAnYChDlN22ds14xiS/ZXrm+cuWme00rUClPZpUBvJA n86gvrz1zieBui4T2NzeOIY7IHZvUdwPQA1dLVkW7DbKYAQkCqlhlstzMOCWOgDskru3gTMHTpAJ 9VH4VeFp+cMAbeNBouNEBRG24oJdbFH/AIcpM8b5refJVG90d0bbmgp1sEk9Hze8RfN7fmqoJTqU 6fwKfE97tndp80VlJdSrbLFyJnvu/aisoIrrzpP4OoVy7S3+1ypjqvuT0SYIkk7B0f71dRnXlSDl hKo4Lt/f3nKfdV0a+inByPyQ6D69qugM6BLShHEUyw7a4dQDMHantuklBHKmjbfY3ijBAO9BviT7 bPdeMJcQQY/Z9dQV6S4n2e+CXgp5tbIUmSUpgyfMGneeGFuYCt5DikKZUlyQJ2Bk+u01st1tWYbS zCNZuGlKkD+DSIG/hMigstuEhlO0gjaqRmppJvXvY3GW7pSiYdMJkDjPjV8Q2EtgDkIqi52tmlG4 L3dE90zxJHDyoAnjDdwzm21uMX7uKXVwA2yIKUJTxWFDZRO3186NzjqWbXCVE8EIT8Ck/fQlunm3 834azcWVtqt3VJQtLkqEjf7d/Si9i7KU4HaPJ4MupmPAgj7qBtdtqRbX51hPs16h8o2GuTATJ9QR 5gVamHSS2VRCk+FRl9bj2m7HY9qLi2JCAY1KA2G+wM86c2F12+CW7y161pQNStpkcZjnQNcetwhZ dA7y1JHHjVTz5cLdcw2wYQHpd1OTuECISvbeZkDx34xVhz1iAscKNyYKUd4jVHI0PsOujfZgaxC5 uVIbZSFFtPNOkOBRj1IA8vSgks4OMpNtZtAFthWohJHArA4egVTjCbFf4txag6XHWCtfA7rOoj6z UFfF66XcXUavablQSTP0Q2qOJ/pD3ir4y2G8Hbti1rBbCVDgIAoOfsaxFOG481hC3LZ21QrtWm1t hayrgrSeRIMRwgUR+ibCWkdpj3cC32g2hCTPZpHI+e31VRs2ZYtHLxePKJUwpxTSWUcdYPDUOCSI 38iKu/RG4ub+xDehhhSVISeI1DfhtxFARnG27izW28gKQRBBHEUEsFwkXnTJcYeo6rSwWl9CI4bC J8d/so3gAtQKpOG4a3adIuL4i0nvOWaJ24qkxQWzKzYdxbEsVIOnULdonwTx+s/VU8rvKK5nwApD CrX2TDmrfYECVkCJJ3P106aSJ8qBEqIAEDhQZ61xB6PmduF83+qujY62nwoJ9a8j8XzQif8ALkfq roH3UsP72LoT/GO7e9FZWvUtIGWn0wZK3if0kVlAy68IJyxO8Tb/AK6/vpfqquBPRfhQJjvuiDz+ cVSXXg3yuE+HYH++uvOqopA6M7DWB3XHY/TNAemBKCBSdy2lDqSY350lZ3KdxIn7a0xJ0ltCkmCn eg0x1CXMJfbIiUECee1UToGzLe461jFtibRVcWF4WUPkfwiOQnxEVaMRxGbRYWZEGo7oktLS0yym 5YA1XTzjyz4kqNBeytU+NVvMFlbYip5t5TqVEBIUhUaTyPhU4t4p4c6qGbHL9DinLI/OIlQSTHLj QDfOeCsYNmBDlpZEvKR2i7kypSjqg78Ad55UUFLL2WlIUkmWwRtPDehPjeM3t04W7nuBtxsFA2JK l/ZANF7C1JVh7bSu8kpjc0Gl1iLbVrh12tQRKkoJJ2hXdH1kVtgbgFvdWyne1LbihqkHUPHbn4+d QePYbcYng7Fqw5p7J4FSvNCpA+IFLNXTeFIxS+dSUKTal5QJ2UUpMke/9njQD7NmNX+dM7KyvhyU 2+HWw7R+4c37VKFaVCBwEggTE0/dbfQWbewabsy6pWs9nJDadjII2Jnlt4VZzY2eH4cvFbawtkYl fNN+0PBAG8TJ99Nra0Q7jTSyhLa3GktrK3ZEDcpTzJkn4cqDW8swi5wu23AbaU85pHFS1AD6pq2L HzACRAAqKdQh7FiscilA9E//AO1KuL1IMcAKATZrFthgdwnEb1NjhhJdU+133DqV3U6dJj135VNZ CxrK7PZ4Xg945erMBb3YmSTJ75gR68KpvSFdtXV3ibz60e0W1ou5LSRsEAqS2k+oMnwIqMyLauYM 3heI3lk9bKfulWt20pWkgLIgn3EEUHQEd07kCq1lwPO5qxRTgGhCkBJ8QBP2mpe2eXZdnaP6ltaQ lp8qmT/NV4Hz5+tQVveLtMcu9AkuOoB9KAgNqUobEA1oHFa9JFJ27mod0+tK7SFzvzoN1lWmZ4UE +taf3gsTzvkAforo0uujSQN6B/WqcKskWyRzvk/qroJfqYT+DTuw2U9+sisrfqZD97D3kp2fXUis oGHXc3y3Eb6WD/vF1TOr7mvCMJyFbWt3i1nbPJdc1IceCSJVI2NXTrrtzgIUebbH1OL++uNnEkHa RvQd42PSFlkAFWP4bI//AJKPvp09n/La298cw0+l0j764C7yTxPrXhKualUHcGN51wJVo6pvGcOJ 0HhcI++k+jDOGCYZlJi1uscw8LC1qANygEBSiY4+dcSHUocTvXg17gK4UH0Jts/ZXUolzMOGJA8b pG/11Vcx59y6rGJbx2xcbKIMXSAkHj41xBqXEaj8a1UTO5NB1FjucsuqddGH3tj3HWe0V24AUe/M c1RIPGN6I+HdImVm7VtCswYYCEj+Up++uFIJ8ayFfzj4bUHdFp0j5TQ++hWYcNSgr1g+0p58ai84 dIeUVWi1s47h90lbKmXbcXAhaVbevjw8fSuLAFR9I153juSZoO2nek3J5Nuk5isAlpIIT2oIGxHv 2Net9KOUWFBacdwwlcqWQ6JnhwiuJAFExJisg8N6Dta16UMn9sFuZhsATqO7nifup4elTJn5OZLD +1rhzSrzrIJTtsaDoPpMz1gb+NG7sL21uBd4Y7au6FA7laon0mansdz9lrEsh2S14tZ+2pQytbYW NWtIAPv2rl7SRsTNbBJ8aDtW36UMn3Fg2HcespU2nUC5wMVBPdIWWk4s1+7to4nWklwrHAHnXJAC toJrfccKDuWz6UsohO+YLEf/AGU5b6UMnkE/hDYE/wC0rhi1WG7hC3UFxCFAlMxqHhNSPttq46Sq 1S2kuqXAPAEyEjyHCg7Vd6T8omNOYLDf/WULOsDmzBMeyqzbYdidtdOi7SvQ2qSBpUJ+uufFLK3V rQAlKjIA5DwpdqeZJoOu+pjtlh8Rtqd/WTWUr1N2+zyo54rLiuP9NI/ZWUDLrop1YAgaTu21B5fw iv8AtXHrzMmNJ25iuzuuIytzLgUB3UtN/Hta5DW1J+jQRBZAMwTWBid441KlqTFZ2IkkpE0EZ7OJ 3FeKY7xhMVK9l4CvOy24cPKgiCx5D41qWoMQRUuWSREDjNeFkapIoEEi0Fro1Q4EJAIRzBnf69/S lXrlpSrwtdnp1ksgtjgVSeXhtXpYT/NE16GeGw28qDxm4bLlmXez0hep8dn4K25eG1aBVsm00KIL obUmQjiZOx90QeVO2WmFFKFMjUTx1QPftTj2FggkBgDURBe3+ygi7z2Zxp/sVJSlTiS03o3QnfaY 9PXjSeG+zNsuJfSky82RIJOkTq5c5G3OpP2JorCAlkd0GS5sd/t+6tnbW3aX2iW0KSFboS7Mj4UE Y8m2DKezUndrTu3BCtczw8K2U1a+03L6XG9Dpc7NGg93+by293hSzrLanCW29KTwSTMV4liOVBHP WiUL0ocS5wkpBiffSaWNztvUr2G86a9DMDYR40EX2BE6RNbJYPGNvGpMMA8q9LJ8B8KBghnfhSoa QdwmDTzsiTukVuGjEQAaBu22EwIpw0gTsK3S0rbu07tmVTPCg606nrShk5bihA1OJHn3xWVKdUtC R0bJUEgEPLB24nWr9kfCsoNOtVaOXeUEttNqWsplISkknStBP1Sa5NXhS9KdVncSAdXcO/hX0PxG wssRt/Z7+0ZuWpnS6gKE+O9RRyblgmfka2HpI/bQcDDDGAynVZ3Xac1Rtz/7Ui9hqVbtMOpE7yJr 6ADKGWxwwlj4q++tGcmZYZWtbWEMoKzKgFqgnxiYoPn/APJrv/oufCvBhywf4Je3ka+hP4L4BEfJ jUep++vBlfAAZGGNT6q++g+e3yavj2S/gaw4a4Y+Zc/RNfQo5XwAmThjU+p++vPwWy/M/JjXxV99 B89Pkp4meycj+qa2GEvT/Aun8019ChlfAASRhrUnzV99bHLeBnjhrJ+P30Hz2ThL+/zDv6Ne/JFw Zlh39E19Ck5dwVPDDmR8aw5dwX/49r6/voPnl8kvD+Idn+qa1+S3Z/gnI80mvoactYESScOaJPmf vrwZZwEGRhjIPv8AvoPnmMLdH8W5+ia9+TF7y0ufSvoYct4EeOGMH1BNe/g7gn/xzP1/fQfPA4cs H6Cx5RXhw9QH0VV9DXcsYAv6WGMn4/fSasqZdJk4Wz8VffQfPgWPPQfSsNlvOk719Al5Qy2RvhLP xV99aLydlk8cIY+KvvoOChZ4fKtSbgbbcONYLO030h7yMV3j+BuWNR/ce34+f316jKGWwSBhLIB2 O6vvoOE7e0swiXRcSBtoAiYP7Yq69FHRxdZ9xt2zs1qtLW3QHLi5WmQkEwEj+keXoTXXyMpZcSNK cJYA4wCfvp5Z4DhNqoqtrQNEiDoWoftoGeRMrYdlDBk4PhfaezoMhThBUSSSSSAPGsqfSAlISOAr KD//2Q== --Multipart_Sun_Oct_17_10:37:40_2010-1 Content-Type: image/jpeg Content-Disposition: inline; filename="custer.jpg" Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD/4Q1kRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB AAAAyAAAABsBBQABAAAAbgAAACgBAwABAAAAAgAAADEBAgAOAAAAdgAAADIBAgAUAAAAhAAAABMC CQABAAAAAQAAAGmHBAABAAAAmAAAAOYAAADIAAAAAQAAAGd0aHVtYiAyLjExLjMAMjAwNTowMTox MCAwMDo1NzowMwAGAACQBwAEAAAAMDIyMQGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA //8AAAKgCQABAAAAyAAAAAOgCQABAAAA9gAAAAAAAAAGAAMBAwABAAAABgAAABoBCQABAAAASAAA ABsBCQABAAAASAAAACgBCQABAAAAAgAAAAECBAABAAAANAEAAAICBAABAAAAJwwAAAAAAAD/2P/g ABBKRklGAAEBAAABAAEAAP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAk LicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIy MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAIAAaAMBIgACEQED EQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0B AgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpD REVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEB AQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFR B2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVW V1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrC w8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANNRzUiIfSlR DmrUUfrXOajEiz2qZYPap0jqlrOr2+i2RkkZDMR+7jLYJPqfanYCd4OOlQGe2ibbLcQo/ozgGvPL vxRqE6kSXsj+yfu1/JeT+dYsl55m7JUZ6kD+vWiwrnsAlgfG2eI/RxUhjrxlLhgMLKcdua0YPE2q 2mDHfSMB2c7h+tFgueqeVimmPiuO034hKWEepW4APHmw9vqK7G3ube9tlntZUliboymiwXECCgpU oSnGPNIZEqgUVKIz3opAVUXDVajFV4/vVT8R6yNC0SW6THnsfLhB/vHv+AyaaApeKPF0WjK1nZ7Z L0jk9RF9ff2rzC71Ge7naaeZpZWOSzHNVZriSaRndyzsdzMTkk1GFLHABP0qhEhnc5xWjpmg32q5 8oY9M96hs7DzHG4MBjJJGK9j8HW1ta6ckqqHVunHQ0BY8wl8H6hDGWOcjttNYtxbzWxIcHivoXWN X0nS7YzXh5I+WMDJNeY+KTb3umHUotOlhgkfYjkfqaLhY4ESZrb8Oa/caLqCMrFrZyBLH2I9fqKx Y4mkcIilmJwABkk16R4V8BmDy9Q1dfnGGjtj292/woCx2qjIB9aftp+MUvFSMYFopxopDKCDDdK5 L4i2V9dWdlJb28kkEJcylBnaTjBP68116ctWjanFUhHzpit7SLa0t1W7u5lCk4AxmvZbzwvoGoOZ rnS7dpDyXUbCfrjFeZ3WkW0OuXEcMMbQK52RljtA9jTESWNvDqExntAJDu2YEf3R6/413Xhq7itb IW88MgKMct2zmvN47h9C1WCS1uzGGbbIqnPynsexFelLMloUMjF4pVBEh75oAvX50q7+RIXknkwG Pt6c9q0bu00e/wBEk0m/kht4pE2KrOAwPYjPcVmWlza2TSXTbWwMgep9K5zWdavNSmISzS4bOMjA x6AUhm3pHhfRNFVZLG3EkmOLiQ72P0PQfhWnITUVjJNLYQPOmyUoN6+hxUjZzQAwr7UgWpKCMUgG 7RRRzmikxlGNavwDiqiDB6VegGQOKpCJygkhaM9GBBrzHxFp50+5la8tnO4DbIg6LnqP0r0S/v5L K1MtvaS3bZKbYcHDDqD7+3WvONT1u+1W/jbUSVtY3yYEGAB396YjCtLQXt1mOBhAjZDPyT7V3GkX bJbtp92pe1I+V8ZMf19qzta8T6dounx6dplhbySkbi/U4PIJP9K4y88Q6reYMk2yPOQiKFU/h3oA 73VLC6trFrmCVJ7dTn5X6fhWLo3ih31i2N3Bi1iYqWjHTPc+uKg0zxCb+2NrNaHj75hOFI9wKs6j FF9hf7CotmjAZWTj659aAPUQVdA6EMpGQR0IpjCuJ8GeKVNtJYXshd4xuiKLyw7jH9K7SK4guGdI nBePG9Dwy5GRkHkUDFA5HWnMBikIwaKkBo64FFAPNFIZVQVi+IvEk+mSJa2OVnxueQKDt9AM1uov NedeIJkufENyySEAEIMHjgY/mKpCZaOri/hjg1G3jnhRtylB5LofVWXHP1zWpqF1pt1pg8+4jmlS PbA6o32lj/02ydpGOMjr+lcsXliHOJF7jHNQSyAYmiPyA5I9DTEQnTVimMwywHOD6VoafJHp13Hc pZW92gJIhnGUORiljcSJnqDUMTBQ0fdf5UAaFhANDj1STUbP7PJN+9EQHHlnJUKPTJNSW4G4jqCB 1qKKzu0EeoXV0k0N0CqiSXc4C5HI9OaSNsXCIvCg4oAzNbj1O01iDVXhS3km+dPKUKpHTOB61b0j xNLYayl3JGBE6COWNOAQOhFMv7QGCa7N5E489kFuZCXTHOcdhWMGBJJ6CgD3BJEliSVTlXAZT7Gn AA1xvgnXJrxX0+5fd5SAxEjnb0wf0rshjFSMTABopwxRSGVgDn615TNCTNMrctHIwP516xzuGK8z 1FBa+I7yJgNrynH48/1qkJleOZo0GMstMlDy5MKxsx6g8GpGUxSnb9084p5RZUyuFb06UxGfazPE WicFWB4B9Kk3fv8Ad68VDOri4BaNt443D+tEchKsfegDQsVQy3DjJcR/d7dR+Va9lDGLR/NkLOzE gAenTn6iudsJtmoMeoaMg+3IrbtpJVjXa+O/NAGK4Q3M5bG7ewz+NZqAZcdlY5q3bxXOrasba0QN LNIxUFgo7nqa1PCPhttZ1a6S7Vha2rnz9p5Yj+EGgDLsZ7i0nS6t5WikU5BHp6V61o2pxatp8c6M vmYxIgP3W9KI/Bukyu0stmgZkwkSsVWMepx1NcOss/h3Wp47K4WRUfa+4cSD6UmM9IGM0VQ0zVrf VLbzImxIB88Z6r/9aipAn3ZNcB41tGj1gzKMGVFcH3HH9K7pDzXJeNCrTRuzHem1AueMNuOfzX9K pAznLa7juUCSEJMvr3qZwydRx6isi4ty3zJw1TWksjrs3kOOoJpiL0twwXlCwHoKxri6WFj8pG/k A1fe6vIODtKmu1+Gc9vcazNHeRxyZh+RXjVuc8nJ5oA4zwxp91rmpSW1nGGnZMZbgAZ5JP5V6Cvw 213Jb7TYgbcKPMbr/wB816pGsESARpGi+iqAKT7VCPlMi5+tAHisPwc8SLJk39hH/tpI+R/47XWe FvBeqeGtHnhlENxO8hlYRSH5vQDIHP1xXoSzow4ZT9DSvIO1AHiepeMNUWaWCBPsgDFWDrl8jsc9 PpXMajqdxe3BuLt90zAAsABkDp0rq/ixpTWeuwanbDC3kZDr/trwT+IIrziS6uUcJcqGWgDpPDOo pb+ILctJxITGQffp+uKKyNFtjc67YxqdyNMrfgDk/wAqKljR64vB5rK8Q6DaapCtxI0qXC7I0Kth eXAGR3+8fzrTDYNQavKRol465DpEZFI6gryP1FJMbPMEcqxRhytNkwrLInDDriqST/ay3z4lB3A+ tSrIWQq3DjqDVEl9h9oiDIC2e1V4H1KxvEuIGe2KHPmZxtqkbuazYtC5FVJ767vG/eyEr6dKAPQr P4k6tZRHcy3KKeGlXJI98VuQ+OdM1EB3nubN3IASWM4Y+gIzXksN4YcB49yjsank1L7Q4ZvlVfuj 3oC57To2vQX8jJBdJujO0hnCnP0ODXWxC6dBuLL6ZFeP2/izwp4hsIodejmtNREflvdRICjnszY5 OepGOtcvc6vqeh3Ij0jXbnyifl8mZth+maLDPU/i9Cf+EOt53YB4rtcHvypBx+leILdgDbICwrqL 7TvHPiVo7fUTdTxp8y+dIAg9+uM/rXS6D8PbDTQs+pst5cjkJ/yzU/T+L8fyp3AyfAmiXHn/ANrz I0duqkQhurk8Z+mM0V6BI+FCqAFHAA6CiobA/9kA/+EMRWh0dHA6Ly9ucy5hZG9iZS5jb20veGFw LzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQi Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUg NC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5 LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgog ICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6dGlm Zj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8v bnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50 cy8xLjEvIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAwNS0wMS0xMFQwMDowNzoyNyswMTowMCIKICAg eG1wOk1vZGlmeURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpNZXRhZGF0 YURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpDcmVhdG9yVG9vbD0iQWRv YmUgUGhvdG9zaG9wIENTIFdpbmRvd3MiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHRpZmY6 WFJlc29sdXRpb249IjIwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIyMDAvMSIKICAgdGlmZjpS ZXNvbHV0aW9uVW5pdD0iMiIKICAgZXhpZjpDb2xvclNwYWNlPSI0Mjk0OTY3Mjk1IgogICBleGlm OlBpeGVsWERpbWVuc2lvbj0iNzU1IgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iOTMwIgogICB4 bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6Zjg2ZTcwZTQtNjI5OC0xMWQ5 LTllM2YtZDQyZjM0NjM5ZGJiIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ1dWlkOmY4NmU3MGU1LTYy OTgtMTFkOS05ZTNmLWQ0MmYzNDYzOWRiYiIKICAgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIi8+CiA8 L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg ICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/2wBDAAUDBAQEAwUEBAQFBQUG BwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUF BQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh7/wAARCAD2AMgDASIAAhEBAxEB/8QAHQAAAAcBAQEAAAAAAAAAAAAAAAIDBAUGBwEICf/E AEIQAAIBAwIEBAQDBgUBCAMBAAECAwAEEQUhBhIxQQcTUWEicYGRFDKhI0JSscHRCBVicoLwFiQl M5KissJDU9Lh/8QAGQEBAQEBAQEAAAAAAAAAAAAAAAECAwQF/8QAGhEBAQEBAQEBAAAAAAAAAAAA AAERAjEhEv/aAAwDAQACEQMRAD8Am8Hl60dEGN+tAbnalEBIr5+PUIoGcmllXPSgkW9LxxntVBET 2pVY/Y0tFEe+KcpFRDYRDHSjrD/pp4sdHWIUwMHhwOlJcpXr0qVeEEd6aXCxxRtLK6xou7MxwB8z TDSSpjelAtQFxxvwjaSmGXW7dnH/AOtWkH3UEUeLjjhGXATV0z7wyD/61cNTTDbG1AKcdKjBxXww RznWbUA9Mkj+lSem3+n6lE0mnXtvdopwzQyBgp9DjpTDXGQ0mY6fFD6Vzy8DpTDTMR7UOT3p0U9q KF3oaasm1F5PSnTr3pMAb1FJrGwGRRuX1o64xvRtqYG5X2oyp7UpgV1agCKAQSKFKZHLQozUEoNL Rj3oBd+lLxJsKrTsaEmnccW+d65Ehz0pzGp9KuI5HHv0pdU7Yrsa+1LqmSBitSAsabdKrXiLxfb8 IabG6xR3V/O4EVsXweXuxx2/maX4w410PhmJknlWe7A2gRhkf7j2/nWCaxxK9/qs+ohea6mbJuJd 2A7BR0UDoKuJaud54gcYX0Jk/wC46Lbno5T4sf8ALJ/Sqlq+sx3j/wDiOoahq7A5HmSFIwfYf2qA uLxpn55ZGkf1Y5ptLPynAxVxEsdQXcwWNrCvuvM33phM8jS+Z5rZBz1pn+JxuWxRGvxzbDI9aYbE rHfXgGFuX/T+1OrPXdZs3LWt/NCx6lTjP2xUGl3EO9HN3Gds/ah8XnTvEjiiywZLsXKj92UBs/ff 9at+heL9jNiPV7FoG7vCdvsf71izzAocGkWkyD1phuPVuh63o+uRc+mX8NwQMlAcOPmp3p8UA7V5 Gs725sp0mtLiSGRDlWRiCD7EVq3Afi5KrR2HFA8yPoLxB8S/7x+8Pcb/ADqYNfkA3pLlpWKWC6t4 7m1lSaGReZJEbKsD3BoKtYWEwvSjhQe1GA9aOoFA2K4JoAb9Kdcq5zQEagnYVKpALkHrQpyAB0Az 6UKioTlIpxCMikyMiloU2qwLRfMU6iGe4puin0p3Ah6kYHf2rUQsgVFLuyqoGSScACsj8TfFURPJ pXDEo2+GW8Hf2T29/t61D+MHiM+pTy6BoMpFhGeWedT/AOeR2B/h/n8qyiSQjuc961ImndzdzTyN NPK0sjHJLHNNZLk5601klY96SZqrOnRuipJApKWdn70iuTTqztWnkUAEg+gqp9JRrJIQEVmPsM0s LG9dsC2lyenwmtO4D4PZ3jmKeYp64GQPnWoDw8WeBZIrZUbqMj+dTVx5l/yvUACfw0gx1yKQaOaM kNGy/MV6UuuALhIyGRDj2qj8W8E3do3mCDIJ3wOgppjICzD2owmPepnWdKaAtlSCO2KgGBBwe1VM OBJnYUA2BTdTR1zQaD4UceXHDd+thfyNJpE74dTv5JP76+3qO/zr0PHiQB42DKwyrA5BHrXjtc16 l8Lo75OBNIF+GE34cYDdeTJ5P/bisdRqLGq980cLjpXQpB60dQc1lonymuBd8UsFocveoogXahSm 2+1CsqglI6E05twu1NU69KdW/XFaiHcYXOKznx44xOi6QvD2ny8t7fITOyneOE7Y+bbj5A+orRlK Rq0kjBUQFmJ6ADqa8l8ba5LxDxTf6vITi4lJjH8KDZR9ABW+WUY03KMCkC5cnJorHJoA4rQ6elBI 2kOFGTjNGUcx32qZsdAubiESoC+eyjcUQwgspVTmZod+ilxk1a+EtC8/UIeZ+WOVSSmNw3/WKjG0 60tpbeCZJTLIQVKHIO/Tb5Vb7Vbhbxr+1jES82Vjkckuo/d6bHHr7VBtXhrp8ttaxhreGKFfzcw/ X1rTbdomQBQCpGxA61ROFW/EaBDcsQrcgyo3GcdKnLNTHmWV0VTjdTjb5f8A+UaieFjHK/mHBQZJ J6Yqr8XXPD8UDie9tkC5BzKPsKrfGnFGu6jM2k8OlxynlaY/EgPYbd/sKyjTOFeK+JeJmtdUSRgk gDM/wDrjI9fuaiadcY22k3EL3MbRgcpCuB136VjOoIou2ZNlJyK9NeKfhUR4fwS6aZDLYEtcIDu4 I3b6YrzJdczTNzdtvtViEQoNKwxs7qiKWZjgADJJqS4b4e1fiC/Wy0ewmu5m68i/Co9WPQD3NejP C7wtseEuTU9UaK+1gj4SBmO3/wBuerf6vtVXFO8K/CR08nXOKoimCHgsGG59DJ//AD9/Sti5eu1O 5zzHem/b3rPqyCgHNGUUXau5qK6a4a4DXcZqApOxwDQoxWhWK0gsdDinEGAelJMgxS0CgVqMmHHM 7wcDa3LHs4sJsEdvgIryU29ez0tobmNre4iSWGVSkiMMhlIwQazXjHwGtLsyXfCt8LWQ7i0uCSny V+o+ufnXSMvPGN66BVl4m4H4p4clZdW0W7hQHaUJzxn5OuR+tV4Ieb8pG/pVE5w/odxcSiaWNhGm CB6g9/lWn6XprWmnk5jifPQ9MHPX5AVQtK1trTTrlDkyOnlg+m2/9BTF77W7iN3Ms5UAFiW6elRF 6gsNQZ31aOS3gnjCrIzDnJU9cdh9KXKZto52hYWkcgAxj2OGx03qF4D1qOyv4oNSvVCznq+8cfpz fP8AStj/AOxtla6VJDaXLXMDp580gwU5zvzAg7/agsXBN7pqWqWYYK4QOQTsexq4wjTrpArMvKw+ 1ZXw1oyxW8byOfPyc4bOKmJWvLRSPMbH5gcYxmoq5axacP2ulSAzx28e55om5Wyfl3qJ4Jt9Gtbt ruCGeaRsBGlkJyM5O9VaOG8v7oJzFhnY9Rg1oGh6BIlp5WQVC9em+KKstmq3isvLEI5MgqNwfXtW O6j4EcH2/Fd7qF811PBNKZorQNyRIDuVyNzv7jtU5xBxbxhoDfh7Hh6wEEPWV7gF3/2ov9a5oPE2 rcUomoXUFvBaRgoAHLSM/cEY+ED077VUSem2NhpNmLLS7K3srdOkcKBR+nU+9CQk96OxBpN8YqVT dzSecjpSjgE7UFUBTtvQJMoAz61xRntSpAoBRisqIF36Cjcu3SukUBt1oOcu9CjKAaFYq6r2AcZI pxEvTem4SnMKYrUZPrUbipa2bG9RNqN6koelbglElBXBGR6Go/VLezNlPKmnW0kgQkc0S7n7UvET iofjLVrjS9PDWyKZHzueigdT+o+9VHmziHSiuu3d6UjJmdZ1B/KBJvnHrnI9iDUqAlxZLplxp6lF AdnXClhk7j164ovF/Pa6v5v4ZppJCWl5JCNic8uOg3Pak5+JntoRaS2DRyQrkCQbgEk9vcn70ZV/ XtFktLCS4ihcRscDKbjpt/Or34IW8eraeLS4vrkJbyeYsAlbkLDcZXoaoGtahf3/ADT80nk8u4J+ EbdKmPCrU7jh/VI71gTaSEeZj933+VFbglytnfGKdVUMxOMGpaSS3uYEBQZGQDmm2o2FvxBpy3tn MAWXmSRNwfqKqY1HUdKumt7wFcbFlG30qYrQOHohBdBlwA24wOm+f+vrSfGfF8nOdN0+8MKQjMrq uSx9B/aqmeJkS2llRsyKhKgnHNjNQcU97qzNGzRKZy3MScg/P5bUDbiHjp7GZFt9OlupWBBeQkkD tnHqMnFO/Crij8fxBLbRwGOKaIyEZ3LZzzH7kU2nsI9Jljt5ktnuZRhZMZ39fUbZH2rQ+H+GtN0x zeW9nDFdSKFkdR1wP0+lBNk5FEkyRSpGBik2HzqBHFGA964QAcZoZHTNWtDADHWuMpxkGuLgnFK4 GOlZoR5cbmisTncUs4xtSRHM1AF+VCjqu2TQrNVAxg9KdQj1pJBvTmMHpWoyc2y5bYU+jUimtuu1 PIx9a0HMewFR/E1g2oaXJChxJjCt6Zp+uwBpxFvjeqjzxfF9O1r8XeIwYebHKh/ccMcbf7Sp981A Xer8P3Mt3eXrPJMIisUUSgZboC3etw8W4OFE0sXWs31tYXiDML8vNJJ/p5Ruw/lXn6/01dXuGvtO tMO8uJQgPIpYZH3wftREUbt7pvwFkk0dtLgSK+Pi9/b9auOkaekdqEIxt27Va+A/CfWbuwTU4tPE kL5BmdwoGOu27Y+QNPf8hcSyxxhAiNy87HkUn5tj+9Ax4M1274duxDcRmfT2OSq9Y/cf2q7cQaVb a1apcWEsTLIAUYbA59ao2qXXDehoX1PU43cDaKE4P1JGfsPrTXhjiXUtUMknDn+W21ssn/kTO3O5 9cbkfeinXEXDuraYhafSJJ0XOJIGz+lUK/4ku9PZ1tw6GPJIdcEe1bXJxWLfTHg1WExl15SVBIBP p9axbibSdT4g1aX/ACyyldJGCmRl5VC+pJoENB41upOIrK/1W1W8tYHBeHJGR6/Mdd69P6Ve2Wp6 bBfafKJLaZOZGH9fevMs3DtnoFsJNTW6ZCcGeJQVQ+46/fFX3wr4ls9Im/Cxail1pc7ZYdHgb+Ir 6euD70I2Vl+HNJkUvsyBlIKkZBBpJhRSLYwaKuD1FKkY61zAA6Vm0EQKW2pZeXuKSU4pQnbaoorh TSZ5RR96IwqDqnOcChRosAdaFZqocYB6UrG2/Sk1BzS8Y3rcQ7tj7U9i3ppboaF1r3D+l+bFf6xY 296q/s4ZSTgnozKu+PatodalfWmm2Zu76dIYV/ebv7AdSfYVmPFnirO4ktOHoDD+7+JlALf8V7fX 7VfOEtW1K0sNSng4j0Xi7VrlwbeGS9NmkSY/IkfKwXt8+5rJ9S401nT9TOleLPAyzo7HlvIIBDOo /iR1+GQD2OKuMqNftPqd1JPqM81xNJ+aSVizH61evCS+s40l0q5jhWYDy/2oGCMgxsc9gwAPtUzx F4ZTDQouJeGmm1TRp4xMvNHyzRoRnJHRh7j7Vmmr+ZYTW+q2p/aQNhwD+de6mojWPETxMvdE0KG1 1LmFxFzJDaKRGWYE/E6rsFHQD296wHiDjbiTXJna71KZUb/8UR5Fx6bdfrTXiC9u9b1iW/uA3NId lySFHTApyugO9mLiHJI3xVVXzzN8TMSfc0406/u9OnE9pO8TjuO9WCLR47q3DACGdR3GVb2P96tX iN4K8Q8G8LR8QXV5YzxYX8RBEx5oS3pnZgDtQJaD4j3z2fk6laC5jGzSR/mX3q26JxClzah47kTI 35SVwe+29UfwY0NNQur2/uA4ihVY0IPVzv8AI7Dv61aNStX03U5YT5fKeZ4+VcdOQ7j1xn7VApqL /wCZWNzbSr+zmjZcHsazC202WwnVortlukJ/J0BHbPetK06QvEzEHIkI+hFI3fh1+O4e1Pia31uO G5t5ARZsm7DAyQc5/Sg0Dwd4ztdU4Y/C6jMyX1rJycnLn4NsfYnGPf2q6aVq2m6tC02n3ccyI5jf BwUYdQwO4Psa8kcLa3LpGssWeTyXfEgU4Jwa2vWLxdQ4e1oWGnabc6Zcwfio721YJcQuF5v2i7MQ G2BGwzvRZWsOuKTIIqgeC/FjaxpB0q+maS+tF+FnOWkj9fmOn2rQGO9ZsUl0augkVwjfOKAB9qiu 52pNmzttRiDRGU5xQGQ9xQrqKcbihWaIxF70snWklB74paJcmtQRXHHEq8NaA90ih7uU8lup9cbs fYf2rDLvWJ9QvGur6WWWd2y7Mc5q9eOV60Op6XbqhYLA8jHtu2P6VQYruAkeZFsepxW2KfQTI35G Gau/DvG99aWo0zW401vR2xz211livujHdSO3p7VSLdrGX9/lPTI9KXaCXk/ZOjemaDW9M02NpbTi LT/FjVrPhaxbzf8AL7mTnmgYDaH4iQVxtuDt69aoviLrXD+u67LcaLpE1pBJnzWcgLMf4gmAVPr2 /nVMnlBcRXCBJ13QkbA06065FxCWcYdG5WT0IoGI0eISMgUe2RR9OJt5Ws3xjtt1qUB518zG6HtU frScskVygIwd8UHZ7copUCjaxe8bcR6Yuj3V7NeW5OUiCc0jY3+ZG2aXibmUSb4YdRU3wbxqvA2q zak1i9z5kBjDkcxjOf5f2FTQXwqnWz4eOmvGY5orhxICuG5tuvv2ol9rcOs69d+UcpZOsRYfvFld f54qNtOJ5+Jtf1XVpMwySzI/L3/Ly/8A1FHg0aLS9aklslKw3flM8Z3AbzR09tzVEjYcoeULsGAc CmvEOnarqt1aWelreSySK3PBbgnnUYO4HYUdJkSWAKd+XlOPepJ+J9S4VhttZ01Od1k5JEJ/OpBy PuB9hQZLxHpk+n6sySQvE2fiR1wVYdQQad2F5eWkb/h5XiJjaNgDsVbZhj3qa4o1W64nu5tVvIDF M7c2CcmohlBJ2xlaB9wzrl1w/r8Go2ZUyxDHK35WB6g16R4T1634i0SHUYF8st8MkZOeRh1FeW/J WRw2enU1qvgnxTp+nR3Gj6lMtuZ5Q8EjbKSRjlJ7dBUpK2QkD0rg3zRGIJ2O1GSstlFwAc0VsA7V 35UXBzUo6p2oV0KaFZVGKm9LxKM9M0T0NdklENvLLjPIhYD5CukRjfi1qCXHF88eQY7ZFgH2yf1J +1VyGOORMYGKbNK97JPLcMWkkcyOT3J60haieGQqmeXO2a0wkJ7FGHwkKexBpBLqeycJOTy9m7U/ tJo5sebGMinM8VpLGedMqN996COvF/Hw+ZFyyOo6A9fl71H2NwEvg2cGQcrjpuOmf+u1PxZRK7SW NyFx+5UfqyK3/eEBW4jPM4/jH96Im7d/jOGG/auXo57RlO/ptTHS7xJ4lIOT0p60mQ4GxopppdwG QwOfy7UtIshlFuInmD4AULnOegqOGI7wMo2JwfSn8mXQgNysR8LZ70Fp4h8ML/hWK01C51Czb8eq q0ETEtGx3HsR7im96wjWJFlVmjKq3fmxlz/8ahtK1TXdc4jjt9cvGljit2EQXbmIAAz9KXnYWVq8 apjCSSDfJGwTr/yNAiMlFy/7oORvjepDihUk0CGFZFjDToDI/wCUZOMn7060vT7OXQVvpLadCwZY wzBEkPL8JJPQBs5OcHp61E8Xs3+TwW867mQcy47gGgl+P+CtL4d0qyv9L1w6glwgLq6gEZHUY/lv Wb3MmG5c4PKR9akYbaRinPczyRpuiO2QufTNRuqxNHdKvYsD/wBfagPFtEAegFFkbzcKq7A9aDDK igx8qHb8zHCiiPQfhJrX+bcJQiWQyT2rGFyeu3Q/aropX0rzp4e8YS8JXqxPCJ7O5I89R+ZcfvCv QOnXttqFlFeWkgkglUMjDuKzY1DvPegGoLRWqNFFbI7UK4g2O1CshgAcelGRFkUxt0YEH610jAo0 LKD71qDzaI1g1Ke1JB5XZM/I4o1xGY2Db4FLcWRfgOML9OgS8kH0LH+4p75AmgDcwwa2wYYLplNi BtiixzzRH4gWA7EVyM/h5miz8NPIRHMOUAADvnFENpYrO7X87wSnoU/tTC80bUoR5kEgvUG+xww+ h/pUxJawr8cec02FxJE27HlHrQQOkTG2lliZCuGyA2xHtUxFKzfER9qYaxBLPci9tD5g5cSRj8w9 x6ijW8+YsZHTegPLvuMDBp2JAIlbbcY6U2tpPMV8/LOKAceXjB2oJ3hCeN9Z3UMywvjbp0rt9bmS OeeQZeQQrGN8AkFjt26ioLhed4uKIU5iOdXBH/GrLqDfEkaDc3BJ/wCKqv8ASirgbmxtLENZ6W7x ae37cMQFYc+Qq8wOSAASfXvvVP8AEdndhLJzeZ+LfmBbJyc7VJPdW0ziW6hilkAzzFFBHyqJ8RLt brTLec8gkacFiFwT8JoIK2lXlBYbVE8RMFaKT19/ejG5wAq0x1uVnhiB3IbagdwqHVc9MUSP9vdF v3E+FaPptvdX7Q2NhDJPdzEJHGgySTWq2/hOukcMvd6teSC/EZZUhwY1OM4JxknttjrQZqYlDGRx sOgNaX4M8Sw2Usmi6hOscUp5rcucAN3X60Xh3wf4k1e1ju7ua105GHMsUuWk/wCQH5flnNUnUrFo NVms5MB7VzGeX+JTg4+1QemVwRkYxRGJzWd+H3HtrJDBo+sytDdKOSOdz8MnoCexrRRg4IIIPTFZ aHRtutCiEkChWappJkLjJrkaEnJo8rCgjYxvW4MN8XrNYeNr3GR5ipJ09VFRWg3XnQlMnmTYg1dP G+z/APGbO8HSa25D81J/oRWXQztYXvm78j7PVYqw6hFGSJOXp1OKbpzKOZExipGIxXduGT4sjam3 lmJzG+du4NULW84YBZCPfaiXtosykxt13xXFCEEDOR39aNDKUk/KceuelEVq/gu4JcRrICD13okr XBhE8x+IHDEdSOxNW55oHT4lXPqKjr+2je0kjHLlxjtRURZNiPYjfvXWbAIYCkbZuVeQnp1HvSU8 yDIBG+2KIW055DxDp7W7AP5uCcA/Dj4v0zVnhdpZIWOxMZkP/Ni230IqrcMRo2tyTqCTDbyMBnbJ HKP/AJVaLGRBcXDkhkiYRqADsFGB/KosSyxQOg50JPuKqvH7CJrG2RsjDyN+gFS9veTT3W4+HPrV X48uObWljBwI4gMDsTRTbhvTbjXtfstHtZooprqURh5Gwq+5qa8ZeCLjgi6s7d9VttRSUkiSIcpV h1BXP61VrW4ltikttIYZo2DI69Qw70e9utS1/VrSPULlp5ZZVXJwOpAJrSN7/wAO3B6Wul/9qL5R +JuU5bcMu6Reo92/lWo2GmS6hqBv78AJGSLe3JyFH8Te/f2+dNOHEkt+FtOtRcRmNY1AePBGw2pT U9RFoHSW9htI+txcu3KIl74z1JOwHr8qjUTkY/Fo0Vs7RW6bM46k98e9Y54+No6alYWtjbRw3MMZ 82RVwFQ9Ax9Sd9/ep3WvFzQ7G3FhollcXap8IkJ8tSPXfcn3xVP408QdP4k4Yk0M6GLbmkEvmGfn JcHOTsPehqhpb2kjiTl53H72dqv/AADxs9hyaZquTaDaObmyY/Y+1Z000iAsRGF6DkJOPnSE1xME ODzKR2rNiPTqyxTQrLDIskbDKspyCKFZ14IX7XGgXFm0vP5MpIBOcA9PpQrNaXlmwBtXRkkGkQwz SqZyKSireMVh+I4TivAPitJ1JP8ApbY/ry1h+pW3OTgnHbevQPHsN3qOjwaDYcguNTnEILdAoBcn /wBtYMRzKFIww2atxmmGl6nPps3lOeaPO4qz2t5aXyghwD71WLy18zJC71FwzSW8uCSKrK+XFpyn nifJPXemp8xdgjZ+RqOtLucqG5+YEbUs2pyoCWJoHEqyn8sbkkdlNRV4uoKwPkuvtykVIwcQRK2G yKPNrEEzpySFGXpk/Cw9DRVT1qeW1lWQoyCUZx03HWo1LtpG3JrdvBySz1fxAtbDUeG7PUomVjI8 8SukAxs45ts5GPXrXp+z4b4chAaHQ9MjI6FbVB/SkMeG/CzTdS1jiRNOtdPu5TduiGZISyRKrcxZ j6bV6l4a8FuDLSxWO7hvb+U7vJNM0ZLHr8KYx+taqkcUIVI41VewUYArsigSZGKuKoKeEHAMJDpo rhvX8VKf5tTW78DfDW+na5udDleV8Zb8ZMOnyatLyCu/pQQgDqKDNbPwJ8M7W6juU4e8xozkLLcy un1Utg/WlX8EfDsasurW+hm3u0bmUxXEiqp9lzj9K0gkAda7nK7VRVZeDdIeJwqzwzH8s6vzPGfU Bsr+lecPGPh/ifh/iBbLW9Sl1GxkzJY3LfCGGdwR0DDv8/evWb7HOdqpvjLwsvGHh9qGnxIDf26G 4sX7iVRnl/5br9amFeQiOQ4yCPbrTeSLLl4zv1371DQX8yA+YCGHX2NBuJCshR0UgbdKiHdxd8jl dwfSm348xnm5Oai/ibe/bflRj0P96ZXHNBLyMpA96GtG8G9cS34rWDIWO9Qxuv8AqG6n+n1oVV/D S3a7490qNCwVZfNbHooJ/pQrnZjUejBjPSl12IG1Nhkt0pxGD1NSVcQ3F17HpeqcOalNJyRRaj5b tnAAdGWsIlYJql7CSMpcOvX0Y1vvG3DZ4q4Yn0qO4W3nLLJDKwyEZTnt7ZFYxxfwJq3DV9LNNfR6 j+xWeeRFK8nM3IOvXfG9bjNRbJzHAxnGMUwurJJVbb608t5gFywJJ9KcZXBBGRVREabM1rP5E26n cGp0Q288PMNjTC/tkki5kwCB2613SblsCF2APT50AvNMj5TIuB3z3qNSMBvi7Hap65TbP9dqirhV QFsYOelNMW7wn1ltC4uspjPy28kgSTmOwB6H23r2Fpl+rwhieY8ucj+9fP1794mJBAxWhcEeJHF1 vYCCDU3Fra/xgNk9lyRnHtRXtA3S8iuCppvd38ceDg7HtWD6B40W7Wwe9tQZlHxRBsBv9p7VcND4 80ni+ynbTGkt7222ntJtnXPQjsw9xV1V8XX7U8o5XO5H8v70dNcsOflZ+VvQmszvG1Yc7RJzxk5y N+tIQX8sbjmkhBO551II+9T9GNfTUbaTHJMjfI04S4UjGay+zvp5CpJBP+kbCp2y1GRByFmPfc9K foxb2lDE7/ShDIomUZ2OxHzquG8nZQyinNpdyNgEYYGmmPH3jLoFpo3iVrulEGBBcmaEqNuST4wP pzY+lUu60i2EBImVpPbvWwf4z7Q23iBpeoRLgXenAE+pR2/owrCo7iUOAT9KrIvJJbyfDkYNSkFx Hd24jlxzr+U0QeVdDHMFIG5NMJk8p/gl+1X0aj4Eaf5vFNzeFdrW2IB92IA/QGhVn8BdNntOF59U uYyn46UeVnqyLtn5Ek/ahXLq/Wo0LlAbY0dAc9TSJOD70ojjY1iNU7iyM4yKqfF8IvNa1KyfcT8N 3DKD3aN1dftirVDKp9Kz/wAbLq60pdO1ywmWN+SazfftIvp8s1uJWNluQq/NsRnrTtJkbBUjGKj7 OSOeE2rN8QGVNNj51jNljzRnvW2U7JKrKQoyR1phM/lSK64z1NKQOsq8/MObrmkJjzty8h9M1BOW E63UIQMOalZdBkul+J8DtvVTE01pLzwyMuPQU4bi3UUQJkEjbJFMNSl1wzbwjnurxQvcLTC61OGO FdO08IkS+nf5moDUtbvr5irynHtSNoxiIbO9WRFnVWEBfm3A9abadJxE16l3pN7cWk8eyyxyFDj0 yO3tSNpqSD4ZcYPWp231e0t7blgYA43FBYLbxG474Zt+eW/ttULYDrOhJP1BGftVguOK+MNV4cte I7LheG4sp8q01rO+Y5FPxKy9j36dMVlVzfSX1zsMhdzWm/4fePLPhniSTh7XJUj0bWCFLufggnH5 XPoD+Un5HtRZVq4J8Q+GDpbza/FrMFzAc3Kxwq/kL/ER1K57gbbVpPDHFPAuuMo0jjbT5XbcQzv5 T/8Apf8AtTTirwj0niC9N7a6oulXirmKSABiT/qGd1x2715q8SOAZ9D4gm0y4jWw1INlMHFtdL2a Mn8pP8JqZFe1raxkKjlaGZT3RhinSWLKdosZr532mu8UcPXLQ2uralp8sZwViuHT+Rqaj8XPEeKE xJxfqnKRjeXJ+53rWJrYv8cd9YC94ask5Wv44pXch90jJUAEe5B+1eaDKSMgkGlNV1HUNVvZL3Ur ye7uZDl5ZnLMfqaluDODeIeLLvydHsHkjU4kuH+GKP5sf5DJ9qvgghI4YkM2/vWreEXhhfcQSxaz r8Ulto6kMkbfC917DuF9+/b1GkcB+EPDvDix3mphdX1Jfi5pF/Yxn/Snf5tn5Cr9PP22A6Vi9Lhp NHFEkcEEaxRRqFRFGAoHQAUKTnkHPmhXOqTcnNBGNChWI0VjbvgVnX+IiMtwbZzKQDHeA/dWoUK6 c+s1g0N2/mrMuzKas0YS/tQ7LguvN8qFCt1kwV2tJ/KU5XNO2cqSQBkj0oUKBheFmzvudyah51Oe tChViVyOFQfenCxKRvQoVUJSRgHrSTMQ2ATQoVYsOIZ3gBAJpve3Ek5BJ70KFIJXh/i/ibQZEl0r Wbu2KkEKJCV/9J2q73Pi3xPxRZ/5JqENhNPe8tsLqSLLICcbD60KFLCM+4gDw63cWUkjTpaSNApb qQpIrWPD/wAMeG+IuDLW/vWvYbqUNl4ZRjrtsQaFCs9XIs9XHhvwa4M04CS9hudVl5sg3EnKg/4r jP1zWhW8dvY2kdpZW8NtbxjCRRIFVR7AUKFc9tawSSViDTSRsnehQqKbyjLbUKFCs0j/2Q== Cheerio! --Multipart_Sun_Oct_17_10:37:40_2010-1-- mu-1.6.10/lib/testdir4/multimime!2,FS000066400000000000000000000011601414367003600171070ustar00rootroot00000000000000Return-path: <> Envelope-to: djcb@localhost Delivery-date: Sun, 20 May 2012 09:59:51 +0300 From: Steve Jobs To: Bill Gates Subject: multimime User-agent: mu4e 0.9.8.4; emacs 23.3.1 Date: Sat, 19 May 2012 20:57:56 +0100 Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain abc --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename="test1.C" Content-Transfer-Encoding: base64 aGVyZSBpcyBhIHNpbXBsZSB0ZXN0IGZpbGUuCg== --=-=-= Content-Type: text/plain def --=-=-=-- mu-1.6.10/lib/testdir4/signed!2,S000066400000000000000000000024151414367003600162540ustar00rootroot00000000000000User-agent: mu4e 1.1.0; emacs 27.0.50 From: Skipio To: Hannibal Subject: test 123 Date: Sun, 24 Mar 2019 11:50:42 +0200 Message-ID: <87zhpky51p.fsf@djcbsoftware.nl> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" --=-=-= Content-Type: text/plain I am signed! --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEaYec7RdFk3UPFNqYEd3+qdzEoDYFAlyXUwAACgkQEd3+qdzE oDbjdw//dAosaEyqSfyUMXjS++iJEeDIwKwO6AjEI0xCbJjHmxq93PA61ApE/BS3 d/sKa1dsfN+plRS+Fh3NNGSA7evar9dXtMBUr6hwL0VTmm5NDwedaPeuW6mgyVcB VNUn5x1e/QdnSClapnGd156sryfcM1pg/667fTHT6WC01Xe0sezpkV9l0j4pslYt y6ud/Hejszax+NcwQY7vkCcVWfB9K4zbiapdoCjHi78S4YAcsbd//KmePOqn04Sa Tg1XsmMzIh7L/3njkJdIOd9XctTwYEcN5geY1QKrHQ/3+gBeaEYvwsvrnqnVKqMY WCg/aYibuXl+xNkPMcKHIj1dXA3m5MkL77RrxODiAYz0YkiQx1/DLZs8PV3IVoB4 f0GGDqyiOwSmSDa4iuCottwO4yG1WM1i7r6pir22qAekIt43wSdwakOrT1IkS8q2 o0VGiQtEPy27D+ufiw06t02Ryf20Q7i2YcueZxYeRBq41m11M41DJ4wH7LQcJsww qG5iBOdwQFCTWpi1UrbbFjlxXXWvKMuIU+4k7nsamrEL4SDXmq1v13vtlcgJ6vnn v7c9+MF7laqdfI+BYnlD1v/9LosPbFTm0hPdvK4yIOORp8Iwj/1PGzTOz6SCUxzA kDu+Y+NN9/SM1ppStg1OikYPcfEXF8igWhuORwqcmpgHxVkIQ9I= =wnkU -----END PGP SIGNATURE----- --=-=-=-- mu-1.6.10/lib/testdir4/signed-bad!2,S000066400000000000000000000016611414367003600170020ustar00rootroot00000000000000Return-path: <> Envelope-to: skipio@localhost Delivery-date: Fri, 11 May 2012 16:21:57 +0300 Received: from localhost.roma.net([127.0.0.1] helo=borealis) by borealis with esmtp (Exim 4.77) id 1SSpnB-00038a-55 for djcb@localhost; Fri, 11 May 2012 16:21:57 +0300 From: Skipio To: Hannibal Subject: signed User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:20:45 +0300 Message-ID: <878vgy97ma.fsf@roma.net> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha1; protocol="application/pgp-signature" --=-=-= Content-Type: text/plain I am signed! But it's not good because I added this later --=-=-= Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iEYEARECAAYFAk+tEi0ACgkQ6WrHoQF92jxTzACeKd/XxY+P7bpymWL3JBRHaW9p DpwAoKw7PDW4z/lNTkWjndVTjoO9jGhs =blXz -----END PGP SIGNATURE----- --=-=-=-- mu-1.6.10/lib/testdir4/signed-encrypted!2,S000066400000000000000000000044011414367003600202440ustar00rootroot00000000000000Return-path: <> Envelope-to: karjala@localhost Delivery-date: Fri, 11 May 2012 16:37:57 +0300 From: karjala@example.com To: lapinkulta@example.com Subject: signed + encrypted User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8 Date: Fri, 11 May 2012 16:36:08 +0300 Message-ID: <874nrm96wn.fsf@example.com> MIME-Version: 1.0 Content-Type: multipart/encrypted; boundary="=-=-="; protocol="application/pgp-encrypted" --=-=-= Content-Type: application/pgp-encrypted Version: 1 --=-=-= Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.12 (GNU/Linux) hQQOA1T38TPQrHD6EA/+K4kSpMa7zk+qihUkQnHSq28xYxisNQx6X5DVNjA/Qx16 uZj/40ae+PoSMTVfklP+B2S/IomuTW6dwVqS7aQ3u4MTzi+YOi11k1lEMD7hR0Wb L0i48o3/iCPuCTpnOsaLZvRL06g+oTi0BF2pgz/YdsgsBTGrTb3pkDGSlLIhvh/J P8eE3OuzkXS6d8ymJKx2S2wQJrc1AFf1BgJfgc5T0iAvcV+zIMG+PIYcVd04zVpj cORFEfvGgfxWkeX+Ks3tu/l5PA1EesnoqFdNFZm+RKBg3RFsOm8tBlJ46xJjfeHg zLgifeSLy3tOX7CvWYs9torrx7s7UOI2gV8kzBqz+a7diyCMezceeQ9l0nIRybwW C9Egp8Bpfb02iXTOGdE/vRiNItQH14GKmXf4nCSwdtQUm3yzaqY9yL3xBxAlW53e YOFfPMESt+E7IlPn0c7llWGrcdrhJbUEoGOIPezES7kdeNPzi8G1lLtvT04/SSZJ QxPH5FNzSFaYFAQSdI7TR69P7L7vtLL8ndkjY49HfLFXochQQzsqrzVxzRCruHxA zbZSRptNf9SuXEaX9buO1vlFHheGvrCKzEWa6O7JD/DiyrE/zqy4jdlh9abMCouQ GWGSbn8jk6SMTQQ2Yv/VOyFqifHZp0UJD59tyIdenpxoYu5M0lwHLNVDlRjLEwUQ AIDz1tbLoM7lxs2FOKGr8QqbKIeMfL+NUmbvVIDc4mJrOlRnHh+cZYm4Z49iTl1v bYNMYgR5nY7W6rqh0ae7ZOW0h2NzpkAwTzuf1YrSjNavd9KBwOCFtAoZhRwfwFVx ju+ByHFNnf7g/R6DekHS0pSiatM0cPDJT05atEZb+13CRHHznonmLHi+VahXjrpg cIUA8Lhjdfm6Fsabo7gNZnTTRxNBqUXKK2vJF/XLbNrH5K2BH2dCCmUNtm3yFWiM DOzaw3665Y3S6MvZdyKpatbNrVoJdBpRgPxJ1YCSEituFUqHJBStay+aRb5fVkQR w3+9hWw+Ob0+2EumKbgfQ7iMwTZBCZP4VOxkoqdHvs9aWm4N7wHtXsyCew3icbJx lyUWsDx/FI+HlQRfOqeAMxmp8kKybmHNw8oGiw+uPPUHSD1NFYVm2DtwhYll3Fvs YY7r5s3yP1ZnwxMqWI3OsExVUXs8MS4UTAgO+cggO7YidPcANbBDihBFP8mTXtni Oo5n5v+/eRoLfHmnsGcaK8EkKsfFHpbqn4gxXGcBuHaTTJ/ZhbW6bi1WWZA9ExaJ IeTDtp5Bks1pJvTjCDacvgwl3rEBM6yaeIvB7575Y/GPMTOZhawhfOxV1smMmTKI JOWYb3+PuN2cvWetkjFgH8re4sRXq22DKBZHJEWYU8sH0sACAePnIr+pkrOtGeJB t1zBqZUnrupH6ptk9n/AjbQ+XSMTEKu55gSjYLAYx1EHApx52QLkdh+ej5xCIVeY 6wS1Iipkoc6/r6F7CKctupXurNY2AlD4uQIOfD6kQgkqK4PY3hsRHQA+Zqj6oRfr kxysFJZvhgt26IeBVapFs10WuYt9iHfpbPUBQUIZCLyPAh08UdVW64Uc2DvUPy+I C+3RrmTHQPP/YNKgDQaZ3ySVEDkqjaDPmXr5K0Ibaib2dtPCLcA= =pv03 -----END PGP MESSAGE----- --=-=-=-- mu-1.6.10/lib/testdir4/special!2,Sabc000066400000000000000000000005361414367003600170730ustar00rootroot00000000000000Date: Thu, 1 Jun 2012 14:57:25 -0200 From: "Rocky Balboa" To: "Ivan Drago" Subject: currying and tail optimization Message-id: <3BE9E653ef345@emss35m06.us.lmco.com> MIME-version: 1.0 Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Test 123. I'm a special message with special flags. mu-1.6.10/lib/tokenize.cc000066400000000000000000000021521414367003600151310ustar00rootroot00000000000000/* ** Copyright (C) 2017-2020 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include #include #include "mu-tokenizer.hh" int main (int argc, char *argv[]) { std::string s; for (auto i = 1; i < argc; ++i) s += " " + std::string(argv[i]); const auto tvec = Mu::tokenize (s); for (const auto& t : tvec) std::cout << t << std::endl; return 0; } mu-1.6.10/lib/utils/000077500000000000000000000000001414367003600141325ustar00rootroot00000000000000mu-1.6.10/lib/utils/Makefile.am000066400000000000000000000055661414367003600162020ustar00rootroot00000000000000## Copyright (C) 2020 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk AM_CFLAGS= \ $(WARN_CFLAGS) \ $(GLIB_CFLAGS) \ $(ASAN_CFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ -DMU_TESTMAILDIR=\"${abs_top_srcdir}/lib/testdir\" \ -DMU_TESTMAILDIR2=\"${abs_top_srcdir}/lib/testdir2\" \ -Wno-format-nonliteral \ -Wno-switch-enum \ -Wno-deprecated-declarations \ -Wno-inline \ -I${top_srcdir}/lib AM_CPPFLAGS= \ $(CODE_COVERAGE_CPPFLAGS) AM_CXXFLAGS= \ $(WARN_CXXFLAGS) \ $(GLIB_CFLAGS) \ $(ASAN_CXXFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ -I${top_srcdir}/lib AM_LDFLAGS= \ $(ASAN_LDFLAGS) noinst_LTLIBRARIES= \ libmu-utils.la third_party= \ optional.hpp \ expected.hpp libmu_utils_la_SOURCES= \ mu-async-queue.hh \ mu-command-parser.cc \ mu-command-parser.hh \ mu-date.c \ mu-date.h \ mu-error.hh \ mu-logger.cc \ mu-logger.hh \ mu-option.hh \ mu-readline.cc \ mu-readline.hh \ mu-result.hh \ mu-sexp.cc \ mu-sexp.hh \ mu-str.c \ mu-str.h \ mu-util.c \ mu-util.h \ mu-utils.cc \ mu-utils.hh \ ${third_party} libmu_utils_la_LIBADD= \ $(GLIB_LIBS) \ $(READLINE_LIBS) \ $(CODE_COVERAGE_LIBS) noinst_PROGRAMS= \ $(TEST_PROGS) TEST_PROGS+= \ test-mu-util test_mu_util_SOURCES= \ test-mu-util.c test_mu_util_LDADD= \ libmu-utils.la TEST_PROGS+= \ test-mu-utils test_mu_utils_SOURCES= \ test-utils.cc test_mu_utils_LDADD= \ libmu-utils.la TEST_PROGS+= \ test-mu-str test_mu_str_SOURCES= \ test-mu-str.c test_mu_str_LDADD= \ libmu-utils.la TEST_PROGS+= \ test-sexp test_sexp_SOURCES= \ test-sexp.cc test_sexp_LDADD= \ libmu-utils.la TEST_PROGS+= \ test-command-parser test_command_parser_SOURCES= \ test-command-parser.cc test_command_parser_LDADD= \ libmu-utils.la TEST_PROGS+= \ test-option test_option_SOURCES= \ test-option.cc test_option_LDADD= \ libmu-utils.la TESTS=$(TEST_PROGS) include $(top_srcdir)/aminclude_static.am mu-1.6.10/lib/utils/expected.hpp000066400000000000000000002555041414367003600164570ustar00rootroot00000000000000/// // expected - An implementation of std::expected with extensions // Written in 2017 by Simon Brand (simonrbrand@gmail.com, @TartanLlama) // // Documentation available at http://tl.tartanllama.xyz/ // // To the extent possible under law, the author(s) have dedicated all // copyright and related and neighboring rights to this software to the // public domain worldwide. This software is distributed without any warranty. // // You should have received a copy of the CC0 Public Domain Dedication // along with this software. If not, see // . /// #ifndef TL_EXPECTED_HPP #define TL_EXPECTED_HPP #define TL_EXPECTED_VERSION_MAJOR 1 #define TL_EXPECTED_VERSION_MINOR 0 #define TL_EXPECTED_VERSION_PATCH 1 #include #include #include #include #if defined(__EXCEPTIONS) || defined(_CPPUNWIND) #define TL_EXPECTED_EXCEPTIONS_ENABLED #endif #if (defined(_MSC_VER) && _MSC_VER == 1900) #define TL_EXPECTED_MSVC2015 #define TL_EXPECTED_MSVC2015_CONSTEXPR #else #define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ !defined(__clang__)) #define TL_EXPECTED_GCC49 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ !defined(__clang__)) #define TL_EXPECTED_GCC54 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ !defined(__clang__)) #define TL_EXPECTED_GCC55 #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ !defined(__clang__)) // GCC < 5 doesn't support overloading on const&& for member functions #define TL_EXPECTED_NO_CONSTRR // GCC < 5 doesn't support some standard C++11 type traits #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::has_trivial_copy_constructor #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::has_trivial_copy_assign // This one will be different for GCC 5.7 if it's ever supported #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ std::is_trivially_destructible // GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector // for non-copyable types #elif (defined(__GNUC__) && __GNUC__ < 8 && \ !defined(__clang__)) #ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX #define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX namespace tl { namespace detail { template struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; #ifdef _GLIBCXX_VECTOR template struct is_trivially_copy_constructible> : std::false_type{}; #endif } } #endif #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ tl::detail::is_trivially_copy_constructible #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible #else #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::is_trivially_copy_constructible #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ std::is_trivially_destructible #endif #if __cplusplus > 201103L #define TL_EXPECTED_CXX14 #endif #ifdef TL_EXPECTED_GCC49 #define TL_EXPECTED_GCC49_CONSTEXPR #else #define TL_EXPECTED_GCC49_CONSTEXPR constexpr #endif #if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ defined(TL_EXPECTED_GCC49)) #define TL_EXPECTED_11_CONSTEXPR #else #define TL_EXPECTED_11_CONSTEXPR constexpr #endif namespace tl { template class expected; #ifndef TL_MONOSTATE_INPLACE_MUTEX #define TL_MONOSTATE_INPLACE_MUTEX class monostate {}; struct in_place_t { explicit in_place_t() = default; }; static constexpr in_place_t in_place{}; #endif template class unexpected { public: static_assert(!std::is_same::value, "E must not be void"); unexpected() = delete; constexpr explicit unexpected(const E &e) : m_val(e) {} constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} constexpr const E &value() const & { return m_val; } TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } constexpr const E &&value() const && { return std::move(m_val); } private: E m_val; }; template constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { return lhs.value() == rhs.value(); } template constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() != rhs.value(); } template constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { return lhs.value() < rhs.value(); } template constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() <= rhs.value(); } template constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { return lhs.value() > rhs.value(); } template constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() >= rhs.value(); } template unexpected::type> make_unexpected(E &&e) { return unexpected::type>(std::forward(e)); } struct unexpect_t { unexpect_t() = default; }; static constexpr unexpect_t unexpect{}; namespace detail { template [[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED throw std::forward(e); #else #ifdef _MSC_VER __assume(0); #else __builtin_unreachable(); #endif #endif } #ifndef TL_TRAITS_MUTEX #define TL_TRAITS_MUTEX // C++14-style aliases for brevity template using remove_const_t = typename std::remove_const::type; template using remove_reference_t = typename std::remove_reference::type; template using decay_t = typename std::decay::type; template using enable_if_t = typename std::enable_if::type; template using conditional_t = typename std::conditional::type; // std::conjunction from C++17 template struct conjunction : std::true_type {}; template struct conjunction : B {}; template struct conjunction : std::conditional, B>::type {}; #if defined(_LIBCPP_VERSION) && __cplusplus == 201103L #define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND #endif // In C++11 mode, there's an issue in libc++'s std::mem_fn // which results in a hard-error when using it in a noexcept expression // in some cases. This is a check to workaround the common failing case. #ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND template struct is_pointer_to_non_const_member_func : std::false_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_const_or_const_ref : std::false_type {}; template struct is_const_or_const_ref : std::true_type {}; template struct is_const_or_const_ref : std::true_type {}; #endif // std::invoke from C++17 // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround template ::value && is_const_or_const_ref::value)>, #endif typename = enable_if_t>::value>, int = 0> constexpr auto invoke(Fn && f, Args && ... args) noexcept( noexcept(std::mem_fn(f)(std::forward(args)...))) -> decltype(std::mem_fn(f)(std::forward(args)...)) { return std::mem_fn(f)(std::forward(args)...); } template >::value>> constexpr auto invoke(Fn && f, Args && ... args) noexcept( noexcept(std::forward(f)(std::forward(args)...))) -> decltype(std::forward(f)(std::forward(args)...)) { return std::forward(f)(std::forward(args)...); } // std::invoke_result from C++17 template struct invoke_result_impl; template struct invoke_result_impl< F, decltype(detail::invoke(std::declval(), std::declval()...), void()), Us...> { using type = decltype(detail::invoke(std::declval(), std::declval()...)); }; template using invoke_result = invoke_result_impl; template using invoke_result_t = typename invoke_result::type; #if defined(_MSC_VER) && _MSC_VER <= 1900 // TODO make a version which works with MSVC 2015 template struct is_swappable : std::true_type {}; template struct is_nothrow_swappable : std::true_type {}; #else // https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept namespace swap_adl_tests { // if swap ADL finds this then it would call std::swap otherwise (same // signature) struct tag {}; template tag swap(T&, T&); template tag swap(T(&a)[N], T(&b)[N]); // helper functions to test if an unqualified swap is possible, and if it // becomes std::swap template std::false_type can_swap(...) noexcept(false); template (), std::declval()))> std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), std::declval()))); template std::false_type uses_std(...); template std::is_same(), std::declval())), tag> uses_std(int); template struct is_std_swap_noexcept : std::integral_constant::value&& std::is_nothrow_move_assignable::value> {}; template struct is_std_swap_noexcept : is_std_swap_noexcept {}; template struct is_adl_swap_noexcept : std::integral_constant(0))> {}; } // namespace swap_adl_tests template struct is_swappable : std::integral_constant< bool, decltype(detail::swap_adl_tests::can_swap(0))::value && (!decltype(detail::swap_adl_tests::uses_std(0))::value || (std::is_move_assignable::value && std::is_move_constructible::value))> {}; template struct is_swappable : std::integral_constant< bool, decltype(detail::swap_adl_tests::can_swap(0))::value && (!decltype( detail::swap_adl_tests::uses_std(0))::value || is_swappable::value)> {}; template struct is_nothrow_swappable : std::integral_constant< bool, is_swappable::value && ((decltype(detail::swap_adl_tests::uses_std(0))::value && detail::swap_adl_tests::is_std_swap_noexcept::value) || (!decltype(detail::swap_adl_tests::uses_std(0))::value && detail::swap_adl_tests::is_adl_swap_noexcept::value))> { }; #endif #endif // Trait for checking if a type is a tl::expected template struct is_expected_impl : std::false_type {}; template struct is_expected_impl> : std::true_type {}; template using is_expected = is_expected_impl>; template using expected_enable_forward_value = detail::enable_if_t< std::is_constructible::value && !std::is_same, in_place_t>::value && !std::is_same, detail::decay_t>::value && !std::is_same, detail::decay_t>::value>; template using expected_enable_from_other = detail::enable_if_t< std::is_constructible::value && std::is_constructible::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value>; template using is_void_or = conditional_t::value, std::true_type, U>; template using is_copy_constructible_or_void = is_void_or>; template using is_move_constructible_or_void = is_void_or>; template using is_copy_assignable_or_void = is_void_or>; template using is_move_assignable_or_void = is_void_or>; } // namespace detail namespace detail { struct no_init_t {}; static constexpr no_init_t no_init{}; // Implements the storage of the values, and ensures that the destructor is // trivial if it can be. // // This specialization is for where neither `T` or `E` is trivially // destructible, so the destructors must be called on destruction of the // `expected` template ::value, bool = std::is_trivially_destructible::value> struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value> * = nullptr> constexpr expected_storage_base(in_place_t, Args &&... args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, Args &&... args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { if (m_has_val) { m_val.~T(); } else { m_unexpect.~unexpected(); } } union { T m_val; unexpected m_unexpect; char m_no_init; }; bool m_has_val; }; // This specialization is for when both `T` and `E` are trivially-destructible, // so the destructor of the `expected` can be trivial. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value> * = nullptr> constexpr expected_storage_base(in_place_t, Args &&... args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, Args &&... args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() = default; union { T m_val; unexpected m_unexpect; char m_no_init; }; bool m_has_val; }; // T is trivial, E is not. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value> * = nullptr> constexpr expected_storage_base(in_place_t, Args &&... args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, Args &&... args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { if (!m_has_val) { m_unexpect.~unexpected(); } } union { T m_val; unexpected m_unexpect; char m_no_init; }; bool m_has_val; }; // E is trivial, T is not. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value> * = nullptr> constexpr expected_storage_base(in_place_t, Args &&... args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, Args &&... args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { if (m_has_val) { m_val.~T(); } } union { T m_val; unexpected m_unexpect; char m_no_init; }; bool m_has_val; }; // `T` is `void`, `E` is trivially-destructible template struct expected_storage_base { TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base() : m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} constexpr expected_storage_base(in_place_t) : m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() = default; struct dummy {}; union { unexpected m_unexpect; dummy m_val; }; bool m_has_val; }; // `T` is `void`, `E` is not trivially-destructible template struct expected_storage_base { constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} template ::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, Args &&... args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args &&... args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { if (!m_has_val) { m_unexpect.~unexpected(); } } union { unexpected m_unexpect; char m_dummy; }; bool m_has_val; }; // This base class provides some handy member functions which can be used in // further derived classes template struct expected_operations_base : expected_storage_base { using expected_storage_base::expected_storage_base; template void construct(Args &&... args) noexcept { new (std::addressof(this->m_val)) T(std::forward(args)...); this->m_has_val = true; } template void construct_with(Rhs &&rhs) noexcept { new (std::addressof(this->m_val)) T(std::forward(rhs).get()); this->m_has_val = true; } template void construct_error(Args &&... args) noexcept { new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); this->m_has_val = false; } #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED // These assign overloads ensure that the most efficient assignment // implementation is used while maintaining the strong exception guarantee. // The problematic case is where rhs has a value, but *this does not. // // This overload handles the case where we can just copy-construct `T` // directly into place without throwing. template ::value> * = nullptr> void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(rhs.get()); } else { assign_common(rhs); } } // This overload handles the case where we can attempt to create a copy of // `T`, then no-throw move it into place if the copy was successful. template ::value && std::is_nothrow_move_constructible::value> * = nullptr> void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { T tmp = rhs.get(); geterr().~unexpected(); construct(std::move(tmp)); } else { assign_common(rhs); } } // This overload is the worst-case, where we have to move-construct the // unexpected value into temporary storage, then try to copy the T into place. // If the construction succeeds, then everything is fine, but if it throws, // then we move the old unexpected value back into place before rethrowing the // exception. template ::value && !std::is_nothrow_move_constructible::value> * = nullptr> void assign(const expected_operations_base &rhs) { if (!this->m_has_val && rhs.m_has_val) { auto tmp = std::move(geterr()); geterr().~unexpected(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { construct(rhs.get()); } catch (...) { geterr() = std::move(tmp); throw; } #else construct(rhs.get()); #endif } else { assign_common(rhs); } } // These overloads do the same as above, but for rvalues template ::value> * = nullptr> void assign(expected_operations_base &&rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(std::move(rhs).get()); } else { assign_common(std::move(rhs)); } } template ::value> * = nullptr> void assign(expected_operations_base &&rhs) { if (!this->m_has_val && rhs.m_has_val) { auto tmp = std::move(geterr()); geterr().~unexpected(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { construct(std::move(rhs).get()); } catch (...) { geterr() = std::move(tmp); throw; } #else construct(std::move(rhs).get()); #endif } else { assign_common(std::move(rhs)); } } #else // If exceptions are disabled then we can just copy-construct void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(rhs.get()); } else { assign_common(rhs); } } void assign(expected_operations_base &&rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(std::move(rhs).get()); } else { assign_common(rhs); } } #endif // The common part of move/copy assigning template void assign_common(Rhs &&rhs) { if (this->m_has_val) { if (rhs.m_has_val) { get() = std::forward(rhs).get(); } else { destroy_val(); construct_error(std::forward(rhs).geterr()); } } else { if (!rhs.m_has_val) { geterr() = std::forward(rhs).geterr(); } } } bool has_value() const { return this->m_has_val; } TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } constexpr const T &get() const & { return this->m_val; } TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } #ifndef TL_EXPECTED_NO_CONSTRR constexpr const T &&get() const && { return std::move(this->m_val); } #endif TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { return this->m_unexpect; } constexpr const unexpected &geterr() const & { return this->m_unexpect; } TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { return std::move(this->m_unexpect); } #ifndef TL_EXPECTED_NO_CONSTRR constexpr const unexpected &&geterr() const && { return std::move(this->m_unexpect); } #endif TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } }; // This base class provides some handy member functions which can be used in // further derived classes template struct expected_operations_base : expected_storage_base { using expected_storage_base::expected_storage_base; template void construct() noexcept { this->m_has_val = true; } // This function doesn't use its argument, but needs it so that code in // levels above this can work independently of whether T is void template void construct_with(Rhs &&) noexcept { this->m_has_val = true; } template void construct_error(Args &&... args) noexcept { new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); this->m_has_val = false; } template void assign(Rhs &&rhs) noexcept { if (!this->m_has_val) { if (rhs.m_has_val) { geterr().~unexpected(); construct(); } else { geterr() = std::forward(rhs).geterr(); } } else { if (!rhs.m_has_val) { construct_error(std::forward(rhs).geterr()); } } } bool has_value() const { return this->m_has_val; } TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { return this->m_unexpect; } constexpr const unexpected &geterr() const & { return this->m_unexpect; } TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { return std::move(this->m_unexpect); } #ifndef TL_EXPECTED_NO_CONSTRR constexpr const unexpected &&geterr() const && { return std::move(this->m_unexpect); } #endif TL_EXPECTED_11_CONSTEXPR void destroy_val() { //no-op } }; // This class manages conditionally having a trivial copy constructor // This specialization is for when T and E are trivially copy constructible template :: value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> struct expected_copy_base : expected_operations_base { using expected_operations_base::expected_operations_base; }; // This specialization is for when T or E are not trivially copy constructible template struct expected_copy_base : expected_operations_base { using expected_operations_base::expected_operations_base; expected_copy_base() = default; expected_copy_base(const expected_copy_base &rhs) : expected_operations_base(no_init) { if (rhs.has_value()) { this->construct_with(rhs); } else { this->construct_error(rhs.geterr()); } } expected_copy_base(expected_copy_base &&rhs) = default; expected_copy_base &operator=(const expected_copy_base &rhs) = default; expected_copy_base &operator=(expected_copy_base &&rhs) = default; }; // This class manages conditionally having a trivial move constructor // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it // doesn't implement an analogue to std::is_trivially_move_constructible. We // have to make do with a non-trivial move constructor even if T is trivially // move constructible #ifndef TL_EXPECTED_GCC49 template >::value &&std::is_trivially_move_constructible::value> struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; }; #else template struct expected_move_base; #endif template struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; expected_move_base() = default; expected_move_base(const expected_move_base &rhs) = default; expected_move_base(expected_move_base &&rhs) noexcept( std::is_nothrow_move_constructible::value) : expected_copy_base(no_init) { if (rhs.has_value()) { this->construct_with(std::move(rhs)); } else { this->construct_error(std::move(rhs.geterr())); } } expected_move_base &operator=(const expected_move_base &rhs) = default; expected_move_base &operator=(expected_move_base &&rhs) = default; }; // This class manages conditionally having a trivial copy assignment operator template >::value &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; }; template struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; expected_copy_assign_base() = default; expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { this->assign(rhs); return *this; } expected_copy_assign_base & operator=(expected_copy_assign_base &&rhs) = default; }; // This class manages conditionally having a trivial move assignment operator // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it // doesn't implement an analogue to std::is_trivially_move_assignable. We have // to make do with a non-trivial move assignment operator even if T is trivially // move assignable #ifndef TL_EXPECTED_GCC49 template , std::is_trivially_move_constructible, std::is_trivially_move_assignable>>:: value &&std::is_trivially_destructible::value &&std::is_trivially_move_constructible::value &&std::is_trivially_move_assignable::value> struct expected_move_assign_base : expected_copy_assign_base { using expected_copy_assign_base::expected_copy_assign_base; }; #else template struct expected_move_assign_base; #endif template struct expected_move_assign_base : expected_copy_assign_base { using expected_copy_assign_base::expected_copy_assign_base; expected_move_assign_base() = default; expected_move_assign_base(const expected_move_assign_base &rhs) = default; expected_move_assign_base(expected_move_assign_base &&rhs) = default; expected_move_assign_base & operator=(const expected_move_assign_base &rhs) = default; expected_move_assign_base & operator=(expected_move_assign_base &&rhs) noexcept( std::is_nothrow_move_constructible::value &&std::is_nothrow_move_assignable::value) { this->assign(std::move(rhs)); return *this; } }; // expected_delete_ctor_base will conditionally delete copy and move // constructors depending on whether T is copy/move constructible template ::value && std::is_copy_constructible::value), bool EnableMove = (is_move_constructible_or_void::value && std::is_move_constructible::value)> struct expected_delete_ctor_base { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base &) = default; expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; expected_delete_ctor_base & operator=(const expected_delete_ctor_base &) = default; expected_delete_ctor_base & operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base &) = default; expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; expected_delete_ctor_base & operator=(const expected_delete_ctor_base &) = default; expected_delete_ctor_base & operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; expected_delete_ctor_base & operator=(const expected_delete_ctor_base &) = default; expected_delete_ctor_base & operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; expected_delete_ctor_base & operator=(const expected_delete_ctor_base &) = default; expected_delete_ctor_base & operator=(expected_delete_ctor_base &&) noexcept = default; }; // expected_delete_assign_base will conditionally delete copy and move // constructors depending on whether T and E are copy/move constructible + // assignable template ::value && std::is_copy_constructible::value && is_copy_assignable_or_void::value && std::is_copy_assignable::value), bool EnableMove = (is_move_constructible_or_void::value && std::is_move_constructible::value && is_move_assignable_or_void::value && std::is_move_assignable::value)> struct expected_delete_assign_base { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base &) = default; expected_delete_assign_base(expected_delete_assign_base &&) noexcept = default; expected_delete_assign_base & operator=(const expected_delete_assign_base &) = default; expected_delete_assign_base & operator=(expected_delete_assign_base &&) noexcept = default; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base &) = default; expected_delete_assign_base(expected_delete_assign_base &&) noexcept = default; expected_delete_assign_base & operator=(const expected_delete_assign_base &) = default; expected_delete_assign_base & operator=(expected_delete_assign_base &&) noexcept = delete; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base &) = default; expected_delete_assign_base(expected_delete_assign_base &&) noexcept = default; expected_delete_assign_base & operator=(const expected_delete_assign_base &) = delete; expected_delete_assign_base & operator=(expected_delete_assign_base &&) noexcept = default; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base &) = default; expected_delete_assign_base(expected_delete_assign_base &&) noexcept = default; expected_delete_assign_base & operator=(const expected_delete_assign_base &) = delete; expected_delete_assign_base & operator=(expected_delete_assign_base &&) noexcept = delete; }; // This is needed to be able to construct the expected_default_ctor_base which // follows, while still conditionally deleting the default constructor. struct default_constructor_tag { explicit constexpr default_constructor_tag() = default; }; // expected_default_ctor_base will ensure that expected has a deleted default // consturctor if T is not default constructible. // This specialization is for when T is default constructible template ::value || std::is_void::value> struct expected_default_ctor_base { constexpr expected_default_ctor_base() noexcept = default; constexpr expected_default_ctor_base( expected_default_ctor_base const &) noexcept = default; constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = default; expected_default_ctor_base & operator=(expected_default_ctor_base const &) noexcept = default; expected_default_ctor_base & operator=(expected_default_ctor_base &&) noexcept = default; constexpr explicit expected_default_ctor_base(default_constructor_tag) {} }; // This specialization is for when T is not default constructible template struct expected_default_ctor_base { constexpr expected_default_ctor_base() noexcept = delete; constexpr expected_default_ctor_base( expected_default_ctor_base const &) noexcept = default; constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = default; expected_default_ctor_base & operator=(expected_default_ctor_base const &) noexcept = default; expected_default_ctor_base & operator=(expected_default_ctor_base &&) noexcept = default; constexpr explicit expected_default_ctor_base(default_constructor_tag) {} }; } // namespace detail template class bad_expected_access : public std::exception { public: explicit bad_expected_access(E e) : m_val(std::move(e)) {} virtual const char *what() const noexcept override { return "Bad expected access"; } const E &error() const & { return m_val; } E &error() & { return m_val; } const E &&error() const && { return std::move(m_val); } E &&error() && { return std::move(m_val); } private: E m_val; }; /// An `expected` object is an object that contains the storage for /// another object and manages the lifetime of this contained object `T`. /// Alternatively it could contain the storage for another unexpected object /// `E`. The contained object may not be initialized after the expected object /// has been initialized, and may not be destroyed before the expected object /// has been destroyed. The initialization state of the contained object is /// tracked by the expected object. template class expected : private detail::expected_move_assign_base, private detail::expected_delete_ctor_base, private detail::expected_delete_assign_base, private detail::expected_default_ctor_base { static_assert(!std::is_reference::value, "T must not be a reference"); static_assert(!std::is_same>::value, "T must not be in_place_t"); static_assert(!std::is_same>::value, "T must not be unexpect_t"); static_assert(!std::is_same>>::value, "T must not be unexpected"); static_assert(!std::is_reference::value, "E must not be a reference"); T *valptr() { return std::addressof(this->m_val); } const T *valptr() const { return std::addressof(this->m_val); } unexpected *errptr() { return std::addressof(this->m_unexpect); } const unexpected *errptr() const { return std::addressof(this->m_unexpect); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &val() { return this->m_val; } TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } template ::value> * = nullptr> constexpr const U &val() const { return this->m_val; } constexpr const unexpected &err() const { return this->m_unexpect; } using impl_base = detail::expected_move_assign_base; using ctor_base = detail::expected_default_ctor_base; public: typedef T value_type; typedef E error_type; typedef unexpected unexpected_type; #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { return and_then_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { return and_then_impl(std::move(*this), std::forward(f)); } template constexpr auto and_then(F &&f) const & { return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && { return and_then_impl(std::move(*this), std::forward(f)); } #endif #else template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & -> decltype(and_then_impl(std::declval(), std::forward(f))) { return and_then_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype( and_then_impl(std::declval(), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } template constexpr auto and_then(F &&f) const & -> decltype( and_then_impl(std::declval(), std::forward(f))) { return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && -> decltype( and_then_impl(std::declval(), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } #endif #endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template constexpr auto map(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } template constexpr auto map(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #else template TL_EXPECTED_11_CONSTEXPR decltype( expected_map_impl(std::declval(), std::declval())) map(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR decltype( expected_map_impl(std::declval(), std::declval())) map(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(expected_map_impl(std::declval(), std::declval())) map(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template constexpr decltype(expected_map_impl(std::declval(), std::declval())) map(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #endif #endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template constexpr auto transform(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } template constexpr auto transform(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #else template TL_EXPECTED_11_CONSTEXPR decltype( expected_map_impl(std::declval(), std::declval())) transform(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR decltype( expected_map_impl(std::declval(), std::declval())) transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(expected_map_impl(std::declval(), std::declval())) transform(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template constexpr decltype(expected_map_impl(std::declval(), std::declval())) transform(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #endif #endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template constexpr auto map_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } template constexpr auto map_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #else template TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), std::declval())) map_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), std::declval())) map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template constexpr decltype(map_error_impl(std::declval(), std::declval())) map_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template constexpr decltype(map_error_impl(std::declval(), std::declval())) map_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #endif #endif template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { return or_else_impl(*this, std::forward(f)); } template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { return or_else_impl(std::move(*this), std::forward(f)); } template expected constexpr or_else(F &&f) const & { return or_else_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template expected constexpr or_else(F &&f) const && { return or_else_impl(std::move(*this), std::forward(f)); } #endif constexpr expected() = default; constexpr expected(const expected &rhs) = default; constexpr expected(expected &&rhs) = default; expected &operator=(const expected &rhs) = default; expected &operator=(expected &&rhs) = default; template ::value> * = nullptr> constexpr expected(in_place_t, Args &&... args) : impl_base(in_place, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template &, Args &&...>::value> * = nullptr> constexpr expected(in_place_t, std::initializer_list il, Args &&... args) : impl_base(in_place, il, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template ::value> * = nullptr, detail::enable_if_t::value> * = nullptr> explicit constexpr expected(const unexpected &e) : impl_base(unexpect, e.value()), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr> constexpr expected(unexpected const &e) : impl_base(unexpect, e.value()), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr> explicit constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) : impl_base(unexpect, std::move(e.value())), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr> constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) : impl_base(unexpect, std::move(e.value())), ctor_base(detail::default_constructor_tag{}) {} template ::value> * = nullptr> constexpr explicit expected(unexpect_t, Args &&... args) : impl_base(unexpect, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected(unexpect_t, std::initializer_list il, Args &&... args) : impl_base(unexpect, il, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template ::value && std::is_convertible::value)> * = nullptr, detail::expected_enable_from_other * = nullptr> explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); } else { this->construct_error(rhs.error()); } } template ::value && std::is_convertible::value)> * = nullptr, detail::expected_enable_from_other * = nullptr> TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); } else { this->construct_error(rhs.error()); } } template < class U, class G, detail::enable_if_t::value && std::is_convertible::value)> * = nullptr, detail::expected_enable_from_other * = nullptr> explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); } else { this->construct_error(std::move(rhs.error())); } } template < class U, class G, detail::enable_if_t<(std::is_convertible::value && std::is_convertible::value)> * = nullptr, detail::expected_enable_from_other * = nullptr> TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); } else { this->construct_error(std::move(rhs.error())); } } template < class U = T, detail::enable_if_t::value> * = nullptr, detail::expected_enable_forward_value * = nullptr> explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward(v)) {} template < class U = T, detail::enable_if_t::value> * = nullptr, detail::expected_enable_forward_value * = nullptr> TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward(v)) {} template < class U = T, class G = T, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr, detail::enable_if_t< (!std::is_same, detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && std::is_assignable::value && std::is_nothrow_move_constructible::value)> * = nullptr> expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); } else { err().~unexpected(); ::new (valptr()) T(std::forward(v)); this->m_has_val = true; } return *this; } template < class U = T, class G = T, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr, detail::enable_if_t< (!std::is_same, detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && std::is_assignable::value && std::is_nothrow_move_constructible::value)> * = nullptr> expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); } else { auto tmp = std::move(err()); err().~unexpected(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(std::forward(v)); this->m_has_val = true; } catch (...) { err() = std::move(tmp); throw; } #else ::new (valptr()) T(std::forward(v)); this->m_has_val = true; #endif } return *this; } template ::value && std::is_assignable::value> * = nullptr> expected &operator=(const unexpected &rhs) { if (!has_value()) { err() = rhs; } else { this->destroy_val(); ::new (errptr()) unexpected(rhs); this->m_has_val = false; } return *this; } template ::value && std::is_move_assignable::value> * = nullptr> expected &operator=(unexpected &&rhs) noexcept { if (!has_value()) { err() = std::move(rhs); } else { this->destroy_val(); ::new (errptr()) unexpected(std::move(rhs)); this->m_has_val = false; } return *this; } template ::value> * = nullptr> void emplace(Args &&... args) { if (has_value()) { val() = T(std::forward(args)...); } else { err().~unexpected(); ::new (valptr()) T(std::forward(args)...); this->m_has_val = true; } } template ::value> * = nullptr> void emplace(Args &&... args) { if (has_value()) { val() = T(std::forward(args)...); } else { auto tmp = std::move(err()); err().~unexpected(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(std::forward(args)...); this->m_has_val = true; } catch (...) { err() = std::move(tmp); throw; } #else ::new (valptr()) T(std::forward(args)...); this->m_has_val = true; #endif } } template &, Args &&...>::value> * = nullptr> void emplace(std::initializer_list il, Args &&... args) { if (has_value()) { T t(il, std::forward(args)...); val() = std::move(t); } else { err().~unexpected(); ::new (valptr()) T(il, std::forward(args)...); this->m_has_val = true; } } template &, Args &&...>::value> * = nullptr> void emplace(std::initializer_list il, Args &&... args) { if (has_value()) { T t(il, std::forward(args)...); val() = std::move(t); } else { auto tmp = std::move(err()); err().~unexpected(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(il, std::forward(args)...); this->m_has_val = true; } catch (...) { err() = std::move(tmp); throw; } #else ::new (valptr()) T(il, std::forward(args)...); this->m_has_val = true; #endif } } private: using t_is_void = std::true_type; using t_is_not_void = std::false_type; using t_is_nothrow_move_constructible = std::true_type; using move_constructing_t_can_throw = std::false_type; using e_is_nothrow_move_constructible = std::true_type; using move_constructing_e_can_throw = std::false_type; void swap_where_both_have_value(expected &/*rhs*/ , t_is_void) noexcept { // swapping void is a no-op } void swap_where_both_have_value(expected &rhs, t_is_not_void) { using std::swap; swap(val(), rhs.val()); } void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( std::is_nothrow_move_constructible::value) { ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); std::swap(this->m_has_val, rhs.m_has_val); } void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { swap_where_only_one_has_value_and_t_is_not_void( rhs, typename std::is_nothrow_move_constructible::type{}, typename std::is_nothrow_move_constructible::type{}); } void swap_where_only_one_has_value_and_t_is_not_void( expected &rhs, t_is_nothrow_move_constructible, e_is_nothrow_move_constructible) noexcept { auto temp = std::move(val()); val().~T(); ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); ::new (rhs.valptr()) T(std::move(temp)); std::swap(this->m_has_val, rhs.m_has_val); } void swap_where_only_one_has_value_and_t_is_not_void( expected &rhs, t_is_nothrow_move_constructible, move_constructing_e_can_throw) { auto temp = std::move(val()); val().~T(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); ::new (rhs.valptr()) T(std::move(temp)); std::swap(this->m_has_val, rhs.m_has_val); } catch (...) { val() = std::move(temp); throw; } #else ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); ::new (rhs.valptr()) T(std::move(temp)); std::swap(this->m_has_val, rhs.m_has_val); #endif } void swap_where_only_one_has_value_and_t_is_not_void( expected &rhs, move_constructing_t_can_throw, t_is_nothrow_move_constructible) { auto temp = std::move(rhs.err()); rhs.err().~unexpected_type(); #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (rhs.valptr()) T(val()); val().~T(); ::new (errptr()) unexpected_type(std::move(temp)); std::swap(this->m_has_val, rhs.m_has_val); } catch (...) { rhs.err() = std::move(temp); throw; } #else ::new (rhs.valptr()) T(val()); val().~T(); ::new (errptr()) unexpected_type(std::move(temp)); std::swap(this->m_has_val, rhs.m_has_val); #endif } public: template detail::enable_if_t::value && detail::is_swappable::value && (std::is_nothrow_move_constructible::value || std::is_nothrow_move_constructible::value)> swap(expected &rhs) noexcept( std::is_nothrow_move_constructible::value &&detail::is_nothrow_swappable::value &&std::is_nothrow_move_constructible::value &&detail::is_nothrow_swappable::value) { if (has_value() && rhs.has_value()) { swap_where_both_have_value(rhs, typename std::is_void::type{}); } else if (!has_value() && rhs.has_value()) { rhs.swap(*this); } else if (has_value()) { swap_where_only_one_has_value(rhs, typename std::is_void::type{}); } else { using std::swap; swap(err(), rhs.err()); } } constexpr const T *operator->() const { return valptr(); } TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } template ::value> * = nullptr> constexpr const U &operator*() const & { return val(); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &operator*() & { return val(); } template ::value> * = nullptr> constexpr const U &&operator*() const && { return std::move(val()); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &&operator*() && { return std::move(val()); } constexpr bool has_value() const noexcept { return this->m_has_val; } constexpr explicit operator bool() const noexcept { return this->m_has_val; } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR const U &value() const & { if (!has_value()) detail::throw_exception(bad_expected_access(err().value())); return val(); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &value() & { if (!has_value()) detail::throw_exception(bad_expected_access(err().value())); return val(); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR const U &&value() const && { if (!has_value()) detail::throw_exception(bad_expected_access(std::move(err()).value())); return std::move(val()); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &&value() && { if (!has_value()) detail::throw_exception(bad_expected_access(std::move(err()).value())); return std::move(val()); } constexpr const E &error() const & { return err().value(); } TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } constexpr const E &&error() const && { return std::move(err().value()); } TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } template constexpr T value_or(U &&v) const & { static_assert(std::is_copy_constructible::value && std::is_convertible::value, "T must be copy-constructible and convertible to from U&&"); return bool(*this) ? **this : static_cast(std::forward(v)); } template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { static_assert(std::is_move_constructible::value && std::is_convertible::value, "T must be move-constructible and convertible to from U&&"); return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); } }; namespace detail { template using exp_t = typename detail::decay_t::value_type; template using err_t = typename detail::decay_t::error_type; template using ret_t = expected>; #ifdef TL_EXPECTED_CXX14 template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval()))> constexpr auto and_then_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f), *std::forward(exp)) : Ret(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval()))> constexpr auto and_then_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); } #else template struct TC; template (), *std::declval())), detail::enable_if_t>::value> * = nullptr> auto and_then_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f), *std::forward(exp)) : Ret(unexpect, std::forward(exp).error()); } template ())), detail::enable_if_t>::value> * = nullptr> constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); } #endif #ifdef TL_EXPECTED_CXX14 template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto expected_map_impl(Exp &&exp, F &&f) { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) : result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> auto expected_map_impl(Exp &&exp, F &&f) { using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); return result(); } return result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto expected_map_impl(Exp &&exp, F &&f) { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f))) : result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> auto expected_map_impl(Exp &&exp, F &&f) { using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f)); return result(); } return result(unexpect, std::forward(exp).error()); } #else template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto expected_map_impl(Exp &&exp, F &&f) -> ret_t> { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) : result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> auto expected_map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); return {}; } return unexpected>(std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto expected_map_impl(Exp &&exp, F &&f) -> ret_t> { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f))) : result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> auto expected_map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f)); return {}; } return unexpected>(std::forward(exp).error()); } #endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> constexpr auto map_error_impl(Exp &&exp, F &&f) { using result = expected, detail::decay_t>; return exp.has_value() ? result(*std::forward(exp)) : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> auto map_error_impl(Exp &&exp, F &&f) { using result = expected, monostate>; if (exp.has_value()) { return result(*std::forward(exp)); } detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> constexpr auto map_error_impl(Exp &&exp, F &&f) { using result = expected, detail::decay_t>; return exp.has_value() ? result() : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> auto map_error_impl(Exp &&exp, F &&f) { using result = expected, monostate>; if (exp.has_value()) { return result(); } detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } #else template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { using result = expected, detail::decay_t>; return exp.has_value() ? result(*std::forward(exp)) : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { using result = expected, monostate>; if (exp.has_value()) { return result(*std::forward(exp)); } detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { using result = expected, detail::decay_t>; return exp.has_value() ? result() : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } template >::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { using result = expected, monostate>; if (exp.has_value()) { return result(); } detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } #endif #ifdef TL_EXPECTED_CXX14 template (), std::declval().error())), detail::enable_if_t::value> * = nullptr> constexpr auto or_else_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? std::forward(exp) : detail::invoke(std::forward(f), std::forward(exp).error()); } template (), std::declval().error())), detail::enable_if_t::value> * = nullptr> detail::decay_t or_else_impl(Exp &&exp, F &&f) { return exp.has_value() ? std::forward(exp) : (detail::invoke(std::forward(f), std::forward(exp).error()), std::forward(exp)); } #else template (), std::declval().error())), detail::enable_if_t::value> * = nullptr> auto or_else_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? std::forward(exp) : detail::invoke(std::forward(f), std::forward(exp).error()); } template (), std::declval().error())), detail::enable_if_t::value> * = nullptr> detail::decay_t or_else_impl(Exp &&exp, F &&f) { return exp.has_value() ? std::forward(exp) : (detail::invoke(std::forward(f), std::forward(exp).error()), std::forward(exp)); } #endif } // namespace detail template constexpr bool operator==(const expected &lhs, const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? false : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); } template constexpr bool operator!=(const expected &lhs, const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? true : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); } template constexpr bool operator==(const expected &x, const U &v) { return x.has_value() ? *x == v : false; } template constexpr bool operator==(const U &v, const expected &x) { return x.has_value() ? *x == v : false; } template constexpr bool operator!=(const expected &x, const U &v) { return x.has_value() ? *x != v : true; } template constexpr bool operator!=(const U &v, const expected &x) { return x.has_value() ? *x != v : true; } template constexpr bool operator==(const expected &x, const unexpected &e) { return x.has_value() ? false : x.error() == e.value(); } template constexpr bool operator==(const unexpected &e, const expected &x) { return x.has_value() ? false : x.error() == e.value(); } template constexpr bool operator!=(const expected &x, const unexpected &e) { return x.has_value() ? true : x.error() != e.value(); } template constexpr bool operator!=(const unexpected &e, const expected &x) { return x.has_value() ? true : x.error() != e.value(); } template ::value || std::is_move_constructible::value) && detail::is_swappable::value && std::is_move_constructible::value && detail::is_swappable::value> * = nullptr> void swap(expected &lhs, expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { lhs.swap(rhs); } } // namespace tl #endif mu-1.6.10/lib/utils/meson.build000066400000000000000000000052501414367003600162760ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. lib_mu_utils=static_library('mu-utils', [ 'mu-async-queue.hh', 'mu-command-parser.cc', 'mu-command-parser.hh', 'mu-date.c', 'mu-date.h', 'mu-error.hh', 'mu-logger.cc', 'mu-logger.hh', 'mu-option.hh', 'mu-readline.cc', 'mu-readline.hh', 'mu-result.hh', 'mu-sexp.cc', 'mu-sexp.hh', 'mu-str.c', 'mu-str.h', 'mu-util.c', 'mu-util.h', 'mu-utils.cc', 'mu-utils.hh', # third party 'optional.hpp', 'expected.hpp'], dependencies: [ glib_dep, config_h_dep, readline_dep ], include_directories: include_directories(['.','..']), install: false) lib_mu_utils_dep = declare_dependency( link_with: lib_mu_utils, include_directories: include_directories(['.', '..']) ) ################################################################################ # tests # testmaildir=join_paths(meson.current_source_dir(),'..') test('test_command_parser', executable('test-command-parser', 'test-command-parser.cc', install: false, dependencies: [glib_dep, lib_mu_utils_dep])) test('test_mu_str', executable('test-mu-str', 'test-mu-str.c', install: false, dependencies: [glib_dep, config_h_dep,lib_mu_utils_dep])) test('test_mu_util', executable('test-mu-util', 'test-mu-util.c', install: false, dependencies: [glib_dep,config_h_dep, lib_mu_utils_dep], c_args: ['-DMU_TESTMAILDIR="' + join_paths(testmaildir, 'testdir') + '"', '-DMU_TESTMAILDIR2="' + join_paths(testmaildir, 'testdir2') + '"'])) test('test_option', executable('test-option', 'test-option.cc', install: false, dependencies: [glib_dep, lib_mu_utils_dep])) test('test_mu_utils', executable('test-mu-utils', 'test-utils.cc', install: false, dependencies: [glib_dep, lib_mu_utils_dep])) test('test_sexp', executable('test-sexp', 'test-sexp.cc', install: false, dependencies: [glib_dep, lib_mu_utils_dep] )) mu-1.6.10/lib/utils/mu-async-queue.hh000066400000000000000000000130371414367003600173350ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_ASYNC_QUEUE_HH__ #define __MU_ASYNC_QUEUE_HH__ #include #include #include #include namespace Mu { constexpr std::size_t UnlimitedAsyncQueueSize{0}; template > /**< allocator the items */ class AsyncQueue { public: using value_type = ItemType; using allocator_type = Allocator; using size_type = std::size_t; using reference = value_type&; using const_reference = const value_type&; using pointer = typename std::allocator_traits::pointer; using const_pointer = typename std::allocator_traits::const_pointer; using Timeout = std::chrono::steady_clock::duration; #define LOCKED std::unique_lock lock(m_); bool push (const value_type& item, Timeout timeout = {}) { return push(std::move(value_type(item))); } /** * Push an item to the end of the queue by moving it * * @param item the item to move to the end of the queue * @param timeout and optional timeout * * @return true if the item was pushed; false otherwise. */ bool push (value_type&& item, Timeout timeout = {}) { LOCKED; if (!unlimited()) { const auto rv = cv_full_.wait_for(lock, timeout,[&](){ return !full_unlocked();}) && !full_unlocked(); if (!rv) return false; } q_.emplace_back(std::move(item)); lock.unlock(); cv_empty_.notify_one(); return true; } /** * Pop an item from the queue * * @param receives the value if the function returns true * @param timeout optional time to wait for an item to become available * * @return true if an item was popped (into val), false otherwise. */ bool pop (value_type& val, Timeout timeout = {}) { LOCKED; if (timeout != Timeout{}) { const auto rv = cv_empty_.wait_for(lock, timeout,[&](){ return !q_.empty(); }) && !q_.empty(); if (!rv) return false; } else if (q_.empty()) return false; val = std::move(q_.front()); q_.pop_front(); lock.unlock(); cv_full_.notify_one(); return true; } /** * Clear the queue * */ void clear() { LOCKED; q_.clear(); lock.unlock(); cv_full_.notify_one(); } /** * Size of the queue * * * @return the size */ size_type size() const { LOCKED; return q_.size(); } /** * Maximum size of the queue if specified through the template * parameter; otherwise the (theoretical) max_size of the inner * container. * * @return the maximum size */ size_type max_size() const { if (unlimited()) return q_.max_size(); else return MaxSize; } /** * Is the queue empty? * * @return true or false */ bool empty() const { LOCKED; return q_.empty(); } /** * Is the queue full? Returns false unless a maximum size was specified * (as a template argument) * * @return true or false. */ bool full() const { if (unlimited()) return false; LOCKED; return full_unlocked(); } /** * Is this queue (theoretically) unlimited in size? * * @return true or false */ constexpr static bool unlimited() { return MaxSize == UnlimitedAsyncQueueSize; } private: bool full_unlocked() const { return q_.size() >= max_size(); } std::deque q_; mutable std::mutex m_; std::condition_variable cv_full_, cv_empty_; }; } // namespace mu #endif /* __MU_ASYNC_QUEUE_HH__ */ mu-1.6.10/lib/utils/mu-command-parser.cc000066400000000000000000000163041414367003600177740ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-command-parser.hh" #include "mu-error.hh" #include "mu-utils.hh" #include #include using namespace Mu; using namespace Command; void Command::invoke(const Command::CommandMap& cmap, const Sexp& call) { if (!call.is_call()) { throw Mu::Error{Error::Code::Command, "expected call-sexpr but got %s", call.to_sexp_string().c_str()}; } const auto& params{call.list()}; const auto cmd_it = cmap.find(params.at(0).value()); if (cmd_it == cmap.end()) throw Mu::Error{Error::Code::Command, "unknown command in call %s", call.to_sexp_string().c_str()}; const auto& cinfo{cmd_it->second}; // all required parameters must be present for (auto&& arg: cinfo.args) { const auto& argname{arg.first}; const auto& arginfo{arg.second}; // calls used keyword-parameters, e.g. // (my-function :bar 1 :cuux "fnorb") // so, we're looking for the odd-numbered parameters. const auto param_it = [&]()->Sexp::Seq::const_iterator { for (size_t i = 1; i < params.size(); i += 2) if (params.at(i).is_symbol() && params.at(i).value() == argname) return params.begin() + i + 1; return params.end(); }(); // it's an error when a required parameter is missing. if (param_it == params.end()) { if (arginfo.required) throw Mu::Error{Error::Code::Command, "missing required parameter %s in call %s", argname.c_str(), call.to_sexp_string().c_str()}; continue; // not required } // the types must match, but the 'nil' symbol is acceptable as // "no value" if (param_it->type() != arginfo.type && !(param_it->is_nil())) throw Mu::Error{Error::Code::Command, "parameter %s expects type %s, but got %s in call %s", argname.c_str(), to_string(arginfo.type).c_str(), to_string(param_it->type()).c_str(), call.to_sexp_string().c_str()}; } // all passed parameters must be known for (size_t i = 1; i < params.size(); i += 2) { if (std::none_of(cinfo.args.begin(), cinfo.args.end(), [&](auto&& arg) {return params.at(i).value() == arg.first;})) throw Mu::Error{Error::Code::Command, "unknown parameter %s in call %s", params.at(i).value().c_str(), call.to_sexp_string().c_str()}; } if (cinfo.handler) cinfo.handler(params); } static Sexp::Seq::const_iterator find_param_node (const Parameters& params, const std::string& argname) { if (params.empty()) throw Error(Error::Code::InvalidArgument, "params must not be empty"); if (argname.empty() || argname.at(0) != ':') throw Error(Error::Code::InvalidArgument, "property key must start with ':' but got '%s')", argname.c_str()); for (size_t i = 1; i < params.size(); i += 2) { if (i + 1 != params.size() && params.at(i).is_symbol() && params.at(i).value() == argname) return params.begin() + i + 1; } return params.end(); } static Error wrong_type (Sexp::Type expected, Sexp::Type got) { return Error(Error::Code::InvalidArgument, "expected <%s> but got <%s>", to_string(expected).c_str(), to_string(got).c_str()); } const std::string& Command::get_string_or (const Parameters& params, const std::string& argname, const std::string& alt) { const auto it = find_param_node (params, argname); if (it == params.end() || it->is_nil()) return alt; else if (!it->is_string()) throw wrong_type(Sexp::Type::String, it->type()); else return it->value(); } const std::string& Command::get_symbol_or (const Parameters& params, const std::string& argname, const std::string& alt) { const auto it = find_param_node (params, argname); if (it == params.end() || it->is_nil()) return alt; else if (!it->is_symbol()) throw wrong_type(Sexp::Type::Symbol, it->type()); else return it->value(); } int Command::get_int_or (const Parameters& params, const std::string& argname, int alt) { const auto it = find_param_node (params, argname); if (it == params.end() || it->is_nil()) return alt; else if (!it->is_number()) throw wrong_type(Sexp::Type::Number, it->type()); else return ::atoi(it->value().c_str()); } bool Command::get_bool_or (const Parameters& params, const std::string& argname, bool alt) { const auto it = find_param_node (params, argname); if (it == params.end()) return alt; else if (!it->is_symbol()) throw wrong_type(Sexp::Type::Symbol, it->type()); else return it->is_nil() ? false : true; } std::vector Command::get_string_vec (const Parameters& params, const std::string& argname) { const auto it = find_param_node (params, argname); if (it == params.end() || it->is_nil()) return {}; else if (!it->is_list()) throw wrong_type(Sexp::Type::List, it->type()); std::vector vec; for (const auto& n: it->list()) { if (!n.is_string()) throw wrong_type(Sexp::Type::String, n.type()); vec.emplace_back (n.value()); } return vec; } mu-1.6.10/lib/utils/mu-command-parser.hh000066400000000000000000000126041414367003600200050ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_COMMAND_PARSER_HH__ #define MU_COMMAND_PARSER_HH__ #include #include #include #include #include #include #include #include "utils/mu-error.hh" #include "utils/mu-sexp.hh" namespace Mu { namespace Command { /// /// Commands are s-expressions with the follow properties: /// 1) a command is a list with a command-name as its first argument /// 2) the rest of the parameters are pairs of colon-prefixed symbol and a value of some /// type (ie. 'keyword arguments') /// 3) each command is described by its CommandInfo structure, which defines the type /// 4) calls to the command must include all required parameters /// 5) all parameters must be of the specified type; however the symbol 'nil' is allowed /// for specify a non-required parameter to be absent; this is for convenience on the /// call side. /// Information about a function argument struct ArgInfo { ArgInfo (Sexp::Type typearg, bool requiredarg, std::string&& docarg): type{typearg}, required{requiredarg},docstring{std::move(docarg)} {} const Sexp::Type type; /**< Sexp::Type of the argument */ const bool required; /**< Is this argument required? */ const std::string docstring; /**< Documentation */ }; /// The arguments for a function, which maps their names to the information. using ArgMap = std::unordered_map; // The parameters to a Handler. using Parameters = Sexp::Seq; int get_int_or (const Parameters& parms, const std::string& argname, int alt=0); bool get_bool_or (const Parameters& parms, const std::string& argname, bool alt=false); const std::string& get_string_or (const Parameters& parms, const std::string& argname, const std::string& alt=""); const std::string& get_symbol_or (const Parameters& parms, const std::string& argname, const std::string& alt="nil"); std::vector get_string_vec (const Parameters& params, const std::string& argname); // A handler function using Handler = std::function; /// Information about some command struct CommandInfo { CommandInfo(ArgMap&& argmaparg, std::string&& docarg, Handler&& handlerarg): args{std::move(argmaparg)}, docstring{std::move(docarg)}, handler{std::move(handlerarg)} {} const ArgMap args; const std::string docstring; const Handler handler; /** * Get a sorted list of argument names, for display. Required args come * first, then alphabetical. * * @return vec with the sorted names. */ std::vector sorted_argnames() const { // sort args -- by required, then alphabetical. std::vector names; for (auto&& arg: args) names.emplace_back(arg.first); std::sort(names.begin(), names.end(), [&](const auto& name1, const auto& name2) { const auto& arg1{args.find(name1)->second}; const auto& arg2{args.find(name2)->second}; if (arg1.required != arg2.required) return arg1.required; else return name1 < name2; }); return names; } }; /// All commands, mapping their name to information about them. using CommandMap = std::unordered_map; /** * Validate that the call (a Sexp) specifies a valid call, then invoke it. * * A call uses keyword arguments, e.g. something like: * (foo :bar 1 :cuux "fnorb") * * On error, throw Error. * * @param cmap map of commands * @param call node describing a call. */ void invoke(const Command::CommandMap& cmap, const Sexp& call); static inline std::ostream& operator<<(std::ostream& os, const Command::ArgInfo& info) { os << info.type << " (" << ( info.required ? "required" : "optional" ) << ")"; return os; } static inline std::ostream& operator<<(std::ostream& os, const Command::CommandInfo& info) { for (auto&& arg: info.args) os << " " << arg.first << " " << arg.second << '\n' << " " << arg.second.docstring << "\n"; return os; } static inline std::ostream& operator<<(std::ostream& os, const Command::CommandMap& map) { for (auto&& c: map) os << c.first << '\n' << c.second; return os; } } // namespace Command } // namespace Mu #endif /* MU_COMMAND_PARSER_HH__ */ mu-1.6.10/lib/utils/mu-date.c000066400000000000000000000042171414367003600156360ustar00rootroot00000000000000/* ** Copyright (C) 2012 ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include #include #include #include "mu-util.h" #include "mu-date.h" #include "mu-str.h" const char* mu_date_str_s (const char* frm, time_t t) { struct tm *tmbuf; static char buf[128]; static int is_utf8 = -1; size_t len; if (G_UNLIKELY(is_utf8 == -1)) is_utf8 = mu_util_locale_is_utf8 () ? 1 : 0; g_return_val_if_fail (frm, NULL); tmbuf = localtime(&t); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" len = strftime (buf, sizeof(buf) - 1, frm, tmbuf); #pragma GCC diagnostic pop if (len == 0) return ""; /* not necessarily an error... */ if (!is_utf8) { /* charset is _not_ utf8, so we need to convert it, so * the date could contain locale-specific characters*/ gchar *conv; GError *err; err = NULL; conv = g_locale_to_utf8 (buf, -1, NULL, NULL, &err); if (err) { g_warning ("conversion failed: %s", err->message); g_error_free (err); strcpy (buf, ""); } else { strncpy (buf, conv, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; } g_free (conv); } return buf; } char* mu_date_str (const char *frm, time_t t) { return g_strdup (mu_date_str_s(frm, t)); } const char* mu_date_display_s (time_t t) { time_t now; static const time_t SECS_IN_DAY = 24 * 60 * 60; now = time (NULL); if (ABS(now - t) > SECS_IN_DAY) return mu_date_str_s ("%x", t); else return mu_date_str_s ("%X", t); } mu-1.6.10/lib/utils/mu-date.h000066400000000000000000000037621414367003600156470ustar00rootroot00000000000000/* ** Copyright (C) 2012-2013 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include #ifndef __MU_DATE_H__ #define __MU_DATE_H__ G_BEGIN_DECLS /** * @addtogroup MuDate * Date-related functions * @{ */ /** * get a string for a given time_t * * mu_date_str_s returns a ptr to a static buffer, * while mu_date_str returns dynamically allocated * memory that must be freed after use. * * @param frm the format of the string (in strftime(3) format) * @param t the time as time_t * * @return a string representation of the time; see above for what to * do with it. Length is max. 128 bytes, inc. the ending \0. if the * format is too long, the value will be truncated. in practice this * should not happen. */ const char* mu_date_str_s (const char* frm, time_t t) G_GNUC_CONST; char* mu_date_str (const char* frm, time_t t) G_GNUC_WARN_UNUSED_RESULT; /** * get a display string for a given time_t; if the given is less than * 24h from the current time, we display the time, otherwise the date, * using the preferred date/time for the current locale * * mu_str_display_date_s returns a ptr to a static buffer, * * @param t the time as time_t * * @return a string representation of the time/date */ const char* mu_date_display_s (time_t t); G_END_DECLS #endif /*__MU_DATE_H__*/ mu-1.6.10/lib/utils/mu-error.hh000066400000000000000000000070351414367003600162300ustar00rootroot00000000000000/* ** Copyright (C) 2019-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_ERROR_HH__ #define MU_ERROR_HH__ #include #include "mu-utils.hh" #include namespace Mu { struct Error final: public std::exception { enum struct Code { AccessDenied = 100, // don't overlap with MuError Command, File, Index, Internal, InvalidArgument, Message, NotFound, Parsing, Query, SchemaMismatch, Store, }; /** * Construct an error * * @param codearg error-code * #param msgarg the error diecription */ Error(Code codearg, const std::string& msgarg): code_{codearg}, what_{msgarg} {} /** * Build an error from an error-code and a format string * * @param code error-code * @param frm format string * @param ... format parameters * * @return an Error object */ __attribute__((format(printf, 3, 0))) Error(Code codearg, const char *frm, ...): code_{codearg} { va_list args; va_start(args, frm); what_ = vformat(frm, args); va_end(args); } Error(Error&& rhs) = default; Error(const Error& rhs) = delete; /** * Build an error from a GError an error-code and a format string * * @param code error-code * @param gerr a GError or {}, which is consumed * @param frm format string * @param ... format parameters * * @return an Error object */ __attribute__((format(printf, 4, 0))) Error(Code codearg, GError **err, const char *frm, ...): code_{codearg} { va_list args; va_start(args, frm); what_ = vformat(frm, args); va_end(args); if (err && *err) what_ += format (": %s", (*err)->message); else what_ += ": something went wrong"; g_clear_error(err); } /** * DTOR * */ virtual ~Error() = default; /** * Get the descriptiove message. * * @return */ virtual const char* what() const noexcept override { return what_.c_str(); } /** * Get the error-code for this error * * @return the error-code */ Code code() const { return code_; } private: const Code code_; std::string what_; }; } // namespace Mu #endif /* MU_ERROR_HH__ */ mu-1.6.10/lib/utils/mu-logger.cc000066400000000000000000000126731414367003600163500ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #define G_LOG_USE_STRUCTURED #include #include #include #include #include #include #include #include #include "mu-logger.hh" using namespace Mu; static bool MuLogInitialized = false; static Mu::LogOptions MuLogOptions; static std::ofstream MuStream; static auto MaxLogFileSize = 1000 * 1024; static std::string MuLogPath; static bool maybe_open_logfile () { if (MuStream.is_open()) return true; MuStream.open (MuLogPath, std::ios::out | std::ios::app ); if (!MuStream.is_open()) { std::cerr << "opening " << MuLogPath << " failed:" << g_strerror(errno) << std::endl; return false; } MuStream.sync_with_stdio(false); return true; } static bool maybe_rotate_logfile () { static unsigned n = 0; if (n++ % 1000 != 0) return true; GStatBuf statbuf; if (g_stat(MuLogPath.c_str(), &statbuf) == -1 || statbuf.st_size <= MaxLogFileSize) return true; const auto old = MuLogPath + ".old"; g_unlink(old.c_str()); // opportunistic if (MuStream.is_open()) MuStream.close(); if (g_rename(MuLogPath.c_str(), old.c_str()) != 0) std::cerr << "failed to rename " << MuLogPath << " -> " << old.c_str() << ": " << g_strerror(errno) << std::endl; return maybe_open_logfile(); } static GLogWriterOutput log_file (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { if (!maybe_open_logfile()) return G_LOG_WRITER_UNHANDLED; char timebuf[22]; time_t now{::time(NULL)}; ::strftime (timebuf, sizeof(timebuf), "%F %T", ::localtime(&now)); char *msg = g_log_writer_format_fields (level, fields, n_fields, FALSE); if (msg && msg[0] == '\n') // hmm... seems lines start with '\n'r msg[0] = ' '; MuStream << timebuf << ' ' << msg << std::endl; g_free (msg); return maybe_rotate_logfile() ? G_LOG_WRITER_HANDLED : G_LOG_WRITER_UNHANDLED; } static GLogWriterOutput log_stdouterr (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { return g_log_writer_standard_streams (level, fields, n_fields, user_data); } static GLogWriterOutput log_journal (GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { return g_log_writer_journald (level, fields, n_fields, user_data); } void Mu::log_init (const std::string& path, Mu::LogOptions opts) { if (MuLogInitialized) { g_error ("logging is already initialized"); return; } MuLogOptions = opts; MuLogPath = path; g_log_set_writer_func ( [](GLogLevelFlags level, const GLogField *fields, gsize n_fields, gpointer user_data) { // filter out debug-level messages? if (level == G_LOG_LEVEL_DEBUG && (none_of (MuLogOptions & Mu::LogOptions::Debug))) return G_LOG_WRITER_HANDLED; // log criticals to stdout / err or if asked if (level == G_LOG_LEVEL_CRITICAL || any_of(MuLogOptions & Mu::LogOptions::StdOutErr)){ log_stdouterr (level, fields, n_fields, user_data); } // log to the journal, or, if not available to a file. if (log_journal (level, fields, n_fields, user_data) != G_LOG_WRITER_HANDLED) return log_file (level, fields, n_fields, user_data); else return G_LOG_WRITER_HANDLED; }, NULL, NULL); g_message ("logging initialized; debug: %s, stdout/stderr: %s", any_of(log_get_options() & LogOptions::Debug) ? "yes" : "no", any_of(log_get_options() & LogOptions::StdOutErr) ? "yes" : "no"); MuLogInitialized = true; } void Mu::log_uninit () { if (!MuLogInitialized) return; if (MuStream.is_open()) MuStream.close(); MuLogInitialized = false; } void Mu::log_set_options (Mu::LogOptions opts) { MuLogOptions = opts; } Mu::LogOptions Mu::log_get_options () { return MuLogOptions; } mu-1.6.10/lib/utils/mu-logger.hh000066400000000000000000000033771414367003600163630ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_LOGGER_HH__ #define MU_LOGGER_HH__ #include #include "utils/mu-utils.hh" namespace Mu { /** * Logging options * */ enum struct LogOptions { None = 0, /**< Nothing specific */ StdOutErr = 1 << 1, /**< Log to stdout/stderr */ Debug = 1 << 2, /**< Include debug-level logs */ }; /** * Initialize the logging system. Note that the path is only used if structured * logging fails -- practically, it goes to the file if there's * systemd/journald. * * @param path path to the log file * @param opts logging options */ void log_init (const std::string& path, LogOptions opts); /** * Uninitialize the logging system * */ void log_uninit(); /** * Change the logging options. * * @param opts options */ void log_set_options (LogOptions opts); /** * Get the current log options * * @return the log options */ LogOptions log_get_options (); } // namespace Mu MU_ENABLE_BITOPS(Mu::LogOptions); #endif /* MU_LOGGER_HH__ */ mu-1.6.10/lib/utils/mu-option.hh000066400000000000000000000014531414367003600164050ustar00rootroot00000000000000/* * Created on 2020-11-08 by Dirk-Jan C. Binnema * * Copyright (c) 2020 Logitech, Inc. All Rights Reserved * This program is a trade secret of LOGITECH, and it is not to be reproduced, * published, disclosed to others, copied, adapted, distributed or displayed * without the prior authorization of LOGITECH. * * Licensee agrees to attach or embed this notice on all copies of the program, * including partial copies or modified versions thereof. * */ #ifndef MU_OPTION__ #define MU_OPTION__ #include "optional.hpp" namespace Mu { /// Either a value of type T, or None template using Option=tl::optional; template Option Some(T&& t) { return t; } constexpr auto Nothing = tl::nullopt; // 'None' is take already } #endif /*MU_OPTION__*/ mu-1.6.10/lib/utils/mu-readline.cc000066400000000000000000000064331414367003600166510ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-readline.hh" #include "config.h" #include #include #include #include #include #ifdef HAVE_LIBREADLINE # if defined(HAVE_READLINE_READLINE_H) # include # elif defined(HAVE_READLINE_H) # include # else /* !defined(HAVE_READLINE_H) */ extern char *readline (); # endif /* !defined(HAVE_READLINE_H) */ char *cmdline = NULL; #else /* !defined(HAVE_READLINE_READLINE_H) */ /* no readline */ #endif /* HAVE_LIBREADLINE */ #ifdef HAVE_READLINE_HISTORY # if defined(HAVE_READLINE_HISTORY_H) # include # elif defined(HAVE_HISTORY_H) # include # else /* !defined(HAVE_HISTORY_H) */ extern void add_history (); extern int write_history (); extern int read_history (); # endif /* defined(HAVE_READLINE_HISTORY_H) */ /* no history */ #endif /* HAVE_READLINE_HISTORY */ #if defined(HAVE_LIBREADLINE) && defined(HAVE_READLINE_HISTORY) #define HAVE_READLINE (1) #else #define HAVE_READLINE (0) #endif using namespace Mu; static bool is_a_tty{}; static std::string hist_path; static size_t max_lines{}; void Mu::setup_readline (const std::string& histpath, size_t maxlines) { is_a_tty = !!::isatty(::fileno(stdout)); hist_path = histpath; max_lines = maxlines; #if HAVE_READLINE rl_bind_key('\t', rl_insert); // default (filenames) is not useful using_history(); read_history(hist_path.c_str()); if (max_lines > 0) stifle_history(max_lines); #endif /*HAVE_READLINE*/ } void Mu::shutdown_readline () { #if HAVE_READLINE if (!is_a_tty) return; write_history(hist_path.c_str()); if (max_lines > 0) history_truncate_file (hist_path.c_str(), max_lines); #endif /*HAVE_READLINE*/ } std::string Mu::read_line(bool& do_quit) { #if HAVE_READLINE if (is_a_tty) { auto buf = readline(";; mu% "); if (!buf) { do_quit = true; return {}; } std::string line{buf}; ::free(buf); return line; } #endif /*HAVE_READLINE*/ std::string line; std::cout << ";; mu> "; if (!std::getline(std::cin, line)) do_quit = true; return line; } void Mu::save_line(const std::string& line) { #if HAVE_READLINE if (is_a_tty) add_history(line.c_str()); #endif /*HAVE_READLINE*/ } mu-1.6.10/lib/utils/mu-readline.hh000066400000000000000000000026211414367003600166560ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include namespace Mu { /** * Setup readline when available and on tty. * * @param histpath path to the history file * @param max_lines maximum number of history to save */ void setup_readline(const std::string &histpath, size_t max_lines); /** * Shutdown readline * */ void shutdown_readline(); /** * Read a command line * * @param do_quit recceives whether we should quit. * * @return the string read or empty */ std::string read_line(bool &do_quit); /** * Save a line to history (or do nothing when readline is not active) * * @param line a line. */ void save_line(const std::string &line); } // namespace Mu mu-1.6.10/lib/utils/mu-result.hh000066400000000000000000000024721414367003600164150ustar00rootroot00000000000000/* ** Copyright (C) 2019-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_RESULT_HH__ #define MU_RESULT_HH__ #include "expected.hpp" #include "utils/mu-error.hh" namespace Mu { /** * A Result is _either_ some value of type T, _or_ an error. */ template using Result = tl::expected; template typename Result::expected_type Ok(T&& t) { return Result::expected(std::move(t)); } template typename Result::unexpected_type Err(Error&& err) { return Result::unexpected(std::move(err)); } } // namespace Mu #endif /* MU_ERROR_HH__ */ mu-1.6.10/lib/utils/mu-sexp.cc000066400000000000000000000166071414367003600160510ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-sexp.hh" #include "mu-utils.hh" #include #include using namespace Mu; __attribute__((format(printf, 2, 0))) static Mu::Error parsing_error(size_t pos, const char* frm, ...) { va_list args; va_start(args, frm); auto msg = vformat(frm, args); va_end(args); if (pos == 0) return Mu::Error(Error::Code::Parsing, "%s", msg.c_str()); else return Mu::Error(Error::Code::Parsing, "%zu: %s", pos, msg.c_str()); } static size_t skip_whitespace (const std::string& s, size_t pos) { while (pos != s.size()) { if (s[pos] == ' ' || s[pos] == '\t' || s[pos] == '\n') ++pos; else break; } return pos; } static Sexp parse (const std::string& expr, size_t& pos); static Sexp parse_list (const std::string& expr, size_t& pos) { if (expr[pos] != '(') // sanity check. throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]); Sexp::List list; ++pos; while (expr[pos] != ')' && pos != expr.size()) list.add(parse(expr, pos)); if (expr[pos] != ')') throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]); ++pos; return Sexp::make_list(std::move(list)); } // parse string static Sexp parse_string (const std::string& expr, size_t& pos) { if (expr[pos] != '"') // sanity check. throw parsing_error(pos, "expected: '\"'' but got '%c", expr[pos]); bool escape{}; std::string str; for (++pos; pos != expr.size(); ++pos) { auto kar = expr[pos]; if (escape && (kar == '"' || kar == '\\')) { str += kar; escape = false; continue; } if (kar == '"') break; else if (kar == '\\') escape = true; else str += kar; } if (escape || expr[pos] != '"') throw parsing_error(pos, "unterminated string '%s'", str.c_str()); ++pos; return Sexp::make_string(std::move(str)); } static Sexp parse_integer (const std::string& expr, size_t& pos) { if (!isdigit(expr[pos]) && expr[pos] != '-') // sanity check. throw parsing_error(pos, "expected: but got '%c", expr[pos]); std::string num; // negative number? if (expr[pos] == '-') { num = "-"; ++pos; } for (; isdigit(expr[pos]); ++pos) num += expr[pos]; return Sexp::make_number(::atoi(num.c_str())); } static Sexp parse_symbol (const std::string& expr, size_t& pos) { if (!isalpha(expr[pos]) && expr[pos] != ':') // sanity check. throw parsing_error(pos, "expected: |: but got '%c", expr[pos]); std::string symbol(1, expr[pos]); for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos) symbol += expr[pos]; return Sexp::make_symbol(std::move(symbol)); } static Sexp parse (const std::string& expr, size_t& pos) { pos = skip_whitespace(expr, pos); if (pos == expr.size()) throw parsing_error(pos, "expected: character '%c", expr[pos]); const auto kar = expr[pos]; const auto node =[&]() -> Sexp { if (kar == '(') return parse_list (expr, pos); else if (kar == '"') return parse_string(expr, pos); else if (isdigit(kar) || kar == '-') return parse_integer(expr, pos); else if (isalpha(kar) || kar == ':') return parse_symbol(expr, pos); else throw parsing_error(pos, "unexpected character '%c", kar); }(); pos = skip_whitespace(expr, pos); return node; } Sexp Sexp::make_parse (const std::string& expr) { size_t pos{}; auto node{::parse (expr, pos)}; if (pos != expr.size()) throw parsing_error(pos, "trailing data starting with '%c'", expr[pos]); return node; } std::string Sexp::to_sexp_string () const { std::stringstream sstrm; switch (type()) { case Type::List: { sstrm << '('; bool first{true}; for (auto&& child : list()) { sstrm << (first ? "" : " ") << child.to_sexp_string(); first = false; } sstrm << ')'; break; } case Type::String: sstrm << quote(value()); break; case Type::Number: case Type::Symbol: case Type::Empty: default: sstrm << value(); } return sstrm.str(); } std::string Sexp::to_json_string () const { std::stringstream sstrm; switch (type()) { case Type::List: { // property-lists become JSON objects if (is_prop_list()) { sstrm << "{"; auto it{list().begin()}; bool first{true}; while (it != list().end()) { sstrm << (first?"":",") << quote(it->value()) << ":"; ++it; sstrm << it->to_json_string(); ++it; first = false; } sstrm << "}"; } else { // other lists become arrays. sstrm << '['; bool first{true}; for (auto&& child : list()) { sstrm << (first ? "" : ", ") << child.to_json_string(); first = false; } sstrm << ']'; } break; } case Type::String: sstrm << quote(value()); break; case Type::Symbol: if (is_nil()) sstrm << "false"; else if (is_t()) sstrm << "true"; else sstrm << quote(value()); break; case Type::Number: case Type::Empty: default: sstrm << value(); } return sstrm.str(); } mu-1.6.10/lib/utils/mu-sexp.hh000066400000000000000000000302321414367003600160510ustar00rootroot00000000000000/* ** Copyright (C) 2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_SEXP_HH__ #define MU_SEXP_HH__ #include #include #include #include "utils/mu-utils.hh" #include "utils/mu-error.hh" namespace Mu { /// Simple s-expression parser & list that parses lists () and atoms (strings /// ("-quoted), (positive) integers ([0..9]+) and symbol starting with alpha or /// ':', then alphanum and '-') /// /// (:foo (1234 "bar" nil) :quux (a b c)) /// Parse node struct Sexp { /// Node type enum struct Type { Empty, List, String, Number, Symbol }; /** * Default CTOR */ Sexp():type_{Type::Empty}{} // Underlying data type for list; we'd like to use std::dequeu here, // but that does not compile with libc++ (it does with libstdc++) using Seq = std::vector; /** * Make a sexp out of an s-expression string. * * @param expr a string containing an s-expression * * @return the parsed s-expression, or throw Error. */ static Sexp make_parse (const std::string& expr); /** * Make a node for a string/integer/symbol/list value * * @param val some value * * @return a node */ static Sexp make_string (std::string&& val) { return Sexp{Type::String, std::move(val)}; } static Sexp make_string (const std::string& val) { return Sexp{Type::String, std::string(val)}; } static Sexp make_number (int val) { return Sexp{Type::Number, format("%d", val)}; } static Sexp make_symbol (std::string&& val) { if (val.empty()) throw Error(Error::Code::InvalidArgument, "symbol must be non-empty"); return Sexp{Type::Symbol, std::move(val)}; } /** * * * The value of this node; invalid for list nodes. * * @return */ const std::string& value() const { if (is_list()) throw Error(Error::Code::InvalidArgument, "no value for list"); if (is_empty()) throw Error{Error::Code::InvalidArgument, "no value for empty"}; return value_; } /** * The underlying container of this list node; only valid for lists * * @return */ const Seq& list() const { if (!is_list()) throw Error(Error::Code::InvalidArgument, "not a list"); return seq_; } /** * Convert a Sexp::Node to its S-expression string representation * * @return the string representation */ std::string to_sexp_string() const; /** * Convert a Sexp::Node to its JSON string representation * * @return the string representation */ std::string to_json_string() const; /** * Return the type of this Node. * * @return the type */ Type type() const { return type_; } /// /// Helper struct to build mutable lists. /// struct List { /** * Add a sexp to the list * * @param sexp a sexp * @param args rest arguments * * @return a ref to this List (for chaining) */ List& add () { return *this; } List& add (Sexp&& sexp) { seq_.emplace_back(std::move(sexp)); return *this; } template List& add (Sexp&& sexp, Args... args) { seq_.emplace_back(std::move(sexp)); seq_.emplace_back(std::forward(args)...); return *this; } /** * Add a property (i.e., :key sexp ) to the list * * @param name a property-name. Must start with ':', length > 1 * @param sexp a sexp * @param args rest arguments * * @return a ref to this List (for chaining) */ List& add_prop (std::string&& name, Sexp&& sexp) { if (!is_prop_name(name)) throw Error{Error::Code::InvalidArgument, "invalid property name ('%s')", name.c_str()}; seq_.emplace_back(make_symbol(std::move(name))); seq_.emplace_back(std::move(sexp)); return *this; } template List& add_prop (std::string&& name, Sexp&& sexp, Args... args) { add_prop(std::move(name), std::move(sexp)); add_prop(std::forward(args)...); return *this; } /** * Get the number of elements in the list * * @return number */ size_t size() const { return seq_.size(); } /** * Is the list empty? * * @return true or false */ size_t empty() const { return seq_.empty(); } private: friend struct Sexp; Seq seq_; }; /** * Construct a list sexp from a List * * @param list a list-list * @param sexp a Sexp * @param args rest arguments * * @return a sexp. */ static Sexp make_list(List&& list) { return Sexp{Type::List, std::move(list.seq_)}; } template static Sexp make_list(Sexp&& sexp, Args... args) { List lst; lst.add(std::move(sexp)).add(std::forward(args)...); return make_list(std::move(lst)); } /** * Construct a property list sexp from a List * * @param name the property name; must start wtth ':' * @param sexp a Sexp * @param args rest arguments (property list) * * @return a sexp. */ template static Sexp make_prop_list(std::string&& name, Sexp&& sexp, Args... args) { List list; list.add_prop(std::move(name), std::move(sexp), std::forward(args)...); return make_list(std::move(list)); } /** * Construct a properrty list sexp from a List * * @param funcname function name for the call * @param name the property name; must start wtth ':' * @param sexp a Sexp * @param args rest arguments (property list) * * @return a sexp. */ template static Sexp make_call(std::string&& funcname, std::string&& name, Sexp&& sexp, Args... args) { List list; list.add(make_symbol(std::move(funcname))); list.add_prop(std::move(name), std::move(sexp), std::forward(args)...); return make_list(std::move(list)); } /// Some type helpers bool is_list() const { return type() == Type::List; } bool is_string() const { return type() == Type::String; } bool is_number() const { return type() == Type::Number; } bool is_symbol() const { return type() == Type::Symbol; } bool is_empty() const { return type() == Type::Empty; } operator bool() const { return !is_empty(); } static constexpr auto SymbolNil{"nil"}; static constexpr auto SymbolT{"t"}; bool is_nil() const { return is_symbol() && value() == SymbolNil; } bool is_t() const { return is_symbol() && value() == SymbolT; } /** * Is this a prop-list? A prop list is a list sexp with alternating * property / sexp * * @return */ bool is_prop_list() const { if (!is_list() || list().size() % 2 != 0) return false; else return is_prop_list(list().begin(), list().end()); } /** * Is this a call? A call is a list sexp with a symbol (function name), * followed by a prop list * * @return */ bool is_call() const { if (!is_list() || list().size() % 2 != 1 || !list().at(0).is_symbol()) return false; else return is_prop_list (list().begin()+1, list().end()); }; private: Sexp(Type typearg, std::string&& valuearg): type_{typearg}, value_{std::move(valuearg)} { if (is_list()) throw Error{Error::Code::InvalidArgument, "cannot be a list type"}; if (is_empty()) throw Error{Error::Code::InvalidArgument, "cannot be an empty type"}; } Sexp(Type typearg, Seq&& seq): type_{Type::List}, seq_{std::move(seq)}{ if (!is_list()) throw Error{Error::Code::InvalidArgument, "must be a list type"}; if (is_empty()) throw Error{Error::Code::InvalidArgument, "cannot be an empty type"}; } /** * Is the sexp a valid property name? * * @param sexp a Sexp. * * @return true or false. */ static bool is_prop_name(const std::string& str) { return str.size() > 1 && str.at(0) == ':'; } static bool is_prop_name(const Sexp& sexp) { return sexp.is_symbol() && is_prop_name(sexp.value()); } static bool is_prop_list (Seq::const_iterator b, Seq::const_iterator e) { while (b != e) { const Sexp& s{*b}; if (!is_prop_name(s)) return false; if (++b == e) return false; ++b; } return b == e; } const Type type_; /**< Type of node */ const std::string value_; /**< String value of node (only for * non-Type::Lst)*/ const Seq seq_; /**< Children of node (only for * Type::Lst) */ }; static inline std::ostream& operator<<(std::ostream& os, Sexp::Type id) { switch (id) { case Sexp::Type::List: os << "list"; break; case Sexp::Type::String: os << "string"; break; case Sexp::Type::Number: os << "number"; break; case Sexp::Type::Symbol: os << "symbol"; break; case Sexp::Type::Empty: os << "empty"; break; default: throw std::runtime_error ("unknown node type"); } return os; } static inline std::ostream& operator<<(std::ostream& os, const Sexp& sexp) { os << sexp.to_sexp_string(); return os; } static inline std::ostream& operator<<(std::ostream& os, const Sexp::List& sexp) { os << Sexp::make_list(Sexp::List(sexp)); return os; } } // Mu #endif /* MU_SEXP_HH__ */ mu-1.6.10/lib/utils/mu-str.c000066400000000000000000000157701414367003600155370ustar00rootroot00000000000000/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE (500) #endif /*_XOPEN_SOURCE*/ #include #include #include #include #include #include "mu-util.h" /* PATH_MAX */ #include "mu-str.h" const char* mu_str_size_s (size_t s) { static char buf[32]; char *tmp; tmp = g_format_size((goffset)s); strncpy (buf, tmp, sizeof(buf)); buf[sizeof(buf) -1] = '\0'; /* just in case */ g_free (tmp); return buf; } char* mu_str_summarize (const char* str, size_t max_lines) { char *summary; size_t nl_seen; unsigned i,j; gboolean last_was_blank; g_return_val_if_fail (str, NULL); g_return_val_if_fail (max_lines > 0, NULL); /* len for summary <= original len */ summary = g_new (gchar, strlen(str) + 1); /* copy the string up to max_lines lines, replace CR/LF/tab with * single space */ for (i = j = 0, nl_seen = 0, last_was_blank = TRUE; nl_seen < max_lines && str[i] != '\0'; ++i) { if (str[i] == '\n' || str[i] == '\r' || str[i] == '\t' || str[i] == ' ' ) { if (str[i] == '\n') ++nl_seen; /* no double-blanks or blank at end of str */ if (!last_was_blank && str[i+1] != '\0') summary[j++] = ' '; last_was_blank = TRUE; } else { summary[j++] = str[i]; last_was_blank = FALSE; } } summary[j] = '\0'; return summary; } char* mu_str_from_list (const GSList *lst, char sepa) { const GSList *cur; char *str; g_return_val_if_fail (sepa, NULL); for (cur = lst, str = NULL; cur; cur = g_slist_next(cur)) { char *tmp; /* two extra dummy '\0' so -Wstack-protector won't complain */ char sep[4] = { '\0', '\0', '\0', '\0' }; sep[0] = cur->next ? sepa : '\0'; tmp = g_strdup_printf ("%s%s%s", str ? str : "", (gchar*)cur->data, sep); g_free (str); str = tmp; } return str; } GSList* mu_str_to_list (const char *str, char sepa, gboolean strip) { GSList *lst; gchar **strs, **cur; /* two extra dummy '\0' so -Wstack-protector won't complain */ char sep[4] = { '\0', '\0', '\0', '\0' }; g_return_val_if_fail (sepa, NULL); if (!str) return NULL; sep[0] = sepa; strs = g_strsplit (str, sep, -1); for (cur = strs, lst = NULL; cur && *cur; ++cur) { char *elm; elm = g_strdup(*cur); if (strip) elm = g_strstrip (elm); lst = g_slist_prepend (lst, elm); } lst = g_slist_reverse (lst); g_strfreev (strs); return lst; } /* this function is critical for sorting performance; therefore, no * regexps, but just some good old c pointer magic */ const gchar* mu_str_subject_normalize (const gchar* str) { const char* cur; g_return_val_if_fail (str, NULL); cur = str; while (isspace(*cur)) ++cur; /* skip space */ /* starts with Re:? */ if (tolower(cur[0]) == 'r' && tolower(cur[1]) == 'e') cur += 2; /* starts with Fwd:? */ else if (tolower(cur[0]) == 'f' && tolower(cur[1]) == 'w' && tolower(cur[2]) == 'd') cur += 3; else /* nope, different string */ return str; /* we're now past either 'Re' or 'Fwd'. Maybe there's a [] now? * ie., the Re[3]: foo case */ if (cur[0] == '[') { /* handle the Re[3]: case */ if (isdigit(cur[1])) { do { ++cur; } while (isdigit(*cur)); if ( cur[0] != ']') { return str; /* nope: no ending ']' */ } else /* skip ']' and space */ do { ++cur; } while (isspace(*cur)); } else /* nope: no number after '[' */ return str; } /* now, cur points past either 're' or 'fwd', possibly with * []; check if it's really a prefix -- after re or fwd * there should either a ':' and possibly some space */ if (cur[0] == ':') { do { ++cur; } while (isspace(*cur)); /* note: there may still be another prefix, such as * Re[2]: Fwd: foo */ return mu_str_subject_normalize (cur); } else return str; /* nope, it was not a prefix */ } /* note: this function is *not* re-entrant, it returns a static buffer */ const char* mu_str_fullpath_s (const char* path, const char* name) { static char buf[PATH_MAX + 1]; g_return_val_if_fail (path, NULL); g_snprintf (buf, sizeof(buf), "%s%c%s", path, G_DIR_SEPARATOR, name ? name : ""); return buf; } /* turn \0-terminated buf into ascii (which is a utf8 subset); convert * any non-ascii into '.' */ char* mu_str_asciify_in_place (char *buf) { char *c; g_return_val_if_fail (buf, NULL); for (c = buf; c && *c; ++c) { if ((!isprint(*c) && !isspace (*c)) || !isascii(*c)) *c = '.'; } return buf; } char* mu_str_utf8ify (const char *buf) { char *utf8; g_return_val_if_fail (buf, NULL); utf8 = g_strdup (buf); if (!g_utf8_validate (buf, -1, NULL)) mu_str_asciify_in_place (utf8); return utf8; } gchar* mu_str_convert_to_utf8 (const char* buffer, const char *charset) { GError *err; gchar * utf8; g_return_val_if_fail (buffer, NULL); g_return_val_if_fail (charset, NULL ); err = NULL; utf8 = g_convert_with_fallback (buffer, -1, "UTF-8", charset, NULL, NULL, NULL, &err); if (!utf8) /* maybe the charset lied; try 8859-15 */ utf8 = g_convert_with_fallback (buffer, -1, "UTF-8", "ISO8859-15", NULL, NULL, NULL, &err); /* final attempt, maybe it was utf-8 already */ if (!utf8 && g_utf8_validate (buffer, -1, NULL)) utf8 = g_strdup (buffer); if (!utf8) { g_warning ("%s: conversion failed from %s: %s", __func__, charset, err ? err->message : ""); } g_clear_error (&err); return utf8; } gchar* mu_str_quoted_from_strv (const gchar **params) { GString *str; int i; g_return_val_if_fail (params, NULL); if (!params[0]) return g_strdup (""); str = g_string_sized_new (64); /* just a guess */ for (i = 0; params[i]; ++i) { if (i > 0) g_string_append_c (str, ' '); g_string_append_c (str, '"'); g_string_append (str, params[i]); g_string_append_c (str, '"'); } return g_string_free (str, FALSE); } char* mu_str_remove_ctrl_in_place (char *str) { char *orig, *cur; g_return_val_if_fail (str, NULL); orig = str; for (cur = orig; *cur; ++cur) { if (isspace(*cur)) { /* squash special white space into a simple space */ *orig++ = ' '; } else if (iscntrl(*cur)) { /* eat it */ } else *orig++ = *cur; } *orig = '\0'; /* ensure the updated string has a NULL */ return str; } mu-1.6.10/lib/utils/mu-str.h000066400000000000000000000111661414367003600155370ustar00rootroot00000000000000/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2008-2017 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_STR_H__ #define __MU_STR_H__ #include #include #include G_BEGIN_DECLS /** * @addtogroup MuStr * Various string utilities * @{ */ /** * get a display size for a given size_t; uses M for sizes > * 1000*1000, k for smaller sizes. Note: this function use the * 10-based SI units, _not_ the powers-of-2 based ones. * * mu_str_size_s returns a ptr to a static buffer, * * @param t the size as an size_t * * @return a string representation of the size; see above * for what to do with it */ const char* mu_str_size_s (size_t s); /** * get a 'summary' of the string, ie. the first /n/ lines of the * strings, with all newlines removed, replaced by single spaces * * @param str the source string * @param max_lines the maximum number of lines to include in the summary * * @return a newly allocated string with the summary. use g_free to free it. */ char* mu_str_summarize (const char* str, size_t max_lines) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * create a full path from a path + a filename. function is _not_ * reentrant. * * @param path a path (!= NULL) * @param name a name (may be NULL) * * @return the path as a statically allocated buffer. don't free. */ const char* mu_str_fullpath_s (const char* path, const char* name); /** * turn a string into plain ascii by replacing each non-ascii * character with a dot ('.'). Replacement is done in-place. * * @param buf a buffer to asciify * * @return the buf ptr (as to allow for function composition) */ char* mu_str_asciify_in_place (char *buf); /** * turn string in buf into valid utf8. If this string is not valid * utf8 already, the function massages the offending characters. * * @param buf a buffer to utf8ify * * @return a newly allocated utf8 string */ char* mu_str_utf8ify (const char *buf); /** * convert a string in a certain charset into utf8 * * @param buffer a buffer to convert * @param charset source character set. * * @return a UTF8 string (which you need to g_free when done with it), * or NULL in case of error */ gchar* mu_str_convert_to_utf8 (const char* buffer, const char *charset); /** * macro to check whether the string is empty, ie. if it's NULL or * it's length is 0 * * @param S a string * * @return TRUE if the string is empty, FALSE otherwise */ #define mu_str_is_empty(S) ((!(S)||!(*S))?TRUE:FALSE) /** * convert a GSList of strings to a #sepa-separated list * * @param lst a GSList * @param the separator character * * @return a newly allocated string */ char* mu_str_from_list (const GSList *lst, char sepa); /** * convert a #sepa-separated list of strings in to a GSList * * @param str a #sepa-separated list of strings * @param the separator character * @param remove leading/trailing whitespace from the string * * @return a newly allocated GSList (free with mu_str_free_list) */ GSList* mu_str_to_list (const char *str, char sepa, gboolean strip); /** * free a GSList consisting of allocated strings * * @param lst a GSList */ #define mu_str_free_list(lst) g_slist_free_full(lst, g_free) /** * strip the subject of Re:, Fwd: etc. * * @param str a subject string * * @return a new string -- this is pointing somewhere inside the @str; * no copy is made, don't free */ const gchar* mu_str_subject_normalize (const gchar* str); /** * take a list of strings, and return the concatenation of their * quoted forms * * @param params NULL-terminated array of strings * * @return the quoted concatenation of the strings */ gchar* mu_str_quoted_from_strv (const gchar **params); /** * Remove control characters from a string * * @param str a string * * @return the str with control characters removed */ char* mu_str_remove_ctrl_in_place (char *str); /** @} */ G_END_DECLS #endif /*__MU_STR_H__*/ mu-1.6.10/lib/utils/mu-util.c000066400000000000000000000326561414367003600157060ustar00rootroot00000000000000/* ** Copyright (C) 2008-2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif /*_GNU_SOURCE*/ #include "mu-util.h" #ifdef HAVE_WORDEXP_H #include /* for shell-style globbing */ #endif /*HAVE_WORDEXP_H*/ #include #include #include /* for setlocale() */ #include #include #include #include #include #include #include #include static char* do_wordexp (const char *path) { #ifdef HAVE_WORDEXP_H wordexp_t wexp; char *dir; if (!path) { /* g_debug ("%s: path is empty", __func__); */ return NULL; } if (wordexp (path, &wexp, 0) != 0) { /* g_debug ("%s: expansion failed for %s", __func__, path); */ return NULL; } /* we just pick the first one */ dir = g_strdup (wexp.we_wordv[0]); /* strangely, below seems to lead to a crash on MacOS (BSD); so we have to allow for a tiny leak here on that platform... maybe instead of __APPLE__ it should be __BSD__? Hmmm., cannot reproduce that crash anymore, so commenting it out for now... */ /* #ifndef __APPLE__ */ wordfree (&wexp); /* #endif /\*__APPLE__*\/ */ return dir; # else /*!HAVE_WORDEXP_H*/ /* E.g. OpenBSD does not have wordexp.h, so we ignore it */ return path ? g_strdup (path) : NULL; #endif /*HAVE_WORDEXP_H*/ } /* note, the g_debugs are commented out because this function may be * called before the log handler is installed. */ char* mu_util_dir_expand (const char *path) { char *dir; char resolved[PATH_MAX + 1]; g_return_val_if_fail (path, NULL); dir = do_wordexp (path); if (!dir) return NULL; /* error */ /* don't try realpath if the dir does not exist */ if (access (dir, F_OK) != 0) return dir; /* now resolve any symlinks, .. etc. */ if (realpath (dir, resolved) == NULL) { /* g_debug ("%s: could not get realpath for '%s': %s", */ /* __func__, dir, g_strerror(errno)); */ g_free (dir); return NULL; } else g_free (dir); return g_strdup (resolved); } GQuark mu_util_error_quark (void) { static GQuark error_domain = 0; if (G_UNLIKELY(error_domain == 0)) error_domain = g_quark_from_static_string ("mu-error-quark"); return error_domain; } const char* mu_util_cache_dir (void) { static char cachedir [PATH_MAX]; g_snprintf (cachedir, sizeof(cachedir), "%s%cmu-%u", g_get_tmp_dir(), G_DIR_SEPARATOR, getuid()); return cachedir; } gboolean mu_util_check_dir (const gchar* path, gboolean readable, gboolean writeable) { int mode; struct stat statbuf; if (!path) return FALSE; mode = F_OK | (readable ? R_OK : 0) | (writeable ? W_OK : 0); if (access (path, mode) != 0) { /* g_debug ("Cannot access %s: %s", path, g_strerror (errno)); */ return FALSE; } if (stat (path, &statbuf) != 0) { /* g_debug ("Cannot stat %s: %s", path, g_strerror (errno)); */ return FALSE; } return S_ISDIR(statbuf.st_mode) ? TRUE: FALSE; } gchar* mu_util_guess_maildir (void) { const gchar *mdir1, *home; /* first, try MAILDIR */ mdir1 = g_getenv ("MAILDIR"); if (mdir1 && mu_util_check_dir (mdir1, TRUE, FALSE)) return g_strdup (mdir1); /* then, try /Maildir */ home = g_get_home_dir(); if (home) { char *mdir2; mdir2 = g_strdup_printf ("%s%cMaildir", home, G_DIR_SEPARATOR); if (mu_util_check_dir (mdir2, TRUE, FALSE)) return mdir2; g_free (mdir2); } /* nope; nothing found */ return NULL; } gboolean mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn) { struct stat statbuf; g_return_val_if_fail (path, FALSE); /* if it exists, it must be a readable dir */ if (stat (path, &statbuf) == 0) { if ((!S_ISDIR(statbuf.st_mode)) || (access (path, W_OK|R_OK) != 0)) { if (!nowarn) g_warning ("not a read-writable" "directory: %s", path); return FALSE; } } if (g_mkdir_with_parents (path, mode) != 0) { if (!nowarn) g_warning ("failed to create %s: %s", path, g_strerror(errno)); return FALSE; } return TRUE; } int mu_util_create_writeable_fd (const char* path, mode_t mode, gboolean overwrite) { errno = 0; /* clear! */ g_return_val_if_fail (path, -1); if (overwrite) return open (path, O_WRONLY|O_CREAT|O_TRUNC, mode); else return open (path, O_WRONLY|O_CREAT|O_EXCL, mode); } gboolean mu_util_is_local_file (const char* path) { /* if it starts with file:// it's a local file (for the * purposes of this function -- if it's on a remote FS it's * still considered local) */ if (g_ascii_strncasecmp ("file://", path, strlen("file://")) == 0) return TRUE; if (access (path, R_OK) == 0) return TRUE; return FALSE; } gboolean mu_util_supports (MuFeature feature) { /* check for Guile support */ #ifndef BUILD_GUILE if (feature & MU_FEATURE_GUILE) return FALSE; #endif /*BUILD_GUILE*/ /* check for Gnuplot */ if (feature & MU_FEATURE_GNUPLOT) if (!mu_util_program_in_path ("gnuplot")) return FALSE; return TRUE; } gboolean mu_util_program_in_path (const char *prog) { gchar *path; g_return_val_if_fail (prog, FALSE); path = g_find_program_in_path (prog); g_free (path); return (path != NULL) ? TRUE : FALSE; } /* * Set the child to a group leader to avoid being killed when the * parent group is killed. */ static void maybe_setsid (G_GNUC_UNUSED gpointer user_data) { #if HAVE_SETSID setsid(); #endif /*HAVE_SETSID*/ } gboolean mu_util_play (const char *path, gboolean allow_local, gboolean allow_remote, GError **err) { gboolean rv; const gchar *argv[3]; const char *prog; g_return_val_if_fail (path, FALSE); g_return_val_if_fail (mu_util_is_local_file (path) || allow_remote, FALSE); g_return_val_if_fail (!mu_util_is_local_file (path) || allow_local, FALSE); prog = g_getenv ("MU_PLAY_PROGRAM"); if (!prog) { #ifdef __APPLE__ prog = "open"; #else prog = "xdg-open"; #endif /*!__APPLE__*/ } if (!mu_util_program_in_path (prog)) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_EXECUTE, "cannot find '%s' in path", prog); return FALSE; } argv[0] = prog; argv[1] = path; argv[2] = NULL; err = NULL; rv = g_spawn_async (NULL, (gchar**)&argv, NULL, G_SPAWN_SEARCH_PATH, maybe_setsid, NULL, NULL, err); return rv; } unsigned char mu_util_get_dtype (const char *path, gboolean use_lstat) { int res; struct stat statbuf; g_return_val_if_fail (path, DT_UNKNOWN); if (use_lstat) res = lstat (path, &statbuf); else res = stat (path, &statbuf); if (res != 0) { g_warning ("%sstat failed on %s: %s", use_lstat ? "l" : "", path, g_strerror(errno)); return DT_UNKNOWN; } /* we only care about dirs, regular files and links */ if (S_ISREG (statbuf.st_mode)) return DT_REG; else if (S_ISDIR (statbuf.st_mode)) return DT_DIR; else if (S_ISLNK (statbuf.st_mode)) return DT_LNK; return DT_UNKNOWN; } gboolean mu_util_locale_is_utf8 (void) { const gchar *dummy; static int is_utf8 = -1; if (G_UNLIKELY(is_utf8 == -1)) is_utf8 = g_get_charset(&dummy) ? 1 : 0; return is_utf8 ? TRUE : FALSE; } gboolean mu_util_fputs_encoded (const char *str, FILE *stream) { int rv; char *conv; g_return_val_if_fail (stream, FALSE); /* g_get_charset return TRUE when the locale is UTF8 */ if (mu_util_locale_is_utf8()) return fputs (str, stream) == EOF ? FALSE : TRUE; /* charset is _not_ utf8, so we need to convert it */ conv = NULL; if (g_utf8_validate (str, -1, NULL)) conv = g_locale_from_utf8 (str, -1, NULL, NULL, NULL); /* conversion failed; this happens because is some cases GMime may gives * us non-UTF-8 strings from e.g. wrongly encoded message-subjects; if * so, we escape the string */ conv = conv ? conv : g_strescape (str, "\n\t"); rv = conv ? fputs (conv, stream) : EOF; g_free (conv); return (rv == EOF) ? FALSE : TRUE; } gboolean mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...) { va_list ap; char *msg; /* don't bother with NULL errors, or errors already set */ if (!err || *err) return FALSE; msg = NULL; va_start (ap, frm); g_vasprintf (&msg, frm, ap); va_end (ap); g_set_error (err, MU_ERROR_DOMAIN, errcode, "%s", msg); g_free (msg); return FALSE; } __attribute__((format(printf, 2, 0))) static gboolean print_args (FILE *stream, const char *frm, va_list args) { gchar *str; gboolean rv; str = g_strdup_vprintf (frm, args); rv = mu_util_fputs_encoded (str, stream); g_free (str); return rv; } gboolean mu_util_print_encoded (const char *frm, ...) { va_list args; gboolean rv; g_return_val_if_fail (frm, FALSE); va_start (args, frm); rv = print_args (stdout, frm, args); va_end (args); return rv; } gboolean mu_util_printerr_encoded (const char *frm, ...) { va_list args; gboolean rv; g_return_val_if_fail (frm, FALSE); va_start (args, frm); rv = print_args (stderr, frm, args); va_end (args); return rv; } char* mu_util_read_password (const char *prompt) { char *pass; g_return_val_if_fail (prompt, NULL); /* note: getpass is obsolete; replace with something better */ pass = getpass (prompt); /* returns static mem, don't free */ if (!pass) { if (errno) g_warning ("error: %s", g_strerror(errno)); return NULL; } return g_strdup (pass); } /* Pick g_canonicalize_file name from glib >= 2.58 */ /** * g_canonicalize_filename: * @filename: (type filename): the name of the file * @relative_to: (type filename) (nullable): the relative directory, or %NULL * to use the current working directory * * Gets the canonical file name from @filename. All triple slashes are turned into * single slashes, and all `..` and `.`s resolved against @relative_to. * * Symlinks are not followed, and the returned path is guaranteed to be absolute. * * If @filename is an absolute path, @relative_to is ignored. Otherwise, * @relative_to will be prepended to @filename to make it absolute. @relative_to * must be an absolute path, or %NULL. If @relative_to is %NULL, it'll fallback * to g_get_current_dir(). * * This function never fails, and will canonicalize file paths even if they don't * exist. * * No file system I/O is done. * * Returns: (type filename) (transfer full): a newly allocated string with the * canonical file path * Since: 2.58 */ gchar * mu_canonicalize_filename (const gchar *filename, const gchar *relative_to) { gchar *canon, *start, *p, *q; guint i; g_return_val_if_fail (relative_to == NULL || g_path_is_absolute (relative_to), NULL); if (!g_path_is_absolute (filename)) { gchar *cwd_allocated = NULL; const gchar *cwd; if (relative_to != NULL) cwd = relative_to; else cwd = cwd_allocated = g_get_current_dir (); canon = g_build_filename (cwd, filename, NULL); g_free (cwd_allocated); } else { canon = g_strdup (filename); } start = (char *)g_path_skip_root (canon); if (start == NULL) { /* This shouldn't really happen, as g_get_current_dir() should return an absolute pathname, but bug 573843 shows this is not always happening */ g_free (canon); return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL); } /* POSIX allows double slashes at the start to * mean something special (as does windows too). * So, "//" != "/", but more than two slashes * is treated as "/". */ i = 0; for (p = start - 1; (p >= canon) && G_IS_DIR_SEPARATOR (*p); p--) i++; if (i > 2) { i -= 1; start -= i; memmove (start, start+i, strlen (start+i) + 1); } /* Make sure we're using the canonical dir separator */ p++; while (p < start && G_IS_DIR_SEPARATOR (*p)) *p++ = G_DIR_SEPARATOR; p = start; while (*p != 0) { if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1]))) { memmove (p, p+1, strlen (p+1)+1); } else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2]))) { q = p + 2; /* Skip previous separator */ p = p - 2; if (p < start) p = start; while (p > start && !G_IS_DIR_SEPARATOR (*p)) p--; if (G_IS_DIR_SEPARATOR (*p)) *p++ = G_DIR_SEPARATOR; memmove (p, q, strlen (q)+1); } else { /* Skip until next separator */ while (*p != 0 && !G_IS_DIR_SEPARATOR (*p)) p++; if (*p != 0) { /* Canonicalize one separator */ *p++ = G_DIR_SEPARATOR; } } /* Remove additional separators */ q = p; while (*q && G_IS_DIR_SEPARATOR (*q)) q++; if (p != q) memmove (p, q, strlen (q) + 1); } /* Remove trailing slashes */ if (p > start && G_IS_DIR_SEPARATOR (*(p-1))) *(p-1) = 0; return canon; } mu-1.6.10/lib/utils/mu-util.h000066400000000000000000000273261414367003600157110ustar00rootroot00000000000000/* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef __MU_UTIL_H__ #define __MU_UTIL_H__ #include #include #include #include /* for mode_t */ /* hopefully, this should get us a sane PATH_MAX */ #include /* not all systems provide PATH_MAX in limits.h */ #ifndef PATH_MAX #include #ifndef PATH_MAX #define PATH_MAX MAXPATHLEN #endif /*!PATH_MAX*/ #endif /*PATH_MAX*/ G_BEGIN_DECLS /** * get the expanded path; ie. perform shell expansion on the path. the * path does not have to exist * * @param path path to expand * * @return the expanded path as a newly allocated string, or NULL in * case of error */ char* mu_util_dir_expand (const char* path) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * See g_canonicalize_filename * * @param filename * @param relative_to * * @return */ char *mu_canonicalize_filename (const gchar *filename, const gchar *relative_to) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * guess the maildir; first try $MAILDIR; if it is unset or * non-existent, try ~/Maildir if both fail, return NULL * * @return full path of the guessed Maildir, or NULL; must be freed (gfree) */ char* mu_util_guess_maildir (void) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * if path exists, check that's a read/writeable dir; otherwise try to * create it (with perms 0700) * * @param path path to the dir * @param mode to set for the dir (as per chmod(1)) * @param nowarn, if TRUE, don't write warnings (if any) to stderr * * @return TRUE if a read/writeable directory `path' exists after * leaving this function, FALSE otherwise */ gboolean mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn) G_GNUC_WARN_UNUSED_RESULT; /** * check whether path is a directory, and optionally, if it's readable * and/or writeable * * @param path dir path * @param readable check for readability * @param writeable check for writability * * @return TRUE if dir exist and has the specified properties */ gboolean mu_util_check_dir (const gchar* path, gboolean readable, gboolean writeable) G_GNUC_WARN_UNUSED_RESULT; /** * get our the cache directory, typically, /tmp/mu-/ * * @return the cache directory; don't free */ const char* mu_util_cache_dir (void) G_GNUC_CONST; /** * create a writeable file and return its file descriptor (which * you'll need to close(2) when done with it.) * * @param path the full path of the file to create * @param the mode to open (ie. 0644 or 0600 etc., see chmod(3) * @param overwrite should we allow for overwriting existing files? * * @return a file descriptor, or -1 in case of error. If it's a file * system error, 'errno' may contain more info. use 'close()' when done * with the file descriptor */ int mu_util_create_writeable_fd (const char* path, mode_t mode, gboolean overwrite) G_GNUC_WARN_UNUSED_RESULT; /** * check if file is local, ie. on the local file system. this means * that it's either having a file URI, *or* that it's an existing file * * @param path a path * * @return TRUE if the file is local, FALSE otherwise */ gboolean mu_util_is_local_file (const char* path); /** * is the current locale utf-8 compatible? * * @return TRUE if it's utf8 compatible, FALSE otherwise */ gboolean mu_util_locale_is_utf8 (void) G_GNUC_CONST; /** * write a string (assumed to be in utf8-format) to a stream, * converted to the current locale * * @param str a string * @param stream a stream * * @return TRUE if printing worked, FALSE otherwise */ gboolean mu_util_fputs_encoded (const char *str, FILE *stream); /** * print a formatted string (assumed to be in utf8-format) to stdout, * converted to the current locale * * @param a standard printf() format string, followed by a parameter list * * @return TRUE if printing worked, FALSE otherwise */ gboolean mu_util_print_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2); /** * print a formatted string (assumed to be in utf8-format) to stderr, * converted to the current locale * * @param a standard printf() format string, followed by a parameter list * * @return TRUE if printing worked, FALSE otherwise */ gboolean mu_util_printerr_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2); /** * read a password from stdin (without echoing), and return it. * * @param prompt the prompt text before the password * * @return the password (free with g_free), or NULL */ char* mu_util_read_password (const char *prompt) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** * Try to 'play' (ie., open with it's associated program) a file. On * MacOS, the the program 'open' is used for this; on other platforms * 'xdg-open' to do the actual opening. In addition you can set it to another program * by setting the MU_PLAY_PROGRAM environment variable * * @param path full path of the file to open * @param allow_local allow local files (ie. with file:// prefix or fs paths) * @param allow_remote allow URIs (ie., http, mailto) * @param err receives error information, if any * * @return TRUE if it succeeded, FALSE otherwise */ gboolean mu_util_play (const char *path, gboolean allow_local, gboolean allow_remote, GError **err); /** * Check whether program prog exists in PATH * * @param prog a program (executable) * * @return TRUE if it exists and is executable, FALSE otherwise */ gboolean mu_util_program_in_path (const char *prog); enum _MuFeature { MU_FEATURE_GUILE = 1 << 0, /* do we support Guile 2.0? */ MU_FEATURE_GNUPLOT = 1 << 1, /* do we have gnuplot installed? */ }; typedef enum _MuFeature MuFeature; /** * Check whether mu supports some particular feature * * @param feature a feature (multiple features can be logical-or'd together) * * @return TRUE if the feature is supported, FALSE otherwise */ gboolean mu_util_supports (MuFeature feature); /** * Get an error-query for mu, to be used in `g_set_error'. Recent * version of Glib warn when using 0 for the error-domain in * g_set_error. * * * @return an error quark for mu */ GQuark mu_util_error_quark (void) G_GNUC_CONST; #define MU_ERROR_DOMAIN (mu_util_error_quark()) /* * for OSs with out support for direntry->d_type, like Solaris */ #ifndef DT_UNKNOWN enum { DT_UNKNOWN = 0, #define DT_UNKNOWN DT_UNKNOWN DT_FIFO = 1, #define DT_FIFO DT_FIFO DT_CHR = 2, #define DT_CHR DT_CHR DT_DIR = 4, #define DT_DIR DT_DIR DT_BLK = 6, #define DT_BLK DT_BLK DT_REG = 8, #define DT_REG DT_REG DT_LNK = 10, #define DT_LNK DT_LNK DT_SOCK = 12, #define DT_SOCK DT_SOCK DT_WHT = 14 #define DT_WHT DT_WHT }; #endif /*DT_UNKNOWN*/ /** * get the d_type (as in direntry->d_type) for the file at path, using either * stat(3) or lstat(3) * * @param path full path * @param use_lstat whether to use lstat (otherwise use stat) * * @return DT_REG, DT_DIR, DT_LNK, or DT_UNKNOWN (other values are not supported * currently ) */ unsigned char mu_util_get_dtype (const char *path, gboolean use_lstat); /** * we need this when using Xapian::Document* from C * */ typedef gpointer XapianDocument; /** * we need this when using Xapian::Enquire* from C * */ typedef gpointer XapianEnquire; /* print a warning for a GError, and free it */ #define MU_HANDLE_G_ERROR(GE) \ do { \ if (!(GE)) \ g_warning ("%s:%u: an error occurred in %s", \ __FILE__, __LINE__, __func__); \ else { \ g_warning ("error %u: %s", (GE)->code, (GE)->message); \ g_error_free ((GE)); \ } \ } while (0) #define MU_G_ERROR_CODE(GE) ((GE)&&(*(GE))?(MuError)(*(GE))->code:MU_ERROR) enum _MuError { /* no error at all! */ MU_OK = 0, /* generic error */ MU_ERROR = 1, MU_ERROR_IN_PARAMETERS = 2, MU_ERROR_INTERNAL = 3, MU_ERROR_NO_MATCHES = 4, /* not really an error; for callbacks */ MU_IGNORE = 5, MU_ERROR_SCRIPT_NOT_FOUND = 8, /* general xapian related error */ MU_ERROR_XAPIAN = 11, /* (parsing) error in the query */ MU_ERROR_XAPIAN_QUERY = 13, /* missing data for a document */ MU_ERROR_XAPIAN_MISSING_DATA = 17, /* can't get write lock */ MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK = 19, /* could not write */ MU_ERROR_XAPIAN_STORE_FAILED = 21, /* could not remove */ MU_ERROR_XAPIAN_REMOVE_FAILED = 22, /* database was modified; reload */ MU_ERROR_XAPIAN_MODIFIED = 23, /* database was modified; reload */ MU_ERROR_XAPIAN_NEEDS_REINDEX = 24, /* database schema version doesn't match */ MU_ERROR_XAPIAN_SCHEMA_MISMATCH = 25, /* failed to open the database */ MU_ERROR_XAPIAN_CANNOT_OPEN = 26, /* GMime related errors */ /* gmime parsing related error */ MU_ERROR_GMIME = 30, /* contacts related errors */ MU_ERROR_CONTACTS = 50, MU_ERROR_CONTACTS_CANNOT_RETRIEVE = 51, /* crypto related errors */ MU_ERROR_CRYPTO = 60, /* File errors */ /* generic file-related error */ MU_ERROR_FILE = 70, MU_ERROR_FILE_INVALID_NAME = 71, MU_ERROR_FILE_CANNOT_LINK = 72, MU_ERROR_FILE_CANNOT_OPEN = 73, MU_ERROR_FILE_CANNOT_READ = 74, MU_ERROR_FILE_CANNOT_EXECUTE = 75, MU_ERROR_FILE_CANNOT_CREATE = 76, MU_ERROR_FILE_CANNOT_MKDIR = 77, MU_ERROR_FILE_STAT_FAILED = 78, MU_ERROR_FILE_READDIR_FAILED = 79, MU_ERROR_FILE_INVALID_SOURCE = 80, MU_ERROR_FILE_TARGET_EQUALS_SOURCE = 81, MU_ERROR_FILE_CANNOT_WRITE = 82, MU_ERROR_FILE_CANNOT_UNLINK = 83, /* not really an error, used in callbacks */ MU_STOP = 99 }; typedef enum _MuError MuError; /** * set an error if it's not already set, and return FALSE * * @param err errptr, or NULL * @param errcode error code * @param frm printf-style format, followed by parameters * * @return FALSE */ gboolean mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...) G_GNUC_PRINTF(3,4); /** * calculate a 64-bit hash for the given string, based on a combination of the * DJB and BKDR hash functions. * * @param a string * * @return the hash */ static inline guint64 mu_util_get_hash (const char* str) { guint32 djbhash; guint32 bkdrhash; guint32 bkdrseed; guint64 hash; djbhash = 5381; bkdrhash = 0; bkdrseed = 1313; for(unsigned u = 0U; str[u]; ++u) { djbhash = ((djbhash << 5) + djbhash) + str[u]; bkdrhash = bkdrhash * bkdrseed + str[u]; } hash = djbhash; return (hash<<32) | bkdrhash; } #define MU_COLOR_RED "\x1b[31m" #define MU_COLOR_GREEN "\x1b[32m" #define MU_COLOR_YELLOW "\x1b[33m" #define MU_COLOR_BLUE "\x1b[34m" #define MU_COLOR_MAGENTA "\x1b[35m" #define MU_COLOR_CYAN "\x1b[36m" #define MU_COLOR_DEFAULT "\x1b[0m" G_END_DECLS #endif /*__MU_UTIL_H__*/ mu-1.6.10/lib/utils/mu-utils.cc000066400000000000000000000326051414367003600162260ustar00rootroot00000000000000/* ** Copyright (C) 2017-2021 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #define _XOPEN_SOURCE #include #define GNU_SOURCE #include #include #include #include #include #include #include #include "mu-utils.hh" #include "mu-util.h" using namespace Mu; namespace { static gunichar unichar_tolower (gunichar uc) { if (!g_unichar_isalpha(uc)) return uc; if (g_unichar_get_script (uc) != G_UNICODE_SCRIPT_LATIN) return g_unichar_tolower (uc); switch (uc) { case 0x00e6: case 0x00c6: return 'e'; /* æ */ case 0x00f8: return 'o'; /* ø */ case 0x0110: case 0x0111: return 'd'; /* đ */ /* todo: many more */ default: return g_unichar_tolower (uc); } } /** * gx_utf8_flatten: * @str: a UTF-8 string * @len: the length of @str, or -1 if it is %NULL-terminated * * Flatten some UTF-8 string; that is, downcase it and remove any diacritics. * * Returns: (transfer full): a flattened string, free with g_free(). */ static char* gx_utf8_flatten (const gchar *str, gssize len) { GString *gstr; char *norm, *cur; g_return_val_if_fail (str, NULL); norm = g_utf8_normalize (str, len, G_NORMALIZE_ALL); if (!norm) return NULL; gstr = g_string_sized_new (strlen (norm)); for (cur = norm; cur && *cur; cur = g_utf8_next_char (cur)) { gunichar uc; uc = g_utf8_get_char (cur); if (g_unichar_combining_class (uc) != 0) continue; g_string_append_unichar (gstr, unichar_tolower(uc)); } g_free (norm); return g_string_free (gstr, FALSE); } } // namespace std::string // gx_utf8_flatten Mu::utf8_flatten (const char *str) { if (!str) return {}; // the pure-ascii case if (g_str_is_ascii(str)) { auto l = g_ascii_strdown (str, -1); std::string s{l}; g_free (l); return s; } // seems we need the big guns char *flat = gx_utf8_flatten (str, -1); if (!flat) return {}; std::string s{flat}; g_free (flat); return s; } std::string Mu::utf8_clean (const std::string& dirty) { GString *gstr = g_string_sized_new (dirty.length()); for (auto cur = dirty.c_str(); cur && *cur; cur = g_utf8_next_char (cur)) { const gunichar uc = g_utf8_get_char (cur); if (g_unichar_iscntrl (uc)) g_string_append_c (gstr, ' '); else g_string_append_unichar (gstr, uc); } std::string clean(gstr->str, gstr->len); g_string_free (gstr, TRUE); clean.erase (0, clean.find_first_not_of(" ")); clean.erase (clean.find_last_not_of(" ") + 1); // remove trailing space return clean; } std::string Mu::remove_ctrl (const std::string& str) { char prev{'\0'}; std::string result; result.reserve(str.length()); for (auto&& c: str) { if (::iscntrl(c) || c == ' ') { if (prev != ' ') result += prev = ' '; } else result += prev = c; } return result; } std::vector Mu::split (const std::string& str, const std::string& sepa) { char **parts = g_strsplit(str.c_str(), sepa.c_str(), -1); std::vector vec; for (auto part = parts; part && *part; ++part) vec.push_back (*part); g_strfreev(parts); return vec; } std::string Mu::quote (const std::string& str) { std::string res{"\""}; for (auto&& k: str) { switch (k) { case '"' : res += "\\\""; break; case '\\': res += "\\\\"; break; default: res += k; } } return res + "\""; } std::string Mu::format (const char *frm, ...) { va_list args; va_start (args, frm); auto str = vformat(frm, args); va_end (args); return str; } std::string Mu::vformat (const char *frm, va_list args) { char *s{}; const auto res = g_vasprintf (&s, frm, args); if (res == -1) { std::cerr << "string format failed" << std::endl; return {}; } std::string str{s}; g_free (s); return str; } constexpr const auto InternalDateFormat = "%010" G_GINT64_FORMAT; constexpr const char InternalDateMin[] = "0000000000"; constexpr const char InternalDateMax[] = "9999999999"; static_assert(sizeof(InternalDateMin) == 10 + 1, "invalid"); static_assert(sizeof(InternalDateMax) == 10 + 1, "invalid"); static std::string date_boundary (bool is_first) { return is_first ? InternalDateMin : InternalDateMax; } std::string Mu::date_to_time_t_string (int64_t t) { char buf[sizeof(InternalDateMax)]; g_snprintf (buf, sizeof(buf), InternalDateFormat, t); return buf; } static std::string delta_ymwdhMs (const std::string& expr) { char *endptr; auto num = strtol (expr.c_str(), &endptr, 10); if (num <= 0 || num > 9999 || !endptr || !*endptr) return date_boundary (true); int years, months, weeks, days, hours, minutes, seconds; years = months = weeks = days = hours = minutes = seconds = 0; switch (endptr[0]) { case 's': seconds = num; break; case 'M': minutes = num; break; case 'h': hours = num; break; case 'd': days = num; break; case 'w': weeks = num; break; case 'm': months = num; break; case 'y': years = num; break; default: return date_boundary (true); } GDateTime *then, *now = g_date_time_new_now_local (); if (weeks != 0) then = g_date_time_add_weeks (now, -weeks); else then = g_date_time_add_full (now, -years, -months,-days, -hours, -minutes, -seconds); time_t t = MAX (0, (gint64)g_date_time_to_unix (then)); g_date_time_unref (then); g_date_time_unref (now); return date_to_time_t_string (t); } static std::string special_date (const std::string& d, bool is_first) { if (d == "now") return date_to_time_t_string (time(NULL)); else if (d == "today") { GDateTime *dt, *midnight; dt = g_date_time_new_now_local (); if (!is_first) { GDateTime *tmp = dt; dt = g_date_time_add_days (dt, 1); g_date_time_unref (tmp); } midnight = g_date_time_add_full (dt, 0, 0, 0, -g_date_time_get_hour(dt), -g_date_time_get_minute (dt), -g_date_time_get_second (dt)); time_t t = MAX(0, (gint64)g_date_time_to_unix (midnight)); g_date_time_unref (dt); g_date_time_unref (midnight); return date_to_time_t_string ((time_t)t); } else return date_boundary (is_first); } // if a date has a month day greater than the number of days in that month, // change it to a valid date point to the last second in that month static void fixup_month (struct tm *tbuf) { decltype(tbuf->tm_mday) max_days; const auto month = tbuf->tm_mon + 1; const auto year = tbuf->tm_year + 1900; switch (month) { case 2: if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) max_days = 29; else max_days = 28; break; case 4: case 6: case 9: case 11: max_days = 30; break; default: max_days = 31; break; } if (tbuf->tm_mday > max_days) { tbuf->tm_mday = max_days; tbuf->tm_hour = 23; tbuf->tm_min = 59; tbuf->tm_sec = 59; } } std::string Mu::date_to_time_t_string (const std::string& dstr, bool is_first) { gint64 t; struct tm tbuf; GDateTime *dtime; /* one-sided dates */ if (dstr.empty()) return date_boundary (is_first); else if (dstr == "today" || dstr == "now") return special_date (dstr, is_first); else if (dstr.find_first_of("ymdwhMs") != std::string::npos) return delta_ymwdhMs (dstr); constexpr char UserDateMin[] = "19700101000000"; constexpr char UserDateMax[] = "29991231235959"; std::string date (is_first ? UserDateMin : UserDateMax); std::copy_if (dstr.begin(), dstr.end(), date.begin(),[](auto c){return isdigit(c);}); memset (&tbuf, 0, sizeof tbuf); if (!strptime (date.c_str(), "%Y%m%d%H%M%S", &tbuf) && !strptime (date.c_str(), "%Y%m%d%H%M", &tbuf) && !strptime (date.c_str(), "%Y%m%d", &tbuf) && !strptime (date.c_str(), "%Y%m", &tbuf) && !strptime (date.c_str(), "%Y", &tbuf)) return date_boundary (is_first); fixup_month(&tbuf); dtime = g_date_time_new_local (tbuf.tm_year + 1900, tbuf.tm_mon + 1, tbuf.tm_mday, tbuf.tm_hour, tbuf.tm_min, tbuf.tm_sec); if (!dtime) { g_warning ("invalid %s date '%s'", is_first ? "lower" : "upper", date.c_str()); return date_boundary (is_first); } t = g_date_time_to_unix (dtime); g_date_time_unref (dtime); if (t < 0 || t > 9999999999) return date_boundary (is_first); else return date_to_time_t_string (t); } constexpr const auto SizeFormat = "%010" G_GINT64_FORMAT; constexpr const char SizeMin[] = "0000000000"; constexpr const char SizeMax[] = "9999999999"; static_assert(sizeof(SizeMin) == 10 + 1, "invalid"); static_assert(sizeof(SizeMax) == 10 + 1, "invalid"); static std::string size_boundary (bool is_first) { return is_first ? SizeMin : SizeMax; } std::string Mu::size_to_string (int64_t size) { char buf[sizeof(SizeMax)]; g_snprintf (buf, sizeof(buf), SizeFormat, size); return buf; } std::string Mu::size_to_string (const std::string& val, bool is_first) { std::string str; GRegex *rx; GMatchInfo *minfo; /* one-sided ranges */ if (val.empty()) return size_boundary (is_first); rx = g_regex_new ("(\\d+)(b|k|kb|m|mb|g|gb)?", G_REGEX_CASELESS, (GRegexMatchFlags)0, NULL); minfo = NULL; if (g_regex_match (rx, val.c_str(), (GRegexMatchFlags)0, &minfo)) { gint64 size; char *s; s = g_match_info_fetch (minfo, 1); size = atoll (s); g_free (s); s = g_match_info_fetch (minfo, 2); switch (s ? g_ascii_tolower(s[0]) : 0) { case 'k': size *= 1024; break; case 'm': size *= (1024 * 1024); break; case 'g': size *= (1024 * 1024 * 1024); break; default: break; } g_free (s); str = size_to_string (size); } else str = size_boundary (is_first); g_regex_unref (rx); g_match_info_unref (minfo); return str; } std::string Mu::canonicalize_filename(const std::string& path, const std::string& relative_to) { char *fname = mu_canonicalize_filename ( path.c_str(), relative_to.empty() ? NULL : relative_to.c_str()); std::string rv{fname}; g_free (fname); return rv; } void Mu::assert_equal(const std::string& s1, const std::string& s2) { g_assert_cmpstr (s1.c_str(), ==, s2.c_str()); } void Mu::assert_equal (const Mu::StringVec& v1, const Mu::StringVec& v2) { g_assert_cmpuint(v1.size(), ==, v2.size()); for (auto i = 0U; i != v1.size(); ++i) assert_equal(v1[i], v2[i]); } void Mu::allow_warnings() { g_test_log_set_fatal_handler( [](const char*, GLogLevelFlags, const char*, gpointer) { return FALSE; },{}); } mu-1.6.10/lib/utils/mu-utils.hh000066400000000000000000000263461414367003600162450ustar00rootroot00000000000000/* ** Copyright (C) 2020-2021 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef __MU_UTILS_HH__ #define __MU_UTILS_HH__ #include #include #include #include #include #include #include #include namespace Mu { using StringVec = std::vector; /** * Flatten a string -- downcase and fold diacritics etc. * * @param str a string * * @return a flattened string */ std::string utf8_flatten (const char *str); inline std::string utf8_flatten (const std::string& s) { return utf8_flatten(s.c_str()); } /** * Replace all control characters with spaces, and remove leading and trailing space. * * @param dirty an unclean string * * @return a cleaned-up string. */ std::string utf8_clean (const std::string& dirty); /** * Remove ctrl characters, replacing them with ' '; subsequent * ctrl characters are replaced by a single ' ' * * @param str a string * * @return the string without control characters */ std::string remove_ctrl (const std::string& str); /** * Split a string in parts * * @param str a string * @param sepa the separator * * @return the parts. */ std::vector split (const std::string& str, const std::string& sepa); /** * Quote & escape a string for " and \ * * @param str a string * * @return quoted string */ std::string quote (const std::string& str); /** * Format a string, printf style * * @param frm format string * @param ... parameters * * @return a formatted string */ std::string format (const char *frm, ...) __attribute__((format(printf, 1, 2))); /** * Format a string, printf style * * @param frm format string * @param ... parameters * * @return a formatted string */ std::string vformat (const char *frm, va_list args) __attribute__((format(printf, 1, 0))); /** * Convert an date to the corresponding time expressed as a string with a * 10-digit time_t * * @param date the date expressed a YYYYMMDDHHMMSS or any n... of the first * characters. * @param first whether to fill out incomplete dates to the start or the end; * ie. either 1972 -> 197201010000 or 1972 -> 197212312359 * * @return the corresponding time_t expressed as a string */ std::string date_to_time_t_string (const std::string& date, bool first); /** * 64-bit incarnation of time_t expressed as a 10-digit string. Uses 64-bit for the time-value, * regardless of the size of time_t. * * @param t some time value * * @return */ std::string date_to_time_t_string (int64_t t); using Clock = std::chrono::steady_clock; using Duration = Clock::duration; template constexpr int64_t to_unit (Duration d) { using namespace std::chrono; return duration_cast(d).count(); } constexpr int64_t to_s (Duration d) { return to_unit(d); } constexpr int64_t to_ms (Duration d) { return to_unit(d); } constexpr int64_t to_us (Duration d) { return to_unit(d); } struct StopWatch { using Clock = std::chrono::steady_clock; StopWatch (const std::string name): start_{Clock::now()}, name_{name} {} ~StopWatch() { const auto us{to_us(Clock::now()-start_)}; if (us > 2000000) g_debug ("%s: finished after %0.1f s", name_.c_str(), us/1000000.0); else if (us > 2000) g_debug ("%s: finished after %0.1f ms", name_.c_str(), us/1000.0); else g_debug ("%s: finished after %" G_GINT64_FORMAT " us", name_.c_str(), us); } private: Clock::time_point start_; std::string name_; }; /** * See g_canonicalize_filename * * @param filename * @param relative_to * * @return */ std::string canonicalize_filename(const std::string& path, const std::string& relative_to); /** * Convert a size string to a size in bytes * * @param sizestr the size string * @param first * * @return the size expressed as a string with the decimal number of bytes */ std::string size_to_string (const std::string& sizestr, bool first); /** * Convert a size into a size in bytes string * * @param size the size * @param first * * @return the size expressed as a string with the decimal number of bytes */ std::string size_to_string (int64_t size); /** * Convert any ostreamable<< value to a string * * @param t the value * * @return a std::string */ template static inline std::string to_string (const T& val) { std::stringstream sstr; sstr << val; return sstr.str(); } struct MaybeAnsi { explicit MaybeAnsi(bool use_color): color_{use_color} {} enum struct Color { Black = 30, Red = 31, Green = 32, Yellow = 33, Blue = 34, Magenta = 35, Cyan = 36, White = 37, BrightBlack = 90, BrightRed = 91, BrightGreen = 92, BrightYellow = 93, BrightBlue = 94, BrightMagenta = 95, BrightCyan = 96, BrightWhite = 97, }; std::string fg(Color c) const { return ansi(c, true); } std::string bg(Color c) const { return ansi(c, false); } std::string reset() const { return color_ ? "\x1b[0m" : ""; } private: std::string ansi(Color c, bool fg=true) const { return color_ ? format("\x1b[%dm", static_cast(c) + (fg ? 0 : 10)) : ""; } const bool color_; }; /** * * don't repeat these catch blocks everywhere... * */ #define MU_XAPIAN_CATCH_BLOCK \ catch (const Xapian::Error &xerr) { \ g_critical ("%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ } catch (const std::runtime_error& re) { \ g_critical ("%s: error: %s", __func__, re.what()); \ } catch (...) { \ g_critical ("%s: caught exception", __func__); \ } #define MU_XAPIAN_CATCH_BLOCK_G_ERROR(GE,E) \ catch (const Xapian::DatabaseLockError &xerr) { \ mu_util_g_set_error ((GE), \ MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK, \ "%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ } catch (const Xapian::DatabaseError &xerr) { \ mu_util_g_set_error ((GE),MU_ERROR_XAPIAN, \ "%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ } catch (const Xapian::Error &xerr) { \ mu_util_g_set_error ((GE),(E), \ "%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ } catch (const std::runtime_error& ex) { \ mu_util_g_set_error ((GE),(MU_ERROR_INTERNAL), \ "%s: error: %s", __func__, ex.what()); \ \ } catch (...) { \ mu_util_g_set_error ((GE),(MU_ERROR_INTERNAL), \ "%s: caught exception", __func__); \ } #define MU_XAPIAN_CATCH_BLOCK_RETURN(R) \ catch (const Xapian::Error &xerr) { \ g_critical ("%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ return (R); \ } catch (const std::runtime_error& ex) { \ g_critical("%s: error: %s", __func__, ex.what()); \ return (R); \ } catch (...) { \ g_critical ("%s: caught exception", __func__); \ return (R); \ } #define MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(GE,E,R) \ catch (const Xapian::Error &xerr) { \ mu_util_g_set_error ((GE),(E), \ "%s: xapian error '%s'", \ __func__, xerr.get_msg().c_str()); \ return (R); \ } catch (const std::runtime_error& ex) { \ mu_util_g_set_error ((GE),(MU_ERROR_INTERNAL), \ "%s: error: %s", __func__, ex.what()); \ return (R); \ } catch (...) { \ if ((GE)&&!(*(GE))) \ mu_util_g_set_error ((GE), \ (MU_ERROR_INTERNAL), \ "%s: caught exception", __func__); \ return (R); \ } /// Allow using enum structs as bitflags #define MU_TO_NUM(ET,ELM) std::underlying_type_t(ELM) #define MU_TO_ENUM(ET,NUM) static_cast(NUM) #define MU_ENABLE_BITOPS(ET) \ constexpr ET operator& (ET e1, ET e2) { return MU_TO_ENUM(ET,MU_TO_NUM(ET,e1)&MU_TO_NUM(ET,e2)); } \ constexpr ET operator| (ET e1, ET e2) { return MU_TO_ENUM(ET,MU_TO_NUM(ET,e1)|MU_TO_NUM(ET,e2)); } \ constexpr ET operator~ (ET e) { return MU_TO_ENUM(ET,~(MU_TO_NUM(ET, e))); } \ constexpr bool any_of(ET e) { return MU_TO_NUM(ET,e) != 0; } \ constexpr bool none_of(ET e) { return MU_TO_NUM(ET,e) == 0; } \ static inline ET& operator&=(ET& e1, ET e2) { return e1 = e1 & e2;} \ static inline ET& operator|=(ET& e1, ET e2) { return e1 = e1 | e2;} /** * For unit tests, assert two std::string's are equal. * * @param s1 string1 * @param s2 string2 */ void assert_equal(const std::string& s1, const std::string& s2); /** * For unit tests, assert that to containers are the same. * * @param c1 container1 * @param c2 container2 */ void assert_equal (const StringVec& v1, const StringVec& v2); /** * For unit-tests, allow warnings in the current function. * */ void allow_warnings(); } // namespace Mu #endif /* __MU_UTILS_HH__ */ mu-1.6.10/lib/utils/optional.hpp000066400000000000000000002175211414367003600165000ustar00rootroot00000000000000 /// // optional - An implementation of std::optional with extensions // Written in 2017 by Simon Brand (simonrbrand@gmail.com, @TartanLlama) // // Documentation available at https://tl.tartanllama.xyz/ // // To the extent possible under law, the author(s) have dedicated all // copyright and related and neighboring rights to this software to the // public domain worldwide. This software is distributed without any warranty. // // You should have received a copy of the CC0 Public Domain Dedication // along with this software. If not, see // . /// #ifndef TL_OPTIONAL_HPP #define TL_OPTIONAL_HPP #define TL_OPTIONAL_VERSION_MAJOR 1 #define TL_OPTIONAL_VERSION_MINOR 0 #define TL_OPTIONAL_VERSION_PATCH 0 #include #include #include #include #include #if (defined(_MSC_VER) && _MSC_VER == 1900) #define TL_OPTIONAL_MSVC2015 #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ !defined(__clang__)) #define TL_OPTIONAL_GCC49 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ !defined(__clang__)) #define TL_OPTIONAL_GCC54 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ !defined(__clang__)) #define TL_OPTIONAL_GCC55 #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ !defined(__clang__)) // GCC < 5 doesn't support overloading on const&& for member functions #define TL_OPTIONAL_NO_CONSTRR // GCC < 5 doesn't support some standard C++11 type traits #define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::has_trivial_copy_constructor::value #define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value // This one will be different for GCC 5.7 if it's ever supported #define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value // GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector // for non-copyable types #elif (defined(__GNUC__) && __GNUC__ < 8 && \ !defined(__clang__)) #ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX #define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX namespace tl { namespace detail { template struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; #ifdef _GLIBCXX_VECTOR template struct is_trivially_copy_constructible> : std::is_trivially_copy_constructible{}; #endif } } #endif #define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ tl::detail::is_trivially_copy_constructible::value #define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable::value #define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value #else #define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::is_trivially_copy_constructible::value #define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable::value #define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value #endif #if __cplusplus > 201103L #define TL_OPTIONAL_CXX14 #endif // constexpr implies const in C++11, not C++14 #if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ defined(TL_OPTIONAL_GCC49)) #define TL_OPTIONAL_11_CONSTEXPR #else #define TL_OPTIONAL_11_CONSTEXPR constexpr #endif namespace tl { #ifndef TL_MONOSTATE_INPLACE_MUTEX #define TL_MONOSTATE_INPLACE_MUTEX /// Used to represent an optional with no data; essentially a bool class monostate {}; /// A tag type to tell optional to construct its value in-place struct in_place_t { explicit in_place_t() = default; }; /// A tag to tell optional to construct its value in-place static constexpr in_place_t in_place{}; #endif template class optional; namespace detail { #ifndef TL_TRAITS_MUTEX #define TL_TRAITS_MUTEX // C++14-style aliases for brevity template using remove_const_t = typename std::remove_const::type; template using remove_reference_t = typename std::remove_reference::type; template using decay_t = typename std::decay::type; template using enable_if_t = typename std::enable_if::type; template using conditional_t = typename std::conditional::type; // std::conjunction from C++17 template struct conjunction : std::true_type {}; template struct conjunction : B {}; template struct conjunction : std::conditional, B>::type {}; #if defined(_LIBCPP_VERSION) && __cplusplus == 201103L #define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND #endif // In C++11 mode, there's an issue in libc++'s std::mem_fn // which results in a hard-error when using it in a noexcept expression // in some cases. This is a check to workaround the common failing case. #ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND template struct is_pointer_to_non_const_member_func : std::false_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_pointer_to_non_const_member_func : std::true_type{}; template struct is_const_or_const_ref : std::false_type{}; template struct is_const_or_const_ref : std::true_type{}; template struct is_const_or_const_ref : std::true_type{}; #endif // std::invoke from C++17 // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround template ::value && is_const_or_const_ref::value)>, #endif typename = enable_if_t>::value>, int = 0> constexpr auto invoke(Fn &&f, Args &&... args) noexcept( noexcept(std::mem_fn(f)(std::forward(args)...))) -> decltype(std::mem_fn(f)(std::forward(args)...)) { return std::mem_fn(f)(std::forward(args)...); } template >::value>> constexpr auto invoke(Fn &&f, Args &&... args) noexcept( noexcept(std::forward(f)(std::forward(args)...))) -> decltype(std::forward(f)(std::forward(args)...)) { return std::forward(f)(std::forward(args)...); } // std::invoke_result from C++17 template struct invoke_result_impl; template struct invoke_result_impl< F, decltype(detail::invoke(std::declval(), std::declval()...), void()), Us...> { using type = decltype(detail::invoke(std::declval(), std::declval()...)); }; template using invoke_result = invoke_result_impl; template using invoke_result_t = typename invoke_result::type; #if defined(_MSC_VER) && _MSC_VER <= 1900 // TODO make a version which works with MSVC 2015 template struct is_swappable : std::true_type {}; template struct is_nothrow_swappable : std::true_type {}; #else // https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept namespace swap_adl_tests { // if swap ADL finds this then it would call std::swap otherwise (same // signature) struct tag {}; template tag swap(T &, T &); template tag swap(T (&a)[N], T (&b)[N]); // helper functions to test if an unqualified swap is possible, and if it // becomes std::swap template std::false_type can_swap(...) noexcept(false); template (), std::declval()))> std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), std::declval()))); template std::false_type uses_std(...); template std::is_same(), std::declval())), tag> uses_std(int); template struct is_std_swap_noexcept : std::integral_constant::value && std::is_nothrow_move_assignable::value> {}; template struct is_std_swap_noexcept : is_std_swap_noexcept {}; template struct is_adl_swap_noexcept : std::integral_constant(0))> {}; } // namespace swap_adl_tests template struct is_swappable : std::integral_constant< bool, decltype(detail::swap_adl_tests::can_swap(0))::value && (!decltype(detail::swap_adl_tests::uses_std(0))::value || (std::is_move_assignable::value && std::is_move_constructible::value))> {}; template struct is_swappable : std::integral_constant< bool, decltype(detail::swap_adl_tests::can_swap(0))::value && (!decltype( detail::swap_adl_tests::uses_std(0))::value || is_swappable::value)> {}; template struct is_nothrow_swappable : std::integral_constant< bool, is_swappable::value && ((decltype(detail::swap_adl_tests::uses_std(0))::value &&detail::swap_adl_tests::is_std_swap_noexcept::value) || (!decltype(detail::swap_adl_tests::uses_std(0))::value && detail::swap_adl_tests::is_adl_swap_noexcept::value))> { }; #endif #endif // std::void_t from C++17 template struct voider { using type = void; }; template using void_t = typename voider::type; // Trait for checking if a type is a tl::optional template struct is_optional_impl : std::false_type {}; template struct is_optional_impl> : std::true_type {}; template using is_optional = is_optional_impl>; // Change void to tl::monostate template using fixup_void = conditional_t::value, monostate, U>; template > using get_map_return = optional>>; // Check if invoking F for some Us returns void template struct returns_void_impl; template struct returns_void_impl>, U...> : std::is_void> {}; template using returns_void = returns_void_impl; template using enable_if_ret_void = enable_if_t::value>; template using disable_if_ret_void = enable_if_t::value>; template using enable_forward_value = detail::enable_if_t::value && !std::is_same, in_place_t>::value && !std::is_same, detail::decay_t>::value>; template using enable_from_other = detail::enable_if_t< std::is_constructible::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value>; template using enable_assign_forward = detail::enable_if_t< !std::is_same, detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && std::is_assignable::value>; template using enable_assign_from_other = detail::enable_if_t< std::is_constructible::value && std::is_assignable::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_constructible &>::value && !std::is_constructible &&>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value && !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value && !std::is_assignable &>::value && !std::is_assignable &&>::value && !std::is_assignable &>::value && !std::is_assignable &&>::value>; // The storage base manages the actual storage, and correctly propagates // trivial destruction from T. This case is for when T is not trivially // destructible. template ::value> struct optional_storage_base { TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept : m_dummy(), m_has_value(false) {} template TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) : m_value(std::forward(u)...), m_has_value(true) {} ~optional_storage_base() { if (m_has_value) { m_value.~T(); m_has_value = false; } } struct dummy {}; union { dummy m_dummy; T m_value; }; bool m_has_value; }; // This case is for when T is trivially destructible. template struct optional_storage_base { TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept : m_dummy(), m_has_value(false) {} template TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) : m_value(std::forward(u)...), m_has_value(true) {} // No destructor, so this class is trivially destructible struct dummy {}; union { dummy m_dummy; T m_value; }; bool m_has_value = false; }; // This base class provides some handy member functions which can be used in // further derived classes template struct optional_operations_base : optional_storage_base { using optional_storage_base::optional_storage_base; void hard_reset() noexcept { get().~T(); this->m_has_value = false; } template void construct(Args &&... args) noexcept { new (std::addressof(this->m_value)) T(std::forward(args)...); this->m_has_value = true; } template void assign(Opt &&rhs) { if (this->has_value()) { if (rhs.has_value()) { this->m_value = std::forward(rhs).get(); } else { this->m_value.~T(); this->m_has_value = false; } } else if (rhs.has_value()) { construct(std::forward(rhs).get()); } } bool has_value() const { return this->m_has_value; } TL_OPTIONAL_11_CONSTEXPR T &get() & { return this->m_value; } TL_OPTIONAL_11_CONSTEXPR const T &get() const & { return this->m_value; } TL_OPTIONAL_11_CONSTEXPR T &&get() && { return std::move(this->m_value); } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr const T &&get() const && { return std::move(this->m_value); } #endif }; // This class manages conditionally having a trivial copy constructor // This specialization is for when T is trivially copy constructible template struct optional_copy_base : optional_operations_base { using optional_operations_base::optional_operations_base; }; // This specialization is for when T is not trivially copy constructible template struct optional_copy_base : optional_operations_base { using optional_operations_base::optional_operations_base; optional_copy_base() = default; optional_copy_base(const optional_copy_base &rhs) { if (rhs.has_value()) { this->construct(rhs.get()); } else { this->m_has_value = false; } } optional_copy_base(optional_copy_base &&rhs) = default; optional_copy_base &operator=(const optional_copy_base &rhs) = default; optional_copy_base &operator=(optional_copy_base &&rhs) = default; }; // This class manages conditionally having a trivial move constructor // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it // doesn't implement an analogue to std::is_trivially_move_constructible. We // have to make do with a non-trivial move constructor even if T is trivially // move constructible #ifndef TL_OPTIONAL_GCC49 template ::value> struct optional_move_base : optional_copy_base { using optional_copy_base::optional_copy_base; }; #else template struct optional_move_base; #endif template struct optional_move_base : optional_copy_base { using optional_copy_base::optional_copy_base; optional_move_base() = default; optional_move_base(const optional_move_base &rhs) = default; optional_move_base(optional_move_base &&rhs) noexcept( std::is_nothrow_move_constructible::value) { if (rhs.has_value()) { this->construct(std::move(rhs.get())); } else { this->m_has_value = false; } } optional_move_base &operator=(const optional_move_base &rhs) = default; optional_move_base &operator=(optional_move_base &&rhs) = default; }; // This class manages conditionally having a trivial copy assignment operator template struct optional_copy_assign_base : optional_move_base { using optional_move_base::optional_move_base; }; template struct optional_copy_assign_base : optional_move_base { using optional_move_base::optional_move_base; optional_copy_assign_base() = default; optional_copy_assign_base(const optional_copy_assign_base &rhs) = default; optional_copy_assign_base(optional_copy_assign_base &&rhs) = default; optional_copy_assign_base &operator=(const optional_copy_assign_base &rhs) { this->assign(rhs); return *this; } optional_copy_assign_base & operator=(optional_copy_assign_base &&rhs) = default; }; // This class manages conditionally having a trivial move assignment operator // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it // doesn't implement an analogue to std::is_trivially_move_assignable. We have // to make do with a non-trivial move assignment operator even if T is trivially // move assignable #ifndef TL_OPTIONAL_GCC49 template ::value &&std::is_trivially_move_constructible::value &&std::is_trivially_move_assignable::value> struct optional_move_assign_base : optional_copy_assign_base { using optional_copy_assign_base::optional_copy_assign_base; }; #else template struct optional_move_assign_base; #endif template struct optional_move_assign_base : optional_copy_assign_base { using optional_copy_assign_base::optional_copy_assign_base; optional_move_assign_base() = default; optional_move_assign_base(const optional_move_assign_base &rhs) = default; optional_move_assign_base(optional_move_assign_base &&rhs) = default; optional_move_assign_base & operator=(const optional_move_assign_base &rhs) = default; optional_move_assign_base & operator=(optional_move_assign_base &&rhs) noexcept( std::is_nothrow_move_constructible::value &&std::is_nothrow_move_assignable::value) { this->assign(std::move(rhs)); return *this; } }; // optional_delete_ctor_base will conditionally delete copy and move // constructors depending on whether T is copy/move constructible template ::value, bool EnableMove = std::is_move_constructible::value> struct optional_delete_ctor_base { optional_delete_ctor_base() = default; optional_delete_ctor_base(const optional_delete_ctor_base &) = default; optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; optional_delete_ctor_base & operator=(const optional_delete_ctor_base &) = default; optional_delete_ctor_base & operator=(optional_delete_ctor_base &&) noexcept = default; }; template struct optional_delete_ctor_base { optional_delete_ctor_base() = default; optional_delete_ctor_base(const optional_delete_ctor_base &) = default; optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; optional_delete_ctor_base & operator=(const optional_delete_ctor_base &) = default; optional_delete_ctor_base & operator=(optional_delete_ctor_base &&) noexcept = default; }; template struct optional_delete_ctor_base { optional_delete_ctor_base() = default; optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; optional_delete_ctor_base & operator=(const optional_delete_ctor_base &) = default; optional_delete_ctor_base & operator=(optional_delete_ctor_base &&) noexcept = default; }; template struct optional_delete_ctor_base { optional_delete_ctor_base() = default; optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; optional_delete_ctor_base & operator=(const optional_delete_ctor_base &) = default; optional_delete_ctor_base & operator=(optional_delete_ctor_base &&) noexcept = default; }; // optional_delete_assign_base will conditionally delete copy and move // constructors depending on whether T is copy/move constructible + assignable template ::value && std::is_copy_assignable::value), bool EnableMove = (std::is_move_constructible::value && std::is_move_assignable::value)> struct optional_delete_assign_base { optional_delete_assign_base() = default; optional_delete_assign_base(const optional_delete_assign_base &) = default; optional_delete_assign_base(optional_delete_assign_base &&) noexcept = default; optional_delete_assign_base & operator=(const optional_delete_assign_base &) = default; optional_delete_assign_base & operator=(optional_delete_assign_base &&) noexcept = default; }; template struct optional_delete_assign_base { optional_delete_assign_base() = default; optional_delete_assign_base(const optional_delete_assign_base &) = default; optional_delete_assign_base(optional_delete_assign_base &&) noexcept = default; optional_delete_assign_base & operator=(const optional_delete_assign_base &) = default; optional_delete_assign_base & operator=(optional_delete_assign_base &&) noexcept = delete; }; template struct optional_delete_assign_base { optional_delete_assign_base() = default; optional_delete_assign_base(const optional_delete_assign_base &) = default; optional_delete_assign_base(optional_delete_assign_base &&) noexcept = default; optional_delete_assign_base & operator=(const optional_delete_assign_base &) = delete; optional_delete_assign_base & operator=(optional_delete_assign_base &&) noexcept = default; }; template struct optional_delete_assign_base { optional_delete_assign_base() = default; optional_delete_assign_base(const optional_delete_assign_base &) = default; optional_delete_assign_base(optional_delete_assign_base &&) noexcept = default; optional_delete_assign_base & operator=(const optional_delete_assign_base &) = delete; optional_delete_assign_base & operator=(optional_delete_assign_base &&) noexcept = delete; }; } // namespace detail /// A tag type to represent an empty optional struct nullopt_t { struct do_not_use {}; constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} }; /// Represents an empty optional static constexpr nullopt_t nullopt{nullopt_t::do_not_use{}, nullopt_t::do_not_use{}}; class bad_optional_access : public std::exception { public: bad_optional_access() = default; const char *what() const noexcept { return "Optional has no value"; } }; /// An optional object is an object that contains the storage for another /// object and manages the lifetime of this contained object, if any. The /// contained object may be initialized after the optional object has been /// initialized, and may be destroyed before the optional object has been /// destroyed. The initialization state of the contained object is tracked by /// the optional object. template class optional : private detail::optional_move_assign_base, private detail::optional_delete_ctor_base, private detail::optional_delete_assign_base { using base = detail::optional_move_assign_base; static_assert(!std::is_same::value, "instantiation of optional with in_place_t is ill-formed"); static_assert(!std::is_same, nullopt_t>::value, "instantiation of optional with nullopt_t is ill-formed"); public: // The different versions for C++14 and 11 are needed because deduced return // types are not SFINAE-safe. This provides better support for things like // generic lambdas. C.f. // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation which returns an optional on the stored /// object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : result(nullopt); } template constexpr auto and_then(F &&f) const & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr auto and_then(F &&f) const && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : result(nullopt); } #endif #else /// Carries out some operation which returns an optional on the stored /// object if there is one. template TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : result(nullopt); } template constexpr detail::invoke_result_t and_then(F &&f) const & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr detail::invoke_result_t and_then(F &&f) const && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : result(nullopt); } #endif #endif #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { return optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { return optional_map_impl(std::move(*this), std::forward(f)); } template constexpr auto map(F &&f) const & { return optional_map_impl(*this, std::forward(f)); } template constexpr auto map(F &&f) const && { return optional_map_impl(std::move(*this), std::forward(f)); } #else /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), std::declval())) map(F &&f) & { return optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), std::declval())) map(F &&f) && { return optional_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(optional_map_impl(std::declval(), std::declval())) map(F &&f) const & { return optional_map_impl(*this, std::forward(f)); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr decltype(optional_map_impl(std::declval(), std::declval())) map(F &&f) const && { return optional_map_impl(std::move(*this), std::forward(f)); } #endif #endif #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { return optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { return optional_map_impl(std::move(*this), std::forward(f)); } template constexpr auto transform(F&& f) const & { return optional_map_impl(*this, std::forward(f)); } template constexpr auto transform(F&& f) const && { return optional_map_impl(std::move(*this), std::forward(f)); } #else /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), std::declval())) transform(F&& f) & { return optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), std::declval())) transform(F&& f) && { return optional_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(optional_map_impl(std::declval(), std::declval())) transform(F&& f) const & { return optional_map_impl(*this, std::forward(f)); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr decltype(optional_map_impl(std::declval(), std::declval())) transform(F&& f) const && { return optional_map_impl(std::move(*this), std::forward(f)); } #endif #endif /// Calls `f` if the optional is empty template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { if (has_value()) return *this; std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { return has_value() ? *this : std::forward(f)(); } template * = nullptr> optional or_else(F &&f) && { if (has_value()) return std::move(*this); std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { return has_value() ? std::move(*this) : std::forward(f)(); } template * = nullptr> optional or_else(F &&f) const & { if (has_value()) return *this; std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { return has_value() ? *this : std::forward(f)(); } #ifndef TL_OPTIONAL_NO_CONSTRR template * = nullptr> optional or_else(F &&f) const && { if (has_value()) return std::move(*this); std::forward(f)(); return nullopt; } template * = nullptr> optional or_else(F &&f) const && { return has_value() ? std::move(*this) : std::forward(f)(); } #endif /// Maps the stored value with `f` if there is one, otherwise returns `u`. template U map_or(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); } template U map_or(F &&f, U &&u) && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u); } template U map_or(F &&f, U &&u) const & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); } #ifndef TL_OPTIONAL_NO_CONSTRR template U map_or(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u); } #endif /// Maps the stored value with `f` if there is one, otherwise calls /// `u` and returns the result. template detail::invoke_result_t map_or_else(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u)(); } template detail::invoke_result_t map_or_else(F &&f, U &&u) && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u)(); } template detail::invoke_result_t map_or_else(F &&f, U &&u) const & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u)(); } #ifndef TL_OPTIONAL_NO_CONSTRR template detail::invoke_result_t map_or_else(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u)(); } #endif /// Returns `u` if `*this` has a value, otherwise an empty optional. template constexpr optional::type> conjunction(U &&u) const { using result = optional>; return has_value() ? result{u} : result{nullopt}; } /// Returns `rhs` if `*this` is empty, otherwise the current value. TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { return has_value() ? *this : rhs; } constexpr optional disjunction(const optional &rhs) const & { return has_value() ? *this : rhs; } TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { return has_value() ? std::move(*this) : rhs; } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr optional disjunction(const optional &rhs) const && { return has_value() ? std::move(*this) : rhs; } #endif TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { return has_value() ? *this : std::move(rhs); } constexpr optional disjunction(optional &&rhs) const & { return has_value() ? *this : std::move(rhs); } TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { return has_value() ? std::move(*this) : std::move(rhs); } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr optional disjunction(optional &&rhs) const && { return has_value() ? std::move(*this) : std::move(rhs); } #endif /// Takes the value out of the optional, leaving it empty optional take() { optional ret = std::move(*this); reset(); return ret; } using value_type = T; /// Constructs an optional that does not contain a value. constexpr optional() noexcept = default; constexpr optional(nullopt_t) noexcept {} /// Copy constructor /// /// If `rhs` contains a value, the stored value is direct-initialized with /// it. Otherwise, the constructed optional is empty. TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) = default; /// Move constructor /// /// If `rhs` contains a value, the stored value is direct-initialized with /// it. Otherwise, the constructed optional is empty. TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; /// Constructs the stored value in-place using the given arguments. template constexpr explicit optional( detail::enable_if_t::value, in_place_t>, Args &&... args) : base(in_place, std::forward(args)...) {} template TL_OPTIONAL_11_CONSTEXPR explicit optional( detail::enable_if_t &, Args &&...>::value, in_place_t>, std::initializer_list il, Args &&... args) { this->construct(il, std::forward(args)...); } /// Constructs the stored value with `u`. template < class U = T, detail::enable_if_t::value> * = nullptr, detail::enable_forward_value * = nullptr> constexpr optional(U &&u) : base(in_place, std::forward(u)) {} template < class U = T, detail::enable_if_t::value> * = nullptr, detail::enable_forward_value * = nullptr> constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} /// Converting copy constructor. template < class U, detail::enable_from_other * = nullptr, detail::enable_if_t::value> * = nullptr> optional(const optional &rhs) { if (rhs.has_value()) { this->construct(*rhs); } } template * = nullptr, detail::enable_if_t::value> * = nullptr> explicit optional(const optional &rhs) { if (rhs.has_value()) { this->construct(*rhs); } } /// Converting move constructor. template < class U, detail::enable_from_other * = nullptr, detail::enable_if_t::value> * = nullptr> optional(optional &&rhs) { if (rhs.has_value()) { this->construct(std::move(*rhs)); } } template < class U, detail::enable_from_other * = nullptr, detail::enable_if_t::value> * = nullptr> explicit optional(optional &&rhs) { if (rhs.has_value()) { this->construct(std::move(*rhs)); } } /// Destroys the stored value if there is one. ~optional() = default; /// Assignment to empty. /// /// Destroys the current value if there is one. optional &operator=(nullopt_t) noexcept { if (has_value()) { this->m_value.~T(); this->m_has_value = false; } return *this; } /// Copy assignment. /// /// Copies the value from `rhs` if there is one. Otherwise resets the stored /// value in `*this`. optional &operator=(const optional &rhs) = default; /// Move assignment. /// /// Moves the value from `rhs` if there is one. Otherwise resets the stored /// value in `*this`. optional &operator=(optional &&rhs) = default; /// Assigns the stored value from `u`, destroying the old value if there was /// one. template * = nullptr> optional &operator=(U &&u) { if (has_value()) { this->m_value = std::forward(u); } else { this->construct(std::forward(u)); } return *this; } /// Converting copy assignment operator. /// /// Copies the value from `rhs` if there is one. Otherwise resets the stored /// value in `*this`. template * = nullptr> optional &operator=(const optional &rhs) { if (has_value()) { if (rhs.has_value()) { this->m_value = *rhs; } else { this->hard_reset(); } } if (rhs.has_value()) { this->construct(*rhs); } return *this; } // TODO check exception guarantee /// Converting move assignment operator. /// /// Moves the value from `rhs` if there is one. Otherwise resets the stored /// value in `*this`. template * = nullptr> optional &operator=(optional &&rhs) { if (has_value()) { if (rhs.has_value()) { this->m_value = std::move(*rhs); } else { this->hard_reset(); } } if (rhs.has_value()) { this->construct(std::move(*rhs)); } return *this; } /// Constructs the value in-place, destroying the current one if there is /// one. template T &emplace(Args &&... args) { static_assert(std::is_constructible::value, "T must be constructible with Args"); *this = nullopt; this->construct(std::forward(args)...); return value(); } template detail::enable_if_t< std::is_constructible &, Args &&...>::value, T &> emplace(std::initializer_list il, Args &&... args) { *this = nullopt; this->construct(il, std::forward(args)...); return value(); } /// Swaps this optional with the other. /// /// If neither optionals have a value, nothing happens. /// If both have a value, the values are swapped. /// If one has a value, it is moved to the other and the movee is left /// valueless. void swap(optional &rhs) noexcept(std::is_nothrow_move_constructible::value &&detail::is_nothrow_swappable::value) { using std::swap; if (has_value()) { if (rhs.has_value()) { swap(**this, *rhs); } else { new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); this->m_value.T::~T(); } } else if (rhs.has_value()) { new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); rhs.m_value.T::~T(); } swap(this->m_has_value, rhs.m_has_value); } /// Returns a pointer to the stored value constexpr const T *operator->() const { return std::addressof(this->m_value); } TL_OPTIONAL_11_CONSTEXPR T *operator->() { return std::addressof(this->m_value); } /// Returns the stored value TL_OPTIONAL_11_CONSTEXPR T &operator*() & { return this->m_value; } constexpr const T &operator*() const & { return this->m_value; } TL_OPTIONAL_11_CONSTEXPR T &&operator*() && { return std::move(this->m_value); } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr const T &&operator*() const && { return std::move(this->m_value); } #endif /// Returns whether or not the optional has a value constexpr bool has_value() const noexcept { return this->m_has_value; } constexpr explicit operator bool() const noexcept { return this->m_has_value; } /// Returns the contained value if there is one, otherwise throws bad_optional_access TL_OPTIONAL_11_CONSTEXPR T &value() & { if (has_value()) return this->m_value; throw bad_optional_access(); } TL_OPTIONAL_11_CONSTEXPR const T &value() const & { if (has_value()) return this->m_value; throw bad_optional_access(); } TL_OPTIONAL_11_CONSTEXPR T &&value() && { if (has_value()) return std::move(this->m_value); throw bad_optional_access(); } #ifndef TL_OPTIONAL_NO_CONSTRR TL_OPTIONAL_11_CONSTEXPR const T &&value() const && { if (has_value()) return std::move(this->m_value); throw bad_optional_access(); } #endif /// Returns the stored value if there is one, otherwise returns `u` template constexpr T value_or(U &&u) const & { static_assert(std::is_copy_constructible::value && std::is_convertible::value, "T must be copy constructible and convertible from U"); return has_value() ? **this : static_cast(std::forward(u)); } template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && { static_assert(std::is_move_constructible::value && std::is_convertible::value, "T must be move constructible and convertible from U"); return has_value() ? **this : static_cast(std::forward(u)); } /// Destroys the stored value if one exists, making the optional empty void reset() noexcept { if (has_value()) { this->m_value.~T(); this->m_has_value = false; } } }; // namespace tl /// Compares two optional objects template inline constexpr bool operator==(const optional &lhs, const optional &rhs) { return lhs.has_value() == rhs.has_value() && (!lhs.has_value() || *lhs == *rhs); } template inline constexpr bool operator!=(const optional &lhs, const optional &rhs) { return lhs.has_value() != rhs.has_value() || (lhs.has_value() && *lhs != *rhs); } template inline constexpr bool operator<(const optional &lhs, const optional &rhs) { return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); } template inline constexpr bool operator>(const optional &lhs, const optional &rhs) { return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); } template inline constexpr bool operator<=(const optional &lhs, const optional &rhs) { return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); } template inline constexpr bool operator>=(const optional &lhs, const optional &rhs) { return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); } /// Compares an optional to a `nullopt` template inline constexpr bool operator==(const optional &lhs, nullopt_t) noexcept { return !lhs.has_value(); } template inline constexpr bool operator==(nullopt_t, const optional &rhs) noexcept { return !rhs.has_value(); } template inline constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept { return lhs.has_value(); } template inline constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept { return rhs.has_value(); } template inline constexpr bool operator<(const optional &, nullopt_t) noexcept { return false; } template inline constexpr bool operator<(nullopt_t, const optional &rhs) noexcept { return rhs.has_value(); } template inline constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept { return !lhs.has_value(); } template inline constexpr bool operator<=(nullopt_t, const optional &) noexcept { return true; } template inline constexpr bool operator>(const optional &lhs, nullopt_t) noexcept { return lhs.has_value(); } template inline constexpr bool operator>(nullopt_t, const optional &) noexcept { return false; } template inline constexpr bool operator>=(const optional &, nullopt_t) noexcept { return true; } template inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { return !rhs.has_value(); } /// Compares the optional with a value. template inline constexpr bool operator==(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs == rhs : false; } template inline constexpr bool operator==(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs == *rhs : false; } template inline constexpr bool operator!=(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs != rhs : true; } template inline constexpr bool operator!=(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs != *rhs : true; } template inline constexpr bool operator<(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs < rhs : true; } template inline constexpr bool operator<(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs < *rhs : false; } template inline constexpr bool operator<=(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs <= rhs : true; } template inline constexpr bool operator<=(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs <= *rhs : false; } template inline constexpr bool operator>(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs > rhs : false; } template inline constexpr bool operator>(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs > *rhs : true; } template inline constexpr bool operator>=(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs >= rhs : false; } template inline constexpr bool operator>=(const U &lhs, const optional &rhs) { return rhs.has_value() ? lhs >= *rhs : true; } template ::value> * = nullptr, detail::enable_if_t::value> * = nullptr> void swap(optional &lhs, optional &rhs) noexcept(noexcept(lhs.swap(rhs))) { return lhs.swap(rhs); } namespace detail { struct i_am_secret {}; } // namespace detail template ::value, detail::decay_t, T>> inline constexpr optional make_optional(U &&v) { return optional(std::forward(v)); } template inline constexpr optional make_optional(Args &&... args) { return optional(in_place, std::forward(args)...); } template inline constexpr optional make_optional(std::initializer_list il, Args &&... args) { return optional(in_place, il, std::forward(args)...); } #if __cplusplus >= 201703L template optional(T)->optional; #endif /// \exclude namespace detail { #ifdef TL_OPTIONAL_CXX14 template (), *std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto optional_map_impl(Opt &&opt, F &&f) { return opt.has_value() ? detail::invoke(std::forward(f), *std::forward(opt)) : optional(nullopt); } template (), *std::declval())), detail::enable_if_t::value> * = nullptr> auto optional_map_impl(Opt &&opt, F &&f) { if (opt.has_value()) { detail::invoke(std::forward(f), *std::forward(opt)); return make_optional(monostate{}); } return optional(nullopt); } #else template (), *std::declval())), detail::enable_if_t::value> * = nullptr> constexpr auto optional_map_impl(Opt &&opt, F &&f) -> optional { return opt.has_value() ? detail::invoke(std::forward(f), *std::forward(opt)) : optional(nullopt); } template (), *std::declval())), detail::enable_if_t::value> * = nullptr> auto optional_map_impl(Opt &&opt, F &&f) -> optional { if (opt.has_value()) { detail::invoke(std::forward(f), *std::forward(opt)); return monostate{}; } return nullopt; } #endif } // namespace detail /// Specialization for when `T` is a reference. `optional` acts similarly /// to a `T*`, but provides more operations and shows intent more clearly. template class optional { public: // The different versions for C++14 and 11 are needed because deduced return // types are not SFINAE-safe. This provides better support for things like // generic lambdas. C.f. // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation which returns an optional on the stored /// object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template constexpr auto and_then(F &&f) const & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr auto and_then(F &&f) const && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #endif #else /// Carries out some operation which returns an optional on the stored /// object if there is one. template TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } template constexpr detail::invoke_result_t and_then(F &&f) const & { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr detail::invoke_result_t and_then(F &&f) const && { using result = detail::invoke_result_t; static_assert(detail::is_optional::value, "F must return an optional"); return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); } #endif #endif #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { return detail::optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } template constexpr auto map(F &&f) const & { return detail::optional_map_impl(*this, std::forward(f)); } template constexpr auto map(F &&f) const && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } #else /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), std::declval())) map(F &&f) & { return detail::optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), std::declval())) map(F &&f) && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(detail::optional_map_impl(std::declval(), std::declval())) map(F &&f) const & { return detail::optional_map_impl(*this, std::forward(f)); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr decltype(detail::optional_map_impl(std::declval(), std::declval())) map(F &&f) const && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } #endif #endif #if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { return detail::optional_map_impl(*this, std::forward(f)); } template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } template constexpr auto transform(F&& f) const & { return detail::optional_map_impl(*this, std::forward(f)); } template constexpr auto transform(F&& f) const && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } #else /// Carries out some operation on the stored object if there is one. template TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), std::declval())) transform(F&& f) & { return detail::optional_map_impl(*this, std::forward(f)); } /// \group map /// \synopsis template auto transform(F &&f) &&; template TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), std::declval())) transform(F&& f) && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } template constexpr decltype(detail::optional_map_impl(std::declval(), std::declval())) transform(F&& f) const & { return detail::optional_map_impl(*this, std::forward(f)); } #ifndef TL_OPTIONAL_NO_CONSTRR template constexpr decltype(detail::optional_map_impl(std::declval(), std::declval())) transform(F&& f) const && { return detail::optional_map_impl(std::move(*this), std::forward(f)); } #endif #endif /// Calls `f` if the optional is empty template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { if (has_value()) return *this; std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { return has_value() ? *this : std::forward(f)(); } template * = nullptr> optional or_else(F &&f) && { if (has_value()) return std::move(*this); std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { return has_value() ? std::move(*this) : std::forward(f)(); } template * = nullptr> optional or_else(F &&f) const & { if (has_value()) return *this; std::forward(f)(); return nullopt; } template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { return has_value() ? *this : std::forward(f)(); } #ifndef TL_OPTIONAL_NO_CONSTRR template * = nullptr> optional or_else(F &&f) const && { if (has_value()) return std::move(*this); std::forward(f)(); return nullopt; } template * = nullptr> optional or_else(F &&f) const && { return has_value() ? std::move(*this) : std::forward(f)(); } #endif /// Maps the stored value with `f` if there is one, otherwise returns `u` template U map_or(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); } template U map_or(F &&f, U &&u) && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u); } template U map_or(F &&f, U &&u) const & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); } #ifndef TL_OPTIONAL_NO_CONSTRR template U map_or(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u); } #endif /// Maps the stored value with `f` if there is one, otherwise calls /// `u` and returns the result. template detail::invoke_result_t map_or_else(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u)(); } template detail::invoke_result_t map_or_else(F &&f, U &&u) && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u)(); } template detail::invoke_result_t map_or_else(F &&f, U &&u) const & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u)(); } #ifndef TL_OPTIONAL_NO_CONSTRR template detail::invoke_result_t map_or_else(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u)(); } #endif /// Returns `u` if `*this` has a value, otherwise an empty optional. template constexpr optional::type> conjunction(U &&u) const { using result = optional>; return has_value() ? result{u} : result{nullopt}; } /// Returns `rhs` if `*this` is empty, otherwise the current value. TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { return has_value() ? *this : rhs; } constexpr optional disjunction(const optional &rhs) const & { return has_value() ? *this : rhs; } TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { return has_value() ? std::move(*this) : rhs; } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr optional disjunction(const optional &rhs) const && { return has_value() ? std::move(*this) : rhs; } #endif TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { return has_value() ? *this : std::move(rhs); } constexpr optional disjunction(optional &&rhs) const & { return has_value() ? *this : std::move(rhs); } TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { return has_value() ? std::move(*this) : std::move(rhs); } #ifndef TL_OPTIONAL_NO_CONSTRR constexpr optional disjunction(optional &&rhs) const && { return has_value() ? std::move(*this) : std::move(rhs); } #endif /// Takes the value out of the optional, leaving it empty optional take() { optional ret = std::move(*this); reset(); return ret; } using value_type = T &; /// Constructs an optional that does not contain a value. constexpr optional() noexcept : m_value(nullptr) {} constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} /// Copy constructor /// /// If `rhs` contains a value, the stored value is direct-initialized with /// it. Otherwise, the constructed optional is empty. TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) noexcept = default; /// Move constructor /// /// If `rhs` contains a value, the stored value is direct-initialized with /// it. Otherwise, the constructed optional is empty. TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; /// Constructs the stored value with `u`. template >::value> * = nullptr> constexpr optional(U &&u) noexcept : m_value(std::addressof(u)) { static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); } template constexpr explicit optional(const optional &rhs) noexcept : optional(*rhs) {} /// No-op ~optional() = default; /// Assignment to empty. /// /// Destroys the current value if there is one. optional &operator=(nullopt_t) noexcept { m_value = nullptr; return *this; } /// Copy assignment. /// /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise /// resets the stored value in `*this`. optional &operator=(const optional &rhs) = default; /// Rebinds this optional to `u`. template >::value> * = nullptr> optional &operator=(U &&u) { static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); m_value = std::addressof(u); return *this; } /// Converting copy assignment operator. /// /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise /// resets the stored value in `*this`. template optional &operator=(const optional &rhs) noexcept { m_value = std::addressof(rhs.value()); return *this; } /// Rebinds this optional to `u`. template >::value> * = nullptr> optional &emplace(U &&u) noexcept { return *this = std::forward(u); } void swap(optional &rhs) noexcept { std::swap(m_value, rhs.m_value); } /// Returns a pointer to the stored value constexpr const T *operator->() const noexcept { return m_value; } TL_OPTIONAL_11_CONSTEXPR T *operator->() noexcept { return m_value; } /// Returns the stored value TL_OPTIONAL_11_CONSTEXPR T &operator*() noexcept { return *m_value; } constexpr const T &operator*() const noexcept { return *m_value; } constexpr bool has_value() const noexcept { return m_value != nullptr; } constexpr explicit operator bool() const noexcept { return m_value != nullptr; } /// Returns the contained value if there is one, otherwise throws bad_optional_access TL_OPTIONAL_11_CONSTEXPR T &value() { if (has_value()) return *m_value; throw bad_optional_access(); } TL_OPTIONAL_11_CONSTEXPR const T &value() const { if (has_value()) return *m_value; throw bad_optional_access(); } /// Returns the stored value if there is one, otherwise returns `u` template constexpr T value_or(U &&u) const & noexcept { static_assert(std::is_copy_constructible::value && std::is_convertible::value, "T must be copy constructible and convertible from U"); return has_value() ? **this : static_cast(std::forward(u)); } /// \group value_or template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && noexcept { static_assert(std::is_move_constructible::value && std::is_convertible::value, "T must be move constructible and convertible from U"); return has_value() ? **this : static_cast(std::forward(u)); } /// Destroys the stored value if one exists, making the optional empty void reset() noexcept { m_value = nullptr; } private: T *m_value; }; // namespace tl } // namespace tl namespace std { // TODO SFINAE template struct hash> { ::std::size_t operator()(const tl::optional &o) const { if (!o.has_value()) return 0; return std::hash>()(*o); } }; } // namespace std #endif mu-1.6.10/lib/utils/test-command-parser.cc000066400000000000000000000110551414367003600203300ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include #include #include #include #include "mu-command-parser.hh" #include "mu-utils.hh" using namespace Mu; static void test_param_getters() { const auto sexp { Sexp::make_parse(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")}; if (g_test_verbose()) std::cout << sexp << "\n"; g_assert_cmpint(Command::get_int_or(sexp.list(), ":bar"), ==, 123); assert_equal(Command::get_string_or(sexp.list(), ":bra", "bla"), "bla"); assert_equal(Command::get_string_or(sexp.list(), ":cuux"), "456"); g_assert_true(Command::get_bool_or(sexp.list(),":boo") == false); g_assert_true(Command::get_bool_or(sexp.list(),":bah") == true); } static bool call (const Command::CommandMap& cmap, const std::string& str) try { const auto sexp{Sexp::make_parse(str)}; invoke (cmap, sexp); return true; } catch (const Error& err) { g_warning ("%s", err.what()); return false; } static void test_command() { using namespace Command; allow_warnings(); CommandMap cmap; cmap.emplace("my-command", CommandInfo{ ArgMap{ {":param1", ArgInfo{Sexp::Type::String, true, "some string" }}, {":param2", ArgInfo{Sexp::Type::Number, false, "some integer"}}}, "My command,", {}}); g_assert_true(call(cmap, "(my-command :param1 \"hello\")")); g_assert_true(call(cmap, "(my-command :param1 \"hello\" :param2 123)")); g_assert_false(call(cmap, "(my-command :param1 \"hello\" :param2 123 :param3 xxx)")); } static void test_command2() { using namespace Command; allow_warnings(); CommandMap cmap; cmap.emplace("bla", CommandInfo{ ArgMap{ {":foo", ArgInfo{Sexp::Type::Number, false, "foo"}}, {":bar", ArgInfo{Sexp::Type::String, false, "bar"}}, },"yeah", [&](const auto& params){}}); g_assert_true (call(cmap, "(bla :foo nil)")); g_assert_false (call(cmap, "(bla :foo nil :bla nil)")); } static void test_command_fail() { using namespace Command; allow_warnings(); CommandMap cmap; cmap.emplace("my-command", CommandInfo{ ArgMap{ {":param1", ArgInfo{Sexp::Type::String, true, "some string" }}, {":param2", ArgInfo{Sexp::Type::Number, false, "some integer"}}}, "My command,", {}}); g_assert_false (call(cmap, "(my-command)")); g_assert_false (call(cmap, "(my-command2)")); g_assert_false(call(cmap, "(my-command :param1 123 :param2 123)")); g_assert_false(call(cmap, "(my-command :param1 \"hello\" :param2 \"123\")")); } static void black_hole() {} int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); g_test_add_func ("/utils/command-parser/param-getters", test_param_getters); g_test_add_func ("/utils/command-parser/command", test_command); g_test_add_func ("/utils/command-parser/command2", test_command2); g_test_add_func ("/utils/command-parser/command-fail", test_command_fail); g_log_set_handler (NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION), (GLogFunc)black_hole, NULL); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } mu-1.6.10/lib/utils/test-mu-str.c000066400000000000000000000073431414367003600165110ustar00rootroot00000000000000/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/ /* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include "mu-str.h" static void assert_cmplst (GSList *lst, const char *items[]) { int i; if (!lst) g_assert (!items); for (i = 0; lst; lst = g_slist_next(lst), ++i) g_assert_cmpstr ((char*)lst->data,==,items[i]); g_assert (items[i] == NULL); } static GSList* create_list (const char *items[]) { GSList *lst; lst = NULL; while (items && *items) { lst = g_slist_prepend (lst, g_strdup(*items)); ++items; } return g_slist_reverse (lst); } static void test_mu_str_from_list (void) { { const char *strs[] = {"aap", "noot", "mies", NULL}; GSList *lst = create_list (strs); gchar *str = mu_str_from_list (lst, ','); g_assert_cmpstr ("aap,noot,mies", ==, str); mu_str_free_list (lst); g_free (str); } { const char *strs[] = {"aap", "no,ot", "mies", NULL}; GSList *lst = create_list (strs); gchar *str = mu_str_from_list (lst, ','); g_assert_cmpstr ("aap,no,ot,mies", ==, str); mu_str_free_list (lst); g_free (str); } { const char *strs[] = {NULL}; GSList *lst = create_list (strs); gchar *str = mu_str_from_list (lst,'@'); g_assert_cmpstr (NULL, ==, str); mu_str_free_list (lst); g_free (str); } } static void test_mu_str_to_list (void) { { const char *items[]= {"foo", "bar ", "cuux", NULL}; GSList *lst = mu_str_to_list ("foo@bar @cuux",'@', FALSE); assert_cmplst (lst, items); mu_str_free_list (lst); } { GSList *lst = mu_str_to_list (NULL,'x',FALSE); g_assert (lst == NULL); mu_str_free_list (lst); } } static void test_mu_str_to_list_strip (void) { const char *items[]= {"foo", "bar", "cuux", NULL}; GSList *lst = mu_str_to_list ("foo@bar @cuux",'@', TRUE); assert_cmplst (lst, items); mu_str_free_list (lst); } static void test_mu_str_remove_ctrl_in_place (void) { unsigned u; struct { char *str; const char *exp; } strings [] = { { g_strdup(""), ""}, { g_strdup("hello, world!"), "hello, world!" }, { g_strdup("hello,\tworld!"), "hello, world!" }, { g_strdup("hello,\n\nworld!"), "hello, world!", }, { g_strdup("hello,\x1f\x1e\x1ew\nor\nld!"), "hello,w or ld!" }, { g_strdup("\x1ehello, world!\x1f"), "hello, world!" } }; for (u = 0; u != G_N_ELEMENTS(strings); ++u) { char *res; res = mu_str_remove_ctrl_in_place (strings[u].str); g_assert_cmpstr (res,==,strings[u].exp); g_free (strings[u].str); } } int main (int argc, char *argv[]) { setlocale (LC_ALL, ""); g_test_init (&argc, &argv, NULL); g_test_add_func ("/mu-str/mu-str-from-list", test_mu_str_from_list); g_test_add_func ("/mu-str/mu-str-to-list", test_mu_str_to_list); g_test_add_func ("/mu-str/mu-str-to-list-strip", test_mu_str_to_list_strip); g_test_add_func ("/mu-str/mu_str_remove_ctrl_in_place", test_mu_str_remove_ctrl_in_place); return g_test_run (); } mu-1.6.10/lib/utils/test-mu-util.c000066400000000000000000000123421414367003600166510ustar00rootroot00000000000000/* ** Copyright (C) 2008-2013 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include "mu-util.h" static void test_mu_util_dir_expand_00 (void) { #ifdef HAVE_WORDEXP_H gchar *got, *expected; got = mu_util_dir_expand ("~/IProbablyDoNotExist"); expected = g_strdup_printf ("%s%cIProbablyDoNotExist", getenv("HOME"), G_DIR_SEPARATOR); g_assert_cmpstr (got,==,expected); g_free (got); g_free (expected); #endif /*HAVE_WORDEXP_H*/ } static void test_mu_util_dir_expand_01 (void) { /* XXXX: the testcase does not work when using some dir * setups; (see issue #585), although the code should still * work. Turn of the test for now */ return; #ifdef HAVE_WORDEXP_H { gchar *got, *expected; got = mu_util_dir_expand ("~/Desktop"); expected = g_strdup_printf ("%s%cDesktop", getenv("HOME"), G_DIR_SEPARATOR); g_assert_cmpstr (got,==,expected); g_free (got); g_free (expected); } #endif /*HAVE_WORDEXP_H*/ } static void test_mu_util_guess_maildir_01 (void) { char *got; const char *expected; /* skip the test if there's no /tmp */ if (access ("/tmp", F_OK)) return; g_setenv ("MAILDIR", "/tmp", TRUE); got = mu_util_guess_maildir (); expected = "/tmp"; g_assert_cmpstr (got,==,expected); g_free (got); } static void test_mu_util_guess_maildir_02 (void) { char *got, *mdir; g_unsetenv ("MAILDIR"); mdir = g_strdup_printf ("%s%cMaildir", getenv("HOME"), G_DIR_SEPARATOR); got = mu_util_guess_maildir (); if (access (mdir, F_OK) == 0) g_assert_cmpstr (got, ==, mdir); else g_assert_cmpstr (got, == , NULL); g_free (got); g_free (mdir); } static void test_mu_util_check_dir_01 (void) { if (g_access ("/usr/bin", F_OK) == 0) { g_assert_cmpuint ( mu_util_check_dir ("/usr/bin", TRUE, FALSE) == TRUE, ==, g_access ("/usr/bin", R_OK) == 0); } } static void test_mu_util_check_dir_02 (void) { if (g_access ("/tmp", F_OK) == 0) { g_assert_cmpuint ( mu_util_check_dir ("/tmp", FALSE, TRUE) == TRUE, ==, g_access ("/tmp", W_OK) == 0); } } static void test_mu_util_check_dir_03 (void) { if (g_access (".", F_OK) == 0) { g_assert_cmpuint ( mu_util_check_dir (".", TRUE, TRUE) == TRUE, ==, g_access (".", W_OK | R_OK) == 0); } } static void test_mu_util_check_dir_04 (void) { /* not a dir, so it must be false */ g_assert_cmpuint ( mu_util_check_dir ("test-util.c", TRUE, TRUE), ==, FALSE); } static void test_mu_util_get_dtype_with_lstat (void) { g_assert_cmpuint ( mu_util_get_dtype (MU_TESTMAILDIR, TRUE), ==, DT_DIR); g_assert_cmpuint ( mu_util_get_dtype (MU_TESTMAILDIR2, TRUE), ==, DT_DIR); g_assert_cmpuint ( mu_util_get_dtype (MU_TESTMAILDIR2 "/Foo/cur/mail5", TRUE), ==, DT_REG); } static void test_mu_util_supports (void) { gboolean has_guile; gchar *path; has_guile = FALSE; #ifdef BUILD_GUILE has_guile = TRUE; #endif /*BUILD_GUILE*/ g_assert_cmpuint (mu_util_supports (MU_FEATURE_GUILE), == ,has_guile); path = g_find_program_in_path ("gnuplot"); g_free (path); g_assert_cmpuint (mu_util_supports (MU_FEATURE_GNUPLOT),==, path ? TRUE : FALSE); g_assert_cmpuint ( mu_util_supports (MU_FEATURE_GNUPLOT|MU_FEATURE_GUILE), ==, has_guile && path ? TRUE : FALSE); } static void test_mu_util_program_in_path (void) { g_assert_cmpuint (mu_util_program_in_path("ls"),==,TRUE); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); /* mu_util_dir_expand */ g_test_add_func ("/mu-util/mu-util-dir-expand-00", test_mu_util_dir_expand_00); g_test_add_func ("/mu-util/mu-util-dir-expand-01", test_mu_util_dir_expand_01); /* mu_util_guess_maildir */ g_test_add_func ("/mu-util/mu-util-guess-maildir-01", test_mu_util_guess_maildir_01); g_test_add_func ("/mu-util/mu-util-guess-maildir-02", test_mu_util_guess_maildir_02); /* mu_util_check_dir */ g_test_add_func ("/mu-util/mu-util-check-dir-01", test_mu_util_check_dir_01); g_test_add_func ("/mu-util/mu-util-check-dir-02", test_mu_util_check_dir_02); g_test_add_func ("/mu-util/mu-util-check-dir-03", test_mu_util_check_dir_03); g_test_add_func ("/mu-util/mu-util-check-dir-04", test_mu_util_check_dir_04); g_test_add_func ("/mu-util/mu-util-get-dtype-with-lstat", test_mu_util_get_dtype_with_lstat); g_test_add_func ("/mu-util/mu-util-supports", test_mu_util_supports); g_test_add_func ("/mu-util/mu-util-program-in-path", test_mu_util_program_in_path); return g_test_run (); } mu-1.6.10/lib/utils/test-option.cc000066400000000000000000000030641414367003600167310ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include "mu-utils.hh" #include "mu-option.hh" using namespace Mu; static Option get_opt_int (bool b) { if (b) return Some(123); else return Nothing; } static void test_option() { { const auto oi{get_opt_int(true)}; g_assert_true(!!oi); g_assert_cmpint(oi.value(),==,123); } { const auto oi{get_opt_int(false)}; g_assert_false(!!oi); g_assert_false(oi.has_value()); g_assert_cmpint(oi.value_or(456),==,456); } } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/option/option", test_option); return g_test_run (); } mu-1.6.10/lib/utils/test-sexp.cc000066400000000000000000000111521414367003600163750ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include #include #include #include #include "mu-command-parser.hh" #include "mu-utils.hh" using namespace Mu; static bool check_parse (const std::string& expr, const std::string& expected) { try { const auto parsed{to_string(Sexp::make_parse(expr))}; assert_equal(parsed, expected); return true; } catch (const Error& err) { g_warning ("caught exception parsing '%s': %s", expr.c_str(), err.what()); return false; } } static void test_parser() { check_parse(":foo-123", ":foo-123"); check_parse("foo", "foo"); check_parse(R"(12345)", "12345"); check_parse(R"(-12345)", "-12345"); check_parse(R"((123 bar "cuux"))", "(123 bar \"cuux\")"); check_parse(R"("foo\"bar\"cuux")", "\"foo\\\"bar\\\"cuux\""); check_parse(R"("foo bar")", "\"foo\nbar\""); } static void test_list() { const auto nstr{Sexp::make_string("foo")}; g_assert_true(nstr.value() == "foo"); g_assert_true(nstr.type() == Sexp::Type::String); assert_equal(nstr.to_sexp_string(), "\"foo\""); const auto nnum{Sexp::make_number(123)}; g_assert_true(nnum.value() == "123"); g_assert_true(nnum.type() == Sexp::Type::Number); assert_equal(nnum.to_sexp_string(), "123"); const auto nsym{Sexp::make_symbol("blub")}; g_assert_true(nsym.value() == "blub"); g_assert_true(nsym.type() == Sexp::Type::Symbol); assert_equal(nsym.to_sexp_string(), "blub"); Sexp::List list; list .add(Sexp::make_string("foo")) .add(Sexp::make_number(123)) .add(Sexp::make_symbol("blub")); const auto nlst = Sexp::make_list(std::move(list)); g_assert_true(nlst.list().size() == 3); g_assert_true(nlst.type() == Sexp::Type::List); g_assert_true(nlst.list().at(1).value() == "123"); assert_equal(nlst.to_sexp_string(),"(\"foo\" 123 blub)"); } static void test_prop_list() { Sexp::List l1; l1.add_prop(":foo", Sexp::make_string("bar")); Sexp s2{Sexp::make_list(std::move(l1))}; assert_equal(s2.to_sexp_string(), "(:foo \"bar\")"); Sexp::List l2; const std::string x{"bar"}; l2.add_prop(":foo", Sexp::make_string(x)); l2.add_prop(":bar", Sexp::make_number(77)); Sexp::List l3; l3.add_prop(":cuux", Sexp::make_list(std::move(l2))); Sexp s3{Sexp::make_list(std::move(l3))}; assert_equal(s3.to_sexp_string(), "(:cuux (:foo \"bar\" :bar 77))"); } static void test_props() { auto sexp2 = Sexp::make_list( Sexp::make_string("foo"), Sexp::make_number(123), Sexp::make_symbol("blub")); auto sexp = Sexp::make_prop_list( ":foo", Sexp::make_string("bär"), ":cuux", Sexp::make_number(123), ":flub", Sexp::make_symbol("fnord"), ":boo", std::move(sexp2)); assert_equal(sexp.to_sexp_string(), "(:foo \"b\303\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))"); } int main (int argc, char *argv[]) try { g_test_init (&argc, &argv, NULL); if (argc == 2) { std::cout << Sexp::make_parse(argv[1]) << '\n'; return 0; } g_test_add_func ("/utils/sexp/parser", test_parser); g_test_add_func ("/utils/sexp/list", test_list); g_test_add_func ("/utils/sexp/proplist", test_prop_list); g_test_add_func ("/utils/sexp/props", test_props); return g_test_run (); } catch (const std::runtime_error& re) { std::cerr << re.what() << "\n"; return 1; } mu-1.6.10/lib/utils/test-utils.cc000066400000000000000000000124751414367003600165670ustar00rootroot00000000000000/* ** Copyright (C) 2017 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #include #include #include #include #include #include "mu-utils.hh" using namespace Mu; struct Case { const std::string expr; bool is_first{}; const std::string expected; }; using CaseVec = std::vector; using ProcFunc = std::function; static void test_cases(const CaseVec& cases, ProcFunc proc) { for (const auto& casus : cases ) { const auto res = proc(casus.expr, casus.is_first); if (g_test_verbose()) { std::cout << "\n"; std::cout << casus.expr << ' ' << casus.is_first << std::endl; std::cout << "exp: '" << casus.expected << "'" << std::endl; std::cout << "got: '" << res << "'" << std::endl; } g_assert_true (casus.expected == res); } } static void test_date_basic () { g_setenv ("TZ", "Europe/Helsinki", TRUE); CaseVec cases = { { "2015-09-18T09:10:23", true, "1442556623" }, { "1972-12-14T09:10:23", true, "0093165023" }, { "1854-11-18T17:10:23", true, "0000000000" }, { "2000-02-31T09:10:23", true, "0951861599" }, { "2000-02-29T23:59:59", true, "0951861599" }, { "2016", true, "1451599200" }, { "2016", false, "1483221599" }, { "fnorb", true, "0000000000" }, { "fnorb", false, "9999999999" }, { "", false, "9999999999" }, { "", true, "0000000000" } }; test_cases (cases, [](auto s, auto f){ return date_to_time_t_string(s,f); }); } static void test_date_ymwdhMs (void) { struct { std::string expr; long diff; int tolerance; } tests[] = { { "3h", 3 * 60 * 60, 1 }, { "21d", 21 * 24 * 60 * 60, 3600 + 1 }, { "2w", 2 * 7 * 24 * 60 * 60, 3600 + 1 }, { "2y", 2 * 365 * 24 * 60 * 60, 24 * 3600 + 1 }, { "3m", 3 * 30 * 24 * 60 * 60, 3 * 24 * 3600 + 1 } }; for (auto i = 0; i != G_N_ELEMENTS(tests); ++i) { const auto diff = time(NULL) - strtol(Mu::date_to_time_t_string(tests[i].expr, true).c_str(), NULL, 10); if (g_test_verbose()) std::cerr << tests[i].expr << ' ' << diff << ' ' << tests[i].diff << std::endl; g_assert_true (tests[i].diff - diff <= tests[i].tolerance); } g_assert_true (strtol(Mu::date_to_time_t_string("-1y", true).c_str(), NULL, 10) == 0); } static void test_size () { CaseVec cases = { { "456", true, "0000000456" }, { "", false, "9999999999" }, { "", true, "0000000000" }, }; test_cases (cases, [](auto s, auto f){ return size_to_string(s,f); }); } static void test_flatten () { CaseVec cases = { { "Менделе́ев", true, "менделеев" }, { "", false, "" }, { "Ångström", true, "angstrom" }, }; test_cases (cases, [](auto s, auto f){ return utf8_flatten(s); }); } static void test_remove_ctrl () { CaseVec cases = { { "Foo\n\nbar", true, "Foo bar" }, { "", false, "" }, { " ", false, " " }, { "Hello World ", false, "Hello World " }, { "Ångström", false, "Ångström" }, }; test_cases (cases, [](auto s, auto f){ return remove_ctrl(s); }); } static void test_clean () { CaseVec cases = { { "\t a\t\nb ", true, "a b" }, { "", false, "" }, { "Ångström", true, "Ångström" }, }; test_cases (cases, [](auto s, auto f){ return utf8_clean(s); }); } static void test_format () { g_assert_true (format ("hello %s", "world") == "hello world"); g_assert_true (format ("hello %s, %u", "world", 123) == "hello world, 123"); } enum struct Bits { None = 0, Bit1 = 1 << 0, Bit2 = 1 << 1 }; MU_ENABLE_BITOPS(Bits); static void test_define_bitmap() { g_assert_cmpuint((guint)Bits::None,==,(guint)0); g_assert_cmpuint((guint)Bits::Bit1,==,(guint)1); g_assert_cmpuint((guint)Bits::Bit2,==,(guint)2); g_assert_cmpuint((guint)(Bits::Bit1|Bits::Bit2),==,(guint)3); g_assert_cmpuint((guint)(Bits::Bit1&Bits::Bit2),==,(guint)0); g_assert_cmpuint((guint)(Bits::Bit1&(~Bits::Bit2)),==,(guint)1); { Bits b{Bits::Bit1}; b|=Bits::Bit2; g_assert_cmpuint((guint)b,==,(guint)3); } { Bits b{Bits::Bit1}; b&=Bits::Bit1; g_assert_cmpuint((guint)b,==,(guint)1); } } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/utils/date-basic", test_date_basic); g_test_add_func ("/utils/date-ymwdhMs", test_date_ymwdhMs); g_test_add_func ("/utils/size", test_size); g_test_add_func ("/utils/flatten", test_flatten); g_test_add_func ("/utils/remove-ctrl", test_remove_ctrl); g_test_add_func ("/utils/clean", test_clean); g_test_add_func ("/utils/format", test_format); g_test_add_func ("/utils/define-bitmap", test_define_bitmap); return g_test_run (); } mu-1.6.10/m4/000077500000000000000000000000001414367003600125445ustar00rootroot00000000000000mu-1.6.10/m4/Makefile.am000066400000000000000000000030161414367003600146000ustar00rootroot00000000000000## Copyright (C) 2008-2020 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk EXTRA_DIST= \ ax_ac_append_to_file.m4 \ ax_ac_print_to_file.m4 \ ax_add_am_macro_static.m4 \ ax_am_macros_static.m4 \ ax_append_compile_flags.m4 \ ax_append_flag.m4 \ ax_append_link_flags.m4 \ ax_check_compile_flag.m4 \ ax_check_enable_debug.m4 \ ax_check_gnu_make.m4 \ ax_check_link_flag.m4 \ ax_code_coverage.m4 \ ax_compiler_flags.m4 \ ax_compiler_flags_cflags.m4 \ ax_compiler_flags_cxxflags.m4 \ ax_compiler_flags_gir.m4 \ ax_compiler_flags_ldflags.m4 \ ax_cxx_compile_stdcxx.m4 \ ax_cxx_compile_stdcxx_14.m4 \ ax_file_escapes.m4 \ ax_is_release.m4 \ ax_lib_readline.m4 \ ax_require_defined.m4 \ ax_valgrind_check.m4 \ guile.m4 \ lib-ld.m4 \ lib-link.m4 \ lib-prefix.m4 mu-1.6.10/m4/ax_ac_append_to_file.m4000066400000000000000000000016221414367003600171120ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_ac_append_to_file.html # =========================================================================== # # SYNOPSIS # # AX_AC_APPEND_TO_FILE([FILE],[DATA]) # # DESCRIPTION # # Appends the specified data to the specified Autoconf is run. If you want # to append to a file when configure is run use AX_APPEND_TO_FILE instead. # # LICENSE # # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_AC_APPEND_TO_FILE],[ AC_REQUIRE([AX_FILE_ESCAPES]) m4_esyscmd( AX_FILE_ESCAPES [ printf "%s" "$2" >> "$1" ]) ]) mu-1.6.10/m4/ax_ac_print_to_file.m4000066400000000000000000000016111414367003600167750ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_ac_print_to_file.html # =========================================================================== # # SYNOPSIS # # AX_AC_PRINT_TO_FILE([FILE],[DATA]) # # DESCRIPTION # # Writes the specified data to the specified file when Autoconf is run. If # you want to print to a file when configure is run use AX_PRINT_TO_FILE # instead. # # LICENSE # # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_AC_PRINT_TO_FILE],[ m4_esyscmd( AC_REQUIRE([AX_FILE_ESCAPES]) [ printf "%s" "$2" > "$1" ]) ]) mu-1.6.10/m4/ax_add_am_macro_static.m4000066400000000000000000000015251414367003600174360ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_add_am_macro_static.html # =========================================================================== # # SYNOPSIS # # AX_ADD_AM_MACRO_STATIC([RULE]) # # DESCRIPTION # # Adds the specified rule to $AMINCLUDE. # # LICENSE # # Copyright (c) 2009 Tom Howard # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_ADD_AM_MACRO_STATIC],[ AC_REQUIRE([AX_AM_MACROS_STATIC]) AX_AC_APPEND_TO_FILE(AMINCLUDE_STATIC,[$1]) ]) mu-1.6.10/m4/ax_am_macros_static.m4000066400000000000000000000021251414367003600170060ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_am_macros_static.html # =========================================================================== # # SYNOPSIS # # AX_AM_MACROS_STATIC # # DESCRIPTION # # Adds support for macros that create Automake rules. You must manually # add the following line # # include $(top_srcdir)/aminclude_static.am # # to your Makefile.am files. # # LICENSE # # Copyright (c) 2009 Tom Howard # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 AC_DEFUN([AMINCLUDE_STATIC],[aminclude_static.am]) AC_DEFUN([AX_AM_MACROS_STATIC], [ AX_AC_PRINT_TO_FILE(AMINCLUDE_STATIC,[ # ]AMINCLUDE_STATIC[ generated automatically by Autoconf # from AX_AM_MACROS_STATIC on ]m4_esyscmd([LC_ALL=C date])[ ]) ]) mu-1.6.10/m4/ax_append_compile_flags.m4000066400000000000000000000033451414367003600176360ustar00rootroot00000000000000# ============================================================================ # https://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html # ============================================================================ # # SYNOPSIS # # AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # For every FLAG1, FLAG2 it is checked whether the compiler works with the # flag. If it does, the flag is added FLAGS-VARIABLE # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. During the check the flag is always added to the # current language's flags. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: This macro depends on the AX_APPEND_FLAG and # AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with # AX_APPEND_LINK_FLAGS. # # LICENSE # # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 7 AC_DEFUN([AX_APPEND_COMPILE_FLAGS], [AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) for flag in $1; do AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3], [$4]) done ])dnl AX_APPEND_COMPILE_FLAGS mu-1.6.10/m4/ax_append_flag.m4000066400000000000000000000030201414367003600157310ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_append_flag.html # =========================================================================== # # SYNOPSIS # # AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) # # DESCRIPTION # # FLAG is appended to the FLAGS-VARIABLE shell variable, with a space # added in between. # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains # FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly # FLAG. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_APPEND_FLAG], [dnl AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) AS_VAR_SET_IF(FLAGS,[ AS_CASE([" AS_VAR_GET(FLAGS) "], [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], [ AS_VAR_APPEND(FLAGS,[" $1"]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) ], [ AS_VAR_SET(FLAGS,[$1]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_APPEND_FLAG mu-1.6.10/m4/ax_append_link_flags.m4000066400000000000000000000032571414367003600171450ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_append_link_flags.html # =========================================================================== # # SYNOPSIS # # AX_APPEND_LINK_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # For every FLAG1, FLAG2 it is checked whether the linker works with the # flag. If it does, the flag is added FLAGS-VARIABLE # # If FLAGS-VARIABLE is not specified, the linker's flags (LDFLAGS) is # used. During the check the flag is always added to the linker's flags. # # If EXTRA-FLAGS is defined, it is added to the linker's default flags # when the check is done. The check is thus made with the flags: "LDFLAGS # EXTRA-FLAGS FLAG". This can for example be used to force the linker to # issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: This macro depends on the AX_APPEND_FLAG and AX_CHECK_LINK_FLAG. # Please keep this macro in sync with AX_APPEND_COMPILE_FLAGS. # # LICENSE # # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 7 AC_DEFUN([AX_APPEND_LINK_FLAGS], [AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) for flag in $1; do AX_CHECK_LINK_FLAG([$flag], [AX_APPEND_FLAG([$flag], [m4_default([$2], [LDFLAGS])])], [], [$3], [$4]) done ])dnl AX_APPEND_LINK_FLAGS mu-1.6.10/m4/ax_check_compile_flag.m4000066400000000000000000000040701414367003600172550ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS mu-1.6.10/m4/ax_check_enable_debug.m4000066400000000000000000000113141414367003600172270ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_enable_debug.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_ENABLE_DEBUG([enable by default=yes/info/profile/no], [ENABLE DEBUG VARIABLES ...], [DISABLE DEBUG VARIABLES NDEBUG ...], [IS-RELEASE]) # # DESCRIPTION # # Check for the presence of an --enable-debug option to configure, with # the specified default value used when the option is not present. Return # the value in the variable $ax_enable_debug. # # Specifying 'yes' adds '-g -O0' to the compilation flags for all # languages. Specifying 'info' adds '-g' to the compilation flags. # Specifying 'profile' adds '-g -pg' to the compilation flags and '-pg' to # the linking flags. Otherwise, nothing is added. # # Define the variables listed in the second argument if debug is enabled, # defaulting to no variables. Defines the variables listed in the third # argument if debug is disabled, defaulting to NDEBUG. All lists of # variables should be space-separated. # # If debug is not enabled, ensure AC_PROG_* will not add debugging flags. # Should be invoked prior to any AC_PROG_* compiler checks. # # IS-RELEASE can be used to change the default to 'no' when making a # release. Set IS-RELEASE to 'yes' or 'no' as appropriate. By default, it # uses the value of $ax_is_release, so if you are using the AX_IS_RELEASE # macro, there is no need to pass this parameter. # # AX_IS_RELEASE([git-directory]) # AX_CHECK_ENABLE_DEBUG() # # LICENSE # # Copyright (c) 2011 Rhys Ulerich # Copyright (c) 2014, 2015 Philip Withnall # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. #serial 9 AC_DEFUN([AX_CHECK_ENABLE_DEBUG],[ AC_BEFORE([$0],[AC_PROG_CC])dnl AC_BEFORE([$0],[AC_PROG_CXX])dnl AC_BEFORE([$0],[AC_PROG_F77])dnl AC_BEFORE([$0],[AC_PROG_FC])dnl AC_MSG_CHECKING(whether to enable debugging) ax_enable_debug_default=m4_tolower(m4_normalize(ifelse([$1],,[no],[$1]))) ax_enable_debug_is_release=m4_tolower(m4_normalize(ifelse([$4],, [$ax_is_release], [$4]))) # If this is a release, override the default. AS_IF([test "$ax_enable_debug_is_release" = "yes"], [ax_enable_debug_default="no"]) m4_define(ax_enable_debug_vars,[m4_normalize(ifelse([$2],,,[$2]))]) m4_define(ax_disable_debug_vars,[m4_normalize(ifelse([$3],,[NDEBUG],[$3]))]) AC_ARG_ENABLE(debug, [AS_HELP_STRING([--enable-debug=]@<:@yes/info/profile/no@:>@,[compile with debugging])], [],enable_debug=$ax_enable_debug_default) # empty mean debug yes AS_IF([test "x$enable_debug" = "x"], [enable_debug="yes"]) # case of debug AS_CASE([$enable_debug], [yes],[ AC_MSG_RESULT(yes) CFLAGS="${CFLAGS} -g -O0" CXXFLAGS="${CXXFLAGS} -g -O0" FFLAGS="${FFLAGS} -g -O0" FCFLAGS="${FCFLAGS} -g -O0" OBJCFLAGS="${OBJCFLAGS} -g -O0" ], [info],[ AC_MSG_RESULT(info) CFLAGS="${CFLAGS} -g" CXXFLAGS="${CXXFLAGS} -g" FFLAGS="${FFLAGS} -g" FCFLAGS="${FCFLAGS} -g" OBJCFLAGS="${OBJCFLAGS} -g" ], [profile],[ AC_MSG_RESULT(profile) CFLAGS="${CFLAGS} -g -pg" CXXFLAGS="${CXXFLAGS} -g -pg" FFLAGS="${FFLAGS} -g -pg" FCFLAGS="${FCFLAGS} -g -pg" OBJCFLAGS="${OBJCFLAGS} -g -pg" LDFLAGS="${LDFLAGS} -pg" ], [ AC_MSG_RESULT(no) dnl Ensure AC_PROG_CC/CXX/F77/FC/OBJC will not enable debug flags dnl by setting any unset environment flag variables AS_IF([test "x${CFLAGS+set}" != "xset"], [CFLAGS=""]) AS_IF([test "x${CXXFLAGS+set}" != "xset"], [CXXFLAGS=""]) AS_IF([test "x${FFLAGS+set}" != "xset"], [FFLAGS=""]) AS_IF([test "x${FCFLAGS+set}" != "xset"], [FCFLAGS=""]) AS_IF([test "x${OBJCFLAGS+set}" != "xset"], [OBJCFLAGS=""]) ]) dnl Define various variables if debugging is disabled. dnl assert.h is a NOP if NDEBUG is defined, so define it by default. AS_IF([test "x$enable_debug" = "xyes"], [m4_map_args_w(ax_enable_debug_vars, [AC_DEFINE(], [,[1],[Define if debugging is enabled])])], [m4_map_args_w(ax_disable_debug_vars, [AC_DEFINE(], [,[1],[Define if debugging is disabled])])]) ax_enable_debug=$enable_debug ]) mu-1.6.10/m4/ax_check_gnu_make.m4000066400000000000000000000077261414367003600164350ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_GNU_MAKE([run-if-true],[run-if-false]) # # DESCRIPTION # # This macro searches for a GNU version of make. If a match is found: # # * The makefile variable `ifGNUmake' is set to the empty string, otherwise # it is set to "#". This is useful for including a special features in a # Makefile, which cannot be handled by other versions of make. # * The makefile variable `ifnGNUmake' is set to #, otherwise # it is set to the empty string. This is useful for including a special # features in a Makefile, which can be handled # by other versions of make or to specify else like clause. # * The variable `_cv_gnu_make_command` is set to the command to invoke # GNU make if it exists, the empty string otherwise. # * The variable `ax_cv_gnu_make_command` is set to the command to invoke # GNU make by copying `_cv_gnu_make_command`, otherwise it is unset. # * If GNU Make is found, its version is extracted from the output of # `make --version` as the last field of a record of space-separated # columns and saved into the variable `ax_check_gnu_make_version`. # * Additionally if GNU Make is found, run shell code run-if-true # else run shell code run-if-false. # # Here is an example of its use: # # Makefile.in might contain: # # # A failsafe way of putting a dependency rule into a makefile # $(DEPEND): # $(CC) -MM $(srcdir)/*.c > $(DEPEND) # # @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND))) # @ifGNUmake@ include $(DEPEND) # @ifGNUmake@ else # fallback code # @ifGNUmake@ endif # # Then configure.in would normally contain: # # AX_CHECK_GNU_MAKE() # AC_OUTPUT(Makefile) # # Then perhaps to cause gnu make to override any other make, we could do # something like this (note that GNU make always looks for GNUmakefile # first): # # if ! test x$_cv_gnu_make_command = x ; then # mv Makefile GNUmakefile # echo .DEFAULT: > Makefile ; # echo \ $_cv_gnu_make_command \$@ >> Makefile; # fi # # Then, if any (well almost any) other make is called, and GNU make also # exists, then the other make wraps the GNU make. # # LICENSE # # Copyright (c) 2008 John Darrington # Copyright (c) 2015 Enrico M. Crisostomo # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 AC_DEFUN([AX_CHECK_GNU_MAKE],dnl [AC_PROG_AWK AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl _cv_gnu_make_command="" ; dnl Search all the common names for GNU make for a in "$MAKE" make gmake gnumake ; do if test -z "$a" ; then continue ; fi ; if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then _cv_gnu_make_command=$a ; AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make") ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }') break ; fi done ;]) dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])]) AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifnGNUmake], [""])], [AS_VAR_SET([ifGNUmake], ["#"])]) AS_VAR_IF([_cv_gnu_make_command], [""], [AS_UNSET(ax_cv_gnu_make_command)], [AS_VAR_SET([ax_cv_gnu_make_command], [${_cv_gnu_make_command}])]) AS_VAR_IF([_cv_gnu_make_command], [""],[$2],[$1]) AC_SUBST([ifGNUmake]) AC_SUBST([ifnGNUmake]) ]) mu-1.6.10/m4/ax_check_link_flag.m4000066400000000000000000000036441414367003600165700ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the linker or gives an error. # (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the linker's default flags # when the check is done. The check is thus made with the flags: "LDFLAGS # EXTRA-FLAGS FLAG". This can for example be used to force the linker to # issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_LINK_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_CHECK_LINK_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ ax_check_save_flags=$LDFLAGS LDFLAGS="$LDFLAGS $4 $1" AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) LDFLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_LINK_FLAGS mu-1.6.10/m4/ax_code_coverage.m4000066400000000000000000000276151414367003600162760ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html # =========================================================================== # # SYNOPSIS # # AX_CODE_COVERAGE() # # DESCRIPTION # # Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS, # CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included # in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every # build target (program or library) which should be built with code # coverage support. Also add rules using AX_ADD_AM_MACRO_STATIC; and # $enable_code_coverage which can be used in subsequent configure output. # CODE_COVERAGE_ENABLED is defined and substituted, and corresponds to the # value of the --enable-code-coverage option, which defaults to being # disabled. # # Test also for gcov program and create GCOV variable that could be # substituted. # # Note that all optimization flags in CFLAGS must be disabled when code # coverage is enabled. # # Usage example: # # configure.ac: # # AX_CODE_COVERAGE # # Makefile.am: # # include $(top_srcdir)/aminclude_static.am # # my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ... # my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ... # my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... # my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ... # # clean-local: code-coverage-clean # distclean-local: code-coverage-dist-clean # # This results in a "check-code-coverage" rule being added to any # Makefile.am which do "include $(top_srcdir)/aminclude_static.am" # (assuming the module has been configured with --enable-code-coverage). # Running `make check-code-coverage` in that directory will run the # module's test suite (`make check`) and build a code coverage report # detailing the code which was touched, then print the URI for the report. # # This code was derived from Makefile.decl in GLib, originally licensed # under LGPLv2.1+. # # LICENSE # # Copyright (c) 2012, 2016 Philip Withnall # Copyright (c) 2012 Xan Lopez # Copyright (c) 2012 Christian Persch # Copyright (c) 2012 Paolo Borelli # Copyright (c) 2012 Dan Winship # Copyright (c) 2015,2018 Bastien ROUCARIES # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or (at # your option) any later version. # # This library is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . #serial 33 m4_define(_AX_CODE_COVERAGE_RULES,[ AX_ADD_AM_MACRO_STATIC([ # Code coverage # # Optional: # - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. # Multiple directories may be specified, separated by whitespace. # (Default: \$(top_builddir)) # - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated # by lcov for code coverage. (Default: # \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info) # - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage # reports to be created. (Default: # \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage) # - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, # set to 0 to disable it and leave empty to stay with the default. # (Default: empty) # - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov # instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov # instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) # - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov # - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the # collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) # - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov # instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) # - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering # lcov instance. (Default: empty) # - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov # instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) # - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the # genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml # instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) # - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore # # The generated report will be titled using the \$(PACKAGE_NAME) and # \$(PACKAGE_VERSION). In order to add the current git hash to the title, # use the git-version-gen script, available online. # Optional variables # run only on top dir if CODE_COVERAGE_ENABLED ifeq (\$(abs_builddir), \$(abs_top_builddir)) CODE_COVERAGE_DIRECTORY ?= \$(top_builddir) CODE_COVERAGE_OUTPUT_FILE ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info CODE_COVERAGE_OUTPUT_DIRECTORY ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage CODE_COVERAGE_BRANCH_COVERAGE ?= CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= \$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc lcov_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_LCOV_SHOPTS ?= \$(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool \"\$(GCOV)\" CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= \$(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) CODE_COVERAGE_LCOV_OPTIONS ?= \$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= CODE_COVERAGE_LCOV_RMOPTS ?= \$(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ \$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc genhtml_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_GENHTML_OPTIONS ?= \$(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) CODE_COVERAGE_IGNORE_PATTERN ?= GITIGNOREFILES = \$(GITIGNOREFILES) \$(CODE_COVERAGE_OUTPUT_FILE) \$(CODE_COVERAGE_OUTPUT_DIRECTORY) code_coverage_v_lcov_cap = \$(code_coverage_v_lcov_cap_\$(V)) code_coverage_v_lcov_cap_ = \$(code_coverage_v_lcov_cap_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_cap_0 = @echo \" LCOV --capture\" \$(CODE_COVERAGE_OUTPUT_FILE); code_coverage_v_lcov_ign = \$(code_coverage_v_lcov_ign_\$(V)) code_coverage_v_lcov_ign_ = \$(code_coverage_v_lcov_ign_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_ign_0 = @echo \" LCOV --remove /tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN); code_coverage_v_genhtml = \$(code_coverage_v_genhtml_\$(V)) code_coverage_v_genhtml_ = \$(code_coverage_v_genhtml_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_genhtml_0 = @echo \" GEN \" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\"; code_coverage_quiet = \$(code_coverage_quiet_\$(V)) code_coverage_quiet_ = \$(code_coverage_quiet_\$(AM_DEFAULT_VERBOSITY)) code_coverage_quiet_0 = --quiet # sanitizes the test-name: replaces with underscores: dashes and dots code_coverage_sanitize = \$(subst -,_,\$(subst .,_,\$(1))) # Use recursive makes in order to ignore errors during check check-code-coverage: -\$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) -k check \$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) code-coverage-capture # Capture code coverage data code-coverage-capture: code-coverage-capture-hook \$(code_coverage_v_lcov_cap)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --capture --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" --test-name \"\$(call code_coverage_sanitize,\$(PACKAGE_NAME)-\$(PACKAGE_VERSION))\" --no-checksum --compat-libtool \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_OPTIONS) \$(code_coverage_v_lcov_ign)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --remove \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"/tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN) --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_RMOPTS) -@rm -f \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \$(code_coverage_v_genhtml)LANG=C \$(GENHTML) \$(code_coverage_quiet) \$(addprefix --prefix ,\$(CODE_COVERAGE_DIRECTORY)) --output-directory \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\" --title \"\$(PACKAGE_NAME)-\$(PACKAGE_VERSION) Code Coverage\" --legend --show-details \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_GENHTML_OPTIONS) @echo \"file://\$(abs_builddir)/\$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html\" code-coverage-clean: -\$(LCOV) --directory \$(top_builddir) -z -rm -rf \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\" -find . \\( -name \"*.gcda\" -o -name \"*.gcno\" -o -name \"*.gcov\" \\) -delete code-coverage-dist-clean: A][M_DISTCHECK_CONFIGURE_FLAGS := \$(A][M_DISTCHECK_CONFIGURE_FLAGS) --disable-code-coverage else # ifneq (\$(abs_builddir), \$(abs_top_builddir)) check-code-coverage: code-coverage-capture: code-coverage-capture-hook code-coverage-clean: code-coverage-dist-clean: endif # ifeq (\$(abs_builddir), \$(abs_top_builddir)) else #! CODE_COVERAGE_ENABLED # Use recursive makes in order to ignore errors during check check-code-coverage: @echo \"Need to reconfigure with --enable-code-coverage\" # Capture code coverage data code-coverage-capture: code-coverage-capture-hook @echo \"Need to reconfigure with --enable-code-coverage\" code-coverage-clean: code-coverage-dist-clean: endif #CODE_COVERAGE_ENABLED # Hook rule executed before code-coverage-capture, overridable by the user code-coverage-capture-hook: .PHONY: check-code-coverage code-coverage-capture code-coverage-dist-clean code-coverage-clean code-coverage-capture-hook ]) ]) AC_DEFUN([_AX_CODE_COVERAGE_ENABLED],[ AX_CHECK_GNU_MAKE([],[AC_MSG_ERROR([not using GNU make that is needed for coverage])]) AC_REQUIRE([AX_ADD_AM_MACRO_STATIC]) # check for gcov AC_CHECK_TOOL([GCOV], [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], [:]) AS_IF([test "X$GCOV" = "X:"], [AC_MSG_ERROR([gcov is needed to do coverage])]) AC_SUBST([GCOV]) dnl Check if gcc is being used AS_IF([ test "$GCC" = "no" ], [ AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) ]) AC_CHECK_PROG([LCOV], [lcov], [lcov]) AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) AS_IF([ test x"$LCOV" = x ], [ AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed]) ]) AS_IF([ test x"$GENHTML" = x ], [ AC_MSG_ERROR([Could not find genhtml from the lcov package]) ]) dnl Build the code coverage flags dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility CODE_COVERAGE_CPPFLAGS="-DNDEBUG" CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" CODE_COVERAGE_LIBS="-lgcov" AC_SUBST([CODE_COVERAGE_CPPFLAGS]) AC_SUBST([CODE_COVERAGE_CFLAGS]) AC_SUBST([CODE_COVERAGE_CXXFLAGS]) AC_SUBST([CODE_COVERAGE_LIBS]) ]) AC_DEFUN([AX_CODE_COVERAGE],[ dnl Check for --enable-code-coverage # allow to override gcov location AC_ARG_WITH([gcov], [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) AC_MSG_CHECKING([whether to build with code coverage support]) AC_ARG_ENABLE([code-coverage], AS_HELP_STRING([--enable-code-coverage], [Whether to enable code coverage support]),, enable_code_coverage=no) AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test "x$enable_code_coverage" = xyes]) AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) AC_MSG_RESULT($enable_code_coverage) AS_IF([ test "x$enable_code_coverage" = xyes ], [ _AX_CODE_COVERAGE_ENABLED ]) _AX_CODE_COVERAGE_RULES ]) mu-1.6.10/m4/ax_compiler_flags.m4000066400000000000000000000174511414367003600164740ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags.html # =========================================================================== # # SYNOPSIS # # AX_COMPILER_FLAGS([CFLAGS-VARIABLE], [LDFLAGS-VARIABLE], [IS-RELEASE], [EXTRA-BASE-CFLAGS], [EXTRA-YES-CFLAGS], [UNUSED], [UNUSED], [UNUSED], [EXTRA-BASE-LDFLAGS], [EXTRA-YES-LDFLAGS], [UNUSED], [UNUSED], [UNUSED]) # # DESCRIPTION # # Check for the presence of an --enable-compile-warnings option to # configure, defaulting to "error" in normal operation, or "yes" if # IS-RELEASE is equal to "yes". Return the value in the variable # $ax_enable_compile_warnings. # # Depending on the value of --enable-compile-warnings, different compiler # warnings are checked to see if they work with the current compiler and, # if so, are appended to CFLAGS-VARIABLE and LDFLAGS-VARIABLE. This # allows a consistent set of baseline compiler warnings to be used across # a code base, irrespective of any warnings enabled locally by individual # developers. By standardising the warnings used by all developers of a # project, the project can commit to a zero-warnings policy, using -Werror # to prevent compilation if new warnings are introduced. This makes # catching bugs which are flagged by warnings a lot easier. # # By providing a consistent --enable-compile-warnings argument across all # projects using this macro, continuous integration systems can easily be # configured the same for all projects. Automated systems or build # systems aimed at beginners may want to pass the --disable-Werror # argument to unconditionally prevent warnings being fatal. # # --enable-compile-warnings can take the values: # # * no: Base compiler warnings only; not even -Wall. # * yes: The above, plus a broad range of useful warnings. # * error: The above, plus -Werror so that all warnings are fatal. # Use --disable-Werror to override this and disable fatal # warnings. # # The set of base and enabled flags can be augmented using the # EXTRA-*-CFLAGS and EXTRA-*-LDFLAGS variables, which are tested and # appended to the output variable if --enable-compile-warnings is not # "no". Flags should not be disabled using these arguments, as the entire # point of AX_COMPILER_FLAGS is to enforce a consistent set of useful # compiler warnings on code, using warnings which have been chosen for low # false positive rates. If a compiler emits false positives for a # warning, a #pragma should be used in the code to disable the warning # locally. See: # # https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Diagnostic-Pragmas.html#Diagnostic-Pragmas # # The EXTRA-* variables should only be used to supply extra warning flags, # and not general purpose compiler flags, as they are controlled by # configure options such as --disable-Werror. # # IS-RELEASE can be used to disable -Werror when making a release, which # is useful for those hairy moments when you just want to get the release # done as quickly as possible. Set it to "yes" to disable -Werror. By # default, it uses the value of $ax_is_release, so if you are using the # AX_IS_RELEASE macro, there is no need to pass this parameter. For # example: # # AX_IS_RELEASE([git-directory]) # AX_COMPILER_FLAGS() # # CFLAGS-VARIABLE defaults to WARN_CFLAGS, and LDFLAGS-VARIABLE defaults # to WARN_LDFLAGS. Both variables are AC_SUBST-ed by this macro, but must # be manually added to the CFLAGS and LDFLAGS variables for each target in # the code base. # # If C++ language support is enabled with AC_PROG_CXX, which must occur # before this macro in configure.ac, warning flags for the C++ compiler # are AC_SUBST-ed as WARN_CXXFLAGS, and must be manually added to the # CXXFLAGS variables for each target in the code base. EXTRA-*-CFLAGS can # be used to augment the base and enabled flags. # # Warning flags for g-ir-scanner (from GObject Introspection) are # AC_SUBST-ed as WARN_SCANNERFLAGS. This variable must be manually added # to the SCANNERFLAGS variable for each GIR target in the code base. If # extra g-ir-scanner flags need to be enabled, the AX_COMPILER_FLAGS_GIR # macro must be invoked manually. # # AX_COMPILER_FLAGS may add support for other tools in future, in addition # to the compiler and linker. No extra EXTRA-* variables will be added # for those tools, and all extra support will still use the single # --enable-compile-warnings configure option. For finer grained control # over the flags for individual tools, use AX_COMPILER_FLAGS_CFLAGS, # AX_COMPILER_FLAGS_LDFLAGS and AX_COMPILER_FLAGS_* for new tools. # # The UNUSED variables date from a previous version of this macro, and are # automatically appended to the preceding non-UNUSED variable. They should # be left empty in new uses of the macro. # # LICENSE # # Copyright (c) 2014, 2015 Philip Withnall # Copyright (c) 2015 David King # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 14 # _AX_COMPILER_FLAGS_LANG([LANGNAME]) m4_defun([_AX_COMPILER_FLAGS_LANG], [m4_ifdef([_AX_COMPILER_FLAGS_LANG_]$1[_enabled], [], [m4_define([_AX_COMPILER_FLAGS_LANG_]$1[_enabled], [])dnl AX_REQUIRE_DEFINED([AX_COMPILER_FLAGS_]$1[FLAGS])])dnl ]) AC_DEFUN([AX_COMPILER_FLAGS],[ # C support is enabled by default. _AX_COMPILER_FLAGS_LANG([C]) # Only enable C++ support if AC_PROG_CXX is called. The redefinition of # AC_PROG_CXX is so that a fatal error is emitted if this macro is called # before AC_PROG_CXX, which would otherwise cause no C++ warnings to be # checked. AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AX_COMPILER_FLAGS_LANG([CXX])], [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[_AX_COMPILER_FLAGS_LANG([CXX])])]) AX_REQUIRE_DEFINED([AX_COMPILER_FLAGS_LDFLAGS]) # Default value for IS-RELEASE is $ax_is_release ax_compiler_flags_is_release=m4_tolower(m4_normalize(ifelse([$3],, [$ax_is_release], [$3]))) AC_ARG_ENABLE([compile-warnings], AS_HELP_STRING([--enable-compile-warnings=@<:@no/yes/error@:>@], [Enable compiler warnings and errors]),, [AS_IF([test "$ax_compiler_flags_is_release" = "yes"], [enable_compile_warnings="yes"], [enable_compile_warnings="error"])]) AC_ARG_ENABLE([Werror], AS_HELP_STRING([--disable-Werror], [Unconditionally make all compiler warnings non-fatal]),, [enable_Werror=maybe]) # Return the user's chosen warning level AS_IF([test "$enable_Werror" = "no" -a \ "$enable_compile_warnings" = "error"],[ enable_compile_warnings="yes" ]) ax_enable_compile_warnings=$enable_compile_warnings AX_COMPILER_FLAGS_CFLAGS([$1],[$ax_compiler_flags_is_release], [$4],[$5 $6 $7 $8]) m4_ifdef([_AX_COMPILER_FLAGS_LANG_CXX_enabled], [AX_COMPILER_FLAGS_CXXFLAGS([WARN_CXXFLAGS], [$ax_compiler_flags_is_release], [$4],[$5 $6 $7 $8])]) AX_COMPILER_FLAGS_LDFLAGS([$2],[$ax_compiler_flags_is_release], [$9],[$10 $11 $12 $13]) AX_COMPILER_FLAGS_GIR([WARN_SCANNERFLAGS],[$ax_compiler_flags_is_release]) ])dnl AX_COMPILER_FLAGS mu-1.6.10/m4/ax_compiler_flags_cflags.m4000066400000000000000000000132311414367003600200030ustar00rootroot00000000000000# ============================================================================= # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_cflags.html # ============================================================================= # # SYNOPSIS # # AX_COMPILER_FLAGS_CFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS]) # # DESCRIPTION # # Add warning flags for the C compiler to VARIABLE, which defaults to # WARN_CFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be # manually added to the CFLAGS variable for each target in the code base. # # This macro depends on the environment set up by AX_COMPILER_FLAGS. # Specifically, it uses the value of $ax_enable_compile_warnings to decide # which flags to enable. # # LICENSE # # Copyright (c) 2014, 2015 Philip Withnall # Copyright (c) 2017, 2018 Reini Urban # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 17 AC_DEFUN([AX_COMPILER_FLAGS_CFLAGS],[ AC_REQUIRE([AC_PROG_SED]) AX_REQUIRE_DEFINED([AX_APPEND_COMPILE_FLAGS]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) # Variable names m4_define([ax_warn_cflags_variable], [m4_normalize(ifelse([$1],,[WARN_CFLAGS],[$1]))]) AC_LANG_PUSH([C]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ [#ifndef __cplusplus #error "no C++" #endif]])], [ax_compiler_cxx=yes;], [ax_compiler_cxx=no;]) # Always pass -Werror=unknown-warning-option to get Clang to fail on bad # flags, otherwise they are always appended to the warn_cflags variable, and # Clang warns on them for every compilation unit. # If this is passed to GCC, it will explode, so the flag must be enabled # conditionally. AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[ ax_compiler_flags_test="-Werror=unknown-warning-option" ],[ ax_compiler_flags_test="" ]) # Check that -Wno-suggest-attribute=format is supported AX_CHECK_COMPILE_FLAG([-Wno-suggest-attribute=format],[ ax_compiler_no_suggest_attribute_flags="-Wno-suggest-attribute=format" ],[ ax_compiler_no_suggest_attribute_flags="" ]) # Base flags AX_APPEND_COMPILE_FLAGS([ dnl -fno-strict-aliasing dnl $3 dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) AS_IF([test "$ax_enable_compile_warnings" != "no"],[ if test "$ax_compiler_cxx" = "no" ; then # C-only flags. Warn in C++ AX_APPEND_COMPILE_FLAGS([ dnl -Wnested-externs dnl -Wmissing-prototypes dnl -Wstrict-prototypes dnl -Wdeclaration-after-statement dnl -Wimplicit-function-declaration dnl -Wold-style-definition dnl -Wjump-misses-init dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) fi # "yes" flags AX_APPEND_COMPILE_FLAGS([ dnl -Wall dnl -Wextra dnl -Wundef dnl -Wwrite-strings dnl -Wpointer-arith dnl -Wmissing-declarations dnl -Wredundant-decls dnl -Wno-unused-parameter dnl -Wno-missing-field-initializers dnl -Wformat=2 dnl -Wcast-align dnl -Wformat-nonliteral dnl -Wformat-security dnl -Wsign-compare dnl -Wstrict-aliasing dnl -Wshadow dnl -Winline dnl -Wpacked dnl -Wmissing-format-attribute dnl -Wmissing-noreturn dnl -Winit-self dnl -Wredundant-decls dnl -Wmissing-include-dirs dnl -Wunused-but-set-variable dnl -Warray-bounds dnl -Wreturn-type dnl -Wswitch-enum dnl -Wswitch-default dnl -Wduplicated-cond dnl -Wduplicated-branches dnl -Wlogical-op dnl -Wrestrict dnl -Wnull-dereference dnl -Wdouble-promotion dnl $4 dnl $5 dnl $6 dnl $7 dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) ]) AS_IF([test "$ax_enable_compile_warnings" = "error"],[ # "error" flags; -Werror has to be appended unconditionally because # it's not possible to test for # # suggest-attribute=format is disabled because it gives too many false # positives AX_APPEND_FLAG([-Werror],ax_warn_cflags_variable) AX_APPEND_COMPILE_FLAGS([ dnl [$ax_compiler_no_suggest_attribute_flags] dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) ]) # In the flags below, when disabling specific flags, always add *both* # -Wno-foo and -Wno-error=foo. This fixes the situation where (for example) # we enable -Werror, disable a flag, and a build bot passes CFLAGS=-Wall, # which effectively turns that flag back on again as an error. for flag in $ax_warn_cflags_variable; do AS_CASE([$flag], [-Wno-*=*],[], [-Wno-*],[ AX_APPEND_COMPILE_FLAGS([-Wno-error=$(AS_ECHO([$flag]) | $SED 's/^-Wno-//')], ax_warn_cflags_variable, [$ax_compiler_flags_test]) ]) done AC_LANG_POP([C]) # Substitute the variables AC_SUBST(ax_warn_cflags_variable) ])dnl AX_COMPILER_FLAGS mu-1.6.10/m4/ax_compiler_flags_cxxflags.m4000066400000000000000000000115051414367003600203650ustar00rootroot00000000000000# =============================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_cxxflags.html # =============================================================================== # # SYNOPSIS # # AX_COMPILER_FLAGS_CXXFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS]) # # DESCRIPTION # # Add warning flags for the C++ compiler to VARIABLE, which defaults to # WARN_CXXFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be # manually added to the CXXFLAGS variable for each target in the code # base. # # This macro depends on the environment set up by AX_COMPILER_FLAGS. # Specifically, it uses the value of $ax_enable_compile_warnings to decide # which flags to enable. # # LICENSE # # Copyright (c) 2015 David King # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_COMPILER_FLAGS_CXXFLAGS],[ AC_REQUIRE([AC_PROG_SED]) AX_REQUIRE_DEFINED([AX_APPEND_COMPILE_FLAGS]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) # Variable names m4_define([ax_warn_cxxflags_variable], [m4_normalize(ifelse([$1],,[WARN_CXXFLAGS],[$1]))]) AC_LANG_PUSH([C++]) # Always pass -Werror=unknown-warning-option to get Clang to fail on bad # flags, otherwise they are always appended to the warn_cxxflags variable, # and Clang warns on them for every compilation unit. # If this is passed to GCC, it will explode, so the flag must be enabled # conditionally. AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[ ax_compiler_flags_test="-Werror=unknown-warning-option" ],[ ax_compiler_flags_test="" ]) # Check that -Wno-suggest-attribute=format is supported AX_CHECK_COMPILE_FLAG([-Wno-suggest-attribute=format],[ ax_compiler_no_suggest_attribute_flags="-Wno-suggest-attribute=format" ],[ ax_compiler_no_suggest_attribute_flags="" ]) # Base flags AX_APPEND_COMPILE_FLAGS([ dnl -fno-strict-aliasing dnl $3 dnl ],ax_warn_cxxflags_variable,[$ax_compiler_flags_test]) AS_IF([test "$ax_enable_compile_warnings" != "no"],[ # "yes" flags AX_APPEND_COMPILE_FLAGS([ dnl -Wall dnl -Wextra dnl -Wundef dnl -Wwrite-strings dnl -Wpointer-arith dnl -Wmissing-declarations dnl -Wredundant-decls dnl -Wno-unused-parameter dnl -Wno-missing-field-initializers dnl -Wformat=2 dnl -Wcast-align dnl -Wformat-nonliteral dnl -Wformat-security dnl -Wsign-compare dnl -Wstrict-aliasing dnl -Wshadow dnl -Winline dnl -Wpacked dnl -Wmissing-format-attribute dnl -Wmissing-noreturn dnl -Winit-self dnl -Wredundant-decls dnl -Wmissing-include-dirs dnl -Wunused-but-set-variable dnl -Warray-bounds dnl -Wreturn-type dnl -Wno-overloaded-virtual dnl -Wswitch-enum dnl -Wswitch-default dnl $4 dnl $5 dnl $6 dnl $7 dnl ],ax_warn_cxxflags_variable,[$ax_compiler_flags_test]) ]) AS_IF([test "$ax_enable_compile_warnings" = "error"],[ # "error" flags; -Werror has to be appended unconditionally because # it's not possible to test for # # suggest-attribute=format is disabled because it gives too many false # positives AX_APPEND_FLAG([-Werror],ax_warn_cxxflags_variable) AX_APPEND_COMPILE_FLAGS([ dnl [$ax_compiler_no_suggest_attribute_flags] dnl ],ax_warn_cxxflags_variable,[$ax_compiler_flags_test]) ]) # In the flags below, when disabling specific flags, always add *both* # -Wno-foo and -Wno-error=foo. This fixes the situation where (for example) # we enable -Werror, disable a flag, and a build bot passes CXXFLAGS=-Wall, # which effectively turns that flag back on again as an error. for flag in $ax_warn_cxxflags_variable; do AS_CASE([$flag], [-Wno-*=*],[], [-Wno-*],[ AX_APPEND_COMPILE_FLAGS([-Wno-error=$(AS_ECHO([$flag]) | $SED 's/^-Wno-//')], ax_warn_cxxflags_variable, [$ax_compiler_flags_test]) ]) done AC_LANG_POP([C++]) # Substitute the variables AC_SUBST(ax_warn_cxxflags_variable) ])dnl AX_COMPILER_FLAGS_CXXFLAGS mu-1.6.10/m4/ax_compiler_flags_gir.m4000066400000000000000000000036631414367003600173350ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_gir.html # =========================================================================== # # SYNOPSIS # # AX_COMPILER_FLAGS_GIR([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS]) # # DESCRIPTION # # Add warning flags for the g-ir-scanner (from GObject Introspection) to # VARIABLE, which defaults to WARN_SCANNERFLAGS. VARIABLE is AC_SUBST-ed # by this macro, but must be manually added to the SCANNERFLAGS variable # for each GIR target in the code base. # # This macro depends on the environment set up by AX_COMPILER_FLAGS. # Specifically, it uses the value of $ax_enable_compile_warnings to decide # which flags to enable. # # LICENSE # # Copyright (c) 2015 Philip Withnall # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_COMPILER_FLAGS_GIR],[ AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) # Variable names m4_define([ax_warn_scannerflags_variable], [m4_normalize(ifelse([$1],,[WARN_SCANNERFLAGS],[$1]))]) # Base flags AX_APPEND_FLAG([$3],ax_warn_scannerflags_variable) AS_IF([test "$ax_enable_compile_warnings" != "no"],[ # "yes" flags AX_APPEND_FLAG([ dnl --warn-all dnl $4 dnl $5 dnl $6 dnl $7 dnl ],ax_warn_scannerflags_variable) ]) AS_IF([test "$ax_enable_compile_warnings" = "error"],[ # "error" flags AX_APPEND_FLAG([ dnl --warn-error dnl ],ax_warn_scannerflags_variable) ]) # Substitute the variables AC_SUBST(ax_warn_scannerflags_variable) ])dnl AX_COMPILER_FLAGS mu-1.6.10/m4/ax_compiler_flags_ldflags.m4000066400000000000000000000100731414367003600201610ustar00rootroot00000000000000# ============================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_ldflags.html # ============================================================================== # # SYNOPSIS # # AX_COMPILER_FLAGS_LDFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS]) # # DESCRIPTION # # Add warning flags for the linker to VARIABLE, which defaults to # WARN_LDFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be # manually added to the LDFLAGS variable for each target in the code base. # # This macro depends on the environment set up by AX_COMPILER_FLAGS. # Specifically, it uses the value of $ax_enable_compile_warnings to decide # which flags to enable. # # LICENSE # # Copyright (c) 2014, 2015 Philip Withnall # Copyright (c) 2017, 2018 Reini Urban # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 9 AC_DEFUN([AX_COMPILER_FLAGS_LDFLAGS],[ AX_REQUIRE_DEFINED([AX_APPEND_LINK_FLAGS]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # Variable names m4_define([ax_warn_ldflags_variable], [m4_normalize(ifelse([$1],,[WARN_LDFLAGS],[$1]))]) # Always pass -Werror=unknown-warning-option to get Clang to fail on bad # flags, otherwise they are always appended to the warn_ldflags variable, # and Clang warns on them for every compilation unit. # If this is passed to GCC, it will explode, so the flag must be enabled # conditionally. AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[ ax_compiler_flags_test="-Werror=unknown-warning-option" ],[ ax_compiler_flags_test="" ]) AX_CHECK_LINK_FLAG([-Wl,--as-needed], [ AX_APPEND_LINK_FLAGS([-Wl,--as-needed], [AM_LDFLAGS],[$ax_compiler_flags_test]) ]) AX_CHECK_LINK_FLAG([-Wl,-z,relro], [ AX_APPEND_LINK_FLAGS([-Wl,-z,relro], [AM_LDFLAGS],[$ax_compiler_flags_test]) ]) AX_CHECK_LINK_FLAG([-Wl,-z,now], [ AX_APPEND_LINK_FLAGS([-Wl,-z,now], [AM_LDFLAGS],[$ax_compiler_flags_test]) ]) AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [ AX_APPEND_LINK_FLAGS([-Wl,-z,noexecstack], [AM_LDFLAGS],[$ax_compiler_flags_test]) ]) # textonly, retpolineplt not yet # macOS and cygwin linker do not have --as-needed AX_CHECK_LINK_FLAG([-Wl,--no-as-needed], [ ax_compiler_flags_as_needed_option="-Wl,--no-as-needed" ], [ ax_compiler_flags_as_needed_option="" ]) # macOS linker speaks with a different accent ax_compiler_flags_fatal_warnings_option="" AX_CHECK_LINK_FLAG([-Wl,--fatal-warnings], [ ax_compiler_flags_fatal_warnings_option="-Wl,--fatal-warnings" ]) AX_CHECK_LINK_FLAG([-Wl,-fatal_warnings], [ ax_compiler_flags_fatal_warnings_option="-Wl,-fatal_warnings" ]) # Base flags AX_APPEND_LINK_FLAGS([ dnl $ax_compiler_flags_as_needed_option dnl $3 dnl ],ax_warn_ldflags_variable,[$ax_compiler_flags_test]) AS_IF([test "$ax_enable_compile_warnings" != "no"],[ # "yes" flags AX_APPEND_LINK_FLAGS([$4 $5 $6 $7], ax_warn_ldflags_variable, [$ax_compiler_flags_test]) ]) AS_IF([test "$ax_enable_compile_warnings" = "error"],[ # "error" flags; -Werror has to be appended unconditionally because # it's not possible to test for # # suggest-attribute=format is disabled because it gives too many false # positives AX_APPEND_LINK_FLAGS([ dnl $ax_compiler_flags_fatal_warnings_option dnl ],ax_warn_ldflags_variable,[$ax_compiler_flags_test]) ]) # Substitute the variables AC_SUBST(ax_warn_ldflags_variable) ])dnl AX_COMPILER_FLAGS mu-1.6.10/m4/ax_cxx_compile_stdcxx.m4000066400000000000000000000454561414367003600174230ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) # or '14' (for the C++14 standard). # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx$1_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) fi fi if test x$ac_success = xno; then HAVE_CXX$1=0 AC_MSG_NOTICE([No compiler with C++$1 support was found]) else HAVE_CXX$1=1 AC_DEFINE(HAVE_CXX$1,1, [define if the compiler supports basic C++$1 syntax]) fi AC_SUBST(HAVE_CXX$1) ]) dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201103L #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual void f() {} }; struct Derived : public Base { virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L ]]) mu-1.6.10/m4/ax_cxx_compile_stdcxx_14.m4000066400000000000000000000025131414367003600177120ustar00rootroot00000000000000# ============================================================================= # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_14.html # ============================================================================= # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX_14([ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the C++14 # standard; if necessary, add switches to CXX and CXXCPP to enable # support. # # This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX # macro with the version set to C++14. The two optional arguments are # forwarded literally as the second and third argument respectively. # Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for # more information. If you want to use this macro, you also need to # download the ax_cxx_compile_stdcxx.m4 file. # # LICENSE # # Copyright (c) 2015 Moritz Klammler # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 5 AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) AC_DEFUN([AX_CXX_COMPILE_STDCXX_14], [AX_CXX_COMPILE_STDCXX([14], [$1], [$2])]) mu-1.6.10/m4/ax_file_escapes.m4000066400000000000000000000013731414367003600161240ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_file_escapes.html # =========================================================================== # # SYNOPSIS # # AX_FILE_ESCAPES # # DESCRIPTION # # Writes the specified data to the specified file. # # LICENSE # # Copyright (c) 2008 Tom Howard # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_FILE_ESCAPES],[ AX_DOLLAR="\$" AX_SRB="\\135" AX_SLB="\\133" AX_BS="\\\\" AX_DQ="\"" ]) mu-1.6.10/m4/ax_is_release.m4000066400000000000000000000063341414367003600156170ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_is_release.html # =========================================================================== # # SYNOPSIS # # AX_IS_RELEASE(POLICY) # # DESCRIPTION # # Determine whether the code is being configured as a release, or from # git. Set the ax_is_release variable to 'yes' or 'no'. # # If building a release version, it is recommended that the configure # script disable compiler errors and debug features, by conditionalising # them on the ax_is_release variable. If building from git, these # features should be enabled. # # The POLICY parameter specifies how ax_is_release is determined. It can # take the following values: # # * git-directory: ax_is_release will be 'no' if a '.git' directory exists # * minor-version: ax_is_release will be 'no' if the minor version number # in $PACKAGE_VERSION is odd; this assumes # $PACKAGE_VERSION follows the 'major.minor.micro' scheme # * micro-version: ax_is_release will be 'no' if the micro version number # in $PACKAGE_VERSION is odd; this assumes # $PACKAGE_VERSION follows the 'major.minor.micro' scheme # * dash-version: ax_is_release will be 'no' if there is a dash '-' # in $PACKAGE_VERSION, for example 1.2-pre3, 1.2.42-a8b9 # or 2.0-dirty (in particular this is suitable for use # with git-version-gen) # * always: ax_is_release will always be 'yes' # * never: ax_is_release will always be 'no' # # Other policies may be added in future. # # LICENSE # # Copyright (c) 2015 Philip Withnall # Copyright (c) 2016 Collabora Ltd. # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. #serial 7 AC_DEFUN([AX_IS_RELEASE],[ AC_BEFORE([AC_INIT],[$0]) m4_case([$1], [git-directory],[ # $is_release = (.git directory does not exist) AS_IF([test -d ${srcdir}/.git],[ax_is_release=no],[ax_is_release=yes]) ], [minor-version],[ # $is_release = ($minor_version is even) minor_version=`echo "$PACKAGE_VERSION" | sed 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'` AS_IF([test "$(( $minor_version % 2 ))" -ne 0], [ax_is_release=no],[ax_is_release=yes]) ], [micro-version],[ # $is_release = ($micro_version is even) micro_version=`echo "$PACKAGE_VERSION" | sed 's/[[^.]]*\.[[^.]]*\.\([[^.]]*\).*/\1/'` AS_IF([test "$(( $micro_version % 2 ))" -ne 0], [ax_is_release=no],[ax_is_release=yes]) ], [dash-version],[ # $is_release = ($PACKAGE_VERSION has a dash) AS_CASE([$PACKAGE_VERSION], [*-*], [ax_is_release=no], [*], [ax_is_release=yes]) ], [always],[ax_is_release=yes], [never],[ax_is_release=no], [ AC_MSG_ERROR([Invalid policy. Valid policies: git-directory, minor-version, micro-version, dash-version, always, never.]) ]) ]) mu-1.6.10/m4/ax_lib_readline.m4000066400000000000000000000076411414367003600161170ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_lib_readline.html # =========================================================================== # # SYNOPSIS # # AX_LIB_READLINE # # DESCRIPTION # # Searches for a readline compatible library. If found, defines # `HAVE_LIBREADLINE'. If the found library has the `add_history' function, # sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the # necessary include files and sets `HAVE_READLINE_H' or # `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or # 'HAVE_HISTORY_H' if the corresponding include files exists. # # The libraries that may be readline compatible are `libedit', # `libeditline' and `libreadline'. Sometimes we need to link a termcap # library for readline to work, this macro tests these cases too by trying # to link with `libtermcap', `libcurses' or `libncurses' before giving up. # # Here is an example of how to use the information provided by this macro # to perform the necessary includes or declarations in a C file: # # #ifdef HAVE_LIBREADLINE # # if defined(HAVE_READLINE_READLINE_H) # # include # # elif defined(HAVE_READLINE_H) # # include # # else /* !defined(HAVE_READLINE_H) */ # extern char *readline (); # # endif /* !defined(HAVE_READLINE_H) */ # char *cmdline = NULL; # #else /* !defined(HAVE_READLINE_READLINE_H) */ # /* no readline */ # #endif /* HAVE_LIBREADLINE */ # # #ifdef HAVE_READLINE_HISTORY # # if defined(HAVE_READLINE_HISTORY_H) # # include # # elif defined(HAVE_HISTORY_H) # # include # # else /* !defined(HAVE_HISTORY_H) */ # extern void add_history (); # extern int write_history (); # extern int read_history (); # # endif /* defined(HAVE_READLINE_HISTORY_H) */ # /* no history */ # #endif /* HAVE_READLINE_HISTORY */ # # LICENSE # # Copyright (c) 2008 Ville Laurikari # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE]) AC_DEFUN([AX_LIB_READLINE], [ AC_CACHE_CHECK([for a readline compatible library], ax_cv_lib_readline, [ ORIG_LIBS="$LIBS" # djcb: we need the _real_ readline_ #for readline_lib in readline edit editline; do for readline_lib in readline; do for termcap_lib in "" termcap curses ncurses; do if test -z "$termcap_lib"; then TRY_LIB="-l$readline_lib" else TRY_LIB="-l$readline_lib -l$termcap_lib" fi LIBS="$ORIG_LIBS $TRY_LIB" AC_LINK_IFELSE([AC_LANG_CALL([], [readline])], [ax_cv_lib_readline="$TRY_LIB"]) if test -n "$ax_cv_lib_readline"; then break fi done if test -n "$ax_cv_lib_readline"; then break fi done if test -z "$ax_cv_lib_readline"; then ax_cv_lib_readline="no" fi LIBS="$ORIG_LIBS" ]) if test "$ax_cv_lib_readline" != "no"; then LIBS="$LIBS $ax_cv_lib_readline" AC_DEFINE(HAVE_LIBREADLINE, 1, [Define if you have a readline compatible library]) AC_CHECK_HEADERS(readline.h readline/readline.h) AC_CACHE_CHECK([whether readline supports history], ax_cv_lib_readline_history, [ ax_cv_lib_readline_history="no" AC_LINK_IFELSE([AC_LANG_CALL([], [add_history])], [ax_cv_lib_readline_history="yes"]) ]) if test "$ax_cv_lib_readline_history" = "yes"; then AC_DEFINE(HAVE_READLINE_HISTORY, 1, [Define if your readline library has \`add_history']) AC_CHECK_HEADERS(history.h readline/history.h) fi fi ])dnl mu-1.6.10/m4/ax_require_defined.m4000066400000000000000000000023021414367003600166250ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED mu-1.6.10/m4/ax_valgrind_check.m4000066400000000000000000000213401414367003600164410ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_valgrind_check.html # =========================================================================== # # SYNOPSIS # # AX_VALGRIND_DFLT(memcheck|helgrind|drd|sgcheck, on|off) # AX_VALGRIND_CHECK() # # DESCRIPTION # # AX_VALGRIND_CHECK checks whether Valgrind is present and, if so, allows # running `make check` under a variety of Valgrind tools to check for # memory and threading errors. # # Defines VALGRIND_CHECK_RULES which should be substituted in your # Makefile; and $enable_valgrind which can be used in subsequent configure # output. VALGRIND_ENABLED is defined and substituted, and corresponds to # the value of the --enable-valgrind option, which defaults to being # enabled if Valgrind is installed and disabled otherwise. Individual # Valgrind tools can be disabled via --disable-valgrind-, the # default is configurable via the AX_VALGRIND_DFLT command or is to use # all commands not disabled via AX_VALGRIND_DFLT. All AX_VALGRIND_DFLT # calls must be made before the call to AX_VALGRIND_CHECK. # # If unit tests are written using a shell script and automake's # LOG_COMPILER system, the $(VALGRIND) variable can be used within the # shell scripts to enable Valgrind, as described here: # # https://www.gnu.org/software/gnulib/manual/html_node/Running-self_002dtests-under-valgrind.html # # Usage example: # # configure.ac: # # AX_VALGRIND_DFLT([sgcheck], [off]) # AX_VALGRIND_CHECK # # in each Makefile.am with tests: # # @VALGRIND_CHECK_RULES@ # VALGRIND_SUPPRESSIONS_FILES = my-project.supp # EXTRA_DIST = my-project.supp # # This results in a "check-valgrind" rule being added. Running `make # check-valgrind` in that directory will recursively run the module's test # suite (`make check`) once for each of the available Valgrind tools (out # of memcheck, helgrind and drd) while the sgcheck will be skipped unless # enabled again on the commandline with --enable-valgrind-sgcheck. The # results for each check will be output to test-suite-$toolname.log. The # target will succeed if there are zero errors and fail otherwise. # # Alternatively, a "check-valgrind-$TOOL" rule will be added, for $TOOL in # memcheck, helgrind, drd and sgcheck. These are useful because often only # some of those tools can be ran cleanly on a codebase. # # The macro supports running with and without libtool. # # LICENSE # # Copyright (c) 2014, 2015, 2016 Philip Withnall # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 17 dnl Configured tools m4_define([valgrind_tool_list], [[memcheck], [helgrind], [drd], [sgcheck]]) m4_set_add_all([valgrind_exp_tool_set], [sgcheck]) m4_foreach([vgtool], [valgrind_tool_list], [m4_define([en_dflt_valgrind_]vgtool, [on])]) AC_DEFUN([AX_VALGRIND_DFLT],[ m4_define([en_dflt_valgrind_$1], [$2]) ])dnl AM_EXTRA_RECURSIVE_TARGETS([check-valgrind]) m4_foreach([vgtool], [valgrind_tool_list], [AM_EXTRA_RECURSIVE_TARGETS([check-valgrind-]vgtool)]) AC_DEFUN([AX_VALGRIND_CHECK],[ dnl Check for --enable-valgrind AC_ARG_ENABLE([valgrind], [AS_HELP_STRING([--enable-valgrind], [Whether to enable Valgrind on the unit tests])], [enable_valgrind=$enableval],[enable_valgrind=]) AS_IF([test "$enable_valgrind" != "no"],[ # Check for Valgrind. AC_CHECK_PROG([VALGRIND],[valgrind],[valgrind]) AS_IF([test "$VALGRIND" = ""],[ AS_IF([test "$enable_valgrind" = "yes"],[ AC_MSG_ERROR([Could not find valgrind; either install it or reconfigure with --disable-valgrind]) ],[ enable_valgrind=no ]) ],[ enable_valgrind=yes ]) ]) AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"]) AC_SUBST([VALGRIND_ENABLED],[$enable_valgrind]) # Check for Valgrind tools we care about. [valgrind_enabled_tools=] m4_foreach([vgtool],[valgrind_tool_list],[ AC_ARG_ENABLE([valgrind-]vgtool, m4_if(m4_defn([en_dflt_valgrind_]vgtool),[off],dnl [AS_HELP_STRING([--enable-valgrind-]vgtool, [Whether to use ]vgtool[ during the Valgrind tests])],dnl [AS_HELP_STRING([--disable-valgrind-]vgtool, [Whether to skip ]vgtool[ during the Valgrind tests])]), [enable_valgrind_]vgtool[=$enableval], [enable_valgrind_]vgtool[=]) AS_IF([test "$enable_valgrind" = "no"],[ enable_valgrind_]vgtool[=no], [test "$enable_valgrind_]vgtool[" ]dnl m4_if(m4_defn([en_dflt_valgrind_]vgtool), [off], [= "yes"], [!= "no"]),[ AC_CACHE_CHECK([for Valgrind tool ]vgtool, [ax_cv_valgrind_tool_]vgtool,[ ax_cv_valgrind_tool_]vgtool[=no m4_set_contains([valgrind_exp_tool_set],vgtool, [m4_define([vgtoolx],[exp-]vgtool)], [m4_define([vgtoolx],vgtool)]) AS_IF([`$VALGRIND --tool=]vgtoolx[ --help >/dev/null 2>&1`],[ ax_cv_valgrind_tool_]vgtool[=yes ]) ]) AS_IF([test "$ax_cv_valgrind_tool_]vgtool[" = "no"],[ AS_IF([test "$enable_valgrind_]vgtool[" = "yes"],[ AC_MSG_ERROR([Valgrind does not support ]vgtool[; reconfigure with --disable-valgrind-]vgtool) ],[ enable_valgrind_]vgtool[=no ]) ],[ enable_valgrind_]vgtool[=yes ]) ]) AS_IF([test "$enable_valgrind_]vgtool[" = "yes"],[ valgrind_enabled_tools="$valgrind_enabled_tools ]m4_bpatsubst(vgtool,[^exp-])[" ]) AC_SUBST([ENABLE_VALGRIND_]vgtool,[$enable_valgrind_]vgtool) ]) AC_SUBST([valgrind_tools],["]m4_join([ ], valgrind_tool_list)["]) AC_SUBST([valgrind_enabled_tools],[$valgrind_enabled_tools]) [VALGRIND_CHECK_RULES=' # Valgrind check # # Optional: # - VALGRIND_SUPPRESSIONS_FILES: Space-separated list of Valgrind suppressions # files to load. (Default: empty) # - VALGRIND_FLAGS: General flags to pass to all Valgrind tools. # (Default: --num-callers=30) # - VALGRIND_$toolname_FLAGS: Flags to pass to Valgrind $toolname (one of: # memcheck, helgrind, drd, sgcheck). (Default: various) # Optional variables VALGRIND_SUPPRESSIONS ?= $(addprefix --suppressions=,$(VALGRIND_SUPPRESSIONS_FILES)) VALGRIND_FLAGS ?= --num-callers=30 VALGRIND_memcheck_FLAGS ?= --leak-check=full --show-reachable=no VALGRIND_helgrind_FLAGS ?= --history-level=approx VALGRIND_drd_FLAGS ?= VALGRIND_sgcheck_FLAGS ?= # Internal use valgrind_log_files = $(addprefix test-suite-,$(addsuffix .log,$(valgrind_tools))) valgrind_memcheck_flags = --tool=memcheck $(VALGRIND_memcheck_FLAGS) valgrind_helgrind_flags = --tool=helgrind $(VALGRIND_helgrind_FLAGS) valgrind_drd_flags = --tool=drd $(VALGRIND_drd_FLAGS) valgrind_sgcheck_flags = --tool=exp-sgcheck $(VALGRIND_sgcheck_FLAGS) valgrind_quiet = $(valgrind_quiet_$(V)) valgrind_quiet_ = $(valgrind_quiet_$(AM_DEFAULT_VERBOSITY)) valgrind_quiet_0 = --quiet valgrind_v_use = $(valgrind_v_use_$(V)) valgrind_v_use_ = $(valgrind_v_use_$(AM_DEFAULT_VERBOSITY)) valgrind_v_use_0 = @echo " USE " $(patsubst check-valgrind-%-am,%,$''@):; # Support running with and without libtool. ifneq ($(LIBTOOL),) valgrind_lt = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=execute else valgrind_lt = endif # Use recursive makes in order to ignore errors during check check-valgrind-am: ifeq ($(VALGRIND_ENABLED),yes) $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k \ $(foreach tool, $(valgrind_enabled_tools), check-valgrind-$(tool)) else @echo "Need to reconfigure with --enable-valgrind" endif # Valgrind running VALGRIND_TESTS_ENVIRONMENT = \ $(TESTS_ENVIRONMENT) \ env VALGRIND=$(VALGRIND) \ G_SLICE=always-malloc,debug-blocks \ G_DEBUG=fatal-warnings,fatal-criticals,gc-friendly VALGRIND_LOG_COMPILER = \ $(valgrind_lt) \ $(VALGRIND) $(VALGRIND_SUPPRESSIONS) --error-exitcode=1 $(VALGRIND_FLAGS) define valgrind_tool_rule check-valgrind-$(1)-am: ifeq ($$(VALGRIND_ENABLED)-$$(ENABLE_VALGRIND_$(1)),yes-yes) ifneq ($$(TESTS),) $$(valgrind_v_use)$$(MAKE) check-TESTS \ TESTS_ENVIRONMENT="$$(VALGRIND_TESTS_ENVIRONMENT)" \ LOG_COMPILER="$$(VALGRIND_LOG_COMPILER)" \ LOG_FLAGS="$$(valgrind_$(1)_flags)" \ TEST_SUITE_LOG=test-suite-$(1).log endif else ifeq ($$(VALGRIND_ENABLED),yes) @echo "Need to reconfigure with --enable-valgrind-$(1)" else @echo "Need to reconfigure with --enable-valgrind" endif endef $(foreach tool,$(valgrind_tools),$(eval $(call valgrind_tool_rule,$(tool)))) A''M_DISTCHECK_CONFIGURE_FLAGS ?= A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-valgrind MOSTLYCLEANFILES ?= MOSTLYCLEANFILES += $(valgrind_log_files) .PHONY: check-valgrind $(add-prefix check-valgrind-,$(valgrind_tools)) '] AC_SUBST([VALGRIND_CHECK_RULES]) m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([VALGRIND_CHECK_RULES])]) ]) mu-1.6.10/m4/guile.m4000066400000000000000000000365501414367003600141240ustar00rootroot00000000000000## Autoconf macros for working with Guile. ## ## Copyright (C) 1998,2001, 2006, 2010, 2012, 2013, 2014 Free Software Foundation, Inc. ## ## This library is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public License ## as published by the Free Software Foundation; either version 3 of ## the License, or (at your option) any later version. ## ## This library is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## Lesser General Public License for more details. ## ## You should have received a copy of the GNU Lesser General Public ## License along with this library; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301 USA # serial 10 ## Index ## ----- ## ## GUILE_PKG -- find Guile development files ## GUILE_PROGS -- set paths to Guile interpreter, config and tool programs ## GUILE_FLAGS -- set flags for compiling and linking with Guile ## GUILE_SITE_DIR -- find path to Guile "site" directories ## GUILE_CHECK -- evaluate Guile Scheme code and capture the return value ## GUILE_MODULE_CHECK -- check feature of a Guile Scheme module ## GUILE_MODULE_AVAILABLE -- check availability of a Guile Scheme module ## GUILE_MODULE_REQUIRED -- fail if a Guile Scheme module is unavailable ## GUILE_MODULE_EXPORTS -- check if a module exports a variable ## GUILE_MODULE_REQUIRED_EXPORT -- fail if a module doesn't export a variable ## Code ## ---- ## NOTE: Comments preceding an AC_DEFUN (starting from "Usage:") are massaged ## into doc/ref/autoconf-macros.texi (see Makefile.am in that directory). # GUILE_PKG -- find Guile development files # # Usage: GUILE_PKG([VERSIONS]) # # This macro runs the @code{pkg-config} tool to find development files # for an available version of Guile. # # By default, this macro will search for the latest stable version of # Guile (e.g. 3.0), falling back to the previous stable version # (e.g. 2.2) if it is available. If no guile-@var{VERSION}.pc file is # found, an error is signalled. The found version is stored in # @var{GUILE_EFFECTIVE_VERSION}. # # If @code{GUILE_PROGS} was already invoked, this macro ensures that the # development files have the same effective version as the Guile # program. # # @var{GUILE_EFFECTIVE_VERSION} is marked for substitution, as by # @code{AC_SUBST}. # AC_DEFUN([GUILE_PKG], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if test "x$PKG_CONFIG" = x; then AC_MSG_ERROR([pkg-config is missing, please install it]) fi _guile_versions_to_search="m4_default([$1], [3.0 2.2 2.0])" if test -n "$GUILE_EFFECTIVE_VERSION"; then _guile_tmp="" for v in $_guile_versions_to_search; do if test "$v" = "$GUILE_EFFECTIVE_VERSION"; then _guile_tmp=$v fi done if test -z "$_guile_tmp"; then AC_MSG_FAILURE([searching for guile development files for versions $_guile_versions_to_search, but previously found $GUILE version $GUILE_EFFECTIVE_VERSION]) fi _guile_versions_to_search=$GUILE_EFFECTIVE_VERSION fi GUILE_EFFECTIVE_VERSION="" _guile_errors="" for v in $_guile_versions_to_search; do if test -z "$GUILE_EFFECTIVE_VERSION"; then AC_MSG_NOTICE([checking for guile $v]) PKG_CHECK_EXISTS([guile-$v], [GUILE_EFFECTIVE_VERSION=$v], []) fi done if test -z "$GUILE_EFFECTIVE_VERSION"; then AC_MSG_ERROR([ No Guile development packages were found. Please verify that you have Guile installed. If you installed Guile from a binary distribution, please verify that you have also installed the development packages. If you installed it yourself, you might need to adjust your PKG_CONFIG_PATH; see the pkg-config man page for more. ]) fi AC_MSG_NOTICE([found guile $GUILE_EFFECTIVE_VERSION]) AC_SUBST([GUILE_EFFECTIVE_VERSION]) ]) # GUILE_FLAGS -- set flags for compiling and linking with Guile # # Usage: GUILE_FLAGS # # This macro runs the @code{pkg-config} tool to find out how to compile # and link programs against Guile. It sets four variables: # @var{GUILE_CFLAGS}, @var{GUILE_LDFLAGS}, @var{GUILE_LIBS}, and # @var{GUILE_LTLIBS}. # # @var{GUILE_CFLAGS}: flags to pass to a C or C++ compiler to build code that # uses Guile header files. This is almost always just one or more @code{-I} # flags. # # @var{GUILE_LDFLAGS}: flags to pass to the compiler to link a program # against Guile. This includes @code{-lguile-@var{VERSION}} for the # Guile library itself, and may also include one or more @code{-L} flag # to tell the compiler where to find the libraries. But it does not # include flags that influence the program's runtime search path for # libraries, and will therefore lead to a program that fails to start, # unless all necessary libraries are installed in a standard location # such as @file{/usr/lib}. # # @var{GUILE_LIBS} and @var{GUILE_LTLIBS}: flags to pass to the compiler or to # libtool, respectively, to link a program against Guile. It includes flags # that augment the program's runtime search path for libraries, so that shared # libraries will be found at the location where they were during linking, even # in non-standard locations. @var{GUILE_LIBS} is to be used when linking the # program directly with the compiler, whereas @var{GUILE_LTLIBS} is to be used # when linking the program is done through libtool. # # The variables are marked for substitution, as by @code{AC_SUBST}. # AC_DEFUN([GUILE_FLAGS], [AC_REQUIRE([GUILE_PKG]) PKG_CHECK_MODULES(GUILE, [guile-$GUILE_EFFECTIVE_VERSION]) dnl GUILE_CFLAGS and GUILE_LIBS are already defined and AC_SUBST'd by dnl PKG_CHECK_MODULES. But GUILE_LIBS to pkg-config is GUILE_LDFLAGS dnl to us. GUILE_LDFLAGS=$GUILE_LIBS dnl Determine the platform dependent parameters needed to use rpath. dnl AC_LIB_LINKFLAGS_FROM_LIBS is defined in gnulib/m4/lib-link.m4 and needs dnl the file gnulib/build-aux/config.rpath. AC_LIB_LINKFLAGS_FROM_LIBS([GUILE_LIBS], [$GUILE_LDFLAGS], []) GUILE_LIBS="$GUILE_LDFLAGS $GUILE_LIBS" AC_LIB_LINKFLAGS_FROM_LIBS([GUILE_LTLIBS], [$GUILE_LDFLAGS], [yes]) GUILE_LTLIBS="$GUILE_LDFLAGS $GUILE_LTLIBS" AC_SUBST([GUILE_EFFECTIVE_VERSION]) AC_SUBST([GUILE_CFLAGS]) AC_SUBST([GUILE_LDFLAGS]) AC_SUBST([GUILE_LIBS]) AC_SUBST([GUILE_LTLIBS]) ]) # GUILE_SITE_DIR -- find path to Guile site directories # # Usage: GUILE_SITE_DIR # # This looks for Guile's "site" directories. The variable @var{GUILE_SITE} will # be set to Guile's "site" directory for Scheme source files (usually something # like PREFIX/share/guile/site). @var{GUILE_SITE_CCACHE} will be set to the # directory for compiled Scheme files also known as @code{.go} files # (usually something like # PREFIX/lib/guile/@var{GUILE_EFFECTIVE_VERSION}/site-ccache). # @var{GUILE_EXTENSION} will be set to the directory for compiled C extensions # (usually something like # PREFIX/lib/guile/@var{GUILE_EFFECTIVE_VERSION}/extensions). The latter two # are set to blank if the particular version of Guile does not support # them. Note that this macro will run the macros @code{GUILE_PKG} and # @code{GUILE_PROGS} if they have not already been run. # # The variables are marked for substitution, as by @code{AC_SUBST}. # AC_DEFUN([GUILE_SITE_DIR], [AC_REQUIRE([GUILE_PKG]) AC_REQUIRE([GUILE_PROGS]) AC_MSG_CHECKING(for Guile site directory) GUILE_SITE=`$PKG_CONFIG --print-errors --variable=sitedir guile-$GUILE_EFFECTIVE_VERSION` AC_MSG_RESULT($GUILE_SITE) if test "$GUILE_SITE" = ""; then AC_MSG_FAILURE(sitedir not found) fi AC_SUBST(GUILE_SITE) AC_MSG_CHECKING([for Guile site-ccache directory using pkgconfig]) GUILE_SITE_CCACHE=`$PKG_CONFIG --variable=siteccachedir guile-$GUILE_EFFECTIVE_VERSION` if test "$GUILE_SITE_CCACHE" = ""; then AC_MSG_RESULT(no) AC_MSG_CHECKING([for Guile site-ccache directory using interpreter]) GUILE_SITE_CCACHE=`$GUILE -c "(display (if (defined? '%site-ccache-dir) (%site-ccache-dir) \"\"))"` if test $? != "0" -o "$GUILE_SITE_CCACHE" = ""; then AC_MSG_RESULT(no) GUILE_SITE_CCACHE="" AC_MSG_WARN([siteccachedir not found]) fi fi AC_MSG_RESULT($GUILE_SITE_CCACHE) AC_SUBST([GUILE_SITE_CCACHE]) AC_MSG_CHECKING(for Guile extensions directory) GUILE_EXTENSION=`$PKG_CONFIG --print-errors --variable=extensiondir guile-$GUILE_EFFECTIVE_VERSION` AC_MSG_RESULT($GUILE_EXTENSION) if test "$GUILE_EXTENSION" = ""; then GUILE_EXTENSION="" AC_MSG_WARN(extensiondir not found) fi AC_SUBST(GUILE_EXTENSION) ]) # GUILE_PROGS -- set paths to Guile interpreter, config and tool programs # # Usage: GUILE_PROGS([VERSION]) # # This macro looks for programs @code{guile} and @code{guild}, setting # variables @var{GUILE} and @var{GUILD} to their paths, respectively. # The macro will attempt to find @code{guile} with the suffix of # @code{-X.Y}, followed by looking for it with the suffix @code{X.Y}, and # then fall back to looking for @code{guile} with no suffix. If # @code{guile} is still not found, signal an error. The suffix, if any, # that was required to find @code{guile} will be used for @code{guild} # as well. # # By default, this macro will search for the latest stable version of # Guile (e.g. 3.0). x.y or x.y.z versions can be specified. If an older # version is found, the macro will signal an error. # # The effective version of the found @code{guile} is set to # @var{GUILE_EFFECTIVE_VERSION}. This macro ensures that the effective # version is compatible with the result of a previous invocation of # @code{GUILE_FLAGS}, if any. # # As a legacy interface, it also looks for @code{guile-config} and # @code{guile-tools}, setting @var{GUILE_CONFIG} and @var{GUILE_TOOLS}. # # The variables are marked for substitution, as by @code{AC_SUBST}. # AC_DEFUN([GUILE_PROGS], [_guile_required_version="m4_default([$1], [$GUILE_EFFECTIVE_VERSION])" if test -z "$_guile_required_version"; then _guile_required_version=3.0 fi _guile_candidates=guile _tmp= for v in `echo "$_guile_required_version" | tr . ' '`; do if test -n "$_tmp"; then _tmp=$_tmp.; fi _tmp=$_tmp$v _guile_candidates="guile-$_tmp guile$_tmp $_guile_candidates" done AC_PATH_PROGS(GUILE,[$_guile_candidates]) if test -z "$GUILE"; then AC_MSG_ERROR([guile required but not found]) fi _guile_suffix=`echo "$GUILE" | sed -e 's,^.*/guile\(.*\)$,\1,'` _guile_effective_version=`$GUILE -c "(display (effective-version))"` if test -z "$GUILE_EFFECTIVE_VERSION"; then GUILE_EFFECTIVE_VERSION=$_guile_effective_version elif test "$GUILE_EFFECTIVE_VERSION" != "$_guile_effective_version"; then AC_MSG_ERROR([found development files for Guile $GUILE_EFFECTIVE_VERSION, but $GUILE has effective version $_guile_effective_version]) fi _guile_major_version=`$GUILE -c "(display (major-version))"` _guile_minor_version=`$GUILE -c "(display (minor-version))"` _guile_micro_version=`$GUILE -c "(display (micro-version))"` _guile_prog_version="$_guile_major_version.$_guile_minor_version.$_guile_micro_version" AC_MSG_CHECKING([for Guile version >= $_guile_required_version]) _major_version=`echo $_guile_required_version | cut -d . -f 1` _minor_version=`echo $_guile_required_version | cut -d . -f 2` _micro_version=`echo $_guile_required_version | cut -d . -f 3` if test "$_guile_major_version" -gt "$_major_version"; then true elif test "$_guile_major_version" -eq "$_major_version"; then if test "$_guile_minor_version" -gt "$_minor_version"; then true elif test "$_guile_minor_version" -eq "$_minor_version"; then if test -n "$_micro_version"; then if test "$_guile_micro_version" -lt "$_micro_version"; then AC_MSG_ERROR([Guile $_guile_required_version required, but $_guile_prog_version found]) fi fi elif test "$GUILE_EFFECTIVE_VERSION" = "$_major_version.$_minor_version" -a -z "$_micro_version"; then # Allow prereleases that have the right effective version. true else as_fn_error $? "Guile $_guile_required_version required, but $_guile_prog_version found" "$LINENO" 5 fi elif test "$GUILE_EFFECTIVE_VERSION" = "$_major_version.$_minor_version" -a -z "$_micro_version"; then # Allow prereleases that have the right effective version. true else AC_MSG_ERROR([Guile $_guile_required_version required, but $_guile_prog_version found]) fi AC_MSG_RESULT([$_guile_prog_version]) AC_PATH_PROG(GUILD,[guild$_guile_suffix]) AC_SUBST(GUILD) AC_PATH_PROG(GUILE_CONFIG,[guile-config$_guile_suffix]) AC_SUBST(GUILE_CONFIG) if test -n "$GUILD"; then GUILE_TOOLS=$GUILD else AC_PATH_PROG(GUILE_TOOLS,[guile-tools$_guile_suffix]) fi AC_SUBST(GUILE_TOOLS) ]) # GUILE_CHECK -- evaluate Guile Scheme code and capture the return value # # Usage: GUILE_CHECK_RETVAL(var,check) # # @var{var} is a shell variable name to be set to the return value. # @var{check} is a Guile Scheme expression, evaluated with "$GUILE -c", and # returning either 0 or non-#f to indicate the check passed. # Non-0 number or #f indicates failure. # Avoid using the character "#" since that confuses autoconf. # AC_DEFUN([GUILE_CHECK], [AC_REQUIRE([GUILE_PROGS]) $GUILE -c "$2" > /dev/null 2>&1 $1=$? ]) # GUILE_MODULE_CHECK -- check feature of a Guile Scheme module # # Usage: GUILE_MODULE_CHECK(var,module,featuretest,description) # # @var{var} is a shell variable name to be set to "yes" or "no". # @var{module} is a list of symbols, like: (ice-9 common-list). # @var{featuretest} is an expression acceptable to GUILE_CHECK, q.v. # @var{description} is a present-tense verb phrase (passed to AC_MSG_CHECKING). # AC_DEFUN([GUILE_MODULE_CHECK], [AC_MSG_CHECKING([if $2 $4]) GUILE_CHECK($1,(use-modules $2) (exit ((lambda () $3)))) if test "$$1" = "0" ; then $1=yes ; else $1=no ; fi AC_MSG_RESULT($$1) ]) # GUILE_MODULE_AVAILABLE -- check availability of a Guile Scheme module # # Usage: GUILE_MODULE_AVAILABLE(var,module) # # @var{var} is a shell variable name to be set to "yes" or "no". # @var{module} is a list of symbols, like: (ice-9 common-list). # AC_DEFUN([GUILE_MODULE_AVAILABLE], [GUILE_MODULE_CHECK($1,$2,0,is available) ]) # GUILE_MODULE_REQUIRED -- fail if a Guile Scheme module is unavailable # # Usage: GUILE_MODULE_REQUIRED(symlist) # # @var{symlist} is a list of symbols, WITHOUT surrounding parens, # like: ice-9 common-list. # AC_DEFUN([GUILE_MODULE_REQUIRED], [GUILE_MODULE_AVAILABLE(ac_guile_module_required, ($1)) if test "$ac_guile_module_required" = "no" ; then AC_MSG_ERROR([required guile module not found: ($1)]) fi ]) # GUILE_MODULE_EXPORTS -- check if a module exports a variable # # Usage: GUILE_MODULE_EXPORTS(var,module,modvar) # # @var{var} is a shell variable to be set to "yes" or "no". # @var{module} is a list of symbols, like: (ice-9 common-list). # @var{modvar} is the Guile Scheme variable to check. # AC_DEFUN([GUILE_MODULE_EXPORTS], [GUILE_MODULE_CHECK($1,$2,$3,exports `$3') ]) # GUILE_MODULE_REQUIRED_EXPORT -- fail if a module doesn't export a variable # # Usage: GUILE_MODULE_REQUIRED_EXPORT(module,modvar) # # @var{module} is a list of symbols, like: (ice-9 common-list). # @var{modvar} is the Guile Scheme variable to check. # AC_DEFUN([GUILE_MODULE_REQUIRED_EXPORT], [GUILE_MODULE_EXPORTS(guile_module_required_export,$1,$2) if test "$guile_module_required_export" = "no" ; then AC_MSG_ERROR([module $1 does not export $2; required]) fi ]) ## guile.m4 ends here mu-1.6.10/m4/host-cpu-c-abi.m4000066400000000000000000000536401414367003600155310ustar00rootroot00000000000000# host-cpu-c-abi.m4 serial 13 dnl Copyright (C) 2002-2020 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible and Sam Steingold. dnl Sets the HOST_CPU variable to the canonical name of the CPU. dnl Sets the HOST_CPU_C_ABI variable to the canonical name of the CPU with its dnl C language ABI (application binary interface). dnl Also defines __${HOST_CPU}__ and __${HOST_CPU_C_ABI}__ as C macros in dnl config.h. dnl dnl This canonical name can be used to select a particular assembly language dnl source file that will interoperate with C code on the given host. dnl dnl For example: dnl * 'i386' and 'sparc' are different canonical names, because code for i386 dnl will not run on SPARC CPUs and vice versa. They have different dnl instruction sets. dnl * 'sparc' and 'sparc64' are different canonical names, because code for dnl 'sparc' and code for 'sparc64' cannot be linked together: 'sparc' code dnl contains 32-bit instructions, whereas 'sparc64' code contains 64-bit dnl instructions. A process on a SPARC CPU can be in 32-bit mode or in 64-bit dnl mode, but not both. dnl * 'mips' and 'mipsn32' are different canonical names, because they use dnl different argument passing and return conventions for C functions, and dnl although the instruction set of 'mips' is a large subset of the dnl instruction set of 'mipsn32'. dnl * 'mipsn32' and 'mips64' are different canonical names, because they use dnl different sizes for the C types like 'int' and 'void *', and although dnl the instruction sets of 'mipsn32' and 'mips64' are the same. dnl * The same canonical name is used for different endiannesses. You can dnl determine the endianness through preprocessor symbols: dnl - 'arm': test __ARMEL__. dnl - 'mips', 'mipsn32', 'mips64': test _MIPSEB vs. _MIPSEL. dnl - 'powerpc64': test _BIG_ENDIAN vs. _LITTLE_ENDIAN. dnl * The same name 'i386' is used for CPUs of type i386, i486, i586 dnl (Pentium), AMD K7, Pentium II, Pentium IV, etc., because dnl - Instructions that do not exist on all of these CPUs (cmpxchg, dnl MMX, SSE, SSE2, 3DNow! etc.) are not frequently used. If your dnl assembly language source files use such instructions, you will dnl need to make the distinction. dnl - Speed of execution of the common instruction set is reasonable across dnl the entire family of CPUs. If you have assembly language source files dnl that are optimized for particular CPU types (like GNU gmp has), you dnl will need to make the distinction. dnl See . AC_DEFUN([gl_HOST_CPU_C_ABI], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([gl_C_ASM]) AC_CACHE_CHECK([host CPU and C ABI], [gl_cv_host_cpu_c_abi], [case "$host_cpu" in changequote(,)dnl i[34567]86 ) changequote([,])dnl gl_cv_host_cpu_c_abi=i386 ;; x86_64 ) # On x86_64 systems, the C compiler may be generating code in one of # these ABIs: # - 64-bit instruction set, 64-bit pointers, 64-bit 'long': x86_64. # - 64-bit instruction set, 64-bit pointers, 32-bit 'long': x86_64 # with native Windows (mingw, MSVC). # - 64-bit instruction set, 32-bit pointers, 32-bit 'long': x86_64-x32. # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': i386. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if (defined __x86_64__ || defined __amd64__ \ || defined _M_X64 || defined _M_AMD64) int ok; #else error fail #endif ]])], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __ILP32__ || defined _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=x86_64-x32], [gl_cv_host_cpu_c_abi=x86_64])], [gl_cv_host_cpu_c_abi=i386]) ;; changequote(,)dnl alphaev[4-8] | alphaev56 | alphapca5[67] | alphaev6[78] ) changequote([,])dnl gl_cv_host_cpu_c_abi=alpha ;; arm* | aarch64 ) # Assume arm with EABI. # On arm64 systems, the C compiler may be generating code in one of # these ABIs: # - aarch64 instruction set, 64-bit pointers, 64-bit 'long': arm64. # - aarch64 instruction set, 32-bit pointers, 32-bit 'long': arm64-ilp32. # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': arm or armhf. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef __aarch64__ int ok; #else error fail #endif ]])], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __ILP32__ || defined _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=arm64-ilp32], [gl_cv_host_cpu_c_abi=arm64])], [# Don't distinguish little-endian and big-endian arm, since they # don't require different machine code for simple operations and # since the user can distinguish them through the preprocessor # defines __ARMEL__ vs. __ARMEB__. # But distinguish arm which passes floating-point arguments and # return values in integer registers (r0, r1, ...) - this is # gcc -mfloat-abi=soft or gcc -mfloat-abi=softfp - from arm which # passes them in float registers (s0, s1, ...) and double registers # (d0, d1, ...) - this is gcc -mfloat-abi=hard. GCC 4.6 or newer # sets the preprocessor defines __ARM_PCS (for the first case) and # __ARM_PCS_VFP (for the second case), but older GCC does not. echo 'double ddd; void func (double dd) { ddd = dd; }' > conftest.c # Look for a reference to the register d0 in the .s file. AC_TRY_COMMAND(${CC-cc} $CFLAGS $CPPFLAGS $gl_c_asm_opt conftest.c) >/dev/null 2>&1 if LC_ALL=C grep 'd0,' conftest.$gl_asmext >/dev/null; then gl_cv_host_cpu_c_abi=armhf else gl_cv_host_cpu_c_abi=arm fi rm -f conftest* ]) ;; hppa1.0 | hppa1.1 | hppa2.0* | hppa64 ) # On hppa, the C compiler may be generating 32-bit code or 64-bit # code. In the latter case, it defines _LP64 and __LP64__. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef __LP64__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=hppa64], [gl_cv_host_cpu_c_abi=hppa]) ;; ia64* ) # On ia64 on HP-UX, the C compiler may be generating 64-bit code or # 32-bit code. In the latter case, it defines _ILP32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=ia64-ilp32], [gl_cv_host_cpu_c_abi=ia64]) ;; mips* ) # We should also check for (_MIPS_SZPTR == 64), but gcc keeps this # at 32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined _MIPS_SZLONG && (_MIPS_SZLONG == 64) int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=mips64], [# In the n32 ABI, _ABIN32 is defined, _ABIO32 is not defined (but # may later get defined by ), and _MIPS_SIM == _ABIN32. # In the 32 ABI, _ABIO32 is defined, _ABIN32 is not defined (but # may later get defined by ), and _MIPS_SIM == _ABIO32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if (_MIPS_SIM == _ABIN32) int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=mipsn32], [gl_cv_host_cpu_c_abi=mips])]) ;; powerpc* ) # Different ABIs are in use on AIX vs. Mac OS X vs. Linux,*BSD. # No need to distinguish them here; the caller may distinguish # them based on the OS. # On powerpc64 systems, the C compiler may still be generating # 32-bit code. And on powerpc-ibm-aix systems, the C compiler may # be generating 64-bit code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __powerpc64__ || defined _ARCH_PPC64 int ok; #else error fail #endif ]])], [# On powerpc64, there are two ABIs on Linux: The AIX compatible # one and the ELFv2 one. The latter defines _CALL_ELF=2. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined _CALL_ELF && _CALL_ELF == 2 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=powerpc64-elfv2], [gl_cv_host_cpu_c_abi=powerpc64]) ], [gl_cv_host_cpu_c_abi=powerpc]) ;; rs6000 ) gl_cv_host_cpu_c_abi=powerpc ;; riscv32 | riscv64 ) # There are 2 architectures (with variants): rv32* and rv64*. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if __riscv_xlen == 64 int ok; #else error fail #endif ]])], [cpu=riscv64], [cpu=riscv32]) # There are 6 ABIs: ilp32, ilp32f, ilp32d, lp64, lp64f, lp64d. # Size of 'long' and 'void *': AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __LP64__ int ok; #else error fail #endif ]])], [main_abi=lp64], [main_abi=ilp32]) # Float ABIs: # __riscv_float_abi_double: # 'float' and 'double' are passed in floating-point registers. # __riscv_float_abi_single: # 'float' are passed in floating-point registers. # __riscv_float_abi_soft: # No values are passed in floating-point registers. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __riscv_float_abi_double int ok; #else error fail #endif ]])], [float_abi=d], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __riscv_float_abi_single int ok; #else error fail #endif ]])], [float_abi=f], [float_abi='']) ]) gl_cv_host_cpu_c_abi="${cpu}-${main_abi}${float_abi}" ;; s390* ) # On s390x, the C compiler may be generating 64-bit (= s390x) code # or 31-bit (= s390) code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __LP64__ || defined __s390x__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=s390x], [gl_cv_host_cpu_c_abi=s390]) ;; sparc | sparc64 ) # UltraSPARCs running Linux have `uname -m` = "sparc64", but the # C compiler still generates 32-bit code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __sparcv9 || defined __arch64__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=sparc64], [gl_cv_host_cpu_c_abi=sparc]) ;; *) gl_cv_host_cpu_c_abi="$host_cpu" ;; esac ]) dnl In most cases, $HOST_CPU and $HOST_CPU_C_ABI are the same. HOST_CPU=`echo "$gl_cv_host_cpu_c_abi" | sed -e 's/-.*//'` HOST_CPU_C_ABI="$gl_cv_host_cpu_c_abi" AC_SUBST([HOST_CPU]) AC_SUBST([HOST_CPU_C_ABI]) # This was # AC_DEFINE_UNQUOTED([__${HOST_CPU}__]) # AC_DEFINE_UNQUOTED([__${HOST_CPU_C_ABI}__]) # earlier, but KAI C++ 3.2d doesn't like this. sed -e 's/-/_/g' >> confdefs.h <&1 /dev/null 2>&1 \ && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ || PATH_SEPARATOR=';' } fi if test -n "$LD"; then AC_MSG_CHECKING([for ld]) elif test "$GCC" = yes; then AC_MSG_CHECKING([for ld used by $CC]) elif test "$with_gnu_ld" = yes; then AC_MSG_CHECKING([for GNU ld]) else AC_MSG_CHECKING([for non-GNU ld]) fi if test -n "$LD"; then # Let the user override the test with a path. : else AC_CACHE_VAL([acl_cv_path_LD], [ acl_cv_path_LD= # Final result of this test ac_prog=ld # Program to search in $PATH if test "$GCC" = yes; then # Check if gcc -print-prog-name=ld gives a path. case $host in *-*-mingw*) # gcc leaves a trailing carriage return which upsets mingw acl_output=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) acl_output=`($CC -print-prog-name=ld) 2>&5` ;; esac case $acl_output in # Accept absolute paths. [[\\/]]* | ?:[[\\/]]*) re_direlt='/[[^/]][[^/]]*/\.\./' # Canonicalize the pathname of ld acl_output=`echo "$acl_output" | sed 's%\\\\%/%g'` while echo "$acl_output" | grep "$re_direlt" > /dev/null 2>&1; do acl_output=`echo $acl_output | sed "s%$re_direlt%/%"` done # Got the pathname. No search in PATH is needed. acl_cv_path_LD="$acl_output" ac_prog= ;; "") # If it fails, then pretend we aren't using GCC. ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac fi if test -n "$ac_prog"; then # Search for $ac_prog in $PATH. acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS="$acl_save_ifs" test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then acl_cv_path_LD="$ac_dir/$ac_prog" # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$acl_cv_path_LD" -v 2>&1 conftest.sh . ./conftest.sh rm -f ./conftest.sh acl_cv_rpath=done ]) wl="$acl_cv_wl" acl_libext="$acl_cv_libext" acl_shlibext="$acl_cv_shlibext" acl_libname_spec="$acl_cv_libname_spec" acl_library_names_spec="$acl_cv_library_names_spec" acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" acl_hardcode_direct="$acl_cv_hardcode_direct" acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" dnl Determine whether the user wants rpath handling at all. AC_ARG_ENABLE([rpath], [ --disable-rpath do not hardcode runtime library paths], :, enable_rpath=yes) ]) dnl AC_LIB_FROMPACKAGE(name, package) dnl declares that libname comes from the given package. The configure file dnl will then not have a --with-libname-prefix option but a dnl --with-package-prefix option. Several libraries can come from the same dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar dnl macro call that searches for libname. AC_DEFUN([AC_LIB_FROMPACKAGE], [ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_frompackage_]NAME, [$2]) popdef([NAME]) pushdef([PACK],[$2]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_libsinpackage_]PACKUP, m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1]) popdef([PACKUP]) popdef([PACK]) ]) dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS_BODY], [ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_ARG_WITH(PACK[-prefix], [[ --with-]]PACK[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib --without-]]PACK[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" if test "$acl_libdirstem2" != "$acl_libdirstem" \ && test ! -d "$withval/$acl_libdirstem"; then additional_libdir="$withval/$acl_libdirstem2" fi fi fi ]) dnl Search the library and its dependencies in $additional_libdir and dnl $LDFLAGS. Using breadth-first-seach. LIB[]NAME= LTLIB[]NAME= INC[]NAME= LIB[]NAME[]_PREFIX= dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been dnl computed. So it has to be reset here. HAVE_LIB[]NAME= rpathdirs= ltrpathdirs= names_already_handled= names_next_round='$1 $2' while test -n "$names_next_round"; do names_this_round="$names_next_round" names_next_round= for name in $names_this_round; do already_handled= for n in $names_already_handled; do if test "$n" = "$name"; then already_handled=yes break fi done if test -z "$already_handled"; then names_already_handled="$names_already_handled $name" dnl See if it was already located by an earlier AC_LIB_LINKFLAGS dnl or AC_LIB_HAVE_LINKFLAGS call. uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` eval value=\"\$HAVE_LIB$uppername\" if test -n "$value"; then if test "$value" = yes; then eval value=\"\$LIB$uppername\" test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" eval value=\"\$LTLIB$uppername\" test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" else dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined dnl that this library doesn't exist. So just drop it. : fi else dnl Search the library lib$name in $additional_libdir and $LDFLAGS dnl and the already constructed $LIBNAME/$LTLIBNAME. found_dir= found_la= found_so= found_a= eval libname=\"$acl_libname_spec\" # typically: libname=lib$name if test -n "$acl_shlibext"; then shrext=".$acl_shlibext" # typically: shrext=.so else shrext= fi if test $use_additional = yes; then dir="$additional_libdir" dnl The same code as in the loop below: dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi fi if test "X$found_dir" = "X"; then for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) case "$x" in -L*) dir=`echo "X$x" | sed -e 's/^X-L//'` dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi ;; esac if test "X$found_dir" != "X"; then break fi done fi if test "X$found_dir" != "X"; then dnl Found the library. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" if test "X$found_so" != "X"; then dnl Linking with a shared library. We attempt to hardcode its dnl directory into the executable's runpath, unless it's the dnl standard /usr/lib. if test "$enable_rpath" = no \ || test "X$found_dir" = "X/usr/$acl_libdirstem" \ || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then dnl No hardcoding is needed. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl Use an explicit option to hardcode DIR into the resulting dnl binary. dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $found_dir" fi dnl The hardcoding into $LIBNAME is system dependent. if test "$acl_hardcode_direct" = yes; then dnl Using DIR/libNAME.so during linking hardcodes DIR into the dnl resulting binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode DIR into the resulting dnl binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $found_dir" fi else dnl Rely on "-L$found_dir". dnl But don't add it if it's already contained in the LDFLAGS dnl or the already constructed $LIBNAME haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" fi if test "$acl_hardcode_minus_L" != no; then dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH dnl here, because this doesn't fit in flags passed to the dnl compiler. So give up. No hardcoding. This affects only dnl very old systems. dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" fi fi fi fi else if test "X$found_a" != "X"; then dnl Linking with a static library. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" else dnl We shouldn't come here, but anyway it's good to have a dnl fallback. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" fi fi dnl Assume the include files are nearby. additional_includedir= case "$found_dir" in */$acl_libdirstem | */$acl_libdirstem/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; */$acl_libdirstem2 | */$acl_libdirstem2/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; esac if test "X$additional_includedir" != "X"; then dnl Potentially add $additional_includedir to $INCNAME. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's /usr/local/include and we are using GCC on Linux, dnl 3. if it's already present in $CPPFLAGS or the already dnl constructed $INCNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then for x in $CPPFLAGS $INC[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $INCNAME. INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" fi fi fi fi fi dnl Look for dependencies. if test -n "$found_la"; then dnl Read the .la file. It defines the variables dnl dlname, library_names, old_library, dependency_libs, current, dnl age, revision, installed, dlopen, dlpreopen, libdir. save_libdir="$libdir" case "$found_la" in */* | *\\*) . "$found_la" ;; *) . "./$found_la" ;; esac libdir="$save_libdir" dnl We use only dependency_libs. for dep in $dependency_libs; do case "$dep" in -L*) additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's /usr/local/lib and we are using GCC on Linux, dnl 3. if it's already present in $LDFLAGS or the already dnl constructed $LIBNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \ && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then haveit= if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \ || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LIBNAME. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" fi fi haveit= for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LTLIBNAME. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" fi fi fi fi ;; -R*) dir=`echo "X$dep" | sed -e 's/^X-R//'` if test "$enable_rpath" != no; then dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $dir" fi dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $dir" fi fi ;; -l*) dnl Handle this in the next round. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` ;; *.la) dnl Handle this in the next round. Throw away the .la's dnl directory; it is already contained in a preceding -L dnl option. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` ;; *) dnl Most likely an immediate library name. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" ;; esac done fi else dnl Didn't find the library; assume it is in the system directories dnl known to the linker and runtime loader. (All the system dnl directories known to the linker should also be known to the dnl runtime loader, otherwise the system is severely misconfigured.) LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" fi fi fi done done if test "X$rpathdirs" != "X"; then if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user must dnl pass all path elements in one option. We can arrange that for a dnl single library, but not when more than one $LIBNAMEs are used. alldirs= for found_dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" done dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" else dnl The -rpath options are cumulative. for found_dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$found_dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" done fi fi if test "X$ltrpathdirs" != "X"; then dnl When using libtool, the option that works for both libraries and dnl executables is -R. The -R options are cumulative. for found_dir in $ltrpathdirs; do LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" done fi popdef([PACKLIBS]) popdef([PACKUP]) popdef([PACK]) popdef([NAME]) ]) dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, dnl unless already present in VAR. dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes dnl contains two or three consecutive elements that belong together. AC_DEFUN([AC_LIB_APPENDTOVAR], [ for element in [$2]; do haveit= for x in $[$1]; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X$element"; then haveit=yes break fi done if test -z "$haveit"; then [$1]="${[$1]}${[$1]:+ }$element" fi done ]) dnl For those cases where a variable contains several -L and -l options dnl referring to unknown libraries and directories, this macro determines the dnl necessary additional linker options for the runtime path. dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) dnl sets LDADDVAR to linker options needed together with LIBSVALUE. dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, dnl otherwise linking without libtool is assumed. AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], [ AC_REQUIRE([AC_LIB_RPATH]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) $1= if test "$enable_rpath" != no; then if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode directories into the resulting dnl binary. rpathdirs= next= for opt in $2; do if test -n "$next"; then dir="$next" dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2"; then rpathdirs="$rpathdirs $dir" fi next= else case $opt in -L) next=yes ;; -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2"; then rpathdirs="$rpathdirs $dir" fi next= ;; *) next= ;; esac fi done if test "X$rpathdirs" != "X"; then if test -n ""$3""; then dnl libtool is used for linking. Use -R options. for dir in $rpathdirs; do $1="${$1}${$1:+ }-R$dir" done else dnl The linker is used for linking directly. if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user dnl must pass all path elements in one option. alldirs= for dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" done acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="$flag" else dnl The -rpath options are cumulative. for dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="${$1}${$1:+ }$flag" done fi fi fi fi fi AC_SUBST([$1]) ]) mu-1.6.10/m4/lib-prefix.m4000066400000000000000000000227201414367003600150520ustar00rootroot00000000000000# lib-prefix.m4 serial 14 dnl Copyright (C) 2001-2005, 2008-2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed dnl to access previously installed libraries. The basic assumption is that dnl a user will want packages to use other packages he previously installed dnl with the same --prefix option. dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate dnl libraries, but is otherwise very convenient. AC_DEFUN([AC_LIB_PREFIX], [ AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_ARG_WITH([lib-prefix], [[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib --without-lib-prefix don't search for libraries in includedir and libdir]], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" fi fi ]) if test $use_additional = yes; then dnl Potentially add $additional_includedir to $CPPFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's already present in $CPPFLAGS, dnl 3. if it's /usr/local/include and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= for x in $CPPFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $CPPFLAGS. CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" fi fi fi fi dnl Potentially add $additional_libdir to $LDFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's already present in $LDFLAGS, dnl 3. if it's /usr/local/lib and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then haveit= for x in $LDFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then if test -n "$GCC"; then case $host_os in linux*) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LDFLAGS. LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" fi fi fi fi fi ]) dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, dnl acl_final_exec_prefix, containing the values to which $prefix and dnl $exec_prefix will expand at the end of the configure script. AC_DEFUN([AC_LIB_PREPARE_PREFIX], [ dnl Unfortunately, prefix and exec_prefix get only finally determined dnl at the end of configure. if test "X$prefix" = "XNONE"; then acl_final_prefix="$ac_default_prefix" else acl_final_prefix="$prefix" fi if test "X$exec_prefix" = "XNONE"; then acl_final_exec_prefix='${prefix}' else acl_final_exec_prefix="$exec_prefix" fi acl_save_prefix="$prefix" prefix="$acl_final_prefix" eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" prefix="$acl_save_prefix" ]) dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the dnl variables prefix and exec_prefix bound to the values they will have dnl at the end of the configure script. AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], [ acl_save_prefix="$prefix" prefix="$acl_final_prefix" acl_save_exec_prefix="$exec_prefix" exec_prefix="$acl_final_exec_prefix" $1 exec_prefix="$acl_save_exec_prefix" prefix="$acl_save_prefix" ]) dnl AC_LIB_PREPARE_MULTILIB creates dnl - a variable acl_libdirstem, containing the basename of the libdir, either dnl "lib" or "lib64" or "lib/64", dnl - a variable acl_libdirstem2, as a secondary possible value for dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or dnl "lib/amd64". AC_DEFUN([AC_LIB_PREPARE_MULTILIB], [ dnl There is no formal standard regarding lib and lib64. dnl On glibc systems, the current practice is that on a system supporting dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine dnl the compiler's default mode by looking at the compiler's library search dnl path. If at least one of its elements ends in /lib64 or points to a dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI. dnl Otherwise we use the default, namely "lib". dnl On Solaris systems, the current practice is that on a system supporting dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([gl_HOST_CPU_C_ABI_32BIT]) case "$host_os" in solaris*) AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef _LP64 int ok; #else error fail #endif ]])], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no]) ]);; esac dnl Allow the user to override the result by setting acl_cv_libdirstems. AC_CACHE_CHECK([for the common suffixes of directories in the library search path], [acl_cv_libdirstems], [acl_libdirstem=lib acl_libdirstem2= case "$host_os" in solaris*) dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment dnl . dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the dnl symlink is missing, so we set acl_libdirstem2 too. if test $gl_cv_solaris_64bit = yes; then acl_libdirstem=lib/64 case "$host_cpu" in sparc*) acl_libdirstem2=lib/sparcv9 ;; i*86 | x86_64) acl_libdirstem2=lib/amd64 ;; esac fi ;; *) dnl If $CC generates code for a 32-bit ABI, the libraries are dnl surely under $prefix/lib, not $prefix/lib64. if test "$HOST_CPU_C_ABI_32BIT" != yes; then dnl The result is a property of the system. However, non-system dnl compilers sometimes have odd library search paths. Therefore dnl prefer asking /usr/bin/gcc, if available, rather than $CC. searchpath=`(if test -f /usr/bin/gcc \ && LC_ALL=C /usr/bin/gcc -print-search-dirs >/dev/null 2>/dev/null; then \ LC_ALL=C /usr/bin/gcc -print-search-dirs; \ else \ LC_ALL=C $CC -print-search-dirs; \ fi) 2>/dev/null \ | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` if test -n "$searchpath"; then acl_save_IFS="${IFS= }"; IFS=":" for searchdir in $searchpath; do if test -d "$searchdir"; then case "$searchdir" in */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; */../ | */.. ) # Better ignore directories of this form. They are misleading. ;; *) searchdir=`cd "$searchdir" && pwd` case "$searchdir" in */lib64 ) acl_libdirstem=lib64 ;; esac ;; esac fi done IFS="$acl_save_IFS" fi fi ;; esac test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" acl_cv_libdirstems="$acl_libdirstem,$acl_libdirstem2" ]) # Decompose acl_cv_libdirstems into acl_libdirstem and acl_libdirstem2. acl_libdirstem=`echo "$acl_cv_libdirstems" | sed -e 's/,.*//'` acl_libdirstem2=`echo "$acl_cv_libdirstems" | sed -e '/,/s/.*,//'` ]) mu-1.6.10/man/000077500000000000000000000000001414367003600127775ustar00rootroot00000000000000mu-1.6.10/man/Makefile.am000066400000000000000000000021051414367003600150310ustar00rootroot00000000000000## Copyright (C) 2008-2020 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk dist_man_MANS = \ mu-add.1 \ mu-bookmarks.5 \ mu-cfind.1 \ mu-easy.1 \ mu-extract.1 \ mu-find.1 \ mu-help.1 \ mu-index.1 \ mu-info.1 \ mu-init.1 \ mu-mkdir.1 \ mu-query.7 \ mu-remove.1 \ mu-server.1 \ mu-script.1 \ mu-verify.1 \ mu-view.1 \ mu.1 mu-1.6.10/man/meson.build000066400000000000000000000021171414367003600151420ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. install_man( ['mu.1', 'mu-add.1', 'mu-bookmarks.5', 'mu-cfind.1', 'mu-easy.1', 'mu-extract.1', 'mu-find.1', 'mu-help.1', 'mu-index.1', 'mu-info.1', 'mu-init.1', 'mu-mkdir.1', 'mu-query.7', 'mu-remove.1', 'mu-script.1', 'mu-server.1', 'mu-verify.1', 'mu-view.1']) mu-1.6.10/man/mu-add.1000066400000000000000000000020201414367003600142220ustar00rootroot00000000000000.TH MU ADD 1 "July 2012" "User Manuals" .SH NAME mu add\- add one or more messages to the database .SH SYNOPSIS .B mu add [] .SH DESCRIPTION \fBmu add\fR is the command to add specific message files to the database. Each file must be specified with an absolute path. .SH OPTIONS \fBmu add\fR does not have its own options, but the general options for determining the location of the database (\fI--muhome\fR) are available. See \fBmu-index\fR(1) for more information. .SH RETURN VALUE \fBmu add\fR returns 0 upon success; in general, the following error codes are returned: .nf | code | meaning | |------+-----------------------------------| | 0 | ok | | 1 | general error | | 5 | some database update error | .fi .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-index (1), .BR mu-remove (1) mu-1.6.10/man/mu-bookmarks.5000066400000000000000000000020241414367003600154720ustar00rootroot00000000000000.TH MU-BOOKMARKS 5 "July 2019" "User Manuals" .SH NAME bookmarks \- file with bookmarks (shortcuts) for mu search expressions .SH DESCRIPTION Bookmarks are named shortcuts for search queries. They allow using a convenient name for often-used queries. The bookmarks are also visible as shortcuts in the mu experimental user interfaces, \fImug\fR and \fImug2\fR. The bookmarks file is read from \fI/bookmarks\fR. On Unix this would typically be w be \fI~/.config/mu/bookmarks\fR, but this can be influenced using the \fB\-\-muhome\fR parameter for \fBmu-find\fR(1) and \fBmug\fR(1). The bookmarks file is a typical key=value \fB.ini\fR-file, which is best shown by means of an example: .nf [mu] inbox=maildir:/inbox # inbox oldhat=maildir:/archive subject:hat # archived with subject containing 'hat' .fi The \fB[mu]\fR group header is required. For practical uses of bookmarks, see \fBmu-find\fR(1). .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), mu-find (1) mu-1.6.10/man/mu-cfind.1000066400000000000000000000100371414367003600145640ustar00rootroot00000000000000.TH MU CFIND 1 "April 2019" "User Manuals" .SH NAME \fBmu cfind\fR is the \fBmu\fR command to find contacts in the \fBmu\fR database and export them for use in other programs. .SH SYNOPSIS .B mu cfind [options] [] .SH DESCRIPTION \fBmu cfind\fR is the \fBmu\fR command for finding \fIcontacts\fR (name and e-mail address of people who were either an e-mail's sender or receiver). There are different output formats available, for importing the contacts into other programs. .SH SEARCHING CONTACTS When you index your messages (see \fBmu index\fR), \fBmu\fR creates a list of unique e-mail addresses found and the accompanying name, and caches this list. In case the same e-mail address is used with different names, the most recent non-empty name is used. \fBmu cfind\fR starts a search for contacts that match a \fIregular expression\fR. For example: .nf $ mu cfind '@gmail\.com' .fi would find all contacts with a gmail-address, while .nf $ mu cfind Mary .fi lists all contacts with Mary in either name or e-mail address. If you do not specify a search expression, \fBmu cfind\fR returns the full list of contacts. Note, \fBmu cfind\fR uses a cache with the e-mail information, which is populated during the indexing process. The regular expressions are Perl-compatible (as per the PCRE-library used by GRegex). .SH OPTIONS .TP \fB\-\-format\fR=\fIplain|mutt-alias|mutt-ab|wl|org-contact|bbdb|csv\fR sets the output format to the given value. The following are available: .nf | --format= | description | |-------------+-----------------------------------| | plain | default, simple list | | mutt-alias | mutt alias-format | | mutt-ab | mutt external address book format | | wl | wanderlust addressbook format | | org-contact | org-mode org-contact format | | bbdb | BBDB format | | csv | comma-separated values (*) | .fi (*) CSV is not fully standardized, but \fBmu cfind\fR follows some common practices: any double-quote is replaced by a double-double quote (thus, "hello" become ""hello"", and fields with commas are put in double-quotes. Normally, this should only apply to name fields. .TP \fB\-\-personal\fR only show addresses seen in messages where one of 'my' e-mail addresses was seen in one of the address fields; this is to exclude addresses only seen in mailing-list messages. See the \fB\-\-my-address\fR parameter in \fBmu index\fR. .TP \fB\-\-after=\fR\fI\fR only show addresses last seen after \fI\fR. \fI\fR is a UNIX \fBtime_t\fR value, the number of seconds since 1970-01-01 (in UTC). From the command line, you can use the \fBdate\fR command to get this value. For example, only consider addresses last seen after 2009-06-01, you could specify .nf --after=`date +%s --date='2009-06-01'` .fi .SH RETURN VALUE \fBmu cfind\fR returns 0 upon successful completion -- that is, at least one contact was found. Anything else leads to a non-zero return value: .nf | code | meaning | |------+--------------------------------| | 0 | ok | | 1 | general error | | 2 | no matches (for 'mu cfind') | .fi .SH INTEGRATION WITH MUTT You can use \fBmu cfind\fR as an external address book server for \fBmutt\fR. For this to work, add the following to your \fImuttrc\fR: .nf set query_command = "mu cfind --format=mutt-ab '%s'" .fi Now, in mutt, you can search for e-mail addresses using the \fBquery\fR-command, which is (by default) accessible by pressing \fBQ\fR. .SH ENCODING \fBmu cfind\fR output is encoded according to the current locale except for \fI--format=bbdb\fR. This is hard-coded to UTF-8, and as such specified in the output-file, so emacs/bbdb can handle things correctly, without guessing. .SH BUGS Please report bugs if you find them at \fBhttps://github.com/djcb/mu/issues\fR. .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-index (1), .BR mu-find (1), .BR pcrepattern(3) mu-1.6.10/man/mu-easy.1000066400000000000000000000230501414367003600144410ustar00rootroot00000000000000.TH MU-EASY 1 "February 2020" "User Manuals" .SH NAME mu easy \- a quick introduction to mu .SH DESCRIPTION \fBmu\fR is a set of tools for dealing with e-mail messages in Maildirs. There are many options, which are all described in the man pages for the various sub-commands. This man pages jumps over all of the details and gives examples of some common use cases. If the use cases described here do not precisely do what you want, please check the more extensive information in the man page about the sub-command you are using -- for example, the \fBmu-index\fR(1) or \fBmu-find\fR(1) man pages. \fBNOTE\fR: the \fBindex\fR command (and therefore, the ones that depend on that, such as \fBfind\fR), require that you store your mail in the Maildir-format. If you don't do so, you can still use the other commands, but you won't be able to index/search your mail. By default, \fBmu\fR uses colorized output when it thinks your terminal is capable of doing so. If you don't like color, you can use the \fB--nocolor\fR command-line option, or set either the \fBMU_NOCOLOR\fR or the \fBNO_COLOR\fR environment variable to non-empty. .SH SETTING THINGS UP The first time you run the mu commands, you need to initialize it. This is done with the \fBinit\fR command. .nf \fB$ mu init\fR .fi This uses the defaults (see \fBmu-init(1)\fR for details on how to change that). .SH INDEXING YOUR E-MAIL Before you can search e-mails, you'll first need to index them: .nf \fB$ mu index\fR .fi The process can take a few minutes, depending on the amount of mail you have, the speed of your computer, hard drive etc. Usually, indexing should be able to reach a speed of a few hundred messages per second. \fBmu index\fR guesses the top-level Maildir to do its job; if it guesses wrongly, you can use the \fI--maildir\fR option to specify the top-level directory that should be processed. See the \fBmu-index\fR(1) man page for more details. Normally, \fBmu index\fR visits all the directories under the top-level Maildir; however, you can exclude certain directories (say, the 'trash' or 'spam' folders) by creating a file called \fI.noindex\fR in the directory. When \fBmu\fR sees such a file, it will exclude this directory and its sub-directories from indexing. Also see \fB.noupdate\fR in the \fBmu-index\fR(1) manpage. .SH SEARCHING YOUR E-MAIL After you have indexed your mail, you can start searching it. By default, the search results are printed on standard output. Alternatively, the output can take the form of Maildir with symbolic links to the found messages. This enables integration with e-mail clients; see the \fBmu-find\fR(1) man page for details, the syntax of the search parameters and so on. Here, we just give some examples for common cases. First, let's search for all messages sent to Julius (Caesar) regarding fruit: .nf \fB$ mu find t:julius fruit\fR .fi This should return something like: .nf 2008-07-31T21:57:25 EEST John Milton Fere libenter homines id quod volunt credunt .fi This means there is a message to 'julius' with 'fruit' somewhere in the message. In this case, it's a message from John Milton. Note that the date format depends on your the language/locale you are using. How do we know that the message was sent to Julius Caesar? Well, it's not visible from the results above, because the default fields that are shown are date/sender/subject. However, we can change this using the \fI--fields\fR parameter (see the \fBmu-find\fR(1) man page for the details): .nf \fB$ mu find --fields="t s" t:julius fruit\fR .fi In other words, display the 'To:'-field (t) and the subject (s). This should return something like: .nf Julius Caesar Fere libenter homines id quod volunt credunt .fi This is the same message found before, only with some different fields displayed. By default, \fBmu\fR uses the logical AND for the search parameters -- that is, it displays messages that match all the parameters. However, we can use logical OR as well: .nf \fB$ mu find t:julius OR f:socrates\fR .fi In other words, display messages that are either sent to Julius Caesar \fBor\fR are from Socrates. This could return something like: .nf 2008-07-31T21:57:25 EEST Socrates cool stuff 2008-07-31T21:57:25 EEST John Milton Fere libenter homines id quod volunt credunt .fi What if we want to see some of the body of the message? You can get a 'summary' of the first lines of the message using the \fI--summary-len\fR option, which will 'summarize' the first \fIn\fR lines of the message: .nf \fB$ mu find --summary-len=3 napoleon m:/archive\fR .fi .nf 1970-01-01T02:00:00 EET Napoleon Bonaparte rock on dude Summary: Le 24 février 1815, la vigie de Notre-Dame de la Garde signala le trois-mâts le Pharaon, venant de Smyrne, Trieste et Naples. Comme d'habitude, un pilote côtier partit aussitôt du port, rasa le château .fi The summary consists of the first n lines of the message with all superfluous whitespace removed. Also note the \fBm:/archive\fR parameter in the query. This means that we only match messages in a maildir called '/archive'. .SH MORE QUERIES Let's list a few more queries that may be interesting; please note that searches for message flags, priority and date ranges are only available in mu version 0.9 or later. Get all important messages which are signed: .nf \fB$ mu find flag:signed prio:high \fR .fi Get all messages from Jim without an attachment: .nf \fB$ mu find from:jim AND NOT flag:attach\fR .fi Get all messages where Jack is in one of the contact fields: .nf \fB$ mu find contact:jack\fR .fi This uses the special contact: pseudo-field which matches (\fBfrom\fR, \fBto\fR, \fBcc\fR and \fBbcc\fR). Get all messages in the Sent Items folder about yoghurt: .nf \fB$mu find maildir:'/Sent Items' yoghurt\fR .fi Note how we need to quote search terms that include spaces. Get all unread messages where the subject mentions Ångström: .nf \fB$ mu find subject:Ångström flag:unread\fR .fi which is equivalent to: .nf \fB$ mu find subject:angstrom flag:unread\fR .fi because does mu is case-insensitive and accent-insensitive. Get all unread messages between March 2002 and August 2003 about some bird (or a Swedish rock band): .nf \fB$ mu find date:20020301..20030831 nightingale flag:unread\fR .fi Get all messages received today: .nf \fB$ mu find date:today..now\fR .fi Get all messages we got in the last two weeks about emacs: .nf \fB$ mu find date:2w..now emacs\fR .fi Another powerful feature (since 0.9.6) are wildcard searches, where you can search for the last \fIn\fR characters in a word. For example, you can search for: .nf \fB$ mu find 'subject:soc*'\fR .fi and get mails about soccer, Socrates, society, and so on. Note, it's important to quote the search query, otherwise the shell will interpret the '*'. You can also search for messages with a certain attachment using their filename, for example: .nf \fB$ mu find 'file:pic*'\fR .fi will get you all messages with an attachment starting with 'pic'. If you want to find attachments with a certain MIME-type, you can use the following: Get all messages with PDF attachments: .nf \fB$ mu find mime:application/pdf\fR .fi or even: Get all messages with image attachments: .nf \fB$ mu find 'mime:image/*'\fR .fi Note that (1) the '*' wildcard can only be used as the rightmost thing in a search query, and (2) that you need to quote the search term, because otherwise your shell will interpret the '*' (expanding it to all files in the current directory -- probably not what you want). .SH DISPLAYING MESSAGES We might also want to display the complete messages instead of the header information. This can be done using \fBmu view\fR command. Note that this command does not use the database; you simply provide it the path to a message. Therefore, if you want to display some message from a search query, you'll need its path. To get the path (think \fBl\fRocation) for our first example we can use: .nf \fB$ mu find --fields="l" t:julius fruit\fR .fi And we'll get something like: .nf /home/someuser/Maildir/archive/cur/1266188485_0.6850.cthulhu:2, .fi We can now display this message: .nf \fB$ mu view /home/someuser/Maildir/archive/cur/1266188485_0.6850.cthulhu:2,\fR From: John Milton To: Julius Caesar Subject: Fere libenter homines id quod volunt credunt Date: 2008-07-31T21:57:25 EEST OF Mans First Disobedience, and the Fruit Of that Forbidden Tree, whose mortal taste Brought Death into the World, and all our woe, [...] .fi .SH FINDING CONTACTS While \fBmu find\fR searches for messages, there is also \fBmu cfind\fR to find \fIcontacts\fR, that is, names + addresses. Without any search expression, \fBmu cfind\fR lists all of your contacts. .nf \fB$ mu cfind julius\fR .fi will find all contacts with 'julius' in either name or e-mail address. Note that \fBmu cfind\fR accepts a \fIregular expression\fR. \fBmu cfind\fR also supports a \fI--format=\fR-parameter, which sets the output to some specific format, so the results can be imported into another program. For example, to export your contact information to a \fBmutt\fR address book file, you can use something like: .nf \fB$ mu cfind --format=mutt-alias > ~/mutt-aliases \fR .fi Then, you can use them in \fBmutt\fR if you add something like \fBsource ~/mutt-aliases\fR to your \fImuttrc\fR. .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-init (1), .BR mu-index (1), .BR mu-find (1), .BR mu-mfind (1), .BR mu-mkdir (1), .BR mu-view (1), .BR mu-extract (1) mu-1.6.10/man/mu-extract.1000066400000000000000000000052501414367003600151540ustar00rootroot00000000000000.TH MU EXTRACT 1 "July 2012" "User Manuals" .SH NAME \fBmu extract\fR is the \fBmu\fR command to display and save message parts (attachments), and open them with other tools. .SH SYNOPSIS .B mu extract [options] .B mu extract [options] .SH DESCRIPTION \fBmu extract\fR is the \fBmu\fR sub-command for extracting MIME-parts (e.g., attachments) from mail messages. The sub-command works on message files, and does not require the message to be indexed in the database. For attachments, the file name used when saving it is the name of the attachment in the message. If there is no such name, or when saving non-attachment MIME-parts, a name is derived from the message-id of the message. If you specify a pattern (a case-insensitive regular expression) as the second argument, all attachments with filenames matching that pattern will be extracted. The regular expressions are Perl-compatible (as per the PCRE-library). Without any options, \fBmu extract\fR simply outputs the list of leaf MIME-parts in the message. Only 'leaf' MIME-parts (including RFC822 attachments) are considered, \fBmultipart/*\fR etc. are ignored. .SH OPTIONS .TP \fB\-a\fR, \fB\-\-save\-attachments\fR save all MIME-parts that look like attachments. .TP \fB\-\-save\-all\fR save all non-multipart MIME-parts. .TP \fB\-\-parts\fR= only consider the following numbered parts (comma-separated list). The numbers for the parts can be seen from running \fBmu extract\fR without any options but only the message file. .TP \fB\-\-target\-dir\fR= save the parts in the target directory rather than the current working directory. .TP \fB\-\-overwrite\fR overwrite existing files with the same name; by default overwriting is not allowed. .TP \fB\-\-play\fR Try to 'play' (open) the attachment with the default application for the particular file type. On MacOS, this uses the \fBopen\fR program, on other platforms it uses \fBxdg-open\fR. You can choose a different program by setting the \fBMU_PLAY_PROGRAM\fR environment variable. .SH EXAMPLES To display information about all the MIME-parts in a message file: .nf $ mu extract msgfile .fi To extract MIME-part 3 and 4 from this message, overwriting existing files with the same name: .nf $ mu extract --parts=3,4 --overwrite msgfile .fi To extract all files ending in '.jpg' (case-insensitive): .nf $ mu extract msgfile '.*\.jpg' .fi To extract an mp3-file, and play it in the default mp3-playing application: .nf $ mu extract --play msgfile 'whoopsididitagain.mp3' .fi .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1) mu-1.6.10/man/mu-find.1000066400000000000000000000273051414367003600144270ustar00rootroot00000000000000.TH MU FIND 1 "22 June 2021" "User Manuals" .SH NAME mu find \- find e-mail messages in the \fBmu\fR database. .SH SYNOPSIS .B mu find [options] .SH DESCRIPTION \fBmu find\fR is the \fBmu\fR command for searching e-mail message that were stored earlier using \fBmu index\fR(1). .SH SEARCHING MAIL \fBmu find\fR starts a search for messages in the database that match some search pattern. The search patterns are described in detail in .BR mu-query (7). . For example: .nf $ mu find subject:snow and date:2017.. .fi would find all messages in 2017 with 'snow' in the subject field, e.g: .nf 2009-03-05 17:57:33 EET Lucia running in the snow 2009-03-05 18:38:24 EET Marius Re: running in the snow .fi Note, this the default, plain-text output, which is the default, so you don't have to use \fB--format=plain\fR. For other types of output (such as symlinks, XML or s-expressions), see the discussion in the \fBOPTIONS\fR-section below about \fB--format\fR. The search pattern is taken as a command-line parameter. If the search parameter consists of multiple parts (as in the example) they are treated as if there were a logical \fBand\fR between them. For details on the possible queries, see .BR mu-query (7). .SH OPTIONS Note, some of the important options are described in the \fBmu\fR(1) man-page and not here, as they apply to multiple mu-commands. The \fBfind\fR-command has various options that influence the way \fBmu\fR displays the results. If you don't specify anything, the defaults are \fI\-\-fields="d f s"\fR, \fI\-\-sortfield=date\fR and \fI\-\-reverse\fR. .TP \fB\-f\fR, \fB\-\-fields\fR=\fI\fR specifies a string that determines which fields are shown in the output. This string consists of a number of characters (such as 's' for subject or 'f' for from), which will replace with the actual field in the output. Fields that are not known will be output as-is, allowing for some simple formatting. For example: .nf $ mu find subject:snow --fields "d f s" .fi would list the date, subject and sender of all messages with 'snow' in the their subject. The table of replacement characters is superset of the list mentions for search parameters; the complete list: .nf t \fBt\fRo: recipient c \fBc\fRc: (carbon-copy) recipient h Bcc: (blind carbon-copy, \fBh\fRidden) recipient d Sent \fBd\fRate of the message f Message sender (\fBf\fRrom:) g Message flags (fla\fBg\fRs) l Full path to the message (\fBl\fRocation) p Message \fBp\fRriority (high, normal, low) s Message \fBs\fRubject i Message-\fBi\fRd m \fBm\fRaildir v Mailing-list Id .fi The message flags are described in \fBmu-query\fR(7). As an example, a message which is 'seen', has an attachment and is signed would have 'asz' as its corresponding output string, while an encrypted new message would have 'nx'. .TP \fB\-s\fR, \fB\-\-sortfield\fR \fR=\fI\fR and \fB\-z\fR, \fB\-\-reverse\fR specifies the field to sort the search results by, and the direction (i.e., 'reverse' means that the sort should be reverted - Z-A). The following fields are supported: .nf cc,c Cc (carbon-copy) recipient(s) bcc,h Bcc (blind-carbon-copy) recipient(s) date,d Message sent date from,f Message sender maildir,m Maildir msgid,i Message id prio,p Nessage priority subject,s Message subject to,t To:-recipient(s) list,v Mailing-list id .fi Thus, for example, to sort messages by date, you could specify: .nf $ mu find fahrrad --fields "d f s" --sortfield=date --reverse .fi Note, if you specify a sortfield, by default, messages are sorted in reverse (descending) order (e.g., from lowest to highest). This is usually a good choice, but for dates it may be more useful to sort in the opposite direction. .TP \fB\-n\fR, \fB\-\-maxnum=\fR If > 0, display maximally that number of entries. If not specified, all matching entries are displayed. .TP \fB\-\-summary-len=\fR If > 0, use that number of lines of the message to provide a summary. .TP \fB\-\-format\fR=\fIplain|links|xquery|xml|sexp\fR output results in the specified format. The default is \fBplain\fR, i.e normal output with one line per message. \fBlinks\fR outputs the results as a maildir with symbolic links to the found messages. This enables easy integration with mail-clients (see below for more information). \fBxml\fR formats the search results as XML. \fBsexp\fR formats the search results as an s-expression as used in Lisp programming environments. \fBxquery\fR shows the Xapian query corresponding to your search terms. This is meant for for debugging purposes. .TP \fB\-\-linksdir\fR \fR=\fI\fR and \fB\-c\fR, \fB\-\-clearlinks\fR output the results as a maildir with symbolic links to the found messages. This enables easy integration with mail-clients (see below for more information). \fBmu\fR will create the maildir if it does not exist yet. If you specify \fB\-\-clearlinks\fR, all existing symlinks will be cleared from the target directories; this allows for re-use of the same maildir. However, this option will delete any symlink it finds, so be careful. .nf $ mu find grolsch --format=links --linksdir=~/Maildir/search --clearlinks .fi will store links to found messages in \fI~/Maildir/search\fR. If the directory does not exist yet, it will be created. Note: when \fBmu\fR creates a Maildir for these links, it automatically inserts a \fI.noindex\fR file, to exclude the directory from \fBmu index\fR. .TP \fB\-\-after=\fR\fI\fR only show messages whose message files were last modified (\fBmtime\fR) after \fI\fR. \fI\fR is a UNIX \fBtime_t\fR value, the number of seconds since 1970-01-01 (in UTC). From the command line, you can use the \fBdate\fR command to get this value. For example, only consider messages modified (or created) in the last 5 minutes, you could specify .nf --after=`date +%s --date='5 min ago'` .fi This is assuming the GNU \fBdate\fR command. .TP \fB\-\-exec\fR=\fI\fR the \fB\-\-exec\fR command causes the \fIcommand\fR to be executed on each matched message; for example, to see the raw text of all messages matching 'milkshake', you could use: .nf $ mu find milkshake --exec='less' .fi which is roughly equivalent to: .nf $ mu find milkshake --fields="l" | xargs less .fi .TP \fB\-b\fR, \fB\-\-bookmark\fR=\fI\fR use a bookmarked search query. Using this option, a query from your bookmark file will be prepended to other search queries. See \fBmu-bookmarks\fR(1) for the details of the bookmarks file. .TP \fB\-\-skip\-dups\fR,\fB-u\fR whenever there are multiple messages with the same name, only show the first one. This is useful if you have copies of the same message, which is a common occurrence when using e.g. Gmail together with \fBofflineimap\fR. .TP \fB\-\-include\-related\fR,\fB-r\fR also include messages being referred to by the matched messages -- i.e.. include messages that are part of the same message thread as some matched messages. This is useful if you want Gmail-style 'conversations'. Note, finding these related messages make searches slower. .TP \fB\-t\fR, \fB\-\-threads\fR show messages in a 'threaded' format -- that is, with indentation and arrows showing the conversation threads in the list of matching messages. When using this, sorting is chronological (by date), based on the newest message in a thread. Messages in the threaded list are indented based on the depth in the discussion, and are prefix with a kind of arrow with thread-related information about the message, as in the following table: .nf | | normal | orphan | duplicate | |-------------+--------+--------+-----------| | first child | `-> | `*> | `=> | | other | |-> | |*> | |=> | .fi Here, an 'orphan' is a message without a parent message (in the list of matches), and a duplicate is a message whose message-id was already seen before; not this may not really be the same message, if the message-id was copied. The algorithm used for determining the threads is based on Jamie Zawinksi's description: .BR http://www.jwz.org/doc/threading.html .SS Integrating mu find with mail clients .TP \fBmutt\fR For \fBmutt\fR you can use the following in your \fImuttrc\fR; pressing the F8 key will start a search, and F9 will take you to the results. .nf # mutt macros for mu macro index "mu find --clearlinks --format=links --linksdir=~/Maildir/search " \\ "mu find" macro index "~/Maildir/search" \\ "mu find results" .fi .TP \fBWanderlust\fR \fBSam B\fR suggested the following on the \fBmu\fR-mailing list. First add the following to your Wanderlust configuration file: .nf (require 'elmo-search) (elmo-search-register-engine 'mu 'local-file :prog "/usr/local/bin/mu" ;; or wherever you've installed it :args '("find" pattern "--fields" "l") :charset 'utf-8) (setq elmo-search-default-engine 'mu) ;; for when you type "g" in folder or summary. (setq wl-default-spec "[") .fi Now, you can search using the \fBg\fR key binding; you can also create permanent virtual folders when the messages matching some expression by adding something like the following to your \fIfolders\fR file. .nf VFolders { [date:today..now]!mu "Today" [size:1m..100m]!mu "Big" [flag:unread]!mu "Unread" } .fi After restarting Wanderlust, the virtual folders should appear. \fBWanderlust (old)\fR Another way to integrate \fBmu\fR and \fBwanderlust\fR is shown below; the aforementioned method is recommended, but if that does not work for some reason, the below can be an alternative. .nf (defvar mu-wl-mu-program "/usr/local/bin/mu") (defvar mu-wl-search-folder "search") (defun mu-wl-search () "search for messages with `mu', and jump to the results" (let* ((muexpr (read-string "Find messages matching: ")) (sfldr (concat elmo-maildir-folder-path "/" mu-wl-search-folder)) (cmdline (concat mu-wl-mu-program " find " "--clearlinks --format=links --linksdir='" sfldr "' " muexpr)) (rv (shell-command cmdline))) (cond ((= rv 0) (message "Query succeeded")) ((= rv 2) (message "No matches found")) (t (message "Error running query"))) (= rv 0))) (defun mu-wl-search-and-goto () "search and jump to the folder with the results" (interactive) (when (mu-wl-search) (wl-summary-goto-folder-subr (concat "." mu-wl-search-folder) 'force-update nil nil t) (wl-summary-sort-by-date))) ;; querying both in summary and folder (define-key wl-summary-mode-map (kbd "Q") ;; => query '(lambda()(interactive)(mu-wl-search-and-goto))) (define-key wl-folder-mode-map (kbd "Q") ;; => query '(lambda()(interactive)(mu-wl-search-and-goto))) .fi .SH RETURN VALUE \fBmu find\fR returns 0 upon successful completion; if the search was performed, there needs to be a least one match. Anything else leads to a non-zero return value, for example: .nf | code | meaning | |------+--------------------------------| | 0 | ok | | 1 | general error | | 2 | no matches (for 'mu find') | | 4 | database is corrupted | .fi .SH ENCODING \fBmu find\fR output is encoded according the locale for \fI--format=plain\fR (the default), and UTF-8 for all other formats (\fIsexp\fR, \fIxml\fR). .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues If you have specific messages which are not matched correctly, please attach them (appropriately censored if needed). .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-index (1), .BR mu-query (7) mu-1.6.10/man/mu-help.1000066400000000000000000000010511414367003600144250ustar00rootroot00000000000000.TH MU HELP 1 "July 2012" "User Manuals" .SH NAME \fBmu help\fR is a \fBmu\fR command that gives help information about mu commands. .SH SYNOPSIS .B mu help .SH DESCRIPTION \fBmu help\fR provides help information about mu commands. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu-index (1), .BR mu-find (1), .BR mu-cfind (1), .BR mu-mkdir (1), .BR mu-view (1), .BR mu-extract (1), .BR mu-easy (1), .BR mu-bookmarks (5) mu-1.6.10/man/mu-index.1000066400000000000000000000154451414367003600146200ustar00rootroot00000000000000.TH MU-INDEX 1 "November 2021" "User Manuals" .SH NAME mu index \- index e-mail messages stored in Maildirs .SH SYNOPSIS .B mu index [options] .SH DESCRIPTION \fBmu index\fR is the \fBmu\fR command for scanning the contents of Maildir directories and storing the results in a Xapian database. The data can then be queried using .BR mu-find (1)\. Note that before the first time you run \fBmu index\fR, you must run \fBmu init\fR to initialize the database. \fBindex\fR understands Maildirs as defined by Daniel Bernstein for \fBqmail\fR(7). In addition, it understands recursive Maildirs (Maildirs within Maildirs), Maildir++. It can also deal with VFAT-based Maildirs which use '!' or ';' as the separators instead of ':'. E-mail messages which are not stored in something resembling a maildir leaf-directory (\fIcur\fR and \fInew\fR) are ignored, as are the cache directories for \fInotmuch\fR and \fIgnus\fR, and any dot-directory. Starting with mu 1.5.x, symlinks are followed, and can be spread over multiple filesystems; however note that moving files around is much faster when multiple filesystems are not involved. If there is a file called \fI.noindex\fR in a directory, the contents of that directory and all of its subdirectories will be ignored. This can be useful to exclude certain directories from the indexing process, for example directories with spam-messages. If there is a file called \fI.noupdate\fR in a directory, the contents of that directory and all of its subdirectories will be ignored, unless we do a full rebuild (with \fBmu init\fR). This can be useful to speed up things you have some maildirs that never change. Note that you can still search for these messages, this only affects updating the database. \fI.noupdate\fR is ignored when you start indexing with an empty database (such as directly after \fImu init\fR. There also the \fB--lazy-check\fR which can greatly speed up indexing; see below for details. The first run of \fBmu index\fR may take a few minutes if you have a lot of mail (tens of thousands of messages). Fortunately, such a full scan needs to be done only once; after that it suffices to index the changes, which goes much faster. See the 'Note on performance (i,ii,iii)' below for more information. The optional 'phase two' of the indexing-process is the removal of messages from the database for which there is no longer a corresponding file in the Maildir. If you do not want this, you can use \fB\-n\fR, \fB\-\-nocleanup\fR. When \fBmu index\fR catches one of the signals \fBSIGINT\fR, \fBSIGHUP\fR or \fBSIGTERM\fR (e.g., when you press Ctrl-C during the indexing process), it tries to shutdown gracefully; it tries to save and commit data, and close the database etc. If it receives another signal (e.g., when pressing Ctrl-C once more), \fBmu index\fR will terminate immediately. .SH OPTIONS Note, some of the general options are described in the \fBmu(1)\fR man-page and not here, as they apply to multiple mu commands. .TP \fB\-\-lazy-check\fR in lazy-check mode, \fBmu\fR does not consider messages for which the time-stamp (ctime) of the directory they reside in has not changed since the previous indexing run. This is much faster than the non-lazy check, but won't update messages that have change (rather than having been added or removed), since merely editing a message does not update the directory time-stamp. Of course, you can run \fBmu-index\fR occasionally without \fB\-\-lazy-check\fR, to pick up such messages. .TP \fB\-\-nocleanup\fR disables the database cleanup that \fBmu\fR does by default after indexing. .SS A note on performance (i) As a non-scientific benchmark, a simple test on the author's machine (a Thinkpad X61s laptop using Linux 2.6.35 and an ext3 file system) with no existing database, and a maildir with 27273 messages: .nf $ sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches' $ time mu index --quiet 66,65s user 6,05s system 27% cpu 4:24,20 total .fi (about 103 messages per second) A second run, which is the more typical use case when there is a database already, goes much faster: .nf $ sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches' $ time mu index --quiet 0,48s user 0,76s system 10% cpu 11,796 total .fi (more than 56818 messages per second) Note that each test flushes the caches first; a more common use case might be to run \fBmu index\fR when new mail has arrived; the cache may stay quite 'warm' in that case: .nf $ time mu index --quiet 0,33s user 0,40s system 80% cpu 0,905 total .fi which is more than 30000 messages per second. .SS A note on performance (ii) As per June 2012, we did the same non-scientific benchmark, this time with an Intel i5-2500 CPU @ 3.30GHz, an ext4 file system and a maildir with 22589 messages. We start without an existing database. .nf $ sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches' $ time mu index --quiet 27,79s user 2,17s system 48% cpu 1:01,47 total .fi (about 813 messages per second) A second run, which is the more typical use case when there is a database already, goes much faster: .nf $ sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches' $ time mu index --quiet 0,13s user 0,30s system 19% cpu 2,162 total .fi (more than 173000 messages per second) .SS A note on performance (iii) As per July 2016, we did the same non-scientific benchmark, again with the Intel i5-2500 CPU @ 3.30GHz, an ext4 file system. This time, the maildir contains 72525 messages. .nf $ sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches' $ time mu index --quiet 40,34s user 2,56s system 64% cpu 1:06,17 total .fi (about 1099 messages per second). As shown, \fBmu\fR has been getting faster with each release, even with relatively expensive new features such as text-normalization (for case-insensitve/accent-insensitive matching). The profiles are dominated by operations in the Xapian database now. .SH FILES \fBmu\fR stores logs of its operations and queries in \fI/mu.log\fR (by default, this is \fI~/.cache/mu/mu.log\fR). Upon startup, \fBmu\fR checks the size of this log file. If it exceeds 1 MB, it will be moved to \fI~/.cache/mu/mu.log.old\fR, overwriting any existing file of that name, and start with an empty log file. This scheme allows for continued use of \fBmu\fR without the need for any manual maintenance of log files. .SH ENVIRONMENT \fBmu index\fR uses \fBMAILDIR\fR to find the user's Maildir if it has not been specified explicitly with \fB\-\-maildir\fR=\fI\fR. If \fBMAILDIR\fR is not set, \fBmu index\fR will try \fI~/Maildir\fR. .SH RETURN VALUE \fBmu index\fR return 0 upon successful completion, and any other number greater than 0 signals an error. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR maildir (5), .BR mu (1), .BR mu-init (1), .BR mu-find (1), .BR mu-cfind (1) mu-1.6.10/man/mu-info.1000066400000000000000000000020261414367003600144330ustar00rootroot00000000000000.TH MU-INFO 1 "February 2020" "User Manuals" .SH NAME mu info \- show information about the mu database .SH SYNOPSIS .B mu info [options] .SH DESCRIPTION \fBmu info\fR is the \fBmu\fR command for getting information about the mu database. .SH OPTIONS Note, some of the general options are described in the \fBmu(1)\fR man-page and not here, as they apply to multiple mu commands. .TP \fB\-\-muhome\fR use an alternative directory to store and read the database, write the logs, etc. By default, \fBmu\fR uses XDG Base Directory Specification (e.g. on Linux this defaults to \fI~/.cache/mu\fR, \fI~/.config/mu\fR). Earlier versions of \fBmu\fR defaulted to \fI~/.mu\fR, which now requires \fI\-\-muhome=~/.mu\fR. .SH RETURN VALUE \fBmu init\fR returns 0 upon successful completion, or a non-zero exit code if there was some error. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR maildir (5), .BR mu (1), .BR mu-index (1) mu-1.6.10/man/mu-init.1000066400000000000000000000045231414367003600144470ustar00rootroot00000000000000.TH MU-INIT 1 "October 2020" "User Manuals" .SH NAME mu init \- initialize the mu message database .SH SYNOPSIS .B mu init [options] .SH DESCRIPTION \fBmu init\fR is the subcommand for setting up the mu message database. After \fBmu init\fR has completed, you can run \fBmu index\fR .SH OPTIONS Note, some of the general options are described in the \fBmu(1)\fR man-page and not here, as they apply to multiple mu commands. .TP \fB\-\-muhome\fR use an alternative directory to store and read the database, write the logs, etc. By default, \fBmu\fR uses XDG Base Directory Specification (e.g. on Linux this defaults to \fI~/.cache/mu\fR, \fI~/.config/mu\fR). Earlier versions of \fBmu\fR defaulted to \fI~/.mu\fR, which now requires \fI\-\-muhome=~/.mu\fR. .TP \fB\-m\fR, \fB\-\-maildir\fR=\fI\fR starts searching at \fI\fR. By default, \fBmu\fR uses whatever the \fBMAILDIR\fR environment variable is set to; if it is not set, it tries \fI~/Maildir\fR. The maildir must be on a single file-system; and symbolic links are not supported. .TP \fB\-\-my-address\fR=\fI\fR specifies that some e-mail addresses are 'my-address' (\fB\-\-my-address\fR can be used multiple times). This is used by \fBmu cfind\fR -- any e-mail address found in the address fields of a message which also has \fI\fR in one of its address fields is considered a \fIpersonal\fR e-mail address. This allows you, for example, to filter out (\fBmu cfind --personal\fR) addresses which were merely seen in mailing list messages. \fI\fR can be either a plain e-mail address (such as \fBfoo@example.com\fR), or a regular-expression (of the 'Basic POSIX' flavor), wrapped in \fB/\fR (such as \fB/foo-.*@example\\.com/\fR). Depending on your shell program, the argument may need to b quoted. .SH ENVIRONMENT \fBmu init\fR uses \fBMAILDIR\fR to find the user's Maildir if it has not been specified explicitly with \fB\-\-maildir\fR=\fI\fR. If \fBMAILDIR\fR is not set, \fBmu init\fR uses \fI~/Maildir\fR. .SH RETURN VALUE \fBmu init\fR returns 0 upon successful completion, or a non-zero exit code if there was some error. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR maildir (5), .BR mu (1), .BR mu-index (1) mu-1.6.10/man/mu-mkdir.1000066400000000000000000000015721414367003600146130ustar00rootroot00000000000000.TH MU MKDIR 1 "July 2012" "User Manuals" .SH NAME mu mkdir\- create a new Maildir .SH SYNOPSIS .B mu mkdir [options] [] .SH DESCRIPTION \fBmu mkdir\fR is the \fBmu\fR command for creating Maildirs. It does \fBnot\fR use the mu database. With the \fBmkdir\fR command, you can create new Maildirs with permissions 0755. For example, .nf mu mkdir tom dick harry .fi creates three maildirs, \fItom\fR, \fIdick\fR and \fIharry\fR. If creation fails for any reason, \fBno\fR attempt is made to remove any parts that were created. This is for safety reasons. .SH OPTIONS .TP \fB\-\-mode\fR= set the file access mode for the new maildir(s) as in \fBchmod(1)\fR. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR maildir (5), .BR mu (1), .BR chmod (1) mu-1.6.10/man/mu-query.7000066400000000000000000000222341414367003600146560ustar00rootroot00000000000000.TH MU QUERY 7 "28 December 2017" "User Manuals" .SH NAME mu query language \- a language for finding messages in \fBmu\fR databases. .SH DESCRIPTION The mu query language is a language used by \fBmu find\fR and \fBmu4e\fR to find messages in \fBmu\fR's Xapian databases. The language is quite similar to Xapian's default query-parser, but is an independent implementation that is customized for the mu/mu4e use-case. In this article, we give a structured but informal overview of the query language and provide examples. \fBNOTE:\fR if you use queries on the command-line (say, for \fBmu find\fR), you need to quote any characters that would otherwise be interpreted by the shell, such as \fB""\fR, \fB(\fR and \fB)\fR and whitespace. .de EX1 .nf .RS .. .de EX2 .RE .fi .. .SH TERMS The basic building blocks of a query are \fBterms\fR; these are just normal words like 'banana' or 'hello', or words prefixed with a field-name which make them apply to just that field. See \fBmu find\fR for all the available fields. Some example queries: .EX1 vacation subject:capybara maildir:/inbox .EX2 Terms without an explicit field-prefix, (like 'vacation' above) are interpreted like: .EX1 to:vacation or subject:vacation or body:vacation or ... .EX2 The language is case-insensitive for terms and attempts to 'flatten' any diacritics, so \fIangtrom\fR matches \fIÅngström\fR. .PP If terms contain whitespace, they need to be quoted: .EX1 subject:"hi there" .EX2 This is a so-called \fIphrase query\fR, which means that we match against subjects that contain the literal phrase "hi there". Remember that you need to escape those quotes when using this from the command-line: .EX1 mu find subject:\\"hi there\\" .EX2 .SH LOGICAL OPERATORS We can combine terms with logical operators -- binary ones: \fBand\fR, \fBor\fR, \fBxor\fR and the unary \fBnot\fR, with the conventional rules for precedence and association, and are case-insensitive. .PP You can also group things with \fB(\fR and \fB)\fR, so you can do things like: .EX1 (subject:beethoven or subject:bach) and not body:elvis .EX2 If you do not explicitly specify an operator between terms, \fBand\fR is implied, so the queries .EX1 subject:chip subject:dale .EX2 .EX1 subject:chip AND subject:dale .EX2 are equivalent. For readability, we recommend the second version. Note that a \fIpure not\fR - e.g. searching for \fBnot apples\fR is quite a 'heavy' query. .SH REGULAR EXPRESSIONS AND WILDCARDS The language supports matching regular expressions that follow ECMAScript; for details, see .BR http://www.cplusplus.com/reference/regex/ECMAScript/ Regular expressions must be enclosed in \fB//\fR. Some examples: .EX1 subject:/h.llo/ # match hallo, hello, ... subject:/ .EX2 Note the difference between 'maildir:/foo' and 'maildir:/foo/'; the former matches messages in the '/foo' maildir, while the latter matches all messages in all maildirs that match 'foo', such as '/foo', '/bar/cuux/foo', '/fooishbar' etc. Wildcards are an older mechanism for matching where a term with a rightmost \fB*\fR (and \fIonly\fR in that position) matches any term that starts with the part before the \fB*\fR; they are supported for backward compatibility and \fBmu\fR translates them to regular expressions internally: .EX1 foo* .EX2 is equivalent to .EX1 /foo.*/ .EX2 As a note of caution, certain wild-cards and regular expression can take quite a bit longer than 'normal' queries. .SH FIELDS We already saw a number of search fields, such as \fBsubject:\fR and \fBbody:\fR. Here is the full table, a shortcut character and a description. .EX1 cc,c Cc (carbon-copy) recipient(s) bcc,h Bcc (blind-carbon-copy) recipient(s) from,f Message sender to,t To: recipient(s) subject,s Message subject body,b Message body maildir,m Maildir msgid,i Message-ID prio,p Message priority (\fIlow\fR, \fInormal\fR or \fIhigh\fR) flag,g Message Flags date,d Date range size,z Message size range embed,e Search inside embedded text parts file,j Attachment filename mime,y MIME-type of one or more message parts tag,x Tags for the message list,v Mailing list (e.g. the List-Id value) .EX2 The shortcut character can be used instead of the full name: .EX1 f:foo@bar .EX2 is the same as .EX1 from:foo@bar .EX2 For queries that are not one-off, we would recommend the longer name for readability. There are also the special fields \fBcontact:\fR, which matches all contact-fields (\fIfrom\fR, \fIto\fR, \fIcc\fR and \fIbcc\fR), and \fBrecip\fR, which matches all recipient-fields (\fIto\fR, \fIcc\fR and \fIbcc\fR). Hence, for instance, .EX1 contact:fnorb@example.com .EX2 is equivalent to .EX1 (from:fnorb@example.com or to:fnorb@example.com or cc:from:fnorb@example.com or bcc:fnorb@example.com) .EX2 .SH DATE RANGES The \fBdate:\fR field takes a date-range, expressed as the lower and upper bound, separated by \fB..\fR. Either lower or upper (but not both) can be omitted to create an open range. Dates are expressed in local time and using ISO-8601 format (YYYY-MM-DD HH:MM:SS); you can leave out the right part, and \fBmu\fR adds the rest, depending on whether this is the beginning or end of the range (e.g., as a lower bound, '2015' would be interpreted as the start of that year; as an upper bound as the end of the year). You can use '/' , '.', '-' and 'T' to make dates more human readable. Some examples: .EX1 date:20170505..20170602 date:2017-05-05..2017-06-02 date:..2017-10-01T12:00 date:2015-06-01.. date:2016..2016 .EX2 You can also use the special 'dates' \fBnow\fR and \fBtoday\fR: .EX1 date:20170505..now date:today.. .EX2 Finally, you can use relative 'ago' times which express some time before now and consist of a number followed by a unit, with units \fBs\fR for seconds, \fBM\fR for minutes, \fBh\fR for hours, \fBd\fR for days, \fBw\fR for week, \fBm\fR for months and \fBy\fR for years. Some examples: .EX1 date:3m.. date:2017.01.01..5w .EX2 .SH SIZE RANGES The \fBsize\fR or \fBz\fR field allows you to match \fIsize ranges\fR -- that is, match messages that have a byte-size within a certain range. Units (b (for bytes), K (for 1000 bytes) and M (for 1000 * 1000 bytes) are supported). Some examples: .EX1 size:10k..2m size:10m.. .EX2 .SH FLAG FIELDS The \fBflag\fR/\fBg\fR field allows you to match message flags. The following fields are available: .EX1 a,attach Message with attachment d,draft Draft Message f,flagged Flagged l,list Mailing-list message n,new New message (in new/ Maildir) p,passed Passed ('Handled') r,replied Replied s,seen Seen t,trashed Marked for deletion u,unread new OR NOT seen x,encrypted Encrypted message z,signed Signed message .EX2 Some examples: .EX1 flag:attach flag:replied g:x .EX2 Encrypted messages may be signed as well, but this is only visible after decrypting and thus, invisible to \fBmu\fR. .SH PRIORITY FIELD The message priority field (\fBprio:\fR) has three possible values: \fBlow\fR, \fBnormal\fR or \fBhigh\fR. For instance, to match high-priority messages: .EX1 prio:high .EX2 .SH MAILDIR The Maildir field describes the directory path starting \fBafter\fR the Maildir-base path, and before the \fI/cur/\fR or \fI/new/\fR part. So for example, if there's a message with the file name \fI~/Maildir/lists/running/cur/1234.213:2,\fR, you could find it (and all the other messages in the same maildir) with: .EX1 maildir:/lists/running .EX2 Note the starting '/'. If you want to match mails in the 'root' maildir, you can do with a single '/': .EX1 maildir:/ .EX2 If you have maildirs (or any fields) that include spaces, you need to quote them, ie. .EX1 maildir:"/Sent Items" .EX2 Note that from the command-line, such queries must be quoted: .EX1 mu find 'maildir:"/Sent Items"' .EX2 .SH MORE EXAMPLES Here are some simple examples of \fBmu\fR queries; you can make many more complicated queries using various logical operators, parentheses and so on, but in the author's experience, it's usually faster to find a message with a simple query just searching for some words. Find all messages with both 'bee' and 'bird' (in any field) .EX1 bee AND bird .EX2 Find all messages with either Frodo or Sam: .EX1 Frodo OR Sam .EX2 Find all messages with the 'wombat' as subject, and 'capibara' anywhere: .EX1 subject:wombat and capibara .EX2 Find all messages in the 'Archive' folder from Fred: .EX1 from:fred and maildir:/Archive .EX2 Find all unread messages with attachments: .EX1 flag:attach and flag:unread .EX2 Find all messages with PDF-attachments: .EX1 mime:application/pdf .EX2 Find all messages with attached images: .EX1 mime:image/* .EX2 .SH CAVEATS With current Xapian versions, the apostroph character is considered part of a word. Thus, you cannot find \fID'Artagnan\fR by searching for \fIArtagnan\fR. So, include the apostroph in search or use a regexp search. Matching on spaces has changed compared to the old query-parser; this applies e.g. to Maildirs that have spaces in their name, such as \fISent Items\fR. See \fBMAILDIR\fR above. .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu-find (1) mu-1.6.10/man/mu-remove.1000066400000000000000000000021321414367003600147730ustar00rootroot00000000000000.TH MU REMOVE 1 "July 2012" "User Manuals" .SH NAME \fBmu remove\fR is the \fBmu\fR command to remove messages from the database. .SH SYNOPSIS .B mu remove [options] [] .SH DESCRIPTION \fBmu remove\fR removes specific messages from the database, each of them specified by their filename. The files do not have to exist in the file system. .SH OPTIONS \fBmu remove\fR does not have its own options, but the general options for determining the location of the database (\fI--muhome\fR) are available. See \fBmu-index(1)\fR for more information. .SH RETURN VALUE \fBmu remove\fR returns 0 upon success; in general, the following error codes are returned: .nf | code | meaning | |------+-----------------------------------| | 0 | ok | | 1 | general error | | 5 | some database update error | .fi .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-index (1), .BR mu-add (1) mu-1.6.10/man/mu-script.1000066400000000000000000000041001414367003600147770ustar00rootroot00000000000000.TH MU SCRIPT 1 "October 2021" "User Manuals" .SH NAME mu script\- show the available mu scripts, and/or run them. .SH SYNOPSIS .B mu script [options] [] .B mu [] .SH DESCRIPTION \fBmu script\fR is the \fBmu\fR command to list available \fBmu\fR scripts. The scripts are to be implemented in the Guile programming language, and therefore only work if your \fBmu\fR is built with support for Guile. In addition, many scripts require you to have \fBgnuplot\fR installed. Without any parameters, \fBmu script\fR lists the available scripts. If you provide a pattern (a regular expression), only the scripts whose name or one-line description match this pattern are listed. See the examples below. \fBmu\fR ships with a number of scripts. .SH OPTIONS .TP \fB\-\-verbose\fR,\fB\-v\fR when listing the available scripts, show the long descriptions. \fB\-\-\fR all options on the right side of the \fB\-\-\fR are passed to the script. .SH EXAMPLES List all available scripts (one-line descriptions): .nf $ mu script .fi List all available scripts matching \fImonth\fR (long descriptions): .nf $ mu script -v month .fi Run the \fImsgs-per-month\fR script for messages matching 'hello', and pass it the \fI--textonly\fR parameter: .nf $ mu msgs-per-month --query=hello --textonly .fi .SH RETURN VALUE \fBmu script\fR returns 0 when all went well, and returns some non-zero error code when this is not the case. .SH FILES You can make your own Scheme scripts accessible through \fBmu script\fR by putting them in either \fI/mu/scripts\fR (e.g., \fI~/.local/share/mu/scripts\fR) or, if \fImuhome\fR is specified, in It is a good idea to document the scripts by using some special comments in the source code: .nf ;; INFO: this is my script -- one-line description ;; INFO: (longer description) ;; INFO: --option1= (describe option1) ;; INFO: etc. .fi .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR guile (1) mu-1.6.10/man/mu-server.1000066400000000000000000000030471414367003600150120ustar00rootroot00000000000000.TH MU-SERVER 1 "January 2020" "User Manuals" .SH NAME mu server \- the mu backend for the mu4e e-mail client .SH SYNOPSIS .B mu server [options] .SH DESCRIPTION \fBmu server\fR starts a simple shell in which one can query and manipulate the mu database. The output uses s-expressions. \fBmu server\fR is not meant for use by humans, except for debugging purposes. Instead, it is designed specifically for the \fBmu4e\fR e-mail client. In this man-page, we document the commands \fBmu server\fR accepts, as well as their responses. In general, the commands sent to the server are s-expressions of the form: .nf ( :param1 value1 :param2 value2) .fi For example, to view a certain message, the command would be: .nf (view :docid 12345) .fi Parameters can be sent in any order; they must be of the correct type though. See \fBlib/utils/mu-sexp-parser.hh\fR and \fBlib/utils/mu-sexp-parser.cc\fR in source-tree for the details. .SH OUTPUT FORMAT \fBmu server\fR accepts a number of commands, and delivers its results in the form: .nf \\376\\377 .fi \\376 (one byte 0xfe), followed by the length of the s-expression expressed as an hexadecimal number, followed by another \\377 (one byte 0xff), followed by the actual s-expression. By prefixing the expression with its length, it can be processed more efficiently. The \\376 and \\377 were chosen since they never occur in valid UTF-8 (in which the s-expressions are encoded). .sh COMMANDS .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1) mu-1.6.10/man/mu-verify.1000066400000000000000000000036121414367003600150060ustar00rootroot00000000000000.TH MU VERIFY 1 "June 2015" "User Manuals" .SH NAME mu verify\- verify message signatures and display information about them .SH SYNOPSIS .B mu verify [options] .SH DESCRIPTION \fBmu verify\fR is the \fBmu\fR command for verifying message signatures (such as PGP/GPG signatures) and displaying information about them. The sub-command works on message files, and does not require the message to be indexed in the database. \fBmu verify\fR depends on \fBgpg\fR, and uses the one it finds in your \fBPATH\fR. If you want to use another one, you need to set \fBMU_GPG_PATH\fB to the full path to the desired \fBgpg\fR. .SH OPTIONS .TP \fB\-r\fR, \fB\-\-auto\-retrieve\fR attempt to find keys online (see the \fBauto-key-retrieve\fR option in the \fBgnupg(1)\fR documentation). \" .TP \" \fB\-u\fR, \fB\-\-use\-agent\fR attempt to use the GPG-agent (see the the \" \fBgnupg-agent(1)\fR documentation). Note that GPG-agent is running many \" desktop-evironment; you can check whether this is the case using: \" .nf \" $ env | grep GPG_AGENT_INFO \" .fi .SH EXAMPLES To display aggregated (one-line) information about the signatures in a message: .nf $ mu verify msgfile .fi To display information about all the signatures: .nf $ mu verify --verbose msgfile .fi If you only want to use the exit code, you can use: .nf $ mu verify --quiet msgfile .fi which does not give any output. .SH RETURN VALUE \fBmu verify\fR returns 0 when all signatures could be verified to be good, and returns some non-zero error code when this is not the case. .nf | code | meaning | |------+--------------------------------| | 0 | ok | | 1 | some non-verified signature(s) | .fi .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR gpg (1) mu-1.6.10/man/mu-view.1000066400000000000000000000024321414367003600144530ustar00rootroot00000000000000.TH MU VIEW 1 "June 2013" "User Manuals" .SH NAME mu view\- display an e-mail message file .SH SYNOPSIS .B mu view [options] [] .SH DESCRIPTION \fBmu view\fR is the \fBmu\fR command for displaying e-mail message files. It works on message files and does \fInot\fR require the message to be indexed in the database. The command shows some common headers (From:, To:, Cc:, Bcc:, Subject: and Date:), the list of attachments and the plain-text body of the message (if any). .SH OPTIONS .TP \fB\-\-summary-len\fR=\fI\fR instead of displaying the full message, output a summary based upon the first \fI\fR lines of the message. .TP \fB\-\-terminate\fR terminate messages with \\f (\fIform-feed\fR) characters when displaying them. This is useful when you want to further process them. .TP \fB\-\-decrypt\fR attempt to decrypt encrypted message bodies. This is only possible if \fBmu\fR was built with crypto-support. Users are strongly recommended to use \fBgpg-agent\fR; however, if needed, \fBmu\fR will request the user password from the console. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu (1), .BR mu-index (1), .BR gpg (1), .BR gpg-agent (1) mu-1.6.10/man/mu.1000066400000000000000000000124621414367003600135070ustar00rootroot00000000000000.TH MU 1 "February 2021" "User Manuals" .SH NAME mu \- a set of tools to deal with Maildirs and message files, in particular to index and search e-mail messages. .SH SYNOPSIS In alphabetical order: .B mu [options] general mu command. .B mu add add specific messages to the database. See .BR mu-add(1) .B mu cfind [options] [] find contacts. See .BR mu-cfind(1) .B mu extract [options] [] [] extract attachments and other MIME-parts. See .BR mu-extract(1) .B mu find [options] find messages. See .BR mu-find(1) .B mu help [command] get help for some command. See .BR mu-help(1) .B mu index [options] (re)index the messages in a Maildir. See .BR mu-index(1) .B mu info [options] show information about the mu database .BR mu-info(1) .B mu init [options] initialize the mu database .BR mu-init(1) .B mu mkdir [options] [] create a new Maildir. See .BR mu-mkdir(1) .B mu remove [options] remove specific messages from the database. See .BR mu-remove(1) .B mu script [options] run a mu (Guile) script. See .BR mu-script(1) .B mu server [options] start a server process (for \fBmu4e\fR-internal use). See .BR mu-server(1) .B mu view [] view a specific message. See .BR mu-view(1) .SH DESCRIPTION \fBmu\fR is a set of tools for dealing with Maildirs and the e-mail messages in them. \fBmu\fR's main purpose is to enable searching of e-mail messages. It does so by periodically scanning a Maildir directory tree and analyzing the e-mail messages found (this is called 'indexing'). The results of this analysis are stored in a database, which can then be queried. In addition to indexing and searching, \fBmu\fR also offers functionality for viewing messages, extracting attachments and creating maildirs, and searching and exporting contact information. \fBmu\fR can be used from the command line or can be integrated with various e-mail clients. This manpage gives a general overview of the available commands (\fBindex\fR, \fBfind\fR, etc.); each \fBmu\fR command has its own man-page as well. .SH COLORS Some \fBmu\fR sub-commands support colorized output, and do so by default. If you don't want colors, you can use \fI--nocolor\fR. Currently, \fBmu find\fR, \fBmu view\fR, \fBmu cfind\fR and \fBmu extract\fR support colors. .SH ENCODING \fBmu\fR's output is in the current locale, with the exceptions of the output specifically meant for output to UTF8-encoded files. In practice, this means that the output of commands \fBindex\fR, \fBview\fR, \fBextract\fR is always encoded according to the current locale. The same is true for \fBfind\fR and \fBcfind\fR, with some exceptions, where the output is always UTF-8, regardless of the locale. For \fBcfind\fR the exception is \fI--format=bbdb\fR. This is hard-coded to UTF-8, and as such specified in the output-file, so emacs/bbdb can handle it correctly without guessing. For \fBfind\fR the output is encoded according the locale for \fI--format=plain\fR (the default), and UTF-8 for all other formats (\fIjson\fR, \fIsexp\fR, \fIxml\fR). .SH DATABASE AND FILE Commands \fBmu index\fR and \fBfind\fR and \fBcfind\fR work with the database, while the other ones work on individual mail files. Hence, running \fBview\fR, \fBmkdir\fR and \fBextract\fR does not require the mu database. The various commands are discussed in more detail in their own separate man-pages; here the general options are discussed. .SH OPTIONS \fBmu\fR offers several general options that apply to all commands, including \fBmu\fR without any command. .TP \fB\-\-muhome\fR use an alternative directory to store and read the database, write the logs, etc. By default, \fBmu\fR uses XDG Base Directory Specification (e.g. on Linux by default \fI~/.cache/mu\fR, \fI~/.config/mu\fR). Earlier versions of \fBmu\fR defaulted to \fI~/.mu\fR, which now requires \fI\-\-muhome=~/.mu\fR. .TP \fB\-d\fR, \fB\-\-debug\fR makes \fBmu\fR generate extra debug information, useful for debugging the program itself. By default, debug information goes to the log file, \fI~/.cache/mu/mu.log\fR. It can safely be deleted when \fBmu\fR is not running. When running with \fB--debug\fR option, the log file can grow rather quickly. See the note on logging below. .TP \fB\-q\fR, \fB\-\-quiet\fR causes \fBmu\fR not to output informational messages and progress information to standard output, but only to the log file. Error messages will still be sent to standard error. Note that \fBmu index\fR is \fBmuch\fR faster with \fB\-\-quiet\fR, so it is recommended you use this option when using \fBmu\fR from scripts etc. .TP \fB\-\-log-stderr\fR causes \fBmu\fR to \fBnot\fR output log messages to standard error, in addition to sending them to the log file. .TP \fB\-V\fR, \fB\-\-version\fR prints \fBmu\fR version and copyright information. .TP \fB\-h\fR, \fB\-\-help\fR lists the various command line options. .SH ERROR CODES The various mu subcommands typically exit with 0 (zero) upon success, and non-zero when some error occurred. .SH BUGS Please report bugs if you find them: .BR https://github.com/djcb/mu/issues .SH AUTHOR Dirk-Jan C. Binnema .SH "SEE ALSO" .BR mu-index (1), mu-find (1), mu-cfind (1), mu-mkdir (1), mu-view (1), .BR mu-extract (1), mu-easy (1), mu-bookmarks (5), mu-query (7) .BR https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html mu-1.6.10/meson.build000066400000000000000000000140541414367003600143720ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ################################################################################ # project setup # project('mu', ['c', 'cpp'], version: '1.6.10', meson_version: '>= 0.52.0', # debian 10 license: 'GPL-3.0-or-later', default_options : [ 'buildtype=debugoptimized', 'warning_level=1', 'c_std=c11', 'cpp_std=c++14' ] ) # installation paths prefixdir = get_option('prefix') bindir = join_paths(prefixdir, get_option('bindir')) datadir = join_paths(prefixdir, get_option('datadir')) mandir = join_paths(prefixdir, get_option('mandir')) infodir = join_paths(prefixdir, get_option('infodir')) ################################################################################ ################################################################################ # compilers / flags # extra_flags = [ '-Wundef', '-Wwrite-strings', '-Wformat', '-Wformat-nonliteral', '-Wformat-security', '-Winit-self', '-Wmissing-include-dirs', '-Wpointer-arith', #'-Wswitch-enum', '-Wswitch-default', ] # compilers cc = meson.get_compiler('c') cxx= meson.get_compiler('cpp') # extra arguments, if available foreach extra_arg : extra_flags if cc.has_argument (extra_arg) add_project_arguments([extra_arg], language: 'c') endif if cxx.has_argument (extra_arg) add_project_arguments([extra_arg], language: 'cpp') endif endforeach ################################################################################ ################################################################################ # config.h setup # config_h_data=configuration_data() config_h_data.set_quoted('MU_STORE_SCHEMA_VERSION', '452') config_h_data.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h_data.set_quoted('PACKAGE_STRING', meson.project_name() + ' ' + meson.project_version()) config_h_data.set_quoted('VERSION', meson.project_version()) config_h_data.set_quoted('PACKAGE_NAME', meson.project_name()) functions=[ 'setsid' ] foreach f : functions if cc.has_function(f) define = 'HAVE_' + f.underscorify().to_upper() config_h_data.set(define, 1) endif endforeach ################################################################################ if cc.has_function('wordexp') config_h_data.set('HAVE_WORDEXP_H',1) endif ################################################################################ # hard dependencies # glib_dep = dependency('glib-2.0', version: '>= 2.50') gobject_dep = dependency('gobject-2.0', version: '>= 2.50') gio_dep = dependency('gio-2.0', version: '>= 2.50') gmime_dep = dependency('gmime-3.0', version: '>= 3.2') xapian_dep = dependency('xapian-core', version:'>= 1.4') thread_dep = dependency('threads') awk=find_program(['gawk', 'awk']) # soft dependencies # emacs -- needed for mu4e compilation emacs=find_program(['emacs'], version: '>=25.3', required:false) if not emacs.found() message('emacs not found; not pre-compiling mu4e sources') endif makeinfo=find_program(['makeinfo'], required:false) if not makeinfo.found() message('makeinfo (texinfo) not found; not building info documentation') endif # readline. annoyingly, macos has incompatible libedit claiming to be readline. # this only a dev/debug convenience for the mu4e repl. readline_dep=[] if get_option('readline').enabled() readline_dep = dependency('readline', version:'>= 8.0') config_h_data.set('HAVE_LIBREADLINE', 1) config_h_data.set('HAVE_READLINE_READLINE_H', 1) config_h_data.set('HAVE_READLINE_HISTORY', 1) config_h_data.set('HAVE_READLINE_HISTORY_H', 1) endif # guile guile_deps=[] if get_option('guile').enabled() guile_dep = dependency('guile-3.0') endif # toys. gtk_dep=[] webkit_dep=[] if get_option('toys').enabled() gtk_dep = dependency('gtk+-3.0') webkit_dep = dependency('webkit2gtk-4.0') endif ################################################################################ ################################################################################ # write-out config. h. configure_file(output : 'config.h', configuration : config_h_data) add_project_arguments(['-DHAVE_CONFIG_H'], language: 'c') add_project_arguments(['-DHAVE_CONFIG_H'], language: 'cpp') config_h_dep=declare_dependency( include_directories: include_directories(['.'])) ################################################################################ ################################################################################ # write out version.texi (for texiinfo builds in mu4e, guile) version_texi_data=configuration_data() version_texi_data.set('VERSION', meson.project_version()) version_texi_data.set('EDITION', meson.project_version()) version_texi_data.set('UPDATED', run_command('date', '+%d %B %Y').stdout().strip()) version_texi_data.set('UPDATEDMONTH', run_command('date', '+%B %Y').stdout().strip()) configure_file(input: 'version.texi.in', output: 'version.texi', configuration: version_texi_data) ################################################################################ ################################################################################ # subdirs subdir('lib') subdir('mu') subdir('man') if emacs.found() subdir('mu4e') endif if get_option('guile').enabled() subdir('guile') endif if get_option('toys').enabled() subdir('toys/mug') endif ################################################################################ mu-1.6.10/meson_options.txt000066400000000000000000000022601414367003600156610ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. option('guile', type : 'feature', value: 'auto', description: 'build the guile scripting support (requires guile-3.x)') option('toys', type : 'feature', value: 'auto', description: 'build various toy programs (requires gtk3/gtk-webkit)') option('readline', type: 'feature', value: 'auto', description: 'enable readline support for the mu4e repl') mu-1.6.10/mu/000077500000000000000000000000001414367003600126455ustar00rootroot00000000000000mu-1.6.10/mu/Makefile.am000066400000000000000000000103241414367003600147010ustar00rootroot00000000000000## Copyright (C) 2010-2020 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. include $(top_srcdir)/gtest.mk AM_CPPFLAGS= \ -I${top_srcdir}/lib \ $(GLIB_CFLAGS) \ $(XAPIAN_CFLAGS) \ $(CODE_COVERAGE_CFLAGS) AM_CXXFLAGS= \ $(JSON_GLIB_CFLAGS) \ -DMU_SCRIPTS_DIR="\"$(pkgdatadir)/scripts/\"" $(ASAN_CXXCFLAGS) \ $(WARN_CXXFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ -Wno-switch-enum AM_LDFLAGS= \ $(ASAN_LDFLAGS) bin_PROGRAMS= \ mu # note, mu.cc is only '.cc' and not '.c' because libmu must explicitly # be linked as c++, not c. mu_SOURCES= \ mu.cc \ mu-cmd-cfind.cc \ mu-config.cc \ mu-config.hh \ mu-cmd-extract.cc \ mu-cmd-find.cc \ mu-cmd-index.cc \ mu-cmd-server.cc \ mu-cmd-script.cc \ mu-cmd.cc \ mu-cmd.hh BUILT_SOURCES= \ mu-help-strings.h mu-help-strings.h: mu-help-strings.txt mu-help-strings.awk $(AM_V_GEN) $(AWK) -f ${top_srcdir}/mu/mu-help-strings.awk < $< > $@ mu_LDADD= \ ${top_builddir}/lib/libmu.la \ ${top_builddir}/lib/utils/libmu-utils.la \ $(GLIB_LIBS) \ $(XAPIAN_LIBS) \ $(READLINE_LIBS) \ $(CODE_COVERAGE_LIBS) EXTRA_DIST= \ mu-help-strings.awk \ mu-help-strings.txt noinst_PROGRAMS= $(TEST_PROGS) test_cxxflags= \ ${AM_CXXFLAGS} \ -DMU_TESTMAILDIR=\"${abs_top_srcdir}/lib/testdir\" \ -DMU_TESTMAILDIR2=\"${abs_top_srcdir}/lib/testdir2\" \ -DMU_TESTMAILDIR4=\"${abs_top_srcdir}/lib/testdir4\" \ -DMU_PROGRAM=\"${abs_top_builddir}/mu/mu\" \ -DABS_CURDIR=\"${abs_builddir}\" \ -DABS_SRCDIR=\"${abs_srcdir}\" TEST_PROGS += test-query test_query_SOURCES= test-mu-query.cc test_query_CXXFLAGS=$(test_cxxflags) test_query_LDADD=${top_builddir}/lib/libtestmucommon.la $(CODE_COVERAGE_LIBS) TEST_PROGS += test-cmd test_cmd_SOURCES= test-mu-cmd.cc test_cmd_CXXFLAGS=$(test_cxxflags) test_cmd_LDADD=${top_builddir}/lib/libtestmucommon.la $(CODE_COVERAGE_LIBS) TEST_PROGS += test-cmd-cfind test_cmd_cfind_SOURCES= test-mu-cmd-cfind.cc test_cmd_cfind_CXXFLAGS=$(test_cxxflags) test_cmd_cfind_LDADD=${top_builddir}/lib/libtestmucommon.la $(CODE_COVERAGE_LIBS) TESTS=$(TEST_PROGS) include $(top_srcdir)/aminclude_static.am CLEANFILES= \ $(BUILT_SOURCES) mu-1.6.10/mu/meson.build000066400000000000000000000050331414367003600150100ustar00rootroot00000000000000## Copyright (C) 2021 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. awk_script=join_paths(meson.current_source_dir(), 'mu-help-strings.awk') mu_help_strings_h=custom_target('mu_help', input: 'mu-help-strings.txt', output: 'mu-help-strings.h', command: [awk, '-f', awk_script, '@INPUT@'], capture: true) mu = executable( 'mu', [ 'mu.cc', 'mu-cmd-cfind.cc', 'mu-cmd-extract.cc', 'mu-cmd-find.cc', 'mu-cmd-index.cc', 'mu-cmd-script.cc', 'mu-cmd-server.cc', 'mu-cmd.cc', 'mu-cmd.hh', 'mu-config.cc', 'mu-config.hh', mu_help_strings_h ], dependencies: [ glib_dep, lib_mu_dep, thread_dep, config_h_dep ], cpp_args: ['-DMU_SCRIPTS_DIR="'+ join_paths(datadir, 'mu', 'scripts') + '"'], install: true) # # tests # mu_binary = mu.full_path() testmaildir=join_paths(meson.current_source_dir(),'../lib') test('test_cmd', executable('test-cmd', 'test-mu-cmd.cc', install: false, cpp_args: ['-DMU_PROGRAM="' + mu_binary + '"', '-DMU_TESTMAILDIR2="'+ join_paths(testmaildir, 'testdir2') + '"', '-DMU_TESTMAILDIR4="'+ join_paths(testmaildir, 'testdir4') + '"'], dependencies: [glib_dep, lib_test_mu_common_dep, config_h_dep, lib_mu_dep])) test('test_cmd_cfind', executable('test-cmd-cfind', 'test-mu-cmd-cfind.cc', install: false, cpp_args: ['-DMU_PROGRAM="' + mu_binary + '"', '-DMU_TESTMAILDIR="'+ join_paths(testmaildir, 'testdir') + '"', ], dependencies: [glib_dep, lib_test_mu_common_dep, config_h_dep])) test('test_cmd_query', executable('test-cmd-query', 'test-mu-query.cc', install: false, cpp_args: ['-DMU_PROGRAM="' + mu_binary + '"', '-DMU_TESTMAILDIR="'+ join_paths(testmaildir, 'testdir') + '"', '-DMU_TESTMAILDIR2="'+ join_paths(testmaildir, 'testdir2') + '"' ], dependencies: [glib_dep, lib_test_mu_common_dep, config_h_dep, lib_mu_dep])) mu-1.6.10/mu/mu-cmd-cfind.cc000066400000000000000000000306311414367003600154220ustar00rootroot00000000000000/* ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it under ** the terms of the GNU General Public License as published by the Free Software ** Foundation; either version 3, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ** details. ** ** You should have received a copy of the GNU General Public License along with ** this program; if not, write to the Free Software Foundation, Inc., 51 ** Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include #include #include #include "mu-cmd.hh" #include "mu-contacts.hh" #include "mu-runtime.hh" #include "utils/mu-util.h" #include "utils/mu-utils.hh" #include "utils/mu-error.hh" #include "utils/mu-str.h" #include "utils/mu-date.h" using namespace Mu; /** * guess the last name for the given name; clearly, * this is just a rough guess for setting an initial value. * * @param name a name * * @return the last name, as a newly allocated string (free with * g_free) */ static gchar* guess_last_name (const char *name) { const gchar *lastsp; if (!name) return g_strdup (""); lastsp = g_strrstr (name, " "); return g_strdup (lastsp ? lastsp + 1 : ""); } /** * guess the first name for the given name; clearly, * this is just a rough guess for setting an initial value. * * @param name a name * * @return the first name, as a newly allocated string (free with * g_free) */ static gchar* guess_first_name (const char *name) { const gchar *lastsp; if (!name) return g_strdup (""); lastsp = g_strrstr (name, " "); if (lastsp) return g_strndup (name, lastsp - name); else return g_strdup (name); } /** * guess some nick name for the given name; if we can determine an * first name, last name, the nick will be first name + the first char * of the last name. otherwise, it's just the first name. clearly, * this is just a rough guess for setting an initial value for nicks. * * @param name a name * * @return the guessed nick, as a newly allocated string (free with g_free) */ static gchar* cleanup_str (const char* str) { gchar *s; const gchar *cur; unsigned i; if (mu_str_is_empty(str)) return g_strdup (""); s = g_new0 (char, strlen(str) + 1); for (cur = str, i = 0; *cur; ++cur) { if (ispunct(*cur) || isspace(*cur)) continue; else s[i++] = *cur; } return s; } static char* uniquify_nick (const char *nick, GHashTable *nicks) { guint u; for (u = 2; u != 1000; ++u) { char *cand; cand = g_strdup_printf ("%s%u", nick, u); if (!g_hash_table_contains (nicks, cand)) return cand; } return g_strdup (nick); /* if all else fails */ } static gchar* guess_nick (const char* name, GHashTable *nicks) { gchar *fname, *lname, *nick; gchar initial[7]; fname = guess_first_name (name); lname = guess_last_name (name); /* if there's no last name, use first name as the nick */ if (mu_str_is_empty(fname) || mu_str_is_empty(lname)) { g_free (lname); nick = fname; goto leave; } memset (initial, 0, sizeof(initial)); /* couldn't we get an initial for the last name? */ if (g_unichar_to_utf8 (g_utf8_get_char (lname), initial) == 0) { g_free (lname); nick = fname; goto leave; } nick = g_strdup_printf ("%s%s", fname, initial); g_free (fname); g_free (lname); leave: { gchar *tmp; tmp = cleanup_str (nick); g_free (nick); nick = tmp; } if (g_hash_table_contains (nicks, nick)) { char *tmp; tmp = uniquify_nick (nick, nicks); g_free (nick); nick = tmp; } g_hash_table_add (nicks, g_strdup(nick)); return nick; } static void print_header (const MuConfigFormat format) { switch (format) { case MU_CONFIG_FORMAT_BBDB: g_print (";; -*-coding: utf-8-emacs;-*-\n" ";;; file-version: 6\n"); break; case MU_CONFIG_FORMAT_MUTT_AB: g_print ("Matching addresses in the mu database:\n"); break; default: break; } } static void each_contact_bbdb (const std::string& email, const std::string& name, time_t tstamp) { char *fname, *lname, *now, *timestamp; fname = guess_first_name (name.c_str()); lname = guess_last_name (name.c_str()); now = mu_date_str ("%Y-%m-%d", time(NULL)); timestamp = mu_date_str ("%Y-%m-%d", tstamp); g_print ("[\"%s\" \"%s\" nil nil nil nil (\"%s\") " "((creation-date . \"%s\") (time-stamp . \"%s\")) nil]\n", fname, lname, email.c_str(), now, timestamp); g_free (now); g_free (timestamp); g_free (fname); g_free (lname); } static void each_contact_mutt_alias (const std::string& email, const std::string& name, GHashTable *nicks) { if (name.empty()) return; char *nick = guess_nick (name.c_str(), nicks); mu_util_print_encoded ("alias %s %s <%s>\n", nick, name.c_str(), email.c_str()); g_free (nick); } static void each_contact_wl (const std::string& email, const std::string& name, GHashTable *nicks) { if (name.empty()) return; char *nick = guess_nick (name.c_str(), nicks); mu_util_print_encoded ("%s \"%s\" \"%s\"\n", email.c_str(), nick, name.c_str()); g_free (nick); } static void print_plain (const std::string& email, const std::string& name, bool color) { if (!name.empty()) { if (color) ::fputs (MU_COLOR_MAGENTA, stdout); mu_util_fputs_encoded (name.c_str(), stdout); ::fputs (" ", stdout); } if (color) ::fputs (MU_COLOR_GREEN, stdout); mu_util_fputs_encoded (email.c_str(), stdout); if (color) fputs (MU_COLOR_DEFAULT, stdout); fputs ("\n", stdout); } struct ECData { MuConfigFormat format; gboolean color, personal; time_t after; GRegex *rx; GHashTable *nicks; size_t n; }; static void each_contact (const Mu::ContactInfo& ci, ECData& ecdata) { if (ecdata.personal && ci.personal) return; if (ci.tstamp < ecdata.after) return; if (ecdata.rx && !g_regex_match (ecdata.rx, ci.email.c_str(), (GRegexMatchFlags)0, NULL) && !g_regex_match (ecdata.rx, ci.name.empty() ? "" : ci.name.c_str(), (GRegexMatchFlags)0, NULL)) return; ++ecdata.n; switch (ecdata.format) { case MU_CONFIG_FORMAT_MUTT_ALIAS: each_contact_mutt_alias (ci.email, ci.name, ecdata.nicks); break; case MU_CONFIG_FORMAT_MUTT_AB: mu_util_print_encoded ("%s\t%s\t\n", ci.email.c_str(), ci.name.c_str()); break; case MU_CONFIG_FORMAT_WL: each_contact_wl (ci.email, ci.name, ecdata.nicks); break; case MU_CONFIG_FORMAT_ORG_CONTACT: if (!ci.name.empty()) mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n", ci.name.c_str(), ci.email.c_str()); break; case MU_CONFIG_FORMAT_BBDB: each_contact_bbdb (ci.email, ci.name, ci.last_seen); break; case MU_CONFIG_FORMAT_CSV: mu_util_print_encoded("%s,%s\n", ci.name.empty() ? "" : Mu::quote(ci.name).c_str(), Mu::quote(ci.email).c_str()); break; case MU_CONFIG_FORMAT_DEBUG: { char datebuf[32]; strftime(datebuf, sizeof(datebuf), "%F %T", gmtime(&ci.last_seen)); g_print ("%s\n\tname: %s\n\t%s\n\tpersonal: %s\n\tfreq: %zu\n" "\tlast-seen: %s\n", ci.email.c_str(), ci.name.empty() ? "" : ci.name.c_str(), ci.full_address.c_str(), ci.personal ? "yes" : "no", ci.freq, datebuf); } break; default: print_plain (ci.email, ci.name, ecdata.color); } } static MuError run_cmd_cfind (const Mu::Store& store, const char* pattern, gboolean personal, time_t after, const MuConfigFormat format, gboolean color, GError **err) { ECData ecdata{}; memset(&ecdata, 0, sizeof(ecdata)); if (pattern) { ecdata.rx = g_regex_new (pattern, (GRegexCompileFlags)(G_REGEX_CASELESS|G_REGEX_OPTIMIZE), (GRegexMatchFlags)0, err); if (!ecdata.rx) return MU_ERROR_CONTACTS; } ecdata.personal = personal; ecdata.n = 0; ecdata.after = after; ecdata.format = format; ecdata.color = color; ecdata.nicks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); print_header (format); store.contacts().for_each([&](const auto& ci) { each_contact(ci, ecdata); }); g_hash_table_unref (ecdata.nicks); if (ecdata.rx) g_regex_unref (ecdata.rx); if (ecdata.n == 0) { g_printerr ("no matching contacts found\n"); return MU_ERROR_NO_MATCHES; } return MU_OK; } static gboolean cfind_params_valid (const MuConfig *opts) { switch (opts->format) { case MU_CONFIG_FORMAT_PLAIN: case MU_CONFIG_FORMAT_MUTT_ALIAS: case MU_CONFIG_FORMAT_MUTT_AB: case MU_CONFIG_FORMAT_WL: case MU_CONFIG_FORMAT_BBDB: case MU_CONFIG_FORMAT_CSV: case MU_CONFIG_FORMAT_ORG_CONTACT: case MU_CONFIG_FORMAT_DEBUG: break; default: g_printerr ("invalid output format %s\n", opts->formatstr ? opts->formatstr : ""); return FALSE; } /* only one pattern allowed */ if (opts->params[1] && opts->params[2]) { g_printerr ("usage: mu cfind [options] []\n"); return FALSE; } return TRUE; } MuError Mu::mu_cmd_cfind (const Mu::Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_CFIND, MU_ERROR_INTERNAL); if (!cfind_params_valid (opts)) throw Mu::Error(Mu::Error::Code::InvalidArgument, "invalid parameters"); auto res = run_cmd_cfind (store, opts->params[1], opts->personal, opts->after, opts->format, !opts->nocolor, err); if (res != MU_OK) throw Mu::Error(Mu::Error::Code::Internal, err/*consumes*/, "error in cfind"); return MU_OK; } mu-1.6.10/mu/mu-cmd-extract.cc000066400000000000000000000215221414367003600160100ustar00rootroot00000000000000/* ** Copyright (C) 2010-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include "mu-msg.hh" #include "mu-msg-part.hh" #include "mu-cmd.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" using namespace Mu; static gboolean save_part (MuMsg *msg, const char *targetdir, guint partidx, const MuConfig *opts) { GError *err; gchar *filepath; gboolean rv; MuMsgOptions msgopts; err = NULL; rv = FALSE; msgopts = mu_config_get_msg_options (opts); filepath = mu_msg_part_get_path (msg, msgopts, targetdir, partidx, &err); if (!filepath) goto exit; if (!mu_msg_part_save (msg, msgopts, filepath, partidx, &err)) goto exit; if (opts->play) rv = mu_util_play (filepath, TRUE, FALSE, &err); else rv = TRUE; exit: if (err) { g_printerr ("error with MIME-part: %s\n", err->message); g_clear_error (&err); } g_free (filepath); return rv; } static gboolean save_numbered_parts (MuMsg *msg, const MuConfig *opts) { gboolean rv; char **parts, **cur; parts = g_strsplit (opts->parts, ",", 0); for (rv = TRUE, cur = parts; cur && *cur; ++cur) { unsigned idx; int i; char *endptr; idx = (unsigned)(i = strtol (*cur, &endptr, 10)); if (i < 0 || *cur == endptr) { g_printerr ("invalid MIME-part index '%s'\n", *cur); rv = FALSE; break; } if (!save_part (msg, opts->targetdir, idx, opts)) { g_printerr ("failed to save MIME-part %d\n", idx); rv = FALSE; break; } } g_strfreev (parts); return rv; } static GRegex* anchored_regex (const char* pattern) { GRegex *rx; GError *err; gchar *anchored; anchored = g_strdup_printf ("%s%s%s", pattern[0] == '^' ? "" : "^", pattern, pattern[strlen(pattern)-1] == '$' ? "" : "$"); err = NULL; rx = g_regex_new (anchored, (GRegexCompileFlags)(G_REGEX_CASELESS|G_REGEX_OPTIMIZE), (GRegexMatchFlags)0, &err); g_free (anchored); if (!rx) { g_printerr ("error in regular expression '%s': %s\n", pattern, err->message ? err->message : "error"); g_error_free (err); return NULL; } return rx; } static gboolean save_part_with_filename (MuMsg *msg, const char *pattern, const MuConfig *opts) { GSList *lst, *cur; GRegex *rx; gboolean rv; MuMsgOptions msgopts; msgopts = mu_config_get_msg_options (opts); /* 'anchor' the pattern with '^...$' if not already */ rx = anchored_regex (pattern); if (!rx) return FALSE; lst = mu_msg_find_files (msg, msgopts, rx); g_regex_unref (rx); if (!lst) { g_printerr ("no matching attachments found"); return FALSE; } for (cur = lst, rv = TRUE; cur; cur = g_slist_next (cur)) rv = rv && save_part (msg, opts->targetdir, GPOINTER_TO_UINT(cur->data), opts); g_slist_free (lst); return rv; } struct _SaveData { gboolean result; guint saved_num; const MuConfig *opts; }; typedef struct _SaveData SaveData; static gboolean ignore_part (MuMsg *msg, MuMsgPart *part, SaveData *sd) { /* something went wrong somewhere; stop */ if (!sd->result) return TRUE; /* only consider leaf parts */ if (!(part->part_type & MU_MSG_PART_TYPE_LEAF)) return TRUE; /* filter out non-attachments? */ if (!sd->opts->save_all && !(mu_msg_part_maybe_attachment (part))) return TRUE; return FALSE; } static void save_part_if (MuMsg *msg, MuMsgPart *part, SaveData *sd) { gchar *filepath; gboolean rv; GError *err; MuMsgOptions msgopts; if (ignore_part (msg, part, sd)) return; rv = FALSE; filepath = NULL; err = NULL; msgopts = mu_config_get_msg_options (sd->opts); filepath = mu_msg_part_get_path (msg, msgopts, sd->opts->targetdir, part->index, &err); if (!filepath) goto exit; if (!mu_msg_part_save (msg, msgopts, filepath, part->index, &err)) goto exit; if (sd->opts->play) rv = mu_util_play (filepath, TRUE, FALSE, &err); else rv = TRUE; ++sd->saved_num; exit: if (err) g_printerr ("error saving MIME part: %s", err->message); g_free (filepath); g_clear_error (&err); sd->result = rv; } static gboolean save_certain_parts (MuMsg *msg, const MuConfig *opts) { SaveData sd; MuMsgOptions msgopts; sd.result = TRUE; sd.saved_num = 0; sd.opts = opts; msgopts = mu_config_get_msg_options (opts); mu_msg_part_foreach (msg, msgopts, (MuMsgPartForeachFunc)save_part_if, &sd); if (sd.saved_num == 0) { g_printerr ("no %s extracted from this message", opts->save_attachments ? "attachments" : "parts"); sd.result = FALSE; } return sd.result; } static gboolean save_parts (const char *path, const char *filename, const MuConfig *opts) { MuMsg* msg; gboolean rv; GError *err; err = NULL; msg = mu_msg_new_from_file (path, NULL, &err); if (!msg) { if (err) { g_printerr ("error: %s", err->message); g_error_free (err); } return FALSE; } /* note, mu_cmd_extract already checks whether what's in opts * is somewhat, so no need for extensive checking here */ /* should we save some explicit parts? */ if (opts->parts) rv = save_numbered_parts (msg, opts); else if (filename) rv = save_part_with_filename (msg, filename, opts); else rv = save_certain_parts (msg, opts); mu_msg_unref (msg); return rv; } #define color_maybe(C) do{ if (color) fputs ((C),stdout);}while(0) static const char* disp_str (MuMsgPartType ptype) { if (ptype & MU_MSG_PART_TYPE_ATTACHMENT) return "attach"; if (ptype & MU_MSG_PART_TYPE_INLINE) return "inline"; return ""; } static void each_part_show (MuMsg *msg, MuMsgPart *part, gboolean color) { /* index */ g_print (" %u ", part->index); /* filename */ color_maybe (MU_COLOR_GREEN); { gchar *fname; fname = mu_msg_part_get_filename (part, FALSE); mu_util_fputs_encoded (fname ? fname : "", stdout); g_free (fname); } /* content-type */ color_maybe (MU_COLOR_BLUE); mu_util_print_encoded ( " %s/%s ", part->type ? part->type : "", part->subtype ? part->subtype : ""); /* /\* disposition *\/ */ color_maybe (MU_COLOR_MAGENTA); mu_util_print_encoded ("[%s]", disp_str(part->part_type)); /* size */ if (part->size > 0) { color_maybe (MU_COLOR_CYAN); g_print (" (%s)", mu_str_size_s (part->size)); } color_maybe (MU_COLOR_DEFAULT); fputs ("\n", stdout); } static gboolean show_parts (const char* path, const MuConfig *opts, GError **err) { MuMsg *msg; MuMsgOptions msgopts; msg = mu_msg_new_from_file (path, NULL, err); if (!msg) return FALSE; msgopts = mu_config_get_msg_options (opts); /* TODO: update this for crypto */ g_print ("MIME-parts in this message:\n"); mu_msg_part_foreach (msg, msgopts, (MuMsgPartForeachFunc)each_part_show, GUINT_TO_POINTER(!opts->nocolor)); mu_msg_unref (msg); return TRUE; } static gboolean check_params (const MuConfig *opts, GError **err) { size_t param_num; param_num = mu_config_param_num (opts); if (param_num < 2) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "parameters missing"); return FALSE; } if (opts->save_attachments || opts->save_all) if (opts->parts || param_num == 3) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "--save-attachments and --save-all don't " "accept a filename pattern or --parts"); return FALSE; } if (opts->save_attachments && opts->save_all) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "only one of --save-attachments and" " --save-all is allowed"); return FALSE; } return TRUE; } MuError Mu::mu_cmd_extract (const MuConfig *opts, GError **err) { int rv; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_EXTRACT, MU_ERROR_INTERNAL); if (!check_params (opts, err)) return MU_ERROR_IN_PARAMETERS; if (!opts->params[2] && !opts->parts && !opts->save_attachments && !opts->save_all) /* show, don't save */ rv = show_parts (opts->params[1], opts, err); else { rv = mu_util_check_dir(opts->targetdir, FALSE, TRUE); if (!rv) mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_WRITE, "target '%s' is not a writable directory", opts->targetdir); else rv = save_parts (opts->params[1], opts->params[2], opts); /* save */ } return rv ? MU_OK : MU_ERROR; } mu-1.6.10/mu/mu-cmd-find.cc000066400000000000000000000537021414367003600152630ustar00rootroot00000000000000/* ** Copyright (C) 2008-2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include #include #include #include #include #include "mu-msg.hh" #include "mu-maildir.hh" #include "mu-query-match-deciders.hh" #include "mu-query.hh" #include "mu-bookmarks.hh" #include "mu-runtime.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" #include "utils/mu-date.h" #include "mu-cmd.hh" using namespace Mu; struct OutputInfo{ Xapian::docid docid{}; bool header{}; bool footer{}; bool last{}; Option match_info; }; constexpr auto FirstOutput{OutputInfo{0, true, false}}; constexpr auto LastOutput{OutputInfo{0, false, true}}; using OutputFunc = std::function; static gboolean print_internal (const Query& query, const std::string& expr, gboolean xapian, gboolean warn, GError **err) { std::cout << query.parse(expr, xapian) << "\n"; return TRUE; } /* returns MU_MSG_FIELD_ID_NONE if there is an error */ static MuMsgFieldId sort_field_from_string (const char* fieldstr, GError **err) { MuMsgFieldId mfid; mfid = mu_msg_field_id_from_name (fieldstr, FALSE); /* not found? try a shortcut */ if (mfid == MU_MSG_FIELD_ID_NONE && strlen(fieldstr) == 1) mfid = mu_msg_field_id_from_shortcut(fieldstr[0], FALSE); if (mfid == MU_MSG_FIELD_ID_NONE) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS, "not a valid sort field: '%s'\n", fieldstr); return mfid; } static Option run_query (const Query& q, const std::string& expr, const MuConfig *opts, GError **err) { MuMsgFieldId sortid; sortid = MU_MSG_FIELD_ID_NONE; if (opts->sortfield) { sortid = sort_field_from_string (opts->sortfield, err); if (sortid == MU_MSG_FIELD_ID_NONE) /* error occurred? */ return Nothing; } Mu::QueryFlags qflags{QueryFlags::None}; if (opts->reverse) qflags |= QueryFlags::Descending; if (opts->skip_dups) qflags |= QueryFlags::SkipDuplicates; if (opts->include_related) qflags |= QueryFlags::IncludeRelated; if (opts->threads) qflags |= QueryFlags::Threading; return q.run(expr, sortid, qflags, opts->maxnum); } static gboolean exec_cmd (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { gint status; char *cmdline, *escpath; gboolean rv; escpath = g_shell_quote (mu_msg_get_path (msg)); cmdline = g_strdup_printf ("%s %s", opts->exec, escpath); rv = g_spawn_command_line_sync (cmdline, NULL, NULL, &status, err); g_free (cmdline); g_free (escpath); return rv; } static gchar* resolve_bookmark (const MuConfig *opts, GError **err) { MuBookmarks *bm; char* val; const gchar *bmfile; bmfile = mu_runtime_path (MU_RUNTIME_PATH_BOOKMARKS); bm = mu_bookmarks_new (bmfile); if (!bm) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_OPEN, "failed to open bookmarks file '%s'", bmfile); return FALSE; } val = (gchar*)mu_bookmarks_lookup (bm, opts->bookmark); if (!val) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES, "bookmark '%s' not found", opts->bookmark); else val = g_strdup (val); mu_bookmarks_destroy (bm); return val; } static Option get_query (const MuConfig *opts, GError **err) { gchar *query, *bookmarkval; /* params[0] is 'find', actual search params start with [1] */ if (!opts->bookmark && !opts->params[1]) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS, "error in parameters"); return Nothing; } bookmarkval = NULL; if (opts->bookmark) { bookmarkval = resolve_bookmark (opts, err); if (!bookmarkval) return Nothing; } query = g_strjoinv (" ", &opts->params[1]); if (bookmarkval) { gchar *tmp; tmp = g_strdup_printf ("%s %s", bookmarkval, query); g_free (query); query = tmp; } g_free (bookmarkval); std::string q{query}; g_free(query); return q; } static Mu::Query get_query_obj (const Store& store, GError **err) { const auto count{store.size()}; if (count == (unsigned)-1) throw Mu::Error(Error::Code::Store, "invalid store"); if (count == 0) throw Mu::Error(Error::Code::Store, "store is empty"); return Mu::Query{store}; } static gboolean prepare_links (const MuConfig *opts, GError **err) { /* note, mu_maildir_mkdir simply ignores whatever part of the * mail dir already exists */ if (!mu_maildir_mkdir (opts->linksdir, 0700, TRUE, err)) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_MKDIR, "error creating %s", opts->linksdir); return FALSE; } if (opts->clearlinks && !mu_maildir_clear_links (opts->linksdir, err)) { mu_util_g_set_error (err, MU_ERROR_FILE, "error clearing links under %s", opts->linksdir); return FALSE; } return TRUE; } static bool output_link (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { if (info.header) return prepare_links (opts, err); else if (info.footer) return true; return mu_maildir_link (mu_msg_get_path (msg), opts->linksdir, err); } static void ansi_color_maybe (MuMsgFieldId mfid, gboolean color) { const char* ansi; if (!color) return; /* nothing to do */ switch (mfid) { case MU_MSG_FIELD_ID_FROM: ansi = MU_COLOR_CYAN; break; case MU_MSG_FIELD_ID_TO: case MU_MSG_FIELD_ID_CC: case MU_MSG_FIELD_ID_BCC: ansi = MU_COLOR_BLUE; break; case MU_MSG_FIELD_ID_SUBJECT: ansi = MU_COLOR_GREEN; break; case MU_MSG_FIELD_ID_DATE: ansi = MU_COLOR_MAGENTA; break; default: if (mu_msg_field_type(mfid) == MU_MSG_FIELD_TYPE_STRING) ansi = MU_COLOR_YELLOW; else ansi = MU_COLOR_RED; } fputs (ansi, stdout); } static void ansi_reset_maybe (MuMsgFieldId mfid, gboolean color) { if (!color) return; /* nothing to do */ fputs (MU_COLOR_DEFAULT, stdout); } static const char* field_string_list (MuMsg *msg, MuMsgFieldId mfid) { char *str; const GSList *lst; static char buf[80]; lst = mu_msg_get_field_string_list (msg, mfid); if (!lst) return NULL; str = mu_str_from_list (lst, ','); if (str) { strncpy (buf, str, sizeof(buf)-1); buf[sizeof(buf)-1]='\0'; g_free (str); return buf; } return NULL; } static const char* display_field (MuMsg *msg, MuMsgFieldId mfid) { gint64 val; switch (mu_msg_field_type(mfid)) { case MU_MSG_FIELD_TYPE_STRING: { const gchar *str; str = mu_msg_get_field_string (msg, mfid); return str ? str : ""; } case MU_MSG_FIELD_TYPE_INT: if (mfid == MU_MSG_FIELD_ID_PRIO) { val = mu_msg_get_field_numeric (msg, mfid); return mu_msg_prio_name ((MuMsgPrio)val); } else if (mfid == MU_MSG_FIELD_ID_FLAGS) { val = mu_msg_get_field_numeric (msg, mfid); return mu_str_flags_s ((MuFlags)val); } else /* as string */ return mu_msg_get_field_string (msg, mfid); case MU_MSG_FIELD_TYPE_TIME_T: val = mu_msg_get_field_numeric (msg, mfid); return mu_date_str_s ("%c", (time_t)val); case MU_MSG_FIELD_TYPE_BYTESIZE: val = mu_msg_get_field_numeric (msg, mfid); return mu_str_size_s ((unsigned)val); case MU_MSG_FIELD_TYPE_STRING_LIST: { const char *str; str = field_string_list (msg, mfid); return str ? str : ""; } default: g_return_val_if_reached (NULL); } } static void print_summary (MuMsg *msg, const MuConfig *opts) { const char* body; char *summ; MuMsgOptions msgopts; msgopts = mu_config_get_msg_options (opts); body = mu_msg_get_body_text(msg, msgopts); if (body) summ = mu_str_summarize (body, (unsigned)opts->summary_len); else summ = NULL; g_print ("Summary: "); mu_util_fputs_encoded (summ ? summ : "", stdout); g_print ("\n"); g_free (summ); } static void thread_indent (const QueryMatch& info, const MuConfig *opts) { const auto is_root{any_of(info.flags & QueryMatch::Flags::Root)}; const auto first_child{any_of(info.flags & QueryMatch::Flags::First)}; const auto last_child{any_of(info.flags & QueryMatch::Flags::Last)}; const auto empty_parent{any_of(info.flags & QueryMatch::Flags::Orphan)}; const auto is_dup{any_of(info.flags & QueryMatch::Flags::Duplicate)}; //const auto is_related{any_of(info.flags & QueryMatch::Flags::Related)}; /* indent */ if (opts->debug) { ::fputs (info.thread_path.c_str(), stdout); ::fputs (" ", stdout); } else for (auto i = info.thread_level; i > 1; --i) ::fputs (" ", stdout); if (!is_root) { if (first_child) ::fputs ("\\", stdout); else if (last_child) ::fputs ("/", stdout); else ::fputs (" ", stdout); ::fputs (empty_parent ? "*> " : is_dup ? "=> " : "-> ", stdout); } } static void output_plain_fields (MuMsg *msg, const char *fields, gboolean color, gboolean threads) { const char* myfields; int nonempty; g_return_if_fail (fields); for (myfields = fields, nonempty = 0; *myfields; ++myfields) { MuMsgFieldId mfid; mfid = mu_msg_field_id_from_shortcut (*myfields, FALSE); if (mfid == MU_MSG_FIELD_ID_NONE || (!mu_msg_field_xapian_value (mfid) && !mu_msg_field_xapian_contact (mfid))) nonempty += printf ("%c", *myfields); else { ansi_color_maybe (mfid, color); nonempty += mu_util_fputs_encoded (display_field (msg, mfid), stdout); ansi_reset_maybe (mfid, color); } } if (nonempty) fputs ("\n", stdout); } static gboolean output_plain (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { if (!msg) return true; /* we reuse the color (whatever that may be) * for message-priority for threads, too */ ansi_color_maybe (MU_MSG_FIELD_ID_PRIO, !opts->nocolor); if (opts->threads && info.match_info) thread_indent (*info.match_info, opts); output_plain_fields (msg, opts->fields, !opts->nocolor, opts->threads); if (opts->summary_len > 0) print_summary (msg, opts); return TRUE; } G_GNUC_UNUSED static std::string to_string (const Mu::Sexp& sexp, bool color, size_t level = 0) { Mu::MaybeAnsi col{color}; using Color = Mu::MaybeAnsi::Color; // clang/libc++ don't allow constexpr here const std::array rainbow = { Color::BrightBlue, Color::Green, Color::Yellow, Color::Magenta, Color::Cyan, Color::BrightGreen, }; std::stringstream sstrm; switch (sexp.type()) { case Sexp::Type::List: { const auto bracecol{col.fg(rainbow[level % rainbow.size()])}; sstrm << bracecol << "("; bool first{true}; for (auto&& child : sexp.list()) { sstrm << (first ? "" : " ") << to_string(child , color, level + 1); first = false; } sstrm << bracecol << ")"; break; } case Sexp::Type::String: sstrm << col.fg(Color::BrightCyan) << Mu::quote(sexp.value()) << col.reset(); break; case Sexp::Type::Number: sstrm << col.fg(Color::BrightMagenta) << sexp.value() << col.reset(); break; case Sexp::Type::Symbol: sstrm << (col.fg(sexp.value().at(0) == ':' ? Color::BrightGreen : Color::BrightBlue)) << sexp.value() << col.reset(); break; default: throw std::logic_error ("invalid type"); } return sstrm.str(); } static bool output_sexp (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { if (!msg) return true; fputs(msg_to_sexp(msg, 0, MU_MSG_OPTION_HEADERS_ONLY) .to_sexp_string().c_str(), stdout); fputs ("\n", stdout); return true; } static bool output_json (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { if (info.header) { g_print ("[\n"); return true; } if (info.footer) { g_print("]\n"); return true; } g_print("%s%s\n", msg_to_sexp(msg, info.docid, MU_MSG_OPTION_HEADERS_ONLY) .to_json_string().c_str(), info.last ? "" : ","); return true; } static void print_attr_xml (const char* elm, const char *str) { gchar *esc; if (mu_str_is_empty(str)) return; /* empty: don't include */ esc = g_markup_escape_text (str, -1); g_print ("\t\t<%s>%s\n", elm, esc, elm); g_free (esc); } static bool output_xml (MuMsg *msg, const OutputInfo& info, const MuConfig *opts, GError **err) { if (info.header) { g_print ("\n"); g_print ("\n"); return true; } if (info.footer) { g_print ("\n"); return true; } g_print ("\t\n"); print_attr_xml ("from", mu_msg_get_from (msg)); print_attr_xml ("to", mu_msg_get_to (msg)); print_attr_xml ("cc", mu_msg_get_cc (msg)); print_attr_xml ("subject", mu_msg_get_subject (msg)); g_print ("\t\t%u\n", (unsigned)mu_msg_get_date (msg)); g_print ("\t\t%u\n", (unsigned)mu_msg_get_size (msg)); print_attr_xml ("msgid", mu_msg_get_msgid (msg)); print_attr_xml ("path", mu_msg_get_path (msg)); print_attr_xml ("maildir", mu_msg_get_maildir (msg)); g_print ("\t\n"); return true; } static OutputFunc get_output_func (const MuConfig *opts, GError **err) { switch (opts->format) { case MU_CONFIG_FORMAT_LINKS: return output_link; case MU_CONFIG_FORMAT_EXEC: return exec_cmd; case MU_CONFIG_FORMAT_PLAIN: return output_plain; case MU_CONFIG_FORMAT_XML: return output_xml; case MU_CONFIG_FORMAT_SEXP: return output_sexp; case MU_CONFIG_FORMAT_JSON: return output_json; default: g_return_val_if_reached (NULL); return NULL; } } static bool output_query_results (const QueryResults& qres, const MuConfig *opts, GError **err) { const auto output_func{get_output_func (opts, err)}; if (!output_func) return false; gboolean rv{true}; output_func (NULL, FirstOutput, opts, NULL); size_t n{0}; for (auto&& item: qres) { n++; auto msg{item.floating_msg()}; if (!msg) continue; if (opts->after != 0 && mu_msg_get_timestamp(msg) < opts->after) continue; rv = output_func (msg, {item.doc_id(), false, false, n == qres.size(), /* last? */ item.query_match()}, opts, err); if (!rv) break; } output_func (NULL, LastOutput, opts, NULL); return rv; } static gboolean process_query (const Query& q, const std::string& expr, const MuConfig *opts, GError **err) { auto qres{run_query (q, expr, opts, err)}; if (!qres) return FALSE; if (qres->empty()) { mu_util_g_set_error (err, MU_ERROR_NO_MATCHES, "no matches for search expression"); return false; } return output_query_results (*qres, opts, err); } static gboolean execute_find (const Store& store, const MuConfig *opts, GError **err) { auto q{get_query_obj (store, err)}; auto expr{get_query (opts, err)}; if (!expr) return FALSE; if (opts->format == MU_CONFIG_FORMAT_XQUERY) return print_internal (q, *expr, TRUE, FALSE, err); else if (opts->format == MU_CONFIG_FORMAT_MQUERY) return print_internal (q, *expr, FALSE, opts->verbose, err); else return process_query (q, *expr, opts, err); } static gboolean format_params_valid (const MuConfig *opts, GError **err) { switch (opts->format) { case MU_CONFIG_FORMAT_EXEC: break; case MU_CONFIG_FORMAT_PLAIN: case MU_CONFIG_FORMAT_SEXP: case MU_CONFIG_FORMAT_JSON: case MU_CONFIG_FORMAT_LINKS: case MU_CONFIG_FORMAT_XML: case MU_CONFIG_FORMAT_XQUERY: case MU_CONFIG_FORMAT_MQUERY: if (opts->exec) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "--exec and --format cannot be combined"); return FALSE; } break; default: mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid output format %s", opts->formatstr ? opts->formatstr : ""); return FALSE; } if (opts->format == MU_CONFIG_FORMAT_LINKS && !opts->linksdir) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing --linksdir argument"); return FALSE; } if (opts->linksdir && opts->format != MU_CONFIG_FORMAT_LINKS) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "--linksdir is only valid with --format=links"); return FALSE; } return TRUE; } static gboolean query_params_valid (const MuConfig *opts, GError **err) { const gchar *xpath; if (!opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing query"); return FALSE; } xpath = mu_runtime_path (MU_RUNTIME_PATH_XAPIANDB); if (mu_util_check_dir (xpath, TRUE, FALSE)) return TRUE; mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_READ, "'%s' is not a readable Xapian directory", xpath); return FALSE; } MuError Mu::mu_cmd_find (const Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_FIND, MU_ERROR_INTERNAL); MuConfig myopts{*opts}; if (myopts.exec) myopts.format = MU_CONFIG_FORMAT_EXEC; /* pseudo format */ if (!query_params_valid (&myopts, err) || !format_params_valid(&myopts, err)) return MU_G_ERROR_CODE (err); if (!execute_find (store, &myopts, err)) return MU_G_ERROR_CODE(err); else return MU_OK; } mu-1.6.10/mu/mu-cmd-index.cc000066400000000000000000000112731414367003600154470ustar00rootroot00000000000000/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include "mu-cmd.hh" #include #include #include #include #include #include #include #include #include "mu-msg.hh" #include "index/mu-indexer.hh" #include "mu-store.hh" #include "mu-runtime.hh" #include "utils/mu-util.h" using namespace Mu; static std::atomic CaughtSignal{}; static void install_sig_handler (void) { struct sigaction action; int i, sigs[] = { SIGINT, SIGHUP, SIGTERM }; sigemptyset(&action.sa_mask); action.sa_flags = SA_RESETHAND; action.sa_handler = [](int sig) { if (!CaughtSignal && sig == SIGINT) /* Ctrl-C */ g_print ("\nshutting down gracefully, " "press again to kill immediately"); CaughtSignal = true; }; for (i = 0; i != G_N_ELEMENTS(sigs); ++i) if (sigaction (sigs[i], &action, NULL) != 0) g_critical ("set sigaction for %d failed: %s", sigs[i], g_strerror (errno));; } static void print_stats (const Indexer::Progress& stats, bool color) { const char *kars = "-\\|/"; static auto i = 0U; MaybeAnsi col{color}; using Color = MaybeAnsi::Color; std::cout << col.fg(Color::Yellow) << kars[++i % 4] << col.reset() << " indexing messages; " << "processed: " << col.fg(Color::Green) << stats.processed << col.reset() << "; updated/new: " << col.fg(Color::Green) << stats.updated << col.reset() << "; cleaned-up: " << col.fg(Color::Green) << stats.removed << col.reset(); } MuError Mu::mu_cmd_index (Mu::Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_INDEX, MU_ERROR); /* param[0] == 'index' there should be no param[1] */ if (opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "unexpected parameter"); return MU_ERROR; } if (opts->max_msg_size < 0) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "the maximum message size must be >= 0"); return MU_ERROR; } const auto mdir{store.metadata().root_maildir}; if (G_UNLIKELY(access (mdir.c_str(), R_OK) != 0)) { mu_util_g_set_error(err, MU_ERROR_FILE, "'%s' is not readable: %s", mdir.c_str(), g_strerror (errno)); return MU_ERROR; } MaybeAnsi col{!opts->nocolor}; using Color = MaybeAnsi::Color; if (!opts->quiet) { if (opts->lazycheck) std::cout << "lazily "; std::cout << "indexing maildir " << col.fg(Color::Green) << store.metadata().root_maildir << col.reset() << " -> store " << col.fg(Color::Green) << store.metadata().database_path << col.reset() << std::endl; } Mu::Indexer::Config conf{}; conf.cleanup = !opts->nocleanup; conf.lazy_check = opts->lazycheck; // ignore .noupdate with an empty store. conf.ignore_noupdate = store.empty(); install_sig_handler (); auto& indexer{store.indexer()}; indexer.start(conf); while (!CaughtSignal && indexer.is_running()) { if (!opts->quiet) print_stats (indexer.progress(), !opts->nocolor); std::this_thread::sleep_for(std::chrono::milliseconds(250)); if (!opts->quiet) { std::cout << "\r"; std::cout.flush(); } } store.indexer().stop(); if (!opts->quiet) { print_stats (store.indexer().progress(), !opts->nocolor); std::cout << std::endl; } return MU_OK; } mu-1.6.10/mu/mu-cmd-script.cc000066400000000000000000000120141414367003600156360ustar00rootroot00000000000000/* ** Copyright (C) 2012-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include "mu-cmd.hh" #include "mu-script.hh" #include "mu-runtime.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" #define MU_GUILE_EXT ".scm" #define MU_GUILE_DESCR_PREFIX ";; INFO: " #define COL(C) ((color)?C:"") using namespace Mu; static void print_script (const char *name, const char *oneline, const char *descr, gboolean color, gboolean verbose) { g_print ("%s%s%s%s%s%s%s%s", verbose ? "\n" : " * ", COL(MU_COLOR_GREEN),name,COL(MU_COLOR_DEFAULT), oneline ? ": " : "", COL(MU_COLOR_BLUE),oneline ? oneline :"",MU_COLOR_DEFAULT); if (verbose && descr) g_print ("%s%s%s", COL(MU_COLOR_MAGENTA),descr,COL(MU_COLOR_DEFAULT)); } static gboolean print_scripts (GSList *scripts, gboolean color, gboolean verbose, const char *rxstr, GError **err) { GSList *cur; const char *verb; if (!scripts) { g_print ("No scripts available\n"); return TRUE; /* not an error */ } verb = verbose ? "" : " (use --verbose for details)"; if (rxstr) g_print ("Available scripts matching '%s'%s:\n", rxstr, verb); else g_print ("Available scripts%s:\n", verb); for (cur = scripts; cur; cur = g_slist_next (cur)) { MuScriptInfo *msi; const char* descr, *oneline, *name; msi = (MuScriptInfo*)cur->data; name = mu_script_info_name (msi); oneline = mu_script_info_one_line (msi); descr = mu_script_info_description (msi); /* if rxstr is provide, only consider matching scriptinfos */ if (rxstr && !mu_script_info_matches_regex (msi, rxstr, err)) { if (err && *err) return FALSE; continue; } print_script (name, oneline, descr, color, verbose); } return TRUE; } static char* get_userpath (const char *muhome) { if (muhome) return g_build_path (G_DIR_SEPARATOR_S, muhome, "scripts", NULL); else return g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir(), "mu", "scripts", NULL); } static GSList* get_script_info_list (const char *muhome, GError **err) { GSList *scripts, *userscripts, *last; char *userpath; scripts = mu_script_get_script_info_list (MU_SCRIPTS_DIR, MU_GUILE_EXT, MU_GUILE_DESCR_PREFIX, err); if (err && *err) return NULL; userpath = get_userpath(muhome); /* is there are userdir for scripts? */ if (!mu_util_check_dir (userpath, TRUE, FALSE)) { g_free (userpath); return scripts; } /* append it to the list we already have */ userscripts = mu_script_get_script_info_list (userpath, MU_GUILE_EXT, MU_GUILE_DESCR_PREFIX, err); g_free (userpath); /* some error, return nothing */ if (err && *err) { mu_script_info_list_destroy (userscripts); mu_script_info_list_destroy (scripts); return NULL; } /* append the user scripts */ last = g_slist_last (scripts); if (last) { last->next = userscripts; return scripts; } else return userscripts; /* apparently, scripts was NULL */ } static gboolean check_params (const MuConfig *opts, GError **err) { if (!mu_util_supports (MU_FEATURE_GUILE)) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "the 'script' command is not available " "in this version of mu"); return FALSE; } return TRUE; } MuError Mu::mu_cmd_script (const MuConfig *opts, GError **err) { MuScriptInfo *msi; GSList *scripts; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_SCRIPT, MU_ERROR_INTERNAL); if (!check_params (opts, err)) return MU_ERROR; scripts = get_script_info_list (opts->muhome, err); if (err && *err) goto leave; if (g_strcmp0 (opts->cmdstr, "script") == 0) { print_scripts (scripts, !opts->nocolor, opts->verbose, opts->script_params[0], err); goto leave; } msi = mu_script_find_script_with_name (scripts, opts->script); if (!msi) { mu_util_g_set_error (err, MU_ERROR_SCRIPT_NOT_FOUND, "command or script not found"); goto leave; } /* do it! */ mu_script_guile_run (msi, mu_runtime_path(MU_RUNTIME_PATH_CACHE), opts->script_params, err); leave: /* this won't be reached, unless there is some error */ mu_script_info_list_destroy (scripts); return (err && *err) ? MU_ERROR : MU_OK; } mu-1.6.10/mu/mu-cmd-server.cc000066400000000000000000000112361414367003600156450ustar00rootroot00000000000000/* ** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include #include #include #include "mu-runtime.hh" #include "mu-cmd.hh" #include "mu-server.hh" #include "utils/mu-utils.hh" #include "utils/mu-command-parser.hh" #include "utils/mu-readline.hh" using namespace Mu; static std::atomic MuTerminate{false}; static bool tty; static void install_sig_handler (void) { struct sigaction action; int i, sigs[] = { SIGINT, SIGHUP, SIGTERM, SIGPIPE }; MuTerminate = false; action.sa_handler = [](int sig){ MuTerminate = true; }; sigemptyset(&action.sa_mask); action.sa_flags = SA_RESETHAND; for (i = 0; i != G_N_ELEMENTS(sigs); ++i) if (sigaction (sigs[i], &action, NULL) != 0) g_critical ("set sigaction for %d failed: %s", sigs[i], g_strerror (errno));; } /* * Markers for/after the length cookie that precedes the expression we write to * output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in * utf8 */ #define COOKIE_PRE "\376" #define COOKIE_POST "\377" static void cookie(size_t n) { const auto num{static_cast(n)}; if (tty) // for testing. ::printf ("[%x]", num); else ::printf (COOKIE_PRE "%x" COOKIE_POST, num); } static void output_sexp_stdout (Sexp&& sexp) { const auto str{sexp.to_sexp_string()}; cookie(str.size() + 1); if (G_UNLIKELY(::puts(str.c_str()) < 0)) { g_critical ("failed to write output '%s'", str.c_str()); ::raise (SIGTERM); /* terminate ourselves */ } } static void report_error(const Mu::Error& err) noexcept { Sexp::List e; e.add_prop(":error", Sexp::make_number(static_cast(err.code()))); e.add_prop(":message", Sexp::make_string(err.what())); output_sexp_stdout(Sexp::make_list(std::move(e))); } MuError Mu::mu_cmd_server (const MuConfig *opts, GError **err) try { Store store{mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), false/*writable*/}; Server server{store, output_sexp_stdout}; g_message ("created server with store @ %s; maildir @ %s; debug-mode %s", store.metadata().database_path.c_str(), store.metadata().root_maildir.c_str(), opts->debug ? "yes" : "no"); tty = ::isatty(::fileno(stdout)); const auto eval = std::string { opts->commands ? "(help :full t)" : opts->eval ? opts->eval : ""}; if (!eval.empty()) { server.invoke(eval); return MU_OK; } // Note, the readline stuff is inactive unless on a tty. const auto histpath{std::string{mu_runtime_path(MU_RUNTIME_PATH_CACHE)} + "/history"}; setup_readline(histpath, 50); install_sig_handler(); std::cout << ";; Welcome to the " << PACKAGE_STRING << " command-server\n" << ";; Use (help) to get a list of commands, (quit) to quit.\n"; bool do_quit{}; while (!MuTerminate && !do_quit) { std::fflush(stdout); // Needed for Windows, see issue #1827. const auto line{read_line(do_quit)}; if (line.find_first_not_of(" \t") == std::string::npos) continue; // skip whitespace-only lines do_quit = server.invoke(line) ? false : true; save_line(line); } shutdown_readline(); return MU_OK; } catch (const Error& er) { /* note: user-level error, "OK" for mu */ report_error(er); g_warning ("server caught exception: %s", er.what()); return MU_OK; } catch (...) { g_critical ("server caught exception"); g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR, "%s", "caught exception"); return MU_ERROR; } mu-1.6.10/mu/mu-cmd.cc000066400000000000000000000444511414367003600143460ustar00rootroot00000000000000/* ** Copyright (C) 2010-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include #include #include #include #include #include "mu-msg.hh" #include "mu-msg-part.hh" #include "mu-cmd.hh" #include "mu-maildir.hh" #include "mu-contacts.hh" #include "mu-runtime.hh" #include "mu-flags.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" #include "utils/mu-date.h" #include "utils/mu-error.hh" #define VIEW_TERMINATOR '\f' /* form-feed */ using namespace Mu; static gboolean view_msg_sexp (MuMsg *msg, const MuConfig *opts) { ::fputs(msg_to_sexp(msg,0, mu_config_get_msg_options(opts)) .to_sexp_string(). c_str(), stdout); return TRUE; } static void each_part (MuMsg *msg, MuMsgPart *part, gchar **attach) { char *fname, *tmp; if (!mu_msg_part_maybe_attachment (part)) return; fname = mu_msg_part_get_filename (part, FALSE); if (!fname) return; tmp = *attach; *attach = g_strdup_printf ("%s%s'%s'", *attach ? *attach : "", *attach ? ", " : "", fname); g_free (tmp); } /* return comma-sep'd list of attachments */ static gchar * get_attach_str (MuMsg *msg, const MuConfig *opts) { gchar *attach; const auto msgopts = (MuMsgOptions) (mu_config_get_msg_options(opts) | MU_MSG_OPTION_CONSOLE_PASSWORD); attach = NULL; mu_msg_part_foreach (msg, msgopts, (MuMsgPartForeachFunc)each_part, &attach); return attach; } #define color_maybe(C) do { if(color) fputs ((C),stdout);} while(0) static void print_field (const char* field, const char *val, gboolean color) { if (!val) return; color_maybe (MU_COLOR_MAGENTA); mu_util_fputs_encoded (field, stdout); color_maybe (MU_COLOR_DEFAULT); fputs (": ", stdout); if (val) { color_maybe (MU_COLOR_GREEN); mu_util_fputs_encoded (val, stdout); } color_maybe (MU_COLOR_DEFAULT); fputs ("\n", stdout); } /* a summary_len of 0 mean 'don't show summary, show body */ static void body_or_summary (MuMsg *msg, const MuConfig *opts) { const char *body; gboolean color; int my_opts = mu_config_get_msg_options(opts) | MU_MSG_OPTION_CONSOLE_PASSWORD; color = !opts->nocolor; body = mu_msg_get_body_text (msg, (MuMsgOptions)my_opts); if (!body) { if (mu_msg_get_flags (msg) & MU_FLAG_ENCRYPTED) { color_maybe (MU_COLOR_CYAN); g_print ("[No body found; " "message has encrypted parts]\n"); } else { color_maybe (MU_COLOR_MAGENTA); g_print ("[No body found]\n"); } color_maybe (MU_COLOR_DEFAULT); return; } if (opts->summary_len != 0) { gchar *summ; summ = mu_str_summarize (body, opts->summary_len); print_field ("Summary", summ, color); g_free (summ); } else { mu_util_print_encoded ("%s", body); if (!g_str_has_suffix (body, "\n")) g_print ("\n"); } } /* we ignore fields for now */ /* summary_len == 0 means "no summary */ static gboolean view_msg_plain (MuMsg *msg, const MuConfig *opts) { gchar *attachs; time_t date; const GSList *lst; gboolean color; color = !opts->nocolor; print_field ("From", mu_msg_get_from (msg), color); print_field ("To", mu_msg_get_to (msg), color); print_field ("Cc", mu_msg_get_cc (msg), color); print_field ("Bcc", mu_msg_get_bcc (msg), color); print_field ("Subject", mu_msg_get_subject (msg), color); if ((date = mu_msg_get_date (msg))) print_field ("Date", mu_date_str_s ("%c", date), color); if ((lst = mu_msg_get_tags (msg))) { gchar *tags; tags = mu_str_from_list (lst,','); print_field ("Tags", tags, color); g_free (tags); } if ((attachs = get_attach_str (msg, opts))) { print_field ("Attachments", attachs, color); g_free (attachs); } body_or_summary (msg, opts); return TRUE; } static gboolean handle_msg (const char *fname, const MuConfig *opts, GError **err) { MuMsg *msg; gboolean rv; msg = mu_msg_new_from_file (fname, NULL, err); if (!msg) return FALSE; switch (opts->format) { case MU_CONFIG_FORMAT_PLAIN: rv = view_msg_plain (msg, opts); break; case MU_CONFIG_FORMAT_SEXP: rv = view_msg_sexp (msg, opts); break; default: g_critical ("bug: should not be reached"); rv = FALSE; } mu_msg_unref (msg); return rv; } static gboolean view_params_valid (const MuConfig *opts, GError **err) { /* note: params[0] will be 'view' */ if (!opts->params[0] || !opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "error in parameters"); return FALSE; } switch (opts->format) { case MU_CONFIG_FORMAT_PLAIN: case MU_CONFIG_FORMAT_SEXP: break; default: mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid output format"); return FALSE; } return TRUE; } static MuError cmd_view (const MuConfig *opts, GError **err) { int i; gboolean rv; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_VIEW, MU_ERROR_INTERNAL); rv = view_params_valid (opts, err); if (!rv) goto leave; for (i = 1; opts->params[i]; ++i) { rv = handle_msg (opts->params[i], opts, err); if (!rv) break; /* add a separator between two messages? */ if (opts->terminator) g_print ("%c", VIEW_TERMINATOR); } leave: if (!rv) return err && *err ? (MuError)(*err)->code : MU_ERROR; return MU_OK; } static MuError cmd_mkdir (const MuConfig *opts, GError **err) { int i; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_MKDIR, MU_ERROR_INTERNAL); if (!opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing directory parameter"); return MU_ERROR_IN_PARAMETERS; } for (i = 1; opts->params[i]; ++i) if (!mu_maildir_mkdir (opts->params[i], opts->dirmode, FALSE, err)) return err && *err ? (MuError)(*err)->code : MU_ERROR_FILE_CANNOT_MKDIR; return MU_OK; } static gboolean check_file_okay (const char *path, gboolean cmd_add) { if (!g_path_is_absolute (path)) { g_printerr ("path is not absolute: %s\n", path); return FALSE; } if (cmd_add && access(path, R_OK) != 0) { g_printerr ("path is not readable: %s: %s\n", path, g_strerror (errno)); return FALSE; } return TRUE; } typedef bool (*ForeachMsgFunc) (Mu::Store& store, const char *path, GError **err); static MuError foreach_msg_file (Mu::Store& store, const MuConfig *opts, ForeachMsgFunc foreach_func, GError **err) { unsigned u; gboolean all_ok; /* note: params[0] will be 'add' */ if (!opts->params[0] || !opts->params[1]) { g_print ("usage: mu %s []\n", opts->params[0] ? opts->params[0] : ""); mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing parameters"); return MU_ERROR_IN_PARAMETERS; } for (u = 1, all_ok = TRUE; opts->params[u]; ++u) { const char* path; path = opts->params[u]; if (!check_file_okay (path, TRUE)) { all_ok = FALSE; g_printerr ("not a valid message file: %s\n", path); continue; } if (!foreach_func (store, path, err)) { all_ok = FALSE; g_printerr ("error with %s: %s\n", path, (err&&*err) ? (*err)->message : "something went wrong"); g_clear_error (err); continue; } } if (!all_ok) { mu_util_g_set_error (err, MU_ERROR_XAPIAN_STORE_FAILED, "%s failed for some message(s)", opts->params[0]); return MU_ERROR_XAPIAN_STORE_FAILED; } return MU_OK; } static bool add_path_func (Mu::Store& store, const char *path, GError **err) { const auto docid = store.add_message (path); g_debug ("added message @ %s, docid=%u", path, docid); return true; } static MuError cmd_add (Mu::Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_ADD, MU_ERROR_INTERNAL); return foreach_msg_file (store, opts, add_path_func, err); } static bool remove_path_func (Mu::Store& store, const char *path, GError **err) { const auto res = store.remove_message (path); g_debug ("removed %s (%s)", path, res ? "yes" : "no"); return true; } static MuError cmd_remove (Mu::Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_REMOVE, MU_ERROR_INTERNAL); return foreach_msg_file (store, opts, remove_path_func, err); } static bool tickle_func (Mu::Store& store, const char *path, GError **err) { MuMsg *msg{mu_msg_new_from_file (path, NULL, err)}; if (!msg) return false; const auto res = mu_msg_tickle (msg, err); g_debug ("tickled %s (%s)", path, res ? "ok" : "failed"); mu_msg_unref (msg); return res == TRUE; } static MuError cmd_tickle (Mu::Store& store, const MuConfig *opts, GError **err) { g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_TICKLE, MU_ERROR_INTERNAL); return foreach_msg_file (store, opts, tickle_func, err); } struct _VData { MuMsgPartSigStatus combined_status; char *report; gboolean oneline; }; typedef struct _VData VData; static void each_sig (MuMsg *msg, MuMsgPart *part, VData *vdata) { MuMsgPartSigStatusReport *report; report = part->sig_status_report; if (!report) return; if (vdata->oneline) vdata->report = g_strdup_printf ("%s%s%s", vdata->report ? vdata->report : "", vdata->report ? "; " : "", report->report); else vdata->report = g_strdup_printf ("%s%s\t%s", vdata->report ? vdata->report : "", vdata->report ? "\n" : "", report->report); if (vdata->combined_status == MU_MSG_PART_SIG_STATUS_BAD || vdata->combined_status == MU_MSG_PART_SIG_STATUS_ERROR) return; vdata->combined_status = report->verdict; } static void print_verdict (VData *vdata, gboolean color, gboolean verbose) { g_print ("verdict: "); switch (vdata->combined_status) { case MU_MSG_PART_SIG_STATUS_UNSIGNED: g_print ("no signature found"); break; case MU_MSG_PART_SIG_STATUS_GOOD: color_maybe (MU_COLOR_GREEN); g_print ("signature(s) verified"); break; case MU_MSG_PART_SIG_STATUS_BAD: color_maybe (MU_COLOR_RED); g_print ("bad signature"); break; case MU_MSG_PART_SIG_STATUS_ERROR: color_maybe (MU_COLOR_RED); g_print ("verification failed"); break; case MU_MSG_PART_SIG_STATUS_FAIL: color_maybe(MU_COLOR_RED); g_print ("error in verification process"); break; default: g_return_if_reached (); } color_maybe (MU_COLOR_DEFAULT); if (vdata->report && verbose) g_print ("%s%s\n", (vdata->oneline) ? ";" : "\n", vdata->report); else g_print ("\n"); } static MuError cmd_verify (const MuConfig *opts, GError **err) { MuMsg *msg; int msgopts; VData vdata; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_VERIFY, MU_ERROR_INTERNAL); if (!opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing message-file parameter"); return MU_ERROR_IN_PARAMETERS; } msg = mu_msg_new_from_file (opts->params[1], NULL, err); if (!msg) return MU_ERROR; msgopts = mu_config_get_msg_options (opts) | MU_MSG_OPTION_VERIFY | MU_MSG_OPTION_CONSOLE_PASSWORD; vdata.report = NULL; vdata.combined_status = MU_MSG_PART_SIG_STATUS_UNSIGNED; vdata.oneline = FALSE; mu_msg_part_foreach (msg, (MuMsgOptions)msgopts, (MuMsgPartForeachFunc)each_sig, &vdata); if (!opts->quiet) print_verdict (&vdata, !opts->nocolor, opts->verbose); mu_msg_unref (msg); g_free (vdata.report); return vdata.combined_status == MU_MSG_PART_SIG_STATUS_GOOD ? MU_OK : MU_ERROR; } template static void key_val(const Mu::MaybeAnsi& col, const std::string& key, T val) { using Color = Mu::MaybeAnsi::Color; std::cout << col.fg(Color::BrightBlue) << std::left << std::setw(18) << key << col.reset() << ": "; std::cout << col.fg(Color::Green) << val << col.reset() << "\n"; } static MuError cmd_info (const Mu::Store& store, const MuConfig *opts, GError **err) { Mu::MaybeAnsi col{!opts->nocolor}; key_val(col, "maildir", store.metadata().root_maildir); key_val(col, "database-path", store.metadata().database_path); key_val(col, "schema-version", store.metadata().schema_version); key_val(col, "max-message-size", store.metadata().max_message_size); key_val(col, "batch-size", store.metadata().batch_size); key_val(col, "messages in store", store.size()); const auto created{store.metadata().created}; const auto tstamp{::localtime (&created)}; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-y2k" char tbuf[64]; strftime (tbuf, sizeof(tbuf), "%c", tstamp); #pragma GCC diagnostic pop key_val(col, "created", tbuf); const auto addrs{store.metadata().personal_addresses}; if (addrs.empty()) key_val(col, "personal-address", ""); else for (auto&& c: addrs) key_val(col, "personal-address", c); return MU_OK; } static MuError cmd_init (const MuConfig *opts, GError **err) { /* not provided, nor could we find a good default */ if (!opts->maildir) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing --maildir parameter and could " "not determine default"); return MU_ERROR_IN_PARAMETERS; } if (opts->max_msg_size < 0) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid value for max-message-size"); return MU_ERROR_IN_PARAMETERS; } else if (opts->batch_size < 0) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid value for batch-size"); return MU_ERROR_IN_PARAMETERS; } Mu::Store::Config conf{}; conf.max_message_size = opts->max_msg_size; conf.batch_size = opts->batch_size; Mu::StringVec my_addrs; auto addrs = opts->my_addresses; while (addrs && *addrs) { my_addrs.emplace_back (*addrs); ++addrs; } Mu::Store store(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), opts->maildir, my_addrs, conf); if (!opts->quiet) { cmd_info (store, opts, NULL); std::cout << "\nstore created; use the 'index' command to fill/update it.\n"; } return MU_OK; } static MuError cmd_find (const MuConfig *opts, GError **err) { Mu::Store store{mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), true/*readonly*/}; return mu_cmd_find(store, opts, err); } static void show_usage (void) { g_print ("usage: mu command [options] [parameters]\n"); g_print ("where command is one of index, find, cfind, view, mkdir, " "extract, add, remove, script, verify or server\n"); g_print ("see the mu, mu- or mu-easy manpages for " "more information\n"); } typedef MuError (*readonly_store_func) (const Mu::Store&, const MuConfig *, GError **err); typedef MuError (*writable_store_func) (Mu::Store&, const MuConfig *, GError **err); static MuError with_readonly_store (readonly_store_func func, const MuConfig *opts, GError **err) { const Mu::Store store{mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), true/*readonly*/}; return func (store, opts, err); } static MuError with_writable_store (writable_store_func func, const MuConfig *opts, GError **err) { Mu::Store store{mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), false/*!readonly*/}; return func (store, opts, err); } static gboolean check_params (const MuConfig *opts, GError **err) { if (!opts->params||!opts->params[0]) {/* no command? */ show_usage (); mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "error in parameters"); return FALSE; } return TRUE; } MuError Mu::mu_cmd_execute (const MuConfig *opts, GError **err) try { MuError merr; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); if (!check_params(opts, err)) return MU_G_ERROR_CODE(err); switch (opts->cmd) { /* already handled in mu-config.c */ case MU_CONFIG_CMD_HELP: return MU_OK; /* no store needed */ case MU_CONFIG_CMD_MKDIR: merr = cmd_mkdir (opts, err); break; case MU_CONFIG_CMD_SCRIPT: merr = mu_cmd_script (opts, err); break; case MU_CONFIG_CMD_VIEW: merr = cmd_view (opts, err); break; case MU_CONFIG_CMD_VERIFY: merr = cmd_verify (opts, err); break; case MU_CONFIG_CMD_EXTRACT: merr = mu_cmd_extract (opts, err); break; /* read-only store */ case MU_CONFIG_CMD_CFIND: merr = with_readonly_store (mu_cmd_cfind, opts, err); break; case MU_CONFIG_CMD_FIND: merr = cmd_find(opts, err); break; case MU_CONFIG_CMD_INFO: merr = with_readonly_store (cmd_info, opts, err); break; /* writable store */ case MU_CONFIG_CMD_ADD: merr = with_writable_store (cmd_add, opts, err); break; case MU_CONFIG_CMD_REMOVE: merr = with_writable_store (cmd_remove, opts, err); break; case MU_CONFIG_CMD_TICKLE: merr = with_writable_store (cmd_tickle, opts, err); break; case MU_CONFIG_CMD_INDEX: merr = with_writable_store (mu_cmd_index, opts, err); break; /* commands instantiate store themselves */ case MU_CONFIG_CMD_INIT: merr = cmd_init (opts,err); break; case MU_CONFIG_CMD_SERVER: merr = mu_cmd_server (opts, err); break; default: merr = MU_ERROR_IN_PARAMETERS; break; } return merr; } catch (const Mu::Error& er) { g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR, "%s", er.what()); return MU_ERROR; } catch (...) { g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR, "%s", "caught exception"); return MU_ERROR; } mu-1.6.10/mu/mu-cmd.hh000066400000000000000000000061051414367003600143520ustar00rootroot00000000000000/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_CMD_HH__ #define MU_CMD_HH__ #include #include #include namespace Mu { /** * execute the 'find' command * * @param store store object to use * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeds and * >MU_OK (0) results, MU_EXITCODE_NO_MATCHES if the command * succeeds but there no matches, some error code for all other errors */ MuError mu_cmd_find (const Mu::Store& store, const MuConfig *opts, GError **err); /** * execute the 'extract' command * * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeds, * some error code otherwise */ MuError mu_cmd_extract (const MuConfig *opts, GError **err); /** * execute the 'script' command * * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeds, * some error code otherwise */ MuError mu_cmd_script (const MuConfig *opts, GError **err); /** * execute the cfind command * * @param store store object to use * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeds, * some error code otherwise */ MuError mu_cmd_cfind (const Mu::Store& store, const MuConfig *opts, GError **err); /** * execute some mu command, based on 'opts' * * @param opts configuration option * @param err receives error information, or NULL * * @return MU_OK if all went wall, some error code otherwise */ MuError mu_cmd_execute (const MuConfig *opts, GError **err); /** * execute the 'index' command * * @param store store object to use * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeded, * some error code otherwise */ MuError mu_cmd_index (Mu::Store& store, const MuConfig *opt, GError **err); /** * execute the server command * @param opts configuration options * @param err receives error information, or NULL * * @return MU_OK (0) if the command succeeds, some error code otherwise */ MuError mu_cmd_server (const MuConfig *opts, GError **err); } // namespace Mu #endif /*__MU_CMD_H__*/ mu-1.6.10/mu/mu-config.cc000066400000000000000000000547741414367003600150610ustar00rootroot00000000000000/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include /* memset */ #include #include #include "mu-config.hh" #include "mu-cmd.hh" using namespace Mu; static MuConfig MU_CONFIG; #define color_maybe(C) (MU_CONFIG.nocolor ? "" : (C)) static MuConfigFormat get_output_format (const char *formatstr) { int i; struct { const char* name; MuConfigFormat format; } formats [] = { {"mutt-alias", MU_CONFIG_FORMAT_MUTT_ALIAS}, {"mutt-ab", MU_CONFIG_FORMAT_MUTT_AB}, {"wl", MU_CONFIG_FORMAT_WL}, {"csv", MU_CONFIG_FORMAT_CSV}, {"org-contact", MU_CONFIG_FORMAT_ORG_CONTACT}, {"bbdb", MU_CONFIG_FORMAT_BBDB}, {"links", MU_CONFIG_FORMAT_LINKS}, {"plain", MU_CONFIG_FORMAT_PLAIN}, {"sexp", MU_CONFIG_FORMAT_SEXP}, {"json", MU_CONFIG_FORMAT_JSON}, {"xml", MU_CONFIG_FORMAT_XML}, {"xquery", MU_CONFIG_FORMAT_XQUERY}, {"mquery", MU_CONFIG_FORMAT_MQUERY}, {"debug", MU_CONFIG_FORMAT_DEBUG} }; for (i = 0; i != G_N_ELEMENTS(formats); i++) if (strcmp (formats[i].name, formatstr) == 0) return formats[i].format; return MU_CONFIG_FORMAT_UNKNOWN; } #define expand_dir(D) \ if ((D)) { \ char *exp; \ exp = mu_util_dir_expand((D)); \ if (exp) { \ g_free((D)); \ (D) = exp; \ } \ } static void set_group_mu_defaults () { /* If muhome is not set, we use the XDG Base Directory Specification * locations. */ if (MU_CONFIG.muhome) expand_dir(MU_CONFIG.muhome); /* check for the MU_NOCOLOR or NO_COLOR env vars; but in any case don't * use colors unless we're writing to a tty */ if (g_getenv (MU_NOCOLOR) != NULL || g_getenv ("NO_COLOR") != NULL) MU_CONFIG.nocolor = TRUE; if (!isatty(fileno(stdout)) || !isatty(fileno(stderr))) MU_CONFIG.nocolor = TRUE; } static GOptionGroup* config_options_group_mu () { GOptionGroup *og; GOptionEntry entries[] = { {"debug", 'd', 0, G_OPTION_ARG_NONE, &MU_CONFIG.debug, "print debug output to standard error (false)", NULL}, {"quiet", 'q', 0, G_OPTION_ARG_NONE, &MU_CONFIG.quiet, "don't give any progress information (false)", NULL}, {"version", 'V', 0, G_OPTION_ARG_NONE, &MU_CONFIG.version, "display version and copyright information (false)", NULL}, {"muhome", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.muhome, "specify an alternative mu directory", ""}, {"log-stderr", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.log_stderr, "log to standard error (false)", NULL}, {"nocolor", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocolor, "don't use ANSI-colors in output (false)", NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &MU_CONFIG.verbose, "verbose output (false)", NULL}, {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.params, "parameters", NULL}, {NULL, 0, 0, (GOptionArg)0, NULL, NULL, NULL} }; og = g_option_group_new("mu", "general mu options", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static void set_group_init_defaults () { if (!MU_CONFIG.maildir) MU_CONFIG.maildir = mu_util_guess_maildir(); expand_dir (MU_CONFIG.maildir); } static GOptionGroup* config_options_group_init () { GOptionGroup *og; GOptionEntry entries[] = { {"maildir", 'm', 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.maildir, "top of the maildir", ""}, {"my-address", 0, 0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.my_addresses, "my e-mail address; can be used multiple times", "
"}, {"max-message-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.max_msg_size, "Maximum allowed size for messages", ""}, {"batch-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.batch_size, "Number of changes in a database transaction batch", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("init", "Options for the 'init' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static gboolean index_post_parse_func (GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { if (!MU_CONFIG.maildir && !MU_CONFIG.my_addresses) return TRUE; g_printerr ("%sNOTE%s: as of mu 1.3.8, 'mu index' no longer uses the\n" "--maildir/-m or --my-address options.\n\n", color_maybe(MU_COLOR_RED), color_maybe(MU_COLOR_DEFAULT)); g_printerr ("Instead, these options should be passed to 'mu init'.\n"); g_printerr ("See the mu-init(1) or the mu4e reference manual,\n'Initializing the message store' for details.\n\n"); return TRUE; } static GOptionGroup* config_options_group_index () { GOptionGroup *og; GOptionEntry entries[] = { /* only here so we can tell users they are deprecated */ {"maildir", 'm', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &MU_CONFIG.maildir, "top of the maildir", ""}, {"my-address", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.my_addresses, "my e-mail address; can be used multiple times", "
"}, {"lazy-check", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.lazycheck, "only check dir-timestamps (false)", NULL}, {"nocleanup", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocleanup, "don't clean up the database after indexing (false)", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("index", "Options for the 'index' command", "", NULL, NULL); g_option_group_add_entries(og, entries); g_option_group_set_parse_hooks(og, NULL, (GOptionParseFunc)index_post_parse_func); return og; } static void set_group_find_defaults () { /* note, when no fields are specified, we use date-from-subject */ if (!MU_CONFIG.fields || !*MU_CONFIG.fields) { MU_CONFIG.fields = g_strdup ("d f s"); if (!MU_CONFIG.sortfield) { MU_CONFIG.sortfield = g_strdup ("d"); } } if (!MU_CONFIG.formatstr) /* by default, use plain output */ MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; else MU_CONFIG.format = get_output_format (MU_CONFIG.formatstr); expand_dir (MU_CONFIG.linksdir); } static GOptionGroup* config_options_group_find () { GOptionGroup *og; GOptionEntry entries[] = { {"fields", 'f', 0, G_OPTION_ARG_STRING, &MU_CONFIG.fields, "fields to display in the output", ""}, {"sortfield", 's', 0, G_OPTION_ARG_STRING, &MU_CONFIG.sortfield, "field to sort on", ""}, {"maxnum", 'n', 0, G_OPTION_ARG_INT, &MU_CONFIG.maxnum, "number of entries to display in the output", ""}, {"threads", 't', 0, G_OPTION_ARG_NONE, &MU_CONFIG.threads, "show message threads", NULL}, {"bookmark", 'b', 0, G_OPTION_ARG_STRING, &MU_CONFIG.bookmark, "use a bookmarked query", ""}, {"reverse", 'z', 0, G_OPTION_ARG_NONE, &MU_CONFIG.reverse, "sort in reverse (descending) order (z -> a)", NULL}, {"skip-dups", 'u', 0, G_OPTION_ARG_NONE, &MU_CONFIG.skip_dups, "show only the first of messages duplicates (false)", NULL}, {"include-related", 'r', 0, G_OPTION_ARG_NONE, &MU_CONFIG.include_related, "include related messages in results (false)", NULL}, {"linksdir", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.linksdir, "output as symbolic links to a target maildir", ""}, {"clearlinks", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.clearlinks, "clear old links before filling a linksdir (false)", NULL}, {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, "output format ('plain'(*), 'links', 'xml'," "'sexp', 'xquery')", ""}, {"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len, "use up to lines for the summary, or 0 for none (0)", ""}, {"exec", 'e', 0, G_OPTION_ARG_STRING, &MU_CONFIG.exec, "execute command on each match message", ""}, {"after", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.after, "only show messages whose m_time > T (t_time)", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("find", "Options for the 'find' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static GOptionGroup * config_options_group_mkdir () { GOptionGroup *og; GOptionEntry entries[] = { {"mode", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.dirmode, "set the mode (as in chmod), in octal notation", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; /* set dirmode before, because '0000' is a valid mode */ MU_CONFIG.dirmode = 0755; og = g_option_group_new("mkdir", "Options for the 'mkdir' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static void set_group_cfind_defaults () { if (!MU_CONFIG.formatstr) /* by default, use plain output */ MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; else MU_CONFIG.format = get_output_format (MU_CONFIG.formatstr); } static GOptionGroup * config_options_group_cfind () { GOptionGroup *og; GOptionEntry entries[] = { {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, "output format (plain(*), mutt-alias, mutt-ab, wl, " "org-contact, bbdb, csv)", ""}, {"personal", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.personal, "whether to only get 'personal' contacts", NULL}, {"after", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.after, "only get addresses last seen after T", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("cfind", "Options for the 'cfind' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static GOptionGroup * config_options_group_script () { GOptionGroup *og; GOptionEntry entries[] = { {G_OPTION_REMAINING, 0,0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.params, "script parameters", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("script", "Options for the 'script' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static void set_group_view_defaults () { if (!MU_CONFIG.formatstr) /* by default, use plain output */ MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; else MU_CONFIG.format = get_output_format (MU_CONFIG.formatstr); } /* crypto options are used in a few different commands */ static GOptionEntry* crypto_option_entries () { static GOptionEntry entries[] = { {"auto-retrieve", 'r', 0, G_OPTION_ARG_NONE, &MU_CONFIG.auto_retrieve, "attempt to retrieve keys online (false)", NULL}, {"use-agent", 'a', 0, G_OPTION_ARG_NONE, &MU_CONFIG.use_agent, "attempt to use the GPG agent (false)", NULL}, {"decrypt", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.decrypt, "attempt to decrypt the message", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; return entries; } static GOptionGroup* config_options_group_view() { GOptionGroup* og; GOptionEntry entries[] = {{"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len, "use up to lines for the summary, or 0 for none (0)", ""}, {"terminate", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.terminator, "terminate messages with ascii-0x07 (\\f, form-feed)", NULL}, {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, "output format ('plain'(*), 'sexp')", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; og = g_option_group_new("view", "Options for the 'view' command", "", NULL, NULL); g_option_group_add_entries(og, entries); g_option_group_add_entries(og, crypto_option_entries()); return og; } static void set_group_extract_defaults () { if (!MU_CONFIG.targetdir) MU_CONFIG.targetdir = g_strdup ("."); expand_dir (MU_CONFIG.targetdir); } static GOptionGroup* config_options_group_extract () { GOptionGroup *og; GOptionEntry entries[] = { {"save-attachments", 'a', 0, G_OPTION_ARG_NONE, &MU_CONFIG.save_attachments, "save all attachments (false)", NULL}, {"save-all", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.save_all, "save all parts (incl. non-attachments) (false)", NULL}, {"parts", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.parts, "save specific parts (comma-separated list)", ""}, {"target-dir", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.targetdir, "target directory for saving", ""}, {"overwrite", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.overwrite, "overwrite existing files (false)", NULL}, {"play", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.play, "try to 'play' (open) the extracted parts", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("extract", "Options for the 'extract' command", "", NULL, NULL); g_option_group_add_entries(og, entries); g_option_group_add_entries(og, crypto_option_entries()); return og; } static GOptionGroup* config_options_group_verify () { GOptionGroup *og; og = g_option_group_new("verify", "Options for the 'verify' command", "", NULL, NULL); g_option_group_add_entries(og, crypto_option_entries()); return og; } static GOptionGroup* config_options_group_server () { GOptionGroup *og; GOptionEntry entries[] = { {"commands", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.commands, "list the available command and their parameters, then exit", NULL}, {"eval", 'e', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &MU_CONFIG.eval, "expression to evaluate", ""}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; og = g_option_group_new("server", "Options for the 'server' command", "", NULL, NULL); g_option_group_add_entries(og, entries); return og; } static MuConfigCmd cmd_from_string (const char *str) { int i; struct { const gchar* name; MuConfigCmd cmd; } cmd_map[] = { { "add", MU_CONFIG_CMD_ADD }, { "cfind", MU_CONFIG_CMD_CFIND }, { "extract", MU_CONFIG_CMD_EXTRACT }, { "find", MU_CONFIG_CMD_FIND }, { "help", MU_CONFIG_CMD_HELP }, { "index", MU_CONFIG_CMD_INDEX }, { "info", MU_CONFIG_CMD_INFO }, { "init", MU_CONFIG_CMD_INIT }, { "mkdir", MU_CONFIG_CMD_MKDIR }, { "remove", MU_CONFIG_CMD_REMOVE }, { "script", MU_CONFIG_CMD_SCRIPT }, { "server", MU_CONFIG_CMD_SERVER }, { "tickle", MU_CONFIG_CMD_TICKLE }, { "verify", MU_CONFIG_CMD_VERIFY }, { "view", MU_CONFIG_CMD_VIEW } }; if (!str) return MU_CONFIG_CMD_UNKNOWN; for (i = 0; i != G_N_ELEMENTS(cmd_map); ++i) if (strcmp (str, cmd_map[i].name) == 0) return cmd_map[i].cmd; #ifdef BUILD_GUILE /* if we don't recognize it and it's not an option, it may be * some script */ if (str[0] != '-') return MU_CONFIG_CMD_SCRIPT; #endif /*BUILD_GUILE*/ return MU_CONFIG_CMD_UNKNOWN; } static gboolean parse_cmd (int *argcp, char ***argvp, GError **err) { MU_CONFIG.cmd = MU_CONFIG_CMD_NONE; MU_CONFIG.cmdstr = NULL; if (*argcp < 2) /* no command found at all */ return TRUE; else if ((**argvp)[1] == '-') /* if the first param starts with '-', there is no * command, just some option (like --version, --help * etc.)*/ return TRUE; MU_CONFIG.cmdstr = g_strdup ((*argvp)[1]); MU_CONFIG.cmd = cmd_from_string (MU_CONFIG.cmdstr); #ifndef BUILD_GUILE if (MU_CONFIG.cmd == MU_CONFIG_CMD_SCRIPT) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "command 'script' not supported"); return FALSE; } #endif /*!BUILD_GUILE*/ if (MU_CONFIG.cmdstr && MU_CONFIG.cmdstr[0] != '-' && MU_CONFIG.cmd == MU_CONFIG_CMD_UNKNOWN) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "unknown command '%s'", MU_CONFIG.cmdstr); return FALSE; } return TRUE; } static GOptionGroup* get_option_group (MuConfigCmd cmd) { switch (cmd) { case MU_CONFIG_CMD_CFIND: return config_options_group_cfind(); case MU_CONFIG_CMD_EXTRACT: return config_options_group_extract(); case MU_CONFIG_CMD_FIND: return config_options_group_find(); case MU_CONFIG_CMD_INDEX: return config_options_group_index(); case MU_CONFIG_CMD_INIT: return config_options_group_init(); case MU_CONFIG_CMD_MKDIR: return config_options_group_mkdir(); case MU_CONFIG_CMD_SERVER: return config_options_group_server(); case MU_CONFIG_CMD_SCRIPT: return config_options_group_script(); case MU_CONFIG_CMD_VERIFY: return config_options_group_verify(); case MU_CONFIG_CMD_VIEW: return config_options_group_view(); default: return NULL; /* no group to add */ } } /* ugh yuck massaging the GOption text output; glib prepares some text * which has a 'Usage:' for the 'help' command. However, we need the * help for the command we're asking help for. So, we remove the Usage: * from what glib generates. :-( */ static gchar* massage_help (const char *help) { GRegex *rx; char *str; rx = g_regex_new ("^Usage:.*\n.*\n", (GRegexCompileFlags)0, G_REGEX_MATCH_NEWLINE_ANY, NULL); str = g_regex_replace (rx, help, -1, 0, "", G_REGEX_MATCH_NEWLINE_ANY, NULL); g_regex_unref (rx); return str; } static const gchar* get_help_string (MuConfigCmd cmd, gboolean long_help) { unsigned u; /* this include gets us MU_HELP_STRINGS */ #include "mu-help-strings.h" for (u = 0; u != G_N_ELEMENTS(MU_HELP_STRINGS); ++u) if (cmd == MU_HELP_STRINGS[u].cmd) { if (long_help) return MU_HELP_STRINGS[u].long_help; else return MU_HELP_STRINGS[u].usage ; } g_return_val_if_reached (""); return ""; } void Mu::mu_config_show_help (MuConfigCmd cmd) { GOptionContext *ctx; GOptionGroup *group; char *help, *cleanhelp; g_return_if_fail (mu_config_cmd_is_valid(cmd)); ctx = g_option_context_new ("- mu help"); g_option_context_set_main_group (ctx, config_options_group_mu()); group = get_option_group (cmd); if (group) g_option_context_add_group (ctx, group); g_option_context_set_description (ctx, get_help_string (cmd, TRUE)); help = g_option_context_get_help (ctx, TRUE, group); cleanhelp = massage_help (help); g_print ("usage:\n\t%s%s", get_help_string (cmd, FALSE), cleanhelp); g_free (help); g_free (cleanhelp); g_option_context_free (ctx); } static gboolean cmd_help () { MuConfigCmd cmd; if (!MU_CONFIG.params) cmd = MU_CONFIG_CMD_UNKNOWN; else cmd = cmd_from_string (MU_CONFIG.params[1]); if (cmd == MU_CONFIG_CMD_UNKNOWN) { mu_config_show_help (MU_CONFIG_CMD_HELP); return TRUE; } mu_config_show_help (cmd); return TRUE; } static gboolean parse_params (int *argcp, char ***argvp, GError **err) { GOptionContext *context; GOptionGroup *group; gboolean rv; context = g_option_context_new("- mu general options"); g_option_context_set_help_enabled (context, TRUE); rv = TRUE; g_option_context_set_main_group(context, config_options_group_mu()); g_option_context_set_ignore_unknown_options (context, FALSE); switch (MU_CONFIG.cmd) { case MU_CONFIG_CMD_NONE: case MU_CONFIG_CMD_HELP: /* 'help' is special; sucks in the options of the * command after it */ rv = g_option_context_parse (context, argcp, argvp, err) && cmd_help (); break; case MU_CONFIG_CMD_SCRIPT: /* all unknown commands are passed to 'script' */ g_option_context_set_ignore_unknown_options (context, TRUE); group = get_option_group (MU_CONFIG.cmd); g_option_context_add_group (context, group); rv = g_option_context_parse (context, argcp, argvp, err); MU_CONFIG.script = g_strdup (MU_CONFIG.cmdstr); /* argvp contains the script parameters */ MU_CONFIG.script_params = (const char**)&((*argvp)[1]); break; default: group = get_option_group (MU_CONFIG.cmd); if (group) g_option_context_add_group (context, group); rv = g_option_context_parse (context, argcp, argvp, err); break; } g_option_context_free (context); return rv ? TRUE : FALSE; } MuConfig* Mu::mu_config_init (int *argcp, char ***argvp, GError **err) { g_return_val_if_fail (argcp && argvp, NULL); memset (&MU_CONFIG, 0, sizeof(MU_CONFIG)); if (!parse_cmd (argcp, argvp, err)) goto errexit; if (!parse_params(argcp, argvp, err)) goto errexit; /* fill in the defaults if user did not specify */ set_group_mu_defaults(); set_group_init_defaults(); set_group_find_defaults(); set_group_cfind_defaults(); set_group_view_defaults(); set_group_extract_defaults(); /* set_group_mkdir_defaults (config); */ return &MU_CONFIG; errexit: mu_config_uninit (&MU_CONFIG); return NULL; } void Mu::mu_config_uninit (MuConfig *opts) { if (!opts) return; g_free (opts->cmdstr); g_free (opts->muhome); g_free (opts->maildir); g_free (opts->fields); g_free (opts->sortfield); g_free (opts->bookmark); g_free (opts->formatstr); g_free (opts->exec); g_free (opts->linksdir); g_free (opts->targetdir); g_free (opts->parts); g_free (opts->script); g_free (opts->eval); g_strfreev (opts->params); memset (opts, 0, sizeof(MU_CONFIG)); } size_t Mu::mu_config_param_num (const MuConfig *opts) { size_t n; g_return_val_if_fail (opts && opts->params, 0); for (n = 0; opts->params[n]; ++n); return n; } MuMsgOptions Mu::mu_config_get_msg_options (const MuConfig *muopts) { int opts; opts = MU_MSG_OPTION_NONE; if (muopts->decrypt) opts |= MU_MSG_OPTION_DECRYPT; if (muopts->verify) opts |= MU_MSG_OPTION_VERIFY; if (muopts->use_agent) opts |= MU_MSG_OPTION_USE_AGENT; if (muopts->auto_retrieve) opts |= MU_MSG_OPTION_AUTO_RETRIEVE; if (muopts->overwrite) opts |= MU_MSG_OPTION_OVERWRITE; return (MuMsgOptions)opts; } mu-1.6.10/mu/mu-config.hh000066400000000000000000000176541414367003600150670ustar00rootroot00000000000000/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_CONFIG_HH__ #define MU_CONFIG_HH__ #include #include /* for mode_t */ #include #include #include namespace Mu { /* env var; if non-empty, color are disabled */ #define MU_NOCOLOR "MU_NOCOLOR" typedef enum { MU_CONFIG_FORMAT_UNKNOWN = 0, /* for cfind, find, view */ MU_CONFIG_FORMAT_PLAIN, /* plain output */ /* for cfind */ MU_CONFIG_FORMAT_MUTT_ALIAS, /* mutt alias style */ MU_CONFIG_FORMAT_MUTT_AB, /* mutt ext abook */ MU_CONFIG_FORMAT_WL, /* Wanderlust abook */ MU_CONFIG_FORMAT_CSV, /* comma-sep'd values */ MU_CONFIG_FORMAT_ORG_CONTACT, /* org-contact */ MU_CONFIG_FORMAT_BBDB, /* BBDB */ MU_CONFIG_FORMAT_DEBUG, /* for find, view */ MU_CONFIG_FORMAT_SEXP, /* output sexps (emacs) */ MU_CONFIG_FORMAT_JSON, /* output JSON */ /* for find */ MU_CONFIG_FORMAT_LINKS, /* output as symlinks */ MU_CONFIG_FORMAT_XML, /* output xml */ MU_CONFIG_FORMAT_XQUERY, /* output the xapian query */ MU_CONFIG_FORMAT_MQUERY, /* output the mux query */ MU_CONFIG_FORMAT_EXEC /* execute some command */ } MuConfigFormat; typedef enum { MU_CONFIG_CMD_UNKNOWN = 0, MU_CONFIG_CMD_ADD, MU_CONFIG_CMD_CFIND, MU_CONFIG_CMD_EXTRACT, MU_CONFIG_CMD_FIND, MU_CONFIG_CMD_HELP, MU_CONFIG_CMD_INDEX, MU_CONFIG_CMD_INFO, MU_CONFIG_CMD_INIT, MU_CONFIG_CMD_MKDIR, MU_CONFIG_CMD_REMOVE, MU_CONFIG_CMD_SCRIPT, MU_CONFIG_CMD_SERVER, MU_CONFIG_CMD_TICKLE, MU_CONFIG_CMD_VERIFY, MU_CONFIG_CMD_VIEW, MU_CONFIG_CMD_NONE } MuConfigCmd; #define mu_config_cmd_is_valid(C) \ ((C) > MU_CONFIG_CMD_UNKNOWN && (C) < MU_CONFIG_CMD_NONE) /* struct with all configuration options for mu; it will be filled * from the config file, and/or command line arguments */ struct _MuConfig { MuConfigCmd cmd; /* the command, or * MU_CONFIG_CMD_NONE */ char *cmdstr; /* cmd string, for user * info */ /* general options */ gboolean quiet; /* don't give any output */ gboolean debug; /* log debug-level info */ gchar *muhome; /* the House of Mu */ gboolean version; /* request mu version */ gboolean log_stderr; /* log to stderr (not logfile) */ gchar** params; /* parameters (for querying) */ gboolean nocolor; /* don't use use ansi-colors * in some output */ gboolean verbose; /* verbose output */ /* options for init */ gchar *maildir; /* where the mails are */ char** my_addresses; /* 'my e-mail address', for mu cfind; * can be use multiple times */ int max_msg_size; /* maximum size for message files */ int batch_size; /* database transaction batch size */ /* options for indexing */ gboolean nocleanup; /* don't cleanup del'd mails from db */ gboolean lazycheck; /* don't check dirs with up-to-date * timestamps */ /* options for querying 'find' (and view-> 'summary') */ gchar *fields; /* fields to show in output */ gchar *sortfield; /* field to sort by (string) */ int maxnum; /* max # of entries to print */ gboolean reverse; /* sort in revers order (z->a) */ gboolean threads; /* show message threads */ gboolean summary; /* OBSOLETE: use summary_len */ int summary_len; /* max # of lines for summary */ gchar *bookmark; /* use bookmark */ gchar *formatstr; /* output type for find * (plain,links,xml,json,sexp) * and view (plain, sexp) and cfind */ MuConfigFormat format; /* the decoded formatstr */ gchar *exec; /* command to execute on the * files for the matched * messages */ gboolean skip_dups; /* if there are multiple * messages with the same * msgid, show only the first * one */ gboolean include_related; /* included related messages * in results */ /* for find and cind */ time_t after; /* only show messages or * addresses last seen after * T */ /* options for crypto * ie, 'view', 'extract' */ gboolean auto_retrieve; /* assume we're online */ gboolean use_agent; /* attempt to use the gpg-agent */ gboolean decrypt; /* try to decrypt the * message body, if any */ gboolean verify; /* try to crypto-verify the * message */ /* options for view */ gboolean terminator; /* add separator \f between * multiple messages in mu * view */ /* options for cfind (and 'find' --> "after") */ gboolean personal; /* only show 'personal' addresses */ /* also 'after' --> see above */ /* output to a maildir with symlinks */ gchar *linksdir; /* maildir to output symlinks */ gboolean clearlinks; /* clear a linksdir before filling */ mode_t dirmode; /* mode for the created maildir */ /* options for extracting parts */ gboolean save_all; /* extract all parts */ gboolean save_attachments; /* extract all attachment parts */ gchar *parts; /* comma-sep'd list of parts * to save / open */ gchar *targetdir; /* where to save the attachments */ gboolean overwrite; /* should we overwrite same-named files */ gboolean play; /* after saving, try to 'play' * (open) the attmnt using xdgopen */ /* for server */ gboolean commands; /* dump documentations for server * commands */ gchar *eval; /* command to evaluate */ /* options for mu-script */ gchar *script; /* script to run */ const char **script_params; /* parameters for scripts */ }; typedef struct _MuConfig MuConfig; /** * initialize a mu config object * * set default values for the configuration options; when you call * mu_config_init, you should also call mu_config_uninit when the data * is no longer needed. * * Note that this is _static_ data, ie., mu_config_init will always * return the same pointer * * @param argcp: pointer to argc * @param argvp: pointer to argv * @param err: receives error information */ MuConfig *mu_config_init (int *argcp, char ***argvp, GError **err) G_GNUC_WARN_UNUSED_RESULT; /** * free the MuConfig structure * * @param opts a MuConfig struct, or NULL */ void mu_config_uninit (MuConfig *conf); /** * execute the command / options in this config * * @param opts a MuConfig struct * * @return a value denoting the success/failure of the execution; * MU_ERROR_NONE (0) for success, non-zero for a failure. This is to used for * the exit code of the process * */ MuError mu_config_execute (const MuConfig *conf); /** * count the number of non-option parameters * * @param opts a MuConfig struct * * @return the number of non-option parameters, or 0 in case of error */ size_t mu_config_param_num (const MuConfig *conf); /** * determine MuMsgOptions for command line args * * @param opts a MuConfig struct * * @return the corresponding MuMsgOptions */ MuMsgOptions mu_config_get_msg_options (const MuConfig *opts); /** * print help text for the current command * * @param cmd the command to show help for */ void mu_config_show_help (const MuConfigCmd cmd); } // namespace Mu. #endif /*__MU_CONFIG_H__*/ mu-1.6.10/mu/mu-help-strings.awk000066400000000000000000000027771414367003600164240ustar00rootroot00000000000000## Copyright (C) 2012 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ## convert text blobs statements into c-strings BEGIN { in_def=0; in_string=0; # srand(); # guard=int(100000*rand()); # print "#ifndef __" guard "__" print "/* Do not edit - auto-generated. */" print "static const struct {" print "\tMuConfigCmd cmd;" print "\tconst char *usage;" print "\tconst char *long_help;" print "} MU_HELP_STRINGS[] = {" } /^#BEGIN/ { print "\t{ " $2 "," # e.g., MU_CONFIG_CMD_ADD in_def=1 } /^#STRING/ { if (in_def== 1) { if (in_string==1) { print ","; } in_string=1 } } /^#END/ { if (in_string==1) { in_string=0; } in_def=0; print "\n\t},\n" } !/^#/ { if (in_string==1) { printf "\n\t\"" $0 "\\n\"" } } END { print "};" # print "#endif /*" guard "*/" print "/* the end */" } mu-1.6.10/mu/mu-help-strings.txt000066400000000000000000000126521414367003600164520ustar00rootroot00000000000000#-*-mode:org-*- # # Copyright (C) 2012-2020 Dirk-Jan C. Binnema # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #BEGIN MU_CONFIG_CMD_ADD #STRING mu add [] #STRING mu add is the command to add specific measage files to the database. Each of the files must be specified with an absolute path. #END #BEGIN MU_CONFIG_CMD_CFIND #STRING mu cfind [options] [--format=] [--personal] [--after=] [] #STRING mu cfind is the mu command to find contacts in the mu database and export them for use in other programs. is one of: plain mutt-alias mutt-ab wl csv org-contact bbdb 'plain' is the default. If you specify '--personal', only addresses that were found in mails that include 'my' e-mail address will be listed - so to exclude e.g. mailing-list posts. Use the --my-address= option in 'mu index' to specify what addresses are considered 'my' address. With '--after=T' you can tell mu to only show addresses that were seen after T. T is a Unix timestamp. For example, to get only addresses seen after the beginning of 2012, you could use --after=`date +%%s -d 2012-01-01` #END #BEGIN MU_CONFIG_CMD_EXTRACT #STRING mu extract [options] #STRING mu extract is the mu command to display and save message parts (attachments), and open them with other tools. #END #BEGIN MU_CONFIG_CMD_FIND #STRING mu find [options] #STRING mu find is the mu command for searching e-mail message that were stored earlier using mu index(1). Some examples: # get all messages with 'bananas' in body, subject or recipient fields: $ mu find bananas # get all messages regarding bananas from John with an attachment: $ mu find from:john flag:attach bananas # get all messages with subject wombat in June 2009 $ mu find subject:wombat date:20090601..20090630 See the `mu-find' and `mu-easy' man-pages for more information. #END #BEGIN MU_CONFIG_CMD_HELP #STRING mu help #STRING mu help is the mu command to get help about , where is one of: add - add message to database cfind - find a contact extract - extract parts/attachments from messages find - query the message database help - get help index - index messages init - init the mu database mkdir - create a maildir remove - remove a message from the database script - run a script (available only when mu was built with guile-support) server - start mu server verify - verify signatures of a message view - view a specific message #END #BEGIN MU_CONFIG_CMD_INDEX #STRING mu index [options] #STRING mu index is the mu command for scanning the contents of Maildir directories and storing the results in a Xapian database.The data can then be queried using mu-find(1). #END #BEGIN MU_CONFIG_CMD_INIT #STRING mu init [options] #STRING mu init is the mu command for setting up the mu database. #END #BEGIN MU_CONFIG_CMD_INFO #STRING mu init [options] #STRING mu info is the command for getting information about a mu database. #END #BEGIN MU_CONFIG_CMD_MKDIR #STRING mu mkdir [options] [] #STRING mu mkdir is the command for creating Maildirs.It does not use the mu database. #END #BEGIN MU_CONFIG_CMD_REMOVE #STRING mu remove [options] [] #STRING mu remove is the mu command to remove messages from the database. #END #BEGIN MU_CONFIG_CMD_SERVER #STRING mu server [options] #STRING mu server starts a simple shell in which one can query and manipulate the mu database.The output of the commands is terms of Lisp symbolic expressions (s-exps). Its main use is for the mu4e e-mail client. #END #BEGIN MU_CONFIG_CMD_SCRIPT #STRING mu script [] [-v] mu [ #+end_html mu-1.6.10/www/mu-small.png000066400000000000000000000147251414367003600153160ustar00rootroot00000000000000PNG  IHDR]3sRGBbKGD pHYs  tIME  $p ZUIDATx՜{]}?s{].EQdʢ%Yrl9c6lu9M&A i Mk(&E EPMI'h:m4j'JlQEIQ"_}93ٳw)^ܻw|;s(|{_r |_~o?wv?w~.0;; !4@|0&!KOʂ(ɧ>)׀;bmq T=Y "܎5p$|>+\Y\buu`ߧg/* ~9_w죡!ݭi+c~nhV|O H: _$;! t)5[D讯s ZXD{엞yn5ʿO IXeegV76(a<O}YDW5ֻt!x|px9G{l#wɫ]$7{GZ fbg౷|9;| L |Rf!I /r֢JNl|b:ypK`M>l|?~]wr"ZviLO똞s|8gqi>g:v6tU[y}gLwl Fw}|@k c۷5#No<އGMai3:Sdf\wuy> F{iQg5fr1b860VWnpS Y&o=z.\J20c*8u'jysXkp>{q XK?W6f 6F[ ia,!5Xk7~'ѷ<죰A㝧n(;Zx"5\zF 'Os[⬭:nDC:62L Q A~7 un܇!}W7HgT"&SDڒqg-s9Kˋ }Xj6!/-09G= }zAcvn'!x.ZVWWp˗"5eLoK? )(*_]7kc1P819񫟶1ohG֘t&gcY 'OCGx"21.>4N.&amA;g VV{hb:l cdxm@^ݻ,`VWWxY c>{zRy"ӛ%1 W/pD˯__Gu@(SgαixǑ~7FwкԔϰy)vϕ`޻Gh5x~ i53z>vu546- ԣ1֢*V1 %KzKQ%5v_E9N-9Xj,B( /y#Z=3vt&ڄA1ENQ |,J)t)ZDy!%(_4-i *"g"a(AoT$Ue.Eb`l7!q<Š D6j,ƫ7lMZKL~zɦa^OqD@kT̷`h2;&v1&w/a݈hWRژ7Lmi9D b8:<˕W^0E14)'q#7qn5cFjSxԮfiF_4{Μ Xkɴp߽wK"u @7ڵE^rgyl&Ceͣ7suFޖ/,9bh'ZہAt?|bGȃ٪֦c&:S5iq{N>ǎeZqߑC|i1E)L'OوWa6t'+F>J6F&`mu B%'XUw )AGz7*zP(kI RMr:a G ^;"s(%8afz1xIo}=8ҫY4nc҆FbXgfQsΥ~1|˜RZ5O*^11n>1ͣb^v(S.'_|,[?@06dH*j(m[ 0E67Ɣ4k*Kkk,(s DM5.TLQ~zzߺˈ)qɖ[ݰ#N  al{u zkn3.n7)@)nbdzt/ԣ>,:( ҩLN)!d^ c@ !SI-[c˭wҡT V6K`Ca#CNJqP3KŚqs_6xTJq*K>9p5;!sT:J-֕hU9ă&.ƹ{Y78z RA"(b%6hQ%(/ Vd /{"ӛA25tIQ"(-@dT)K+(.]S*nֈ(μzrW',G7\gLiP:r#_2]/t*`~B}ݜ,~ʝ;&vkVdJE ?"قŨ*VEљV" *'z<©o]hed&8N~kfCǦ9WΔϋ&lNMK%'2EJ`QO#jcJ 2ZZgdgɡ-%fkVR'ң$2`ziFCs}qn$y`,c-+k,jfB`FƩ'^0PGȴQ2f:wjHgM֐dCTd|ڸҴm#7k~6JψRPjݕZ|uӇ?c.^}6%` ˩/lh2〗ԼzJ'V)Z+HPDD֡Ʋao19NFQ;`{f#{,c nTtn!%:l #o4$U:J*J {_jV~2dJٗ^b'.^~A+!Ӛ^8eΞs r"KK+z"c&J)WVgVx%ٖQ_2m:Q[Vqxr&cgDv5v!*71=eJS #y;?#l!U(PϨn3M}t[gg&~4Ըbr$wi r"#sLgf3C74n?'/L9ed^JUg#7NsiΩ."2=fzrBڲsKWWrnb2٨2Hя5yǾLqAqy(5wNdxw>FR##ÖVDQ|7:`yu'_zذG/]rpIMWOJIJ)E5YhdLk M#SZwpfCKtE("/BA(=%@qaZ>#|Q:F@8ؽ{0ހZ*]T ʲƦ`HZ6`eeWY\^%^ u%RD$a ]}y}_0KY7 odQBJHK o'ذU^B[MoPʹ׹4صkO<>uv Ws;nu\X¥}~lC?oo- b*7LTD62[;(1rIRJj @+Z'pFFW,qlx+{#ZNpy&&hdY6 `}zNoevC^X._[diFʵGall+EVx!k(E,J!%. @E^Hdo373_g^pSMpbRJ:mfg&7bk]vk=m4tgnz>OLLq90>֦aqe g~6nίsjJ)Xɱ2oxH=+7s)Luؽs20-hU`nG3宽8c:z0v_kT=od1asl{n\z^[pkg72ɻL-VzO?2Au:fh3?ۡ3ъ6-n77 FaYIߥIwL07](3HZ, )硲l> &ʿ6w=7kϼTA I9~o%w- ~;]B)9Ǻxez;|<%),]\°Hߞzg_{2;)JbqYOqsLhT.U5SMyAWFb녇7>Ru/#nZȈUOZ+Ry|㜋;_nlZz/k׾xŚ &CӚze{PjzT6Ft&7LUQ]x^U|MW~U*Ӫ]ߠ7Q^vD:>m=GU'x/U̸ lgj7FF]s mM ȶYhu]xύܿ y ,V_o # }-urOĨ ?!7I(IENDB`mu-1.6.10/www/mu.css000066400000000000000000000036261414367003600142120ustar00rootroot00000000000000/* stylesheet for mu website */ body { background:#ffffff; margin: 50px; padding: 10px; font-family: arial, Helvetica, 'Bitstream Vera Sans', 'Luxi Sans', Verdana, Sans-Serif; font-size: 12px; } .content { margin:30px; background: #5fb6de; } a.menu {font-weight: bold} a.menu:link {color: #ffffff; text-decoration: none; } a.menu:active {color: #ff0000; text-decoration: none; } a.menu:visited {color: #ffffff; text-decoration: none; } a.menu:hover {color: #ff0000; text-decoration: underline; } .mine {color: #ffffff; font-weight: bold} /* emacs-code -----------------------*/ /* zenburnesque code blocks in for html-exported org mode */ pre.src { background: #dddddd; color: #555555; } .org-preprocessor { color: #8cd0d3; } .org-variable-name { color: #0084C8; font-weight: bold; } .org-string { color: #4E9A06; } .org-type { color: #2F8B58; font-weight: bold; } .org-function-name { color: #00578E; font-weight: bold } .org-keyword { color: #A52A2A; font-weight: bold; } .org-comment { color: #204A87; } .org-doc { color: #afd8af; } .org-comment-delimiter { color: #708070; } .org-constant { color: #F5666D; } .org-builtin { color: #A020F0; } .warning, .org-warning { color: yellow; font-weight: bold } /* emacs other stuff --------------------- */ .org-date, .org-org-date { /* org-date */ color: #00ffff; text-decoration: underline; } .org-hide, .org-org-hide { /* org-hide */ color: #000000; } .org-level-1, .org-org-level-1 { /* org-level-1 */ color: #356da0; } .org-level-2,.org-org-level-2 { /* org-level-2 */ color: #7685de; } .org-todo,.org-org-todo { /* org-todo */ color: #ffc0cb; font-weight: bold; } mu-1.6.10/www/mu.jpg000066400000000000000000000465361414367003600142110ustar00rootroot00000000000000JFIFHHExifII* z(2izCanonCanon DIGITAL IXUS 30HH2008:09:10 21:00:444<0220DXl t|   |$0100 nD,4<G 2005:07:23 13:42:382005:07:23 13:42:38? t !.n" *<t|[  U V$\@Ct!Dz?EE q.qqq///IMG:DIGITAL IXUS 30 JPEGFirmware Version 1.00\&& I\ I3$)$)6>o$5m$8% 1364402@pR980100(0JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?a)=Hmj|T/5&9JsT^f9IQcEiLRi͓֠3bgY; )&vɤ&H1 j":ĐYҥIH HI(X) Vs4˲v4}IrK,+CVjUnj\I<'ֳ1+drED4 aja4iCJ4P۲i;TӺ֔ &)€).L-Έڜ9}\A{2ﴳt{z4Z[6IBz+CsZ|RjQ62;_Rd;Fway7H23{U:&܆RWeț ? NiDq"+9㜣I=k_+k@~,XĠ0 Tll 2I&ʌ:z5—C fYm.7Q?$pG2Jzz ]  6T+u5I<;}G<5Kc,?>6w){g vD =1YEեu<(Lƀ)~]SHOHqr'7eǙ">o](|IG`֓bINkiwƹ#ֻ 90#‹5T1\@lr?jتZAf#q~JlZz^E!ee8$j|UJmJ@XA{#>nEgtḣMMm:ֺh-E[aPvr}AO]̗`UuطKj~0e~5%8)2Z3RUAs &O1ЀϤ? C'5(믲g{ SZ0zRqQ4-1kUK0m6rnp{@,wOU~tRmS ZMuFdl8Q+i$ z;6wX9mt5a{.rA#8d=wSdT($`4_B}v*FUFI8fqۏC^9M]WW#\[}v6Q[#@ވ쁛+M09$zzF7sJp+Kro5Kj#FkZ54a_oi _nE"O#zv UMZYnz[ڢTBW;i0&Zϭq 2éuʷڷCYGr\m͕ũ"XGoβwG]Fz8 Y Kt:V$5rT_]s09xnۇWǘ?~dTI1H1q{W<摿bi#Gc /ӥLy;z}ӈS簭]—&uG?a׮xs,),8>T?JXZ"/iYҠPz55jhghpiDfJ`R8DmPwRIadG9jMRFҸr EicG)Lpq^s&%r ?a;ƥ O+Ϗ''@sjIGb6&tKEۿXxzhGZ'QʻϔxѣU#StX٢ƵFVhAjt-1IINM"CžمY_' zWJwJ]ҫȓa+ ?ixL̃Mf 9kʏz?Q[^*#( bqTlL0*_΢Y%JvՎ3I@YA&PՊNMjuB<}v]cI\fk>$ 5v*́j)c $Y.bhBm,q}~)!WtUV ӜRJ+uV";/sZlz6d[^?ss怱SY ʤ2qmnmm2!NC0sRW-o3YN@PRèNsIY'򮎘mx+xQSm<}k ϧI'އ[P([iqwW#8ǥ_*djW*v~=j`$HvT[I P0K* @܌9NǍ8ֳנ9$Dc7ËEM/+k:# p[j7K+>RsnVT Q~3Ԗm{y?O.1z}k+ök:-i19u0J,EEo·tuR¸Rx>)ݔdx,*߆No Ljn5sF`u'զh(G@8#Be⪲ZQtr Tg UhlԊJ캨9Ħ32HXTplNƇóNQE) `ǻKLVabSN(IFYAcM9I{/i %${GDP#!YTMMX7DPY h ܒ.HEIJaF9"d[I Ĉ,CzU`hi26HӚz6_z3t.I"A1pB]k"(ZTqʢz!٫^cnWH+KܖiV{dM䘖:6w$Dtee#U\mΊWΩ"3XDF+rLtAJtyXh֣%`V>`H]z$$,ҸM#)4Ws@o %@Ā4I,P#I0dVv.RnMŴ%1d.: CbsG p.9!9=:/:iwt@Z,/|86fa.~dnDS^ZQ[%𶶚 Hukp_ "렟Z8xDBw/i@v0B$n9ddAR FrI."őUR<@4b Z3=MAT642M;!RI0x񷳌EJOEJ-7zϩs pӆ*.q$ߪHYwF=w!) kִt\*L iO~_.MB@jPls Q =d@٭gҊ%q&V5Hǽ|Pvcl,>Bω4u2?$Z 'lkceH fgw D2F-?fhbǕtwP,y| 9 w;Ժm>0 I' &⫊81Iq eSF`x~:#s^ !kgM:jp#{s3@ b+'Da=BJ>˸΄Cz (s.n:+YEki A-h &c^F} f l-(UaE}"OP9!"ቶ7*[$p*DpE]Suw"ە$x->p+iA^}/$60BS0H*$1by'1b6쁧aUI=3(Vˢ]< =k&z1l TUs7V#{l͜ӑ/䡨eDJk}ǹ%*:067qvw͜ ũ0};UNOO(,u69:e;VC\\YtFN$KKNʱmp=(Fʃoq(54ַڬ/=ɧ/ `TQAGw(j#sT8Qy(i*]m/Éz5.. rT\2J/rS f-v~,okee۬R_"BϣR߲ p,ӣst'K# x$UUx]ob98s\fʭv{r @$/o^p!ːwnGU) `\otk"iug(dfDJl=& Bț&XxtRk$ YF|Y0[tEaJ&؄Zt)ARg[$ۄ$ԦtI.R~ԛ?xU){qFxZ>ڮI lrN)7 sâ@ktU3 ,52VVIQ!ۢTz K5 &=ѰL".Fy\<=<\02J;V"Dn[F 81WVJf-scs>qx->7Y*WvFdr>qȟES7L x*X"i3x.9D*)YF攗9ks**QNͤ]V2b5sd9fI ҾvK^ȅX@jbRFع++dc\l.}+){N 05rMħİJz 1،eqCCx-6sG@f| Pnz >gc_AO<2 XWL"gcRSbtFF3bV% >:Ѳv9܁ZMjNI}9ћfW8FZgf i7E3 &ֽ2$߸UF$QR 7.ѡj:ic]P/umU&Egg4$Ym=OqU ]^)QǶsLjv!%3i)#G˲fkqkQ^ _ WS/M3Hz9-.g+~}aT$p <>&(2.$} KЮq! [qgj0b,vZCkK9Xg(m`ij]|/spAs[i*1gaz]ۇ4:|u<1^Qd4:tΜ1=v-Nw5Ж04]=_Y5UC窔WrztQ_#$.{)*9jq>Y\lѧzMNjƆ. .q@t&2V㍎y qGg|,p 5Ťh\1[yg7W8D˚؇V:"lS xdhޜPR𗙦ݤҸX5ܔL)j_&5V$gZxHy窣wn{k>̏uH5MUӲjw2X}b:%٩+m4Xf9ds$s,Fcqf3ߔwܮ(kvi28:J) Fٷ=x9.;F\fNzwY -`{ sR!Z dW Mܩ[$74fBdW4dc{h#nqC$ -(vhAK$"g77wcc}4|;xqkoewx.{&252V޷i΄|AλPh8A!?(Y[xn2}8H75ӉߨU$2.g$$QaF64#G<@wG/RԊYkAYCۙb6:yD\|Ȭ&9c2$pd ݑ4ޢR|8^Fݧߌ&Ǥ<:Rŀ`j%u9W8k>GlqJ X8.|VbJh]K i]`t5CaXe[!6h-x-6vԫeM}Y0'ӗUTOY(ohDı,7Ց͏s=ʃ 5UU0[{Ϲj'P-LJՆҐzo@j};WVql NlK~G/Xg㸅vEr$Zg KeR,|bEt&L:QenP~&X x{ۮNI9iDDM8l`d-ysOEnal6gh6he@CC~31|=kǛaخ/@py dX|4-gvf&7K+u^M .Zkri|eLudM8 s17)hHJ`2d*fl?,bo`}jQ8f29rm",dp#C> F4l$-p ظi,K,g8q::0iܝN}EL{ @0NX=fzOG{XfOrĩ)0"کbZcH)udq GUV-tpkz9=qDآcYErɭR2wmaQ;cUUGU2K'gP0\ZT7i SV:,2p+nq2RVGF[Sqñ(}+#Kx8jQ^  o.<netMqPAĶum3d }>70bf5TeI Z\֋-c%;N 1TMxmԸg1r#O6iu]Oy-UMUQG,hmaFwwgX\i-Ouxdy<7[T08G|@%UҲ\Ej\L1Q{߼'gSY^OuYcYZ;kf2\ (90ˠڜ~&Nr^u}ed]kC\=CD4Ij INAHl~wtT0E{ 1A;c7`埭]1\3EK1'6BjNWuU4(@#'%h)0I+\(挈2ν, iB؊Yb'Kʊ6 q f_[GZF5ɶ.Ww`Πُ.?b9Sa)#dQ1XgDi]b/BRned뤋#pH2Y&:9R[+ r P\rfk6-\S>I Ξ]NB(sIK.7)Ьڰ {%aq%7sDFwOjYGPۄonU+F`Lh]R5.L8W7+nnG^UL Jf~^ih˗ok`^%0Jpg \~:J&ЍR>dGkUR7 5$lS=xZ3z.g76υJM#=@:SRj5 9'[űM%e#nM 9Q :ne{fmeiJnY$fO6lm][K)Zr/#~ r8lpMH=U#T8u ܲ{؆qN $h=˸.$6hy%Y(O6jU[Lۤ8Ӧ1ꪅd=NJ1912G%( - a(\+h" L$ )uƆɶ'r[,H3|qVgz \n>+9+ݥЎÙ4Ʋ\o6 n&<^v*c0:;H}Bq ZE(~BVseϴi C-S uGz/Em^ վ~"r_crۃz,(tgHʰq*;ԒphUnV#ֳ;T/e6+a l6Rٴ +s!Rvd[-~>9}|:bF4C,8ׁHǩ7E42S(pSI'+*8OS,8cs`.V Cq躖⾲64gUP/(Y˛Y.v_cuDNNLƗZj{^ ޶Ecq-ҋu.cGȯMmTG e}GpZq{{4yy2#>êOΉJ[aZ#29kA#^|4Bu9ÒoYdc. !)L) l(ѧ$LxժdX !#^v]nQ#7+"p' 󹧢;,jʹoШ/EњFS8o`WnyrmMYsiP~tU#'6--;1lUx5%Dd0!573??!5}~lk9HO<Йl6MhϬ οqx)3I,ж#Пu6OR?Bjp;,^yV oJDv'?T) G oۊ.￶-Ug˪MgkdKa2cEhښQBO4h>3b;/KG4p7Ķ)pjeybЏGoFcūi 钱-) yvzQSUP?=B'lP׷~j6Z͔ 5.=n~i$S0NXR6Wi{QZje`9j/ov /?xN O{Mp/g6ͣЧhCō0#A+#4dIH$f)da@E˹!4R}Be@?n|mڣC:FwxNlX3v FT/$ ͦҰ|ze_~]8"f0 fVd'&da]"Sa$m" ؉vA=@&$%E4ZvC.Jcw;YPF˛cpSo2~zh~c\?\JA)x55n[?kX4JD)A 4g !B<~C臹~K55I¶YD0N٤Tnls`_Rs` ;ֱ ޣniji04Bb QaF@@Q[eB[ $M,w ɻOJ׫ws v:y8Է%fkF@$k$y!.SHvJ`K$L-l|% dGyΗ??Z~aKY$t3(KfHA 'mCb;"4u٭a\ד(u,@G,V_ҽD3/&2_,߳>An 9.Bp3FH`\oSep#bnӳ>t躪yGtMAS?nx 99O&TI-m>!12^d\&tihj^*Hm4<NOڳʥuٰ޵^c KE 4|Ğ{MͮMj=k*YMKRMPE\HHeM=5.)[K$.$)h3ᶖR1~?RmLS&S!nNuW'5u_\8/ r\/tf-US{xd~ns]{yUf?տI; s永4 ȱ^[EIemmpvn-TF?B1:V5渌#03} eM#3R-8ܘTYjGSHM7ݞv&[$߿Q}-gzgp*WWM9ZYlVK6C$1a-@ Y=_b k+詪vkOu’CE>CtIvcNZȨ0 =m5#^n M`qRPL$A}?Foһ_|%?MۡŲڻnv8Yo4#Ь[rw%X L^|+1zsN\f-ZL~Q\'@AAA: E  (" @A: UCe+̻'hd~ ڶl ?%uϱME~Ua'I"RFSI X&a (և:$R/4\#;6؜ RkYlOb4y>6o{ օڔv{z'KWj.>D@w]CSx~ʡeozSKY# <_v 0)%{fohۂN;Iv8u5LJ .\"24M bHHʻ wF.E.ѻbMS[ێ86W&DE>̴[BlEYItS ' :% u魯;dYmԟҧ hJ]C{в?T1|^0Ifl(0_v?AWn{Ǐs= .ڜ0G do10gI@_,GhglX2^op3۷ Ív=c ,w;?U6g?q+&3g=#=wfHϩ;bՋ0%Ir6"6*((VPJ1uf(Cub+^ 4@O+of0{>x m/fc@ϓXipd0TR1+((Hs&2s 30;W>0k?Ps$J//}YS$]i'ܿA "uμ)5Obl_n4&H3L17sR lfNK*VG9|R=?0gX$I`pxjPdfKr ,!8}g f?Ͽ{#6Լb>20`rȶ] TedlaǶxq!\ܞG10ŧK!*#U;X$+Š`ZvJ<}kԼA=OV ۶nr&1MRkDSk3J,-weYe0 _kn5L5$I4W&O4>5lmg.lpkFW]7xbzXtxd&Vwhv 3Ys}LC)9`hhVqT\չ} (K,wz:]aNZffy2Ó$\+%XAJv9˨ P ^&;"믿˝>nn^fgk^ !esYMsXt15Is\؁fMos>4pIJNNnndc L$ 0_1 6ҩFCabD\С76׵@ۊxҪni78NNxָ?^&NcjjCI,<)Sz2PT%q`3I0ߤN9R5MP# tߴ7 kvu sݵKgTjtb@N(MR>6ymPU:O{eN6'2. hskJJ@ P_v٥B_A驷yU%KfVl͜_3l2i Dgqv +^FC*AYMìS)]2I0_Iny!hEGӧO,*)wށ`0(0Rswap\- yy{Y^ve9} eQ82K)Q tFTYGx$I̵19JbZQ |* q:0Pߟ7x˛ބEUsM~e;,LrY yS2f7mѹ3w@U>@6Ws]+eyEY!0t.)$I.(Uȹb>h(P⯿Ŧx()i<*WT,JcNSc&7+V`i8Cߋ9&<+&( HJ+P#l|ŨDUUR|a?$k̈́ }%Gdڿ C =,9ozS̱ o3Z,EUdMXQ1I1<l3D1#H^GPDBA%% _ (~ԊyHYabbo6T0f.dЬsvk>Oǝ0+Bs|f ~70nWt!(8T0QI^U.6ӵ8M;fkhNIńj6'ك H\;(2ziX=, 6@3l֔Uf&V`x !pH %x7h_VS fo}LNihH\uη>cVXlfx199N^u)5E|hh5AU{N4sH[X]uCz3EidJqTbv~{Llw Aq|D_l%<'.F?WJi5!>7_ƛtBTزu+uQ@%ڭ^ð^UEп^ EO`T*~W^z:7llOkۢtܫR} i| 0B2!Ef1&!Ogp] ߩQwA>Ȫr\e9nM( DtvYVرmdb^OBzunz~s6, _q*:dq7XY&<aǎ U,f/1a2?frXځAFt^geERJ1>%GܓQY ,o3+گx PA[Jӳ sؿUe|$?iX&Js *"o$o_~ݨ Y#*)KL tcozӚ*XE]Lϙ)HlvzM4A^xD2miȽ{C54GxEn@1quWc4dZZbd@8r88ťeP^}}-_Jz<\fCGNXZ\6|'`S ӚaxQ3#7 -4̿ˁgN޽/⪫v-V z#c JUU.n>vJ]6gHrkd9E;Ur麯"{ cmTRBͨxT Pkd*x@d(}_sWEN, ߄gBLzdOԑޠbu^;",GSL1ৰi~:pm%.wШpӶVz:b"3>7&U?/{?JKiy)5c(ٰyMIy}uQ '$GI,166ѦJZ kw]$_QL[R7yΌv S"à4ry™x091TWJ:.9vsS55]qk <qyZԠDQ(q`ZxUkE%-bC/0U/bL3nzs\C,Bg(=4ʢEl7K]b=.++PfNlփ*۶Q!j澼凙ffJl>mf*XkJk7: / EeW;x,#ͺ@k_*=A1_y^ǗLs u1πƄ1Uv5CCk LL=_ܸj1xym;[A**yGeUWqHK.YSag'L@ۉ4gݼ n߼!LL r5̨m؂Z쐀05\R 24+3֩F:ܜB%EG0Z2+ NfJG[îfz @vGh~!̔dbC/L)*¿J-VqBpAf3O>3 g0'flFgn701>( Y&n}'A*Y/gw`„ ̓*ǎ⥃Gp-‰oi6ܭO(= 0%K[l8YvLIUwJ$ޠoTRbpu=ͥiك/6YHfmw8І.zWar"3LX^ZĽ}V(ʘ~*VP5b 娔ޚk`lChLnfnwL&Lf7p 6ɡTf"zibT }S֔aaR_l/9LgMq6հj1BDYTfzL{;P'1cffC "ȪBYeh[Nϟ:[,TɏG楒 h4h4vfLЌ} mWZBkZTWic-ŗ _nJ)Unga1&=жp0Fez+%kPSLqN"TS&6`CJ[0W!D0Wj(znpm׀4ae[\DA﫲BwxeҀ ՖO6#r,+b N&I=|c/90jC=dpk9pBOS:RH!'!b>?Rz3\UaC2*in. `/) cfLO( aAtE&p`p2n5s'8p&ȥj?+i9h9&N ʘp~@mRFtJ !,LպB'IIRUf ڥS PvKrDaݾm%<MF z (jNEurdk5|1wAG Э*6^ x'P Y%0n]qť/߻S5rVIO_Ǭ^]c&Ʀ!f#5CBr=NFm!TXv/Q$!؃kA}1Y{!y#bGe.`4̐tfx?JlQPb| 0%`Jb+x9,G_\7٧FG1H@#P{]Kv4 }J"2îvmϯcVR9'b4 lYVD=Dq?jii?$ye* sVDӈH=rЮbMVܑ.kd׬L7Ї 2qF I.*N=FYQ~eP^~.LDLT%<Z \&۹V'nt#z%Ï]CJ ]o4ʲJɔ'DḌ?L)A;f0?#P#2Cj\jDJI^kj0r #ۛ2_s-{,}: IDATcAYps@UtTO5 ルB$Zc+DBk9o%<f\&g^ms_MIΨqPX?o{a;LhÈDbo7oV"T@e7\s=\f؆6Z7\s;`u㔚4 f`0Eĉػ ϘhejM&*ޔ5fVIϯq5w[> ɘp4 *w((KBn 0uKʔv#ԁ3UBh8Z_ sMFL.\T96V\cS_.-H O>,4ORD&r=.^+mWImz ~*柭6Th زe3ʲCR+(%YƃnMtf&0ۚ K8jɘyg ߨ4F+7B+)%Zc-ܲ*< d!V󚋶m?z+ϿNZfS :{ .N=fS73D:v8|HR8:prY*]ЬG.Hk|zF {^ٺJbƥPS{.O)+KyjLp"rrR peQB0 <6&2_:K/Bc8V)کFu]}B .)t[?VIk?L)Ħ͛ JVѮaֱ<܋( mJAs|a< „ a&ŵ[lVb ⸼kVѥt'[އ0AP*\{%AiH-}maǮOǫ51SQAgƥvFEڧNZ@)#i6M7\/AdJgl2w}GX-LX3Ǡ*iA P1#J %e< ) Y08P d9||OUYu.Oke\ʯª"!|5iדR:d('%aB"ۅJi2rM"ZY\dU.aW]mlҚ{aȪ4V(LM@gs<*%IXf#xQVaK/b$"ZcG@ߋʙb 4=F0#OA|ypjayL2Y>8LrPtDـdpNyoB0.F"ܾ1$Bϖ6tg1%fj| 0ϱi(0J,uCۺ4/ 7oy&pN^t:D#pM|?* wĆ`b}6@P3!df SKA YILNYv>YUf0==ޠT/Ú]nǞޫKz)_`>ك'uA&)T}kn<Җ`O򭏣~IkYV#UƻRaQz@r.1wR&UR8I_[8$|#2-q݆<|/ۯ+D8t8_ &L 5HxHy=[g IVH#JP}:i&,(a֪`婼~˟:ť.&[V'.r"|}O LNIVRkÖ-U*f0Zm`PE=~Z %LX MA-j##eDsK=Ld@aFFRf l$@Q.%}ԁft鳾}u/4# d@l6^`3?ne 98@uA'1k\sNm2 e3"E[t5S(JD̲*̞CD溆Mf[=Bn(HW+BaV~hdX\xaΗ7kFlEE/, u/KؘjP 6}$  aÀA7}&̿k%4=u(\%Mp}]̌qlLpRaRE[j6!k0B13ƌ,%~LD/RK(@ ,L[0I!k& k!mMS"r!\KY{3svw |"րhz1um%<G~z,қ$܆^cAi7LG"_x!8OiW1cSj ԍjN"{5%6D ('1|P7V%ؼ@>p"M>ǚ>y6jS˲R_p hO-?UN5Fĭa*W_B`S в?(Kt jJE q[/>sKӠBR̅U&5d%:&ȼLrbw,2Ajҿ=uS,rgIafDhd2>b<ז $ ld%z 7%'aPTxكQ垍>lK0hYib$ wmq\ jc eAa5V-Gr^{4kK=?j."4 M5+H-Ɛ̱\T_o#!֌0y P-A B A̠"Èhc0!pH͆'LQA 23&'pǭW_< Ky( J#>MD 2k@A"I($D|ͳCUJNh͵, <ԋ.H>:{j`9Jig"p9PqaKǨ u능XM3 +Bz| fV+ ϲf 怈‰k%Pð/!a."8,ͯi_F3ÉQ%XL`.VVnw-9 5]װȆwv6oI.@3xPnZ@\$8$~~HDEK.;P+ %RPLs0AQ38vb1L^"Õ06S"P}0wh DNqԂLccNC47f50"gXl WX߯ UL`||E)zmњ I~`5LDEh V WDP9<QWeD`P}ž[bc3&mT|Y5{SA=4}U/,v=ǫPdn Nje ¶l/6꥞Q_̮N@a`33vTW4*@(?;@eǃ2np2Y VX$;`V Q .Q {Nx,2GDe>*0zk"05vMŶ[ \X8LOODK8ǏpuI\x!0AㅪaTY; &WCFa%T?CrF8~n4#)\!Lٚ yz8<Ek(gDqHQ0Oy隯ՑtX]'y.033>n5  ;I#a-V`fzp|PI ƟZuj(6GIF^D$m%,E?8i5 a$v:{|zP+*x2>3ʆ={_&C@חUOUȱ}ˬ_՟m#XmZo_wipc NMbp(C9rbz 5/,M!> ]R:i,D!"W?o%h6p)E&7yf-=r.1F Ʌa;lAS]țz-L_H?XsϛA~n;C$Wl0û˜dųٸ A!Z>t4cy/&L7g7oEհ]#:~LtZA_crIMژd2a^cW/NuZ;/9`bR$kKp~c_/$ 3]¢^i=deGͣ\DW +=C}&u^XrJ(jµF1(p"^:| y.~Խ~9(+!XJ=QO„%n7,%ֱA*NR4,SZ \wpmON^j5kB6|~rtc&9a%; qнV}>}-QfG>n/q26"Kڇ߭0!+lp 342ؽ&HM1aDʝ*T\vXI,_ Hf4 }l.x3J9-&j:kVwD dy-,^a~lu`mùVJjPg)H8Mj 1"@~<jnߵ8>1]_ 4]:n۸+3l~oW]';Y'([_z  M@$gCk̨ JQu" k-XEK{ .̣,LrkYApܻ_ɂnCׯ /_⻞p[}aa R2+HPRA)ӟ\M4 49ڍ \@)^Z @?x^FhQ%oi +<$1_. O!KRS}(.1o"#\wh6rٸMsSo߾c #@s%M笁=%N&`jj f+R*IrH5hRIl n#)6V94RzQl(T|@G>.;<Q!sC&WD4ahY: ,?HV7!"`xvlƎms( ^Ġ(1T( E)QVU%! r'(#LYw&yL=gĆXJ^_όu`8uf 3B>ěo>48#7)^zK(x"37~=ӗ\ʎ`9&'}LMMbYX%Aŋ0~Xƀ΋f011C!}{6) B>z݁g`*@I4 Lr=arܱE^Ta ?o}p5, 2 JyA\1yjVKր|Rjб3e@o 4LDlά$0<(%o?=]u| ޟArڹq[1=3Kv^^Qh2=,ayYOdwE#k#B7(fLr=MO//hrPՀktm_*UDЍظ?OD(:? 5  (L= uc0 y89zg휯d>WK8ujRysJ w?~ {5W\=Zk j%ordgZU5ŦhA9MF¯2Tm X Jۊ̹.i6s=','8w,Pvbܮ;|A)ܜhsI2+2t?y [6"o)6Qk<Sqg"~K+|Ͼc~C{W1͝~G O |HfSĮ s r=ֶҨVPz_D)ۗ砉8~U 4 xnxS &ApDY|G>5ZPuA~|@,10Q{ͣeޓNVuct aNJ#R`Ďjmq1VfPBؼy>Y9nkR׵h6Ͻ,Rb(73T֜7v/yU2p!)|qjr,llf٥$pu`DG gcml4r0]+P}!RڍO 9hm"5/{gȇ:_ýhXJF%EVp[9_'hn|<8>wS&m4Z;hXZd~ ` Mev#0D!k"y:%Wx=⮨"~o毾Dt7{g^:th B.pZ<#?da䨙$tw ZHM*RJor2E$_lFG7 -'Sٞz 2aG>=^zkrD C(0mW0 2t&y=8#ZoJc,;Wu[6'ccK" HQd)ȏN "Ȇd@mq"LǓd[f<=wG-zuOO/3}>;rYOk!RR㮲B"ix0uZ)xqMǐ$}.Ssw7*J (mp>xgAJre`IMT@$T"LMqLNp115ƽNp iJ↨XOT%>K#Wm_- 6o4EP,J"Wu&zڨLs5k`iLU@kj[kMM+VTé,Vrl0S 3潤5ku|DDeR[ /@YhmdZ@*V^x7x=H$TލݭE R%z1|3C:M7: 6:dp?|CH0{i 9nciRcV=SV*vh @[q睷_3^J4H Փ՞$A$huRtuR >LNOP ⹬%mc-,.JqxwLTEGl WYVzҰWHȲRJ&C)0S.BdazՉ"Gj !^f3Åұ!igTgN zlЅN7zPbKjJ>J~gN O|GDղnS/y9^CRWVXތv+}\ܥ V2E:inI178z=KT%yt$!&ښ"㑧1=1vB$K&tW “ј5a%rNlHPŅrnB|;1Mkz\9r9ƖT3Rض&panS_BADʺ[ehd1'oo)fcCv*3.'z Lbbc=DQJ8(䥨A(17a:^"2dʫ !Q.X@2YJe%v u5m0eEI!%F ܅ՅD݅64Lj8?S sYEĐY!l^ս-zDŽ'\"c<+Obv&P-i-t(K]2HXJi@kE~<+׿u xYQ"/4 Q z @:c":FA@"6a iYZ~rm+ȣWū9Wr2^dXu(:k;@䴐dc#u6DԔwTbD6k5ҍ&Yo3}Up" 0!HcCh&]w]Z%Zs $%FZ0"+EXE%@╅6խӸ||owۭԉ1:5hGONS?ĉgyn~ @u1IC|CVS#yA ẜ%1?S_C.-qp{y`LEnN(:g*2$j72^lP5ԣ:nԚn P/0u\oȄ8 8y\4ohS&IG$E|=6# H ={3s $>. cZuħi'RvLju3¿>xކ[n>qLMNiI:d[ÿ /rW0?>KxS_ctGM&(KU$8s<.^Z%DRI]ӡy fq9^D족5k[$IAFX?w_<tZ 6֚@v^D D"7gqkd+6;10F@jL}p2 XǍخ12~{OLHRKa*!/ IDAT-=<..-K-hmppmooE(UTs #d\~3@b|MAzg j#h/|_8WM5S؇cL"1 e` p)=4wfJ,r',6'4XTVTnl #6ZҎ#C4X YV`\B)#}}Wߦ1;ӿ ?wD1@jRL Ɂ LMt<+gfqLcq$}v[h ڭBcc?ss9= s$6GLuY9XXZዯ<;O`d7>X+Fz*:B3M$0\UqE+D_xGMo#k&w0/eV 6Vk@ +%-}gP\cW TxU(Jvr –j`na0Ll;7',X`޽ݿ/vPF@|R7tJiKP*rSk,ڥVR_'ۤ4q|ǿ|9JszO`p{:Jrr6| +X[wX/ DMi*i[hiUE矚[z}{J[mLhk?KEf]L/M$XyX33n4#MX'$kFK[U*R@e=JeO,.Wyо JJ1ϡj%֌Q0 eƑB="Mfk^ϷO߇JTONņS؁ƴ6y ֮mM9z~L =WiLl*4ST#{뗏=)T(y(JMbo a=R#rKճOϢNM;2=w4IY_FRt^&> Y3U^ }66-v>1ӽ| f}/0E&0̟5PoZ"k uۜ\vc:j͖{?y[rݻ{c_RR:J ˒TėCjOVa=gW^ +Qߣ\rЖPQ|>I悊Q|pLI&ʫ2zŞ{ˠ:Z}Xs9Fn똮L=Lr;Mݞ& G!!2mmLKk#4VYPZJ\i=Ȳb_X.-gR&5$+yt!/Ǯ a6`ǟց@l~Qeda0{'*oo<}(vІn'}k5$3baۼ˽2M`"=&u7 (Ϧxik"2bg7k>`ӓ>^DribN!V[^S1sH6ЦkI&5f e*G6<(l<}S flXCi?vUc7:bezom~1UiVcob_k>r>hW&Br/N`(1sf<1\QjÞ܇blz' LnO1kl< j`uM4Xk͚=.L2m/8b8ly3PMޓmʽ8 Ml`+4 >5G Vl1bF=(aWX׾C{o/ `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `l'y^V\yIENDB`mu-1.6.10/www/mu4e-1.png000066400000000000000000001360361414367003600145770ustar00rootroot00000000000000PNG  IHDR`V{sBIT|dtEXtSoftwaregnome-screenshot> IDATxwS^&" v%1h,ƒĨ`!F+XAF]}i?޾Ǯ~?{{澙3g=#ƺu6le0 !@  M,b۟9rSӧO$>QFM2e v}pE@ ȶmX|9s̉iK.#0eee~vO7o^?:W\'²,Lӌ@ x#IŴMY,+ƺmY)**"??+VoTK'LF@ Oe#YYYSkjj~sƏeY1=@  ȲsCQQj;''Dݎ1=@  m'R4V :}?5oCPDHRq+/>-DzL/q VzvqCɽ媕+}>F_ݔ@O0ƭ(2*t+T?N@W70P#4{q=׏ * }e0{w IR̓(iQ=ݷbŊ~( &MBeill$991c`&W*Ws+*&,zf8┋ÑJaj(6yG#7mGj!\?_)SU$Ĵ,8\)OgcI%r0N\1oWΟz[`䙜wN|ꢫছ3e/]m-^Ͼ}g [=+G0,ǐ$EIo;qz++GR ,a܆m7Zu˲e1cưc"4OL,K,b\6/~>årY,~~H0$-k)|>z<.7s]OSu4/K_OcY_֗p7Gw'ZLBC$XѯDwh{Z=\(}vwC;fi7_gSSoG]v'@ Yގqew,$EX.i3}YzmMxw#+**F7׸LrX@04yw$;}]^whY_rB$4ϚNeϿJOLr)5_??Bn%,g_U,;ְ.]rEuow<μk9x}ARƧ7pմE]Kw_.?3}=2}6[PB%+Z03f̈,aҶVXப*+VpDm:%HN@ ==/Wӊ]LsdE0MӲچ5oqYf_m?2ڎ;!a`rpWR0/#88cn~vfdY<Kˇۖ[Gf{㙇O?βV^m>']v 5}GChMG6%,2 K8q"&L@uTU4M>`6l@}}}Ty"T LKPYO`KvZ7w_{j苜pi3t%ȗMJwjgY7䬫)޼V//_·z?q(ʫViko"K4-l|DZ 3OI;Y{0V=2}`?K=mv7ac;?|'|ҫ6{3;]ES۪2~x$I0 -[A$IL0˗G7A܊E,CyЄoJji૏_)bқu _@d< ,7pb{@k=veqލ 1mꤶJgz_r` ˲efy]ʄ_q{mK=w+]Pww@,bKoվۛ{j:+e|M ÈlUX@ r౗^ycْ7y{gϕWp׋'a'?Oȋosi/&rE7Fcd䃸?[p/Oyx<{ Pgw; z"l܆svElj^aou޴SۆaOcYS^^?$IޑXܮ.\h͝;m!@0(6;v"`Y Fbhmmպp:(2;ײL`f +6v"#aa&ZЏ*x}!^}vڕw=ݶp#:!{0ݕu֑vUU%--۶Î׎FlWv$duQg@ 3IJJ2Ht: 5ZZZشi.+E!99;...a @0С`Djj*`0jA_ em@ ~dffRZZfʾ/HR(埪=BmC e¸@ 鑑磪 ˅nIBdn?>@ `HNNlQE @ ~`1V @ KD*0@ O@ 1&@ @0[@  @ qs[CIU~XxS4~Ɉs&!ӒUwVΉkXs\{XeX.v=s̹ԗhCe뜂w<zWuyn{U]x﹈N۶.z˲h|'Y9ܾSK8z8#9 xOvzvYbX2Tq[^m\K߾[U,y։5ʼ?~Ə[y+Yv)qEz a][/:Wk՘7"fq4Xc7+lT^*n%NWn{x;ջ_~9KHYZ ?^s(A מ[K ,ˢY@æ/MnjzԺ\y^3Ook#sryK<g׷wQΝkMc;/%~ -T6,cvN.9c, Ǧ(z,Ox> yqv>23ۭ|G@A~!3j|:^ʰ;?=eLuZ5qaYqތOO~n^U??ھmZVٖ/]MǟںwɷZn''o*hDh 򫳎 'فls1_jN;g] >:[KJ͇I]s&Quc=ٹ]1rW>*vTǵ;M=05u";_=υC^/o&0&ɘtn*R/[Kno!]uwc[ץ\Q ~H .̞cܼ|*+y),7k?95o]޴AV=*\h'gz 0*hLR(9ֲ$(Xy: z認z+w,fRa!g1J RX`B0nk2: GƎ","SѨر}Mrt\ܗ Fݩ^w?7ݫe7s*׀Q.S[t6}il*-#~ѿdMjiI< W2&"Uu?lݲ,3ݿ7אrwz:DZ-t] !?u,}ࢧnW7=e&cE%O|F'\ `Kq;dò/$.e;'ぴl7nj䶽#xwY1)Zs}ϻ,/ާg.㑻G%0?r dd4F:u${s.&Uذ%/KLUsp=TL)ڍhr\ֱ/_IEeV.^tfJKq6q1ޞ%붗o#g(,/m/o`Mz>yXݍhX{/,ZNP]1W {BUsw*Sݳ6\v sR^\vQ1g^q OhI]O4j+yzwZ(/g^{̾N.9o8znu/t$~IsqVn]%΢< Ey70r'E9ab5W~('pW޷(:U;d8NV؎Wv>^4.Yw-gL{x rlIv^ތhXCm}g(?u Zg, 8;) - =e)X'}F#`rN^[8߂=hcyO{J?+<;wŪ)O q{ 2~Aws+@  6}Ζ @  Uq+@ cP~,2r@ F-@ @0 J֖E @ 0"V @ [@ 0n@ { ¸@ 1V @ [@ 0n@ { j< jAR[_eYXn݉$%',ˀւeQ\Ʀ.TYweɘQc =L YHt֯eAԸ)Kq98˶xqjx<Zi<(FZss3iiiȊ닙q:YY._QQAM@OScG l]3u ͆$IXE G4JjZJjZz\8Uō5M\V|1-M͕(<ϘůwWL>h2 AoŸ쓥=^OHkeu@&aG>"Ѷ3 Wֵ. LK"]?~;|aȲn(~wLs|PPQch I***"(*(!B)L nژ>CIi)O߭zy7ƅ_HjjjdzviDģ3n`*aDBk4WniDrrڠ[`4)^֭qk> t]93¯u]'=X?":evC%45pH1 V #iZx^,ˊt$,+7S͞ 7.f}yI6k"YS$(ieOYwGǭO/ㅛ!->(r AoŸ{ ] #gkf8RPoc.lE!%J\~o|dԬ%K?eYl޲n}II8o߃$G4_7aVloO>s{_{<;L;[ԳOqULp:u=](v(4C"l&L6bfcٰ, EscD`0H(BrJ2M@fV$a&X? >LӤ ̌P.Buv瓕,:HGIkk+n;㿯mj  Uw9n:du~ؼ>;#s5s'ee3Mv@ }؆1 Gl m@`"RG=qv"kZտJ]G}/YASS#@p}ujR=z aOXma!ﮦi,䝘$x:ujfo4 Mд š" sfS_Wǘ1cp̿o>]}mx| =z45Ռ9?0XXȲ*zA rE7=jCudYpt=jPDT=4 bYt: eԆ/msA]UJJJzruj]W9E|]͞Ȝ"=#7除gc#--'bK1_w.@ q K0P\m8qfZX4:} {a );oݫ)bLux>jk4x#ed23д] b&,ye0X~֯c>7v,n:6nQc((ix^LÌM|X#Ə|#FDdܿ뮾-p0FCYYylڴIblyt8Dz[* ́11a6!I`;HϠrr1 4z}xEMm-9dfynwqalb|̎kkźrr.6m0Ɯ'KJr YтZ1ɜ&|gd¸C%tuo HN)oxW\`PUqޮt]`0@MM3/<0/ι睃%Jx<^TEEe֬Y>>_|){gx?e˸SLj3e] yy6܈˙0hԴ j+Qm*P;F2_Dò,4-,IQYM b LkDuNRS[KjZZ$^윘?2%Ik@Yi4IMM2{G҄E{p`ȥ. cAt]Ǯm6TEAAH Hjզ@>=t'#~RNÝ+1QC-@% ]O0؆%Ȓ!kE4652y膎n衛bw8x=5n \{,ovv>&׋n Hdi&̬\j+HNInr&GkK3z,ˤ ߇&۝1_N'ՑEss3455r#== Uwa:Ξۮ3hXI}}=>/ni¢ ea/*tdæ cdb6liˤ)d2@0KXfHmrV4 4zž0U`e)5Pݰز,Z[[>ldT#?ڈ abtS&O%1)W|^/+ 5mkk+ _@" "r#eذB ϵ6.J))XEbb"5U!oi*m5< =5Up8%yP&QUYyTWWnr'SWSeIIHH$> l. IINuu5ZZx$&&r'CxRū\4L%9)9ni¢~x. 3sRǡy'8嘉8N̶vos~w?*go#w@ $Y vi:{QLc1c^@ 7gmۖW]STvwH佧q[41 f[tewCNnjo1?a"fɈ5o>#3|pBYʰ,(**dGy#Fj˃{}LS?AqzhoKk$$&exЂ:nVNp=ynʣĤ$\.7^OKyn-󒐐iHD Dƾb;JK! NTWW#+2Mض-N̠pyq'7n$ltAIbqoYijl ŨC\҄E{+0S9p(L O_rsc.z{)9Ήl_am AOXnrv0jwY"Ӝ,\v/Jm{j?7Ţ?{qʼ3^]o- lYOm]À2M4hnj||G??|^X<%%gq}YVhjjBc {M}{)Eod;$ 5reee /*²,Yp_{C=a]ը4`]a)*RR;L5q =(J0a;i=hWr9̈imi|t윜~5~^S}Pq6rrrڲ)t:#o?:4ab겎9᷏H9O? ;5 ӑ-%Un+oF:P+evpv@-mH!+Kr( $!Ir'7<,|c 4brrN8~ii@8-CP]]mA[RS3Xn=W4M à k=3- CsS3c5ny6lK(,,d[ %%\qٕXC̳O?n[ zK>u y{"k`mm -VFK0\X /kL;ayy۷oNfĈ4MLӠM ;(2;C0 czS,^ɜ9GP]]MA0JJJV.(4Ea#GQG;oЀ1i`>CBB.w(m'e>okYYAJ[,kOTrd)V._4ˣχaL>˲XgdP[]3;,3E%{&,6@Rn{o<|)uVmZAJ=5')x4A%hrFe/N?qđ"߭@ r-,&&rȸN"d$,!6 /5!O ގߌ ֭_<`B^FfvFBt:`6`04ny 455cF8>cƲeƌÏl+)0 ^/v˲8¢4Ǝ ʦ9rlඝ']kPx<cZZ#򩨬cݾbasG+uuI)0_"41T4~?F[S.]Qd&\.W{*pџ4apy L}%=̓7ŧnojv/EQ^:S&e1eu[x6ͭ.㜳OOBZZ:SLAQa !C\^̶/^Յ+uIbBeYL?/zʈێ!4B }n2lĆ?jYWݦiILHaĈ68LDVB_tQh `sxwy)VĉǝBQUN'NIHNNggK#==#f=4:NKLLnSTT uЌm ]q\p0iij"11M6`Sm$'',ҳh'19uD`n0bH҄ 0Pχ˹W}0dA`W~?>- 7(3O?]qO?/F4p@biyY}&OMyBo˲PY7˒%_-d8㼈_`&roFeYAVB O<3 3( eF^Izz'0/ddpLRR$t:im }OAB_}l8N<( $)>Z[[#z!uzuiZ# ^pp8oKNŃAJj*U$$#- #+r&IQufR4ˉ]?iiAhjhΨ ӕ[eXu]4`PCׂ>?χyS[e&9pFλ˯/dכ@  1 K,: _v`Id}%I"++#_<зgpY9,Ajj cǎp zm6/={xNvaқxޘ6tѴPSUUl*$&&:sUŦ1:U0D;yn].ܹ>u]Y~mA;Tp 1;h Q$A"TUEUU$Ib)S().0PLmx-MXmݮӰYq8ge\w]}niHRۄMM0mtnkO ';~>t>K~{U$@  b-IL?1 QQU)OIjhhl,˝nv3s5GxUxֆb? 뫿|~n:p 6y' K mj 폏bW=\ȗ?m&ŒVuN:HEfnE p1MbN)jU _̈ON8La/,@c ' |4?#7p>.I qNmR[V2}L=Wy47sӴo/߫^< N p~?>8RX@/g1'NZ%L>4lw_A|zl˱ޮci7ւl0h"\.sE`# wa1_; wsΆןp&ʸHlT}(K ur%EcG!ʙ$yV.F{q)V@B4ӏDΰg?؄ev#|l{gzcQo *vӇE"&Ntus̩雧1cQͅO~4hHlEa}\o)鮪MT%.6[8{(\Dqwat/9S%MBSe8&.$WY*83FXilAnI Is:7ӂ+V ql /u^| DX=dH^@:B'.ކ4i&_Fڑ%Jz~~,ɉ|ʵa(V!|19}ioP!%Wc\ȧ^o2gs* t12?r7#Cf4,Z=KQ''a.~ /Pyנ^^3vhE98Z P Q\<.%-$ ˌ҇hz*imQzrb!pC{^`ڨ5@=@r ` Y#WC=`ΑM*{1CΪ:[LOm3L֬rG2q׸( X[28a!%͒%!fS ߵ41錣Pϛ  mG )PG}Ԓ?YVkMHM?.p-VBML;L 4UA"ǜ5Wq àz Mpp, EhPh5+4۔Mr0XĜQ^%=ր>lꠗ??ɨB7jN~E@ /Q_h7.WVbvih/@'ce;yb,/em`x1{r6g$0}<>Ë\mFTokcv3+]rlxG~og9ݙgD2b|]%u/Ap`;D?'{9-/0eo"w!2J@^H.bOޜs:+i#5 jMuM6v-,w熖f8j&J?z=;~.$ d?D]p$ʠuR 2>@K7]Tual ʰځw\>pRal8vfnZfT2:3S@?BQra`3A{myՐs혫!}:4H|ŨFbzJ1cVv0RЇaiY|Iș`Yֳu+4i -vȇsP l>Ñ3aVD`O IDAT  ?;= ڊ>DyfYcGnL=Th +%S̚i).c硫Yvp=3*XNoFgH|*k,mvTYSnK\TOq!Sl)>-0IJ8FncZv$y[(!Ǿe1mz W̙)@oi@ vI%ƛsB9,7Ɯx>2X׷].GZ,ӏʽ/Q]M bmZھy3݇b0ߏ%W v!+lԏٝβR*f^X?ߢN=eԗhyeY/Ae c;:gJr2 Ae22`**ZZ"VZ? XlAP~B 2!B@3AAI !%k]km^/s` m.=Ԣo+. }vA`*M%:ﯹF>ù )_Tl+ަpWbZt]5\? 6>_[/r뫱:Msxf _} FȢaph87en;˓ n$dc6^1L,|w,ׄsXɭ\aŋͩN(_PӖ%\AJnEDDDPr+""""[4܊HVDDDD* %""""Ri(JCɭTJnEDDDPr+""""[4܊HVDDDD* %""""Ri(JCɭTJnEDDDҰUXKamOyٲz|8(oXF^&PTDDDDR5x&wbvYTF>xoBJ[W:fraaЇ 1-g F˻ KNX0Flj8^s WnCb8kF񾟀6Y#_+PskŎQ`;i1~(VoS!FZWGCax7bW;sBfKa?!X~7[X̏^^0הҦTVr[wYÊ>glsce@v#> v`Q1$2+8N 9 Kya?XFCj| o i0b)""""R''Wim37܂nƹ_5 w`??Y1l̤(#_OnGamlcDrow k|o~[ow?ex.X:܅:Oɒb`ҡFmɗצU|rѨ Ft{@QL ]`TǸFX6cl_*L,°Y hKQTvC] ?| C.ަTZ}ls5faNFaks'}{!o?@|۲! ';.pnׄ^ Gbsb>>j|6EDDD2/^lNL?c8"""""ec`""""Ry(JCɭTJnEDDDPr+""""[4܊HVDDDD* %""""Ri(JCɭTJnEDDDPr+""""[4܊HVDDDD* %""""Ri(J&?Z]=ζLYghQvx^z?+yޏm{,?UK&KK֜S߄ADZlٖ) ;kSDDD[R8h =TMVNSۋ0il)5O&[H;y?늓7zW0.-&5lye*?gf>[g?ð}vkSDDD*zIۺ#ZW[k3I{ ϬΙGKҹX{3 ӊQg7?C69]6չZr:q ؚk׆0R`\}[f2[ \O^cowj=yؙҨnyjPkb-+5{ kߺ3+>r0}cŒ7p" All&cb;s^OCx'1Q)` Ah><36f~RJƃP6omdRoQDDe &BbO1Fp:<),/%o8\qabmB-:" W1in=i՘2~8Fopo6]=q 0':b8iǑܖ1#2h$̓[ii31Æ4d <6 5"RJ_8azg-e ARlκcCqY<0bB4㽿L` ]t Mnź9F 9s6YȽ/dq yh؊[Ln0|^V2wm:`[Y>NfwWH oƝJ$["#PZ%b I7k°/8J3 Z]hV>*ǔaqe>\yUp42UzHU$QֵtAd XB# .kbu1"2ݡ @~qC\ٜ^t G7pfZ#hc(3=cr '$5h*[v!:2/^";k=צ%k'2x|Vgt?~<3*Y/$C!`8ߥ+ e= ɽ/"飈(pa%4"+ {"zYK$ד݉ Үf&LI=t<_@9}Iһ1u0zŕbcsMݨ^n ΈKjX u:u^!"""WVnN#,q{$i×i[^='8i_YU@!>Zpsr?yjf""vo|0U ~N^Ô) ?s7wfY8Lw$AڤMɍ|p)o0 7G3T%R}3mw夳}r4ѓY~ʔ8i=>>4_d Zfk5hƨwNI/$h%pr9~ ov<F3| ֣#7>4I\|h?g}B.W{9Ni\Ĭ==dO|%"""T%kn U8a%sצ( :t5xoaEr 6 1Vz$?y7i!5,R4x-.YWmd쇩x0KKzh-Z F@ʶ7m gcwԡǵp 1zv_w\BkUsm|ەr|K"e>@.s}:`W||H#W\sۋN޺l7YCGI7k°<$0 X(5gf>O/Bt҅f`8 KKhGvB;LT*YpY~ VB#BW{fI,!D; 9v</Nԍz;Ꮂ^\5/fz\W͊|}ZWjPKgm_H'CpPKW:WjG8B9#҃&"""e۲2 j|>fa9RʽC|ų14D2Aڼ\d)bҴYܕW(FOgeusRyڒ2"라,PJA\@Qyp"sY듓=ظdWk#,q{$i×iEqQmyq5yoQG."""lsKè֞vcӯIT:~WpҢ ;J,XDDDLe rfu.w|lV^ j݋'#Va⩙i&io[ URYTjU,!_gdgQqeGsMnΏ?74ЊG:զʵ;!G⃿DDDD~`radS=$7 [Y:2Ut095/\#q{ۓ;?ν/""""WFLnm‘[qgqFo୵4qcǚ8IMJT;xKn5ݭr&0o*v[?C69]6չZr:q ؚk׆0R`\}[\ ԠZW*}k>,Q͹`4 %o27DI=FF1 3Ma^bV'ؾ77vf1}]:ޒbhpOp {j70ufWVAb~8kgJ+mA,ص`blrC@&{yk t\s*yB+k||S̆v?i46'- ٓUҹO|S6Hn]Ƙc4bNv-MeßYGĀ4 #&ѭ'ӡCƏǨ ԴfXc i&0'xlAjkD0¹~ZG<~񥝶v,1HiZ&X<~ӗ&!AbSva؈I1`TAm YCYkCzcYL2IHͺg%]Jψmn`ׂ͋#652,fI9e?*Y$;uo4#X*ck #bk (ri^ <&`b|yMJ"ۀ=QJdNy_*g4KqWb,rUڭZnsG2Jb ΋d΂] 6/Ӯ$_sm; uy>l1|՜ڼv^<)fkx alP F)`mYhQu@Y9Yd^B ҈!#ّ_Y/&H,)G7KGHChN2/n: ߓƿ}%͙;|d^L/~; L ?}ADDD*ZPLQW1O3ZVvUj {":MK$ד݉):v,Ķ'rcպ0aݴ 7O~xsު ?vaҦ>!6>QN@mXw3f`#8(䫅oam~Hbf*e ٕ̲5EL6rپ|9JIR WRr,5cf,uv-"o[W`a٩rwa¥̟Y5`LǧA|n]WEDDt:~R٧vŨ4UDDDDWIe """"R)JCɭTJnEDDDPr+""""[4܊HVDDDD* %""""Ri(JCɭTJnEDDDPr+""""[4܊Habe/-#p:P6=oW ODDDD*Tůf;e1ޅTYt$ 5R33!ڮE>Ʒ k/!'yhM;C^_ s?>s<˲ ~[8s2}y0O+v ֦@ /s۝nl뀢ֿo\ujn 1}G0 2`vQ~ۥ IDATƻt'fZ9VC s,|{rl \i]:#bVoS!FZW)Q0o4sczޟ ֡}w ֫?""""rY*~0}dϾ{c,"J|4O⿩fD|ͱ=5F`‰[U|!b0 ;&v'`}fl}*X/ˍ=Po N׼_q?}{!o?@|۲/DDDDB/6^t&ޟ11_c]<܊HVDDDD* %""""Ri(JCɭTJnEDDDPr+""""[4܊HVDDDD* %""""Ri(JCɭTJnEDDDPr+""""[4܊HQuf.g[,Y3X4 m+yH&8K4 d' ?OgKXJO % 7?ll7fE4WIx}[0EO?&X<~ӗ&!ŗYȽ/dq yh>Hks靵77x⭀Cx`8~#Coi`b]CGMf~z#ꅔ-Nkljmg1`0M7ˆIpt#K%1Æ4d <6 5"Bk`,{!v ϡJ7l>z +6QA g$ޮtsM# /9OS`'&-k%8Pp^JCkHXlV$נkH 6I~佤֢p6vfGڗ9.c46=;+Iǝymb11tֆ& b]`ׂ!652,fI9l^'"""?*X$;uo5*"?~W-Ԏ_;Hߖak3ߛ&`[OPal zT]'M N J.-2jHU &,,>]]KL̀" 1-ΐ8X,dNy_LǧA|n]WEDDD~ŋͩY1bUNg]1IږKbT3n1^sG#"""%1_c,Al?iIa!eGjg&VS=DDDDe *_4i?~Rj}!&,\ i>a|4{i_%ȯDDDDRe RO?&;Ґ-S>krs.,Kqi/>AnҤML38} Rx^(slK5,F1a2dmS)^_*:h =TMVNL`㗘YBiU?{/uy8h2,R`r%m:;k]3zoͨJhߊ;8vW̪.@X4@&/Wh?T#{* `bgIh><36f+LȨ1Ƃaf9K̡f߉긎Q7eب_yx=%Eb#`ԚX Jog兩9~$msbջls;w<#ӧQ8@_,y '/x,{~ Mnc}hdO8omΖD5Q\)Uĕ]H5>V)fNg)1?quwѸ#[ad%I?yj^m 'm8r1fXļyrxK"tV˞cOoD7:i=>>F7Rh?v8zI3\5Il Ґ/L`#O܃j׈ՙ`mB-:" W1i1pZG<~z<˲2j*m7H /#bwXk 4e$ޮtsM# 5K'q 5_!E8` ~rXk1[ߤ_E%N(|wqp:lYnїQCЬ 1a)|`</^;!׬h34bȮ=;Evzkz:ѽ!"{tډ2)YhQu@Y˭7'$hJ/H,o'a@5:g_Dfqt\EDDTU,኉m. IRR..KqWqcX5-~J#aO][\i7z;#y':#.-a5ԙ3YBvrx.^4vg2rQ>NtȆOf„w2> o}Iһ1u0zY4`cd7]ZDa1¸ ;;gˊEDDDV ߱|wupf7Oׅ42/+RYĮƜaϩk2 ~xw`/irQ21i,Ig+i'K3K#Dw `[dhZn̸ٳpHx-g,䫅K9=|a9͒ lXt| mu6XDDDJT+8֞vcӯz#ͨ։gcfwNZ{>aGnUJlm?iIa!eGjg&vrrQY¥ kWg\xn9\pnX +*( ފf]z^|oJGڦO䃼F0@Z2DDD䧧URYTjU,!_gdgY_ reGsMn΅e !4=R'(0.(KJBٕzKgsخڷݱE<3z) ^_*:h =TMVNL`u璻-&5l* ~;{{Y&|~$ """?*zIۺ#ZW[k3*8$2˦׃]WBepUY5:X0,63yYm0O j]|+5{ 6c1FWg2}1qx)=҇eڰLƁ,ٺd.>jȬ5y3Y&zG^M"k 㻌y_*Xsk.$+aTHWIx}[05B 1(+퀓Cx`8~#CoiژaC { {Zd: ]p?G&%$a) %܊us 5鍨Rb=GC̙xztvo( 2>wXk pF2J'!0G1h$ aD7:kԨ"VBz$/y/页fL/f Ih9in=`we[w6vfGڗ9r֢OѺDDDס %9Ɏ_'o[q UD>` r"̆hެ&"?uA2|t9,D˨!]hV>p'p{wo?21BOÿ/br~lo!m~1s8((|wqWsAΏ[)aa&JODDD~-dr{8É@ahCff>Xz4Cuo@w(҈!#ّ_yI,_ dϘqkfkeMM/^;!`uF㴖e Pp`=[^=|:ęB_*Yp6`Wqk$))dڣh~Ͼ=(~ 59BcsMݨ0„w2> |i("5`X 5iǞc.OuF ]ZVj8ө3'd D @lH/(u|"""RikN$f^ %ǧt}mk Pd ,q5yoϟ;DDDJ4C .Q=M_e+R,"""p:~W*IwPw'Ub+"""SY¥ kWg\xn9\pnX +*(J+OM<5"""r%ȯDDDDRmh3xap-S,`,w>SKOgx/1 [^8gaՒ tr1 XT3Bi55V--]XsOyK9rer]IO :h =TMVNSۋ0gG;[Bt7Q;iHk" gX԰qODDD~rU0u GnĝfTLqH4oŝ d~+sKʠQ;/y ' u o SsH ]TĪwTvj鵳SDDe &BbO1Fp:]C]E-"5$VT|ٵQ_Ly?gU3Q*k CkHXlV$נkH KCnmh*f.ENvBz$/y/页fq_g78HeˍDN#~4Ϋ0mC$!5u򬄻SXa Lݤ"LgiiYN`>cdKOʧ %9Ɏ_'o[q UD>` 0pk0;\6<ΆRR۾[JH-R&}*m$/e011|^pss{~s{VعJBu:M=6W{62uԛ\wM7e4f, we@4Ew+.AST$g/CfIrwS1Rd[F̙ ^ͻf 8l  Y@ w+>2ܖXl}!ցqXy<!h?`+立<-Yެ8R<ST{ ^ʁSX;^ ѿ#_ŭ2 Ƿg;Іy'q.r-U=8Yw o%IthkRv%+4ҳ?>WHMR}zQ ;4kjmYsmXGY)XaCT[]gC~\ݎj>|-c v&&XMeW۶'ȡ? IDATD MD]XݬHClFϢtm)H(c [s~~޾ř9q;0o #z.0xaaćUP;U2ӔArAu/'oEHYJz%l5* fI}+q,ݭzm]s$7nN?::5mUo

J썫FrϬ_m"ײٸ4w2vUӒw/D,/dky^ k%Ҧi"oG†_KZODDDv*,[W݊>rL}9~Py;:بܼ+zti'.[yt{V03HNpe6 E3>~J%t.X`#SGutI\ƻMc삧Tp[nbbIAKZ㗕v{_FeߑevK%l_8*Ė2Xmc~`=e) G=MR}zQ ;4k_R&Tvm~BNu9u܋bfUF2'`34xu]9{ }*Z!d?F_0>Î'΃(DDDC#e(m ;_};׮`Z?h#oc\njkNWڸ 7^M?1״O\^ 1ʾh|'O\uz0mgm?pQ̸q|- 5]l^Mgx\˿ºyK?LO3@<0n!RODDD1t8p9:'dFv<=,8ŹZ?ߞT`jwsw$ްز-5%ԓED,bc1#JwD)*6eȄn[ n;VN)ڞpa|Ɗ}xu&`W%wR,6g GP) ""RNHKg{_ R64-ADD/psV9~"0)? """lÆ \qt׹GŝQ)# """'`{gy=9g4pEJKVDD98z{(W_}s=ǸMSpEJ"1M`0H$aΜ9ԫW0p:xnNV߈a.f"%p+""rpE1w\f̘qU7`w8000l60e)܊%jbGG⍍tb쌙"?0o6-Z-eGl5XZcP<~@oYDDo0l<:r,ޘxlv``?oPڷ=Og]=1usEBXؚL a\=xZBC0Z^ؒ\ۉ넿Xe:د{zF.֒5+`mEDDʘafc/Xl9`4SP,;܇a)gcoZF~VM%4~,ԣN޳MKhArnl 6 g Fڴӱ|`l8NH*ᴳ o>.(w; lGF>F&0'_jue?cEڵ#/oEDDD0e?v2L l`e ~`7>Ÿ g`!W4c1b}\?+ӊ1}]GdBBkۄV aksh=yX> &ڴ0'F܊>F20羊 ppj-~ #b1Ꞔ͗ C=0N#N+P*lqD㸦,|"""""G)p{,&8xدWMH8y "Y ), }p\>3 }4[DDDDY3gZOMvhE/lH8x^D[ """"5nEDDD$j(܊HPp+""""QCVDDDD­D [ """"5nEDDD$j(܊HPp+""""QCVDDDD­D [7z2dNLkF͚2mҽ#;vguI2Q3#&e˓hPŔ15uIt~wɶsy1dmG#,a9uL,_cMYnAk9̤;f_I;3TE[ǸYW-]C]Js$qR=忥aaćU4d{|e#ZDD6DElF.V Mm ;6 ;c^|Xx\CC/+=lYl 6 +Ob " 5 {i?a,~+Bg2nKhn↱8,~/Yh?n<9lߜHӪ7XPRhľ/&2dntaCbVF) q6V.ix&ٷpʥ[pթ!la3/F?VtnlE +/3sefAǨ UDD䟢NKg(ܗjǾ_li.CÀ }YTʜsNuCq|=}~+g&nOaE: ^w a #3nK s7N{,h<>4+oɷpyƕmPz ŠUp[un cog>aKag.ԡuλ߱qmkA<գ=1^wKS$ 9F"""H `a6}:3/vawNfY -˗j3jW SD6V1TXdo\.O5*c\t&3bܾ9,zF(H%˟9n#d [vvm7}T*hˣ v"e ՛SUH=+DHNqRu1nrLJ~lEDDLV0eKqX#` -ሟ<3K  09elJ6*7ʀhZ DV/l[vUqh:G,ڋ,}3Dn,,3/d,lTjޕ,f;KQKzf̾x07KdtN|Ja+.^C=3zL&]ӍG'ós6+1KzlEDDL e \.6=?ҭE&6O<`.0B䄜ĻmcbLQ?wCzWGqLmy ogެ8Rw rzď%J*|%o|ƅΝ񬛱Ar$|IDDD?%ڸhZ53⎧WҎL8`Q1nhKnj"f r,ixya.e?Hd{KGn6\]K 8[Wmŋ|\^1/v*ZѶl^kpn-' hx8AԐd0xw 0brY\uJe<#~ b]3@> ")F ^Oߊٳ {ۚ':xZv~ j_طy1w0ۓrk'vLgZ-4qK0gN$DDDDNKg(ܗjǾ@!@jPdSF(ҌOZ=0ܖ$`p-d }7>֖ϿOaE: ^w ‚-h<>4+oɷpy9F*ۨF}i+sK#3nK s7N{QzFDDD*`י9./| Xh<Y+nMP7? `Eҫ7 p|kȊf+?OF6+zK4G #,:ػLW- _3VmVM}t5E٘LfEDDDNK+β߸xUgxpN 2sؓOjg{!m8 |Ĕ"7hHڶJy1DZ$ۂHfP%:s 髽"\N!"""W e͛Rfvj3kvhyb$xlkbs 0ހ6DLr"&;Wg{t9`8q9 Z>ެ8ReEDDUi eNfIx ×x6XPIU aw-;#'6OkKU6毌[IxNv+ٗ`"kpQCG_H^ ;85Mr_ym\~S?я+o9i>Nwk㸠Kc 0X[YW'MKs#o^B c^䓾J%DسSF<>GCV:k]¯Yph"w#c& t:[BfH3~Xy#,^ NKpѠkfROq*Tc]дWK(+WqꈈTp0܆H[jS[^b_v4[B3p7if㬗xa΂vlkFNU_رVo+V""""QNKg(ܗjǾ@B-6#3nK s7NBe-1mreH{o9񗈈Tpp #̦^gϙnY$Z5Z|Wm:cɌX}s6^♳3L[&K}Csj&W} li7.^Uv+޻?~GohFžoP%:s 髽ɢ+Fd\X\eEDDD*d-7<~ߑECnQ7k;G>άoqx rMlx\fI-"""=*ۿ`*߭.3ԡ[^t^ɳڜwF2Nl$rUWH{^+Yy7ύlkxq(p m ݨa/Ns2'Lbo1[^~ :#ĩEl1T$~+?ȇvW?nn"q)d=;nRr`*o ZK 'R\@t.gnFˈ\)طc]ݸbR{pRYb;b݉IM%r>݀e~8 GoP[ɘ㛜Ⱥ;p5(0q.zkGՀ0:+gڹWKb XUQY uIMhRՌ` G]2Tr;ny`xpcwPcq쑣jd^x& 7yggBkہ}40Ov0MpDtANO9~ Ԩ?vvS:S9G:laAm*%0SXh|}:' xw9˨rpT&ێMx޴ qcJ1XpZAVr]9oRY:r{Iօnz/g4MH5;^!m1jC0XMN=rlmrrܸAaZGe$p[.,2m"3.&鿯aGfc66:Xv¹{ɩ҂˜+x|{[!Et夈CUtMJZg;R13) UO ݓ9;x*nq3ܓXUq=kvb0ZUcd.Đa/0=]YXdిf m[=b MV ֽ{p"rrfKwp2?!e8[SSຄ]/0XM5[ =n[F\s;/>>Ki+U,zKA܌$cŵ$xk?n `$i81 ω4i5lI35`KxZh&]Dpn*=S9:1į-' G&;u,f~P!>[~BL8/{Tpn78nN:~yZzh@gGկALa{ LW) й_  rPi@ G-?˗ѻm=jf&ɶ1GW3HFHIg_:9_Q0Z?a7+m\ ܹy`$T0-y{,EdxRgr-2Bgp?S].x#iHg7p"Kr\4yem^27΍^.m‡y2Gϑq /o}.ߦjetj|Z{?_J ʬ'O2tV/M#+3BW+Pj8 ]zKvFT_b &;9^-H.\nE^"9gUSX>wD" 0y;OZ7|2z73qG W{v1=؛?͉nGn8K rX^V́׃Y~ g9;w2[ē'Hcx\X %]TꕈdB~ǩ]陟3띅n!Ƈ81Ϻ[96R62Km'l&鳥C`>7"/v `9X ft:0㗅ݴY{%.,ZRa$NkŎ^- AZu ڳ7hD}TّW&?,E=*8Oܼg8"L:7[, T-'vvv%xT7~t(/"n8 4 'S b/]\6R*~&o4'9?'H_ }qAZUæF=]'ᵘ0f یO8w p3N ,2 ycq{lk;2CdH'P8;jIe]cVdm 5G;ˠu0a/ n~Le'zc[oT_J?v28l;Tf}l0#G֗W^ ;%b7xX!{o1I?3 =_?6?jG 1_;#o ןmKʗ\*0"fg FvX7Tfgs8ͻ9.}wUj)c&8r#IXI%i)rƱoߡ!=;e]J!FX,~O+#3ؚac}*A+h`Zm?mp]٠vl1mLOs`vg0[~"T1,;-R%l㥟X#G,n1\xcZL"塽|1}L^]vb ~Y9W >ٳɉ YfAnX|7ժ09E"n37  Ќ;%Y/?bo;$G*ԟЙf04n0 -$י uV#8LǧG *p5{,m/kBdWyT=2 b,}$ L w:[>uW?_0K+Ⱦ`#;fi+FҢ KAvHmQqpJ.P.Jׂ[@PCK8YFbM>g $9K^svMfξ>ɟA`B5&a\>y ɏnkve;~ekKꜦ֘÷bEe#&(>eTSPظ8Leky%'8(kY1q$sϲ)dU;F]Ժjj93vb:7d.dڹm?ex~wjL O>л3>ktn(p @1[!P bC-n(p @1[!P b4:ܬ|[P3IENDB`mu-1.6.10/www/mu4e-2.png000066400000000000000000004034171414367003600146000ustar00rootroot00000000000000PNG  IHDR  ]Y"sBIT|dtEXtSoftwaregnome-screenshot> IDATxwuה{rBhR)TD)"E)/ATD"6@*B!HrmcK+{w{wIx?g>3ٹyϧBڵkׯ\2< B!c8FYfC1g^tRnfU!B}֭[Yr%˗/kO>${,+;D"Acc#۶m{Ooҷl}饗?xqlCօB!`F^TUqnVUzjkkZjտ~W^y… z%B!"HIO&_`uB!B }o5MdҮc:vcf^7 B!]mK B9&=)oc:YIdB@!ĻšGKoRQ; mmS}Z^_Z@2HM|Iٞ{h5iFlޠ\>mk|N6_6}h+LNxRc5Y#ĞJQliڈzCNI[Q/^̒%KضM `ɒ%,^`u5 Bb?W{!e8SǦXkh.53n3x:r_8:L?/ᅥJ*Xz\ugve^]/583?w%~]oD Ι;3s@~^|!N?9 }8Ψem 6o_A{gcÞp=hEyB>G(U#cGoq_#$k4tJL2|{B;d|R5)m4qUU;w.;v젦&H}̂$L2Wq&vgƳ*xk7e8|ˈѸM?H6ٽ㥕Or׏>~pMwTq<R.+8{x/ް[~/rP-85}ɮ/]o]\{ͷ1Ccs9ƃM>r5O?ѹ7߸qiTk~v ?XƴϺ]VTCPdyR{V[I}mR%{C)43np G_}}}655;'i%G}+z>Esջ۲W2o7^~<|U'* Fd̀eϻQe{?e9j-㣗VBUT&fˮU6C[>~1 pl;*.]V`W|9&U> Ҩ<~-\G|qǍ_9 $#H )=LxReš`932^e'm]I$Z 5HuVZ6}KQI+!ĞHⓖoD^aE Z lv`瓎5;䍍b`%gY2m7+.|?\y)Eu 9o٣b#5܊L}߳dbg\%EMfOaك~1>|3Oe=S#/==s)T:tcڏioHe:Ryj2ݳʴ?y)M 4&X-3&X-b…ضQG6?:쬂Be:+X|F|wn9s].j4c'ZV|_ztHg9W9M\}{3q/"KEU i;0v[&?p1DZ'GuwMF9s)E~l,阱wƴ]He:Ry w|ڦԖP.ĻA~(駟)\~O{߭)m]Y``Y?4$a$xi[92vr9&R{NL2S.M3RI7GJ;c&i׿7˲e,!DΦqwdn !]4KGSUp,L&p8ϊq5,vH&Hvi* ec$Ǿno4&j_Ʈ:15>(^\ƮKf[CHe?a|\t}2xB]:%%%5\T`4Tr'f{ !{('b8RNݬz ϗA4M# ݝ4'#͛7K,!{/;H9wBI&6PLs2K!B U^^ζmp\6O EI  AiCN!Beeeb1Z[[|4ǚZ |!B1$3f  iӦw}ĉWB!Bdb1 GuTgeJ;!B!&N" HLu6 B!BIj0B!B'5 B!BIB!"R"B!4R"B!4AEeTg!kO˻]́fdb}eC2B= &X#MU\TӀyMO}ʪ ]w*ʪA|ner<3'~5j_LuU >t~ضֽqcikmTOCc-8s7ic4=<`ަq~F**aCe۟䒏ʢ93{?i~uc>]}>Ie,L2$Sj z+cf8&}oThkmmN5?u9.olٶ }yLMq̱nڼ;}{-lp̢!?O]ϣ:>ӿ3y`s8c<ԟZC=;`O,+h&w^sRN۔B1UrIxqhzJ@[?d~%i\6C4wuŇ]7|M.rfL~߾ڑGn%sꙹp_|?~kd|n?3E[G/n>y䱲ʪq~>_q4_<ڝ1t}6DGy'zg_{,27q2-;coYeU5P]?rn?y9jjk&lpOcb,ʪjksѬwcN.#U/O;7;S]WZg{-g5&zz| zP]>TnS9CGnkx·]CL2$L7e\]wx1m/_Z_ʗWry⍏^J!ϰfOPlؼL+|{߇xf;.MǤf4GVnËW|붶4gX-%J+^x1Ѷ?w_ɧxiÛzW>~WN9>;&9/{׀*s. w65lAǵc]=}6'~G\g}lG:Gnj=??MT6]F:-ʹ4m{.G_?ogO(ɇZ!1j([5M~5/v]v9qkvK7k`ᑾ;rG!G7547qKyy#X󯋲{߹eY۫|񀅜j>\q]i5S4gqAMeX Fy;X<}:@K2Z?h|8,>7oԵ3VN>fڶlgȎkG' Xo277T^?=L>r~|-ֈ198ҹz[#UD`yT64&,7; bMC(:M٧=lkj­fŬ*ZW]q__]%ѹ_|M߿C?{.Α~C}7q5hsy쯱qO˧Oo̮;|]&unJ/WoW=`v|oO6>wOySW&j/?S_?v o7vQ6}1v^Vg9gCFnOe3Qy7s9s^~7ogO-M}i|ټy8;>+\;B/{/?9n㖯k`hn}B1y9ccǸ| /i=_Mmݸo0SgWC!vO>j@S[7 Epޭr.b'-mWC!v>JJeػ W~~!ľ o5 B!B1Q%B!c%B!b[6o|!B!tC;b!B!mk|0}S!B!ĻB!BL @B!F!B!ĤD!B1i$B!BL @B!F!B!Ĥ'"Ѥd{6:1M ppquO0XHEY%N:e"q !xǡ(㴫D"A[GPVR6֯{Ï<ޞfozx#.DTUU{{H$B_$c;yP(Yn ;!n9̩>!s 9d^3,B3!HUpA,0M;X̩W(4b_EӰi=8l޺@a!/^i(88888؎m4[l`|FIƀy]TW LQQ n)4Mcqjjj`lۦ7Bub8>K."ee:; SXXmFWO7UhgYy@/jMfqY9Upi tcۨ,չ򬹔:B!p!GOım N8n-p@UUN9L.?/ŖMS[Seض6'},1tlx{=7iylhͥS f)B[{ >JD)**r |?e׋)(,B$0Ec1JKKD"HB!JJJP5x4İL*+r^?8=ݘ L4 \.8D0hlܖ^AtKJ'.W70/XƉլz)UzC-h98}nz>CNYB;v @F{4 DQL>w~Om4cpNr;P,>#u1f>qyϔSYY=m墳Y( :::i:hWH=vmh,IiI)k׾<랻iض9N=mțh4?^|%Og yK4vxm5CL [B= dP4`Q^wMyE`Y6ae~mmnea$ ۨU$9spMTv :[xnݣ|!G8`aҒ*Ly5oŏ_ ߾ʭ_>|.b߰[ҿc5ey2iː J7ɞ)u/WT'D!:7_ cKiYL$w3Faa1sűtS1'*UKbO/h4.D_.LDUb`(i$6EEEl۾@ L&qzcSV^ٯh3ח̼ɨ0E5F\$m͗7f?k(-+'P৴ҲR\.7U5j/sױjf{b! ie~WdtB ǀ Xcw橫h4@GGЈ7+RQ=x+۶QTh,jؖukYn-akׯe[7{.uӧah۲~dl hjYP[x+W? IDAT\vv+ӧMcܹ466R[S[om`gu/Wc&H4%G[x<̚5ffϙ( Eaz}=eǎFMMhmm˯k^27hc22fiَc;aa*4l]1 4]GUu|uٶetwwH0R))-4M==q^/hϗ*GT"0UA\O*IW ? D@jv}2SV3z[z8QTT.bx\nJK8%<[Q7l !7LP, 0O%TRlv :M'N<1c@LT?顔aZF.mUQFc蚆j,\ p$pmH$0T y@~?W| V` Υv{3gDmm۷o犯]1Hc(d2AK[sBÑ>"p@Quww4d2:דi}UP5z{颸xXH$].4Mjjjd2a^h+P( ttt!;#lRQzmhjoUтxXt=;p0BQQ --;0$.ԍa/_zB GuU5=;k).*&c6&Xjɤ:eUtPXT0m;r]EQxܔc&iPR\J8|b EYY)TTTiBzzz$CUU/yёM7˫J;BM$HL { UM.dq29rں K!7bg}5]QS:IXT3Yvȅ(C~5O=˵~mۘE4>qH8d|7Pm KooL1yNx<"hH$ەiHQ)넮.+iok&XT& PQYC/DiOǦX<'i$py`^/@N+*Bۃ秳t=6|d4DZ"M5 8p~%>Bj|} j3KMezvrB-,],ЅBτ427ꙷ=g)D,އm]`&8Xf$jq̘6kfOec[G.9@aaNƢQ^zE @0VgS7m--ٛMN"dm+O27xEE8C 5U+d[Zp˴(-.C]x<TENf An7x?Hg{(JAA6B1\A B,ˤ #ў^"(@obF d RO&lb w|O68fP_<#:?3NYN?pYkzkoaK+ ʏզW !7M܋PT-c''\Q ALZ{H$y<utsקn}(’3s~lƶlz{CBtvuҰm+wP]]EUuֶ6o΂Yp=[8V0~3f̠15Ucc#Y3qp 2î$M<K=U LӠ !`$M,!m_ৼj &|~!8Xm( d<eYضv2/zt͘A[[a֭uiyg[meÆ dn_~eL ;\ڞ8lpfZl^njy0>fN=a..~j>-ÓTym Fc;iBo&XzgCc);|M: q7t?3/lMfJ#UUa8k^}0bE.jkj &lJUUUzԸ5ES5^o| ;\p'w.:|#(v%X9ƆVVoHMhmc |LjB[&htt*jErLK{;J>p锔6ؙwL8bښǣ;;^2evǕ/۶,P(D<4x4B7eV~GL6m> 4lk/_8䧷cgd{vדL${ -/sHi:w2,׿eYNvzG߻\n\.7:/ #NoO;ߌN|>BZ[[ӵUUUwt`$^_OsSa*tS;mZ^2Ԑ"q((1n!z!N6x2UgrM,ʹh^KEQC?;-gYU>D~{~nR-b1A/"Y{zj7|dp7隙۷9sθe6m0xڛWuϛO4!Lۋayqq_~Oi444a+TT ;slbYw1{ya|xb  -,"D(((O7la!HƱm'd Jgg'~_IG{>,]ᥕ+Symjjjlo'aYrƫRZVFG[[8vj=EUilxZ <&BY*bz^ 71"M.%]|t~S7}:'>yBQ&X7eQSc +{md%sw 0 JRS&Snڵ8_wROmB~ٳfc&GqnM2|vƢ$ z{CX !1o<6n9sٲy3[8#gcYhۍ8{Ǹߓ4 M-[YQIo(-q!r2l$;vί9c,=mgjq/^4tvu^WX4/}SH &axM1-+^>4TPo/>o0e|p]7v=ͦ*OUs5̫yMް7P_S,7DF~rO;aks;_KII)K.E4 DBlB`ᐽ9NpHT%dV<a)̜9#~LsFj^I5mW_}@A3gMmۨZ?GcX5,c^4g|/so̙&df!72 :ּ,Y:w6MEyfX9iw?O<7 glp2֛H$m߼UUT UK߲lLgu۲Ͷ4MvlzN{C0EQza4M<H]rz)((wd25쬢(!uN8e@3%05[d{" NjMo*VUu@_PT\LkS3EAJKmD"A4:ݴ&_P0MPR 3xemd2IAn:jڼIxdNa$$HI$Dc1|~< h^{a̞5Gy/\pLB! 8tvvcr2tKtԂ:gK_Q***9}&mՍʊx8=GRQ^EWF*..b޼y=qFQ.+W>d :kN4Kr(--% bw<躎S\\L 4MLLPKd2 |I0T6MTE+LбL9;I&C݌zt]GuEaKiؼP(iYFiy9tDn29Wx(--d~F]]qS .ep&۵shrURR.X9+^u*++uC( .]XnsaG(y_ ۝ z{zxGK).*ʦ%FUU**+)-+4l͌ij&].s,Jv2 7D`UU=Սx韏)ߓQΥ(hkiz&~?avyO>5o zy{T,bϧw}'Dթ2_=tBK1 ufP!hLL=iB&B}B!BL @B!F!B!ĤD!B1i$B!BL @B!FXͩ·B!]@eMu>B!o\~4B!BLoB|!B!Qi,7 /eI Kza⌸J"=a=.aNہʖrWstI5ٙd|NqOz(G)ڒv:feJx"yqg ^ܭ}qᖩyy<^~]B!& 0L\,{*[F}0)J n4&9c#c*L$֮cPw܏STCb0SzRJ.~=̧QDcXvhhP)lp`N=a҉MW7hXs{[utKm'%^#qX`c'];?'35Cl/91{ ݅M|zDC2`6+X^5.Z86&D ")~2̮5 ]C /SՅ394%!BxbcyuM^M Q-;Tu3j@y;87}~M:nm,t[pM#.}9|ė1ߙn Wp{t/{zZܝ=%\'Ft߇(x^)A#qH]QEۅe&"|%td5Nw Mr{+Ax%/ Hr3BQDV 4:O_G~-e]t@ZDj7ʰ%!BdOU"f~3}k֠88`(5V!-(f 8dErZP(}l-jA:Cqc:f rT0Lh~ 3LdJ$4RNwWi0]v#<'6(l`70& ifƅfF1t@3aq ܋ٵ=tWJ:7ͅ7RT^⹜f15 6jt̍c&Pw25d~6 ͋cI:bh=uI!Y- )7[`-2GA5Q_J\/%D!*9_uyH&Rv}_2mᨀy'F}•> H`Tҕ!űݛ9eʡVSX. 6`c$ u(wu!>zG89lOb8t<> (~#F]]s d*Pb4:>H-B!v]b/@Z4n2GvTvYe`@RCqeִu! ū nѾ<3}帜:SI MVY'@pC}Q\O =,GBk1YAEB߽]"uNI-45ĥKi*->\JLc𑶄B1@ǫ&eQl\GQBmJjH࡮KB!뭙]Gh=OK9P $[uЫR@[$}K_S<1:?R˺豋 ^q#.N=OM7E߅dFԉqU IDATTkAEQ qN JAnQP-s{虙K'|sHIYB᫥x1fsPky *I1kF؞ڛ9$uҭ\Lۀ$޹(eQuiBQ> MM#5lr-Ĩ$|Wm'-G!(7&E|L0 Umء=$zqHL oP].#BLiqH,&+R|EbQ7=3{#-@nTƘCq BI7&-o%4b`.eTC!xwdf !B!DYwxUUִ{@BRDFQtFё8Oeaym,`#" BO#!ޛ# $7}G9{^{ﳻeCTkpmCklCDZI}4iI-F %&p+ӯ;}A ¸c;QXXi.^22222P`/y9r}`X*+'IEi+?[-(Y˭ώ};[Z^tWAD,څKl&-h1 =I>Pp$N5" pVk`Ir-&nO^iU3FP3Z4+ 1lЦsG:\[h ̕ίo/ 8V* ƌDB:D=UdVĥ%536`+DY"@B[GdY@_Sf^_>b5ۇLbQHi|}FFH7(q8Owy+DsC?2`>ʋGa)ʧ\[ŅTѭ/ XF%ᑟoX)esauG"1PE*K|}3!@jΡ^,JgA g o/t^6@IQ'Ѡ"Ak PpxG(՞5ggTTF"UPSN: R} ;=sJWl+gIÏrjDM<̮P4[/n hv Wlţ6f^~7 ]/J!f%a)v]&tFC:VCŐn"nɣY=(`$/;M̀w4ht`RYCtRp u֗ 9ѓ zСN'%3~[=,P 'm5E.UWk!A 7xdz| V aY r 4XdF0tTMl$3ldXtA@J\P-@`dhXcB(z F |fG0k\B/zJuvJq%"z.pj^9?)sMfӍ}݅+aoQ#x:WxT(Q+vĈ4gb]ɣeդ$\~ķ[5)6s`m-il%0:Z@_R) H$>0PtM>yqQJY'SωjCd1lŕ#ivw||KăpMDf1snN˭ϊg@4!q(h,upf*xw@)v`*n%-/ լ Rߒjʽt/lfnYP"s1ŦTl4З)*xj@Qz$'+Td"[1)Y8Yϑ;{kQw+C(Cw|V@C5I#+af|t;[H`b kO@oVcĦLiϲ}(F-x}4inpS 9ej9Wch}>1#Q'sU?sDccd4+季|8TՀZɾbn| 0米ȸhWm0tz/}ѱ8?$[7\3vRĢA8YyNbk]?˭aڵ_䏭L"_;W={e܀>6 :7;B}f>~ YH!&u5yNt!ՍQs!9Iʋe "5P/~KS] |]w&'Ey9#yGY={r/^M첻o:tA]+4L;W%퓑 +}|@r43?i!^9H6"afiPLQ<݃V\\Ă(k'i/<^$/G|Rq+!~`"xz;xJW~/oz8f\I%5s2:&?q4eY #+PZOvc6z7UYms*F}{<`qM8@+ԕf?N׹bၒ_|0HܔV,pi"pbS,OQ"Jsgg15 \9FBgm4tEJb,:)(Φi+EI @+@{=˭J|—,uCtZDs2❓ٴ%^Kܨh`e\ROAvp5qA `뢃;^G5S#\EAu6 5u J-BtK]U&k^a{APL~u޹ RczώȤMG@z*ksA 魐N+e6WGX~+64\1q9WGGʒxwW2 ˦(Qn^dcY8w)Bh,7/RTF]ō2Cud_n)/\5ïz3 }U5g8O9'7a5ϳ|󑞏>ɺy2222@z5h*3V{` gMmE)+V0XΝ!|nsF<#"+N4o+ x36y9r}`X_*_y2222@zW ZZ^tWAD=,څKl&-h1 =I>PpH5" pVk`Ir-&nO^iU3FP3Z4+ 1lЦ-t%:-Az+ _*^PδSTe-tƒ1#K;y;0|YeXQ8ֳD@!5f=ɲts燜23չ?H&Gs>:D=UdVĥt㝦o$Z5G 5% d~6绢~n !;RgQ^APlN_bJ7=мϳQ̸[D[dbb_>b5ۇLbQH˦ZJ.7P[Ĝۈ@[x_Lw僃p w\h9ț?CCe/So&iݝ})OFFFH: jÆg#n!#G&҆6px5TI i#.w+n@AVyBs#*3CAvӫpJ7.tZCymVODi^'g߲4bo\&Sr964=RiueF=o݅‘\>ݵ,"KwdL5Z:sbUv|Z ڌXAL3D2?]# ϸojGTK"1Xk'5*چluj_ @Hyj*,:Z`7cTw4"UA>El( ȿ$hTMkV`߅֝WW/^t@!Qs0l7zh:wt>^,JgA g o/t^6@IQ'Ѡ"Ak PpxG(՞5*#*M]'HPX>9] `v DHDGXz]I)? n`_^9 L"~PT{:={G,ঠTPO %XU{Cb19v'fUCF];`+說+Jkh`/ K %|Y6߇-f>nKVH_OSΎw5Vv# f@뇻pO f.+p S&?"}Zd gx̧N&%l>㺼dddd~J@0ɞ,whQSTB^ yZfY`0}#GѠ Ohe%!GN;AftZ CGUqV\L:?#IFEZ"NxXptF1&ĊpNakgvt %C%j`7. BkWJuvJq< &$DaZHݱײj u.g?ڭ!h2/!  H$>0PtEJ _\]㙞CP)fA/uь[>9i>(^ˑ|DΔQ72J)*`ea(& G8v KhtYk}.*\8,Jgekk&1u<°qvg˴Ju\d45k'2cԼҝ}RuyQ^{Ol_TRG9t0^cG[ ?ѓG9TA>^ŠGj@EǗݩnrHff'l],,{G535܊9 +ܺzZ "(0?cyD>V}'_)^qOʔοI/nWi]&į=v@t mfpý-yd CgY7bӸk V4Df5j8&|g*3>fkvGEAsX>¯CS8SB^feGiJ6~(hO$2.vKȀEj"xZm_ki$9 s?Vޯmo=cU5nM1 '}ge?C2(HCw&O%\u Zع-(&7Һ>!Z6~N 8[n כs Wg: -l9̆әzraD΄/rS.]r IG`Q w/HeR N͹߲ u+@mHO y_QSshy2;^EJb,)7.=I $3{k bq=&+ˆ`\hZ^ۻZx4~ЛYpvAzAr\;F*K=_q(1N NJrHv07jw;г\1KX:!:-colZD HkwϾqrNn:x Ckg#G\Bك_eJLbjn F_Oc¤V؆J f<1RǞmvI3#Ic$tQWWsidt>aK:-&it@T¾9kG]"DϲYx38 +m]e>x%[c)_L8!ITF]XO |G=v>zag{x(͜ϳ ¹KEcya7Z 8 GwcU?[2(Un:??YI_ͮ o)SJ yb)Gjx#DC\ht'IBO)yʾ&ԓUقG,4p 7_Մ_d& RS^Vy8“S_\wr{76C@`'on}~L΍319.č}whd{'G)Y8Xs4k7 G+ )+YBىլ]ҽ?, %6<;6Q p҉zvvdZK.x36u)<[طw {5<T( DZ͛ y7W Z5YgU\"a~QhRN) I4mϰ&Qw1WKش;iȡ9a:EXorB1U:XoUV%eCJ<#a}KKI %67>!BCҬ,9||\'|^xz;Z(e_/[(R9zBQ7*1C(q^B9%HGZJ>\ T'L"͜N&]5~wMn0l~K! \8-w>~+7b%<Z6~NV!W%mԃI4JWb } EWJDw4j/1ayIG歼c;uv8zJחtƒ1#d",T=r")׶0;QO;NmS 6a,N?oOR}2A .ap8db_>b5ۇLbQH᪣궑i+{W3POeigMIjjfXA/j }u=9ub{M{7#9 VѨ0wt2ިT.L|ur{6ɬK#(3jsA3 ί)mDPPh 4USb4H*#T )M?C${R-i!k  oԑ][<{5̌HE& ܁Bk!n9zeǭJ|+E*K|}s/AVs(rm9FRVFcDplCs&>ydQ`*ұdK.)+ J9S{n6dl>H)Fym&;Fym.FtUI,Ԟ.i_t˻O=؂Oa|>>;#kO/I5D{$Y;Uz_S&HRuAAqwAIu{ 0U]RgR6h1q0O%b#e;lj3(lHH{V=w gTϛ%CJ f ش#2,{&/vѠrRV,2IHϢ^rL H4dql<4u:Qs ٙߗpW4f0lt޸Jٛ88?BrD k>W g;6#@Px FiaJ4MԀQnz6Z:s:bU@Mɠ{mYyk^i-G j@߀6IόX 5w۳ؒWت6|0U6@CP8O[.ڀw|MIeR%SM=n}PpSf^nBoƆUю,<⼵HUy& '?EiWHRHuO'}@}5ZG]>G>B)thOκX;'ed*aٿt—z.:Z`7wJ.LFDT|_'w?.d; 7qT͛Y}2(h((it'h=M䨯D[W@4P %Ob^;oasa&MTcBL$BGqJ}"v4%EqW/c&e.H`ofsTvRn:Ei3.R~f1mn hpW@iΊbf3ƣ'u3k俈DNQO:K $M"ʞRpmԵͼnۨk.^o[43SoX.92mZK.i_GoܰPT3mh&4r}1*k9`X[1ۍidJסvbHb~7f7ѬjzCƗvZtz-=t+enM̓'+JDZIK,}͇{7tھZ˻} &Dutcjh(lu>:RC]I=_.ygMڿgRif/*]\TI;`1|RbWl!'ƌE5Hx;#1Ѓ6?{2W<^4p8 f0Ç(D+&57r l.QY.v yV-gZ`4pf-Kũ!)%I[#e?&ZzKV S:Vq; 9r5HGbD@0(q p!T+$bn%-L͢v}.JU U$y2ŕ/jq Fv1䜞b뙒0뇞;8nO2 v:ELKJ%uP;H93Jo*HFE]Yhj .vұ0!lI,t|P"k9?EX ;;DY#L|+ Zok8\ƕs*`+.jpsxtR| PVKb Ad|I"atwm젡\?mJZK?Ry?~ϗ3(usX(=DMfPbQ)RVMT T:|wT=|н_ⰼKR7:V@j ܎r!"^o<lc8g)_D'Z/ю9Sq"TqwH4[1v)_Uõd# EŴK~k4ij2DS6J)7S5t2UkضǤN ȩLM'ͧh?Ipfnek'0<Ļ舋OJ rs8QEɳPXYo ӇaJT b>SRPK虹D=_E&%2cT&ĸ-5q̉q'VZQge[Es#jNtÜxo2SYSh"3U?Ҍ`ڄ qfHt&lL:a>엘QPyju1+q c}Lx7 0ښEIײ+*C<|"~=l. W]A qkQ$smD5_'%qMgl+agTy.?K^52.v0ʒ- %s)uv;~*N#LHjsiC}%9^K9seW'аXDYȈD%(c`2]:0]Ke^'Q()ӎen;O=(墼Օ':ugp^g(jbrDYZۍ5ҝI墹qz*k("'19a*\9@ Q'a}aDMPoYρ~V{aӓ.Cw~mu\OHDɱ7۴ rGu0vl 'sCV%%ɕRC6:fQh.(h)s/ԛӷ+(/j 'iQIm^#.rYTZ'հɿY!]V_u陾^DO9?  . z,ny|rB}3~L~22PT^̵Kd%8KFFFFfq%y<ge^H 2iˊ222=&>|4^C0-;{@ddddd.Fr_f}?D-9yq`9dddz@5f =^ 9KFFFFFFFFFF쳏3 22222222222p5Sb^h 3yΓ&!LJbBA~`7]S\?$BBZ7%odjPW=Oߋ-Ѿ>(kf E0|Gc`ɇxp]Sصq+78f<o;n/݅2| "5P/~Kt`ܸ59/y5PɃ7?ˆ!x`On{ zU6)Gfgg1c ϗϟJŗLOlr=~(ѸUD/^qOʔ8.H'K;&YiwsMlG?hܪ/ 7osr{wH*Te \ꃧ(Q+/qJ!!OœY1[Ň[_* L )='no,~/mz e)'>!~`"xz;ʻd~/oz8f\I%5s2: &?q4eY #+PZOvc&J9o[cK^ܵ7}>1jQ4`Ka6ΤՓ+ #28u&|srSM8Z{D']@g/KX:!:-colZz䍥pnFz$GPG0u2W }^;8V ao^Xuoy#c::%NdTP!:ti+zR]PL~u޹Y RczώȤMG@z*ksA q¾˦6r MW>]ϑ(]T̜[ 杼MP{Ki$7k(Mwϱ*şJ\*{Li:W&+&.1RYRb,}< ܄u8E!]_KSgr]wjj6L+/Lf*#Ԁ]tgw&_z>,NgX]ʼ/K ;T17Mt/ _]ļy&1 m~0PҗO8|=>D;Iï%Ƣ'DCMvLגk_,wfׅH)<#V<!.4[M:eߨvR5ROVe h`(j&q#<9u5{'Gz#~BMyu350d]Հ%6wk;o4Gj$Ǖ,MjV?.^ ;"u uΉ|>319T>oNzerZ/ua F˵xmEY[mGM IDAT~*l:.GVC@`'on}~Lf)5GsqpԂ7cS2γ}{װW3ȳ!: W .ICwK!NJG9ڋ#F,i#%Efl4r]^U,.č}whd{'G+T}&Gu.RqJH"a/9)O2lWyo$[sKl;"}[{BK !B*(bCDWYuuu-]aJ$M F9<Cw-sfΜ\rf볼t<吒)/uG*O'zJ۳ x>8,@`11GyfaP;b+X@<}_&h<>t[XRA+z40RjAGI Ud~RB5QЈG{v'xWl];#W& =ո@Qz>Lo .]R&c@Ii% 2K,c#i4*Hc~ˆ %8L 2o+ߦ(יqnD/n?;F)j4͏;gBZvEB*K!KSIR˻ _SB#6z RIkޖX(m%#Ɗ$=l6~gZB|Ȓx\pUDТ[OtTjNy)-hZh0JXmt!$FhDdhąGE DtR&eHT e~,O{TȔBnޅr@7a^u͏,ۗ@m,''VĶ(ݦ8v_PЧޟpɜtίIn-g f^^xpVf c#z@<=;Ǖ̟xnu9U݌%%OD{BP[akRn06wJ,0wI΍cS%& cLav/|*B%Sb?8g'Nn:ĞF\9D *74R9bh%jzJkXh^z ХUlD[K"E ]~=Wz]B,xMlŊ$lϖrL`jiZkv:wGOev>gU\[k>(wA]7$lc9um㏣0U6ZuiIv2)|_X+%R)k[ْuj1ŞURL=I-bKdr|RҨ,빭H-p7cu=nS5?9gJk7ұfw죲MPO> })Y |?ygaVKeXFzDઆj(`TI @*;9bLhFhVBUl۾ӄ0G4On1@nAmN L T.(\, @]f ) ߋsV {Bo:ρu<]d#%3z1=%'KRH'e+=EB;͞g=-c&+6IG *{]H?9?O߽`bt\ J JS)R3&rcp1DڹDhQAqRKȄ)O',BTyr2t4 aP &:ZLRBO$ʓBc L fx]#%Y;5g@0"L# ˢ¤BL4+\E ػE~ܵL$Bk6S@u h\b`@/Peycc@vU0▲0<Bԩ]Th|mޱ]:9]LeOMGP g~FJJ \B_\(68l/R 8QŤixR#4%#XUM3&CfȤUZ;tq>͐Á\MaNX4Qso{ɓ Ia7fb f"}!9`3yb [b$ZS .s;!mw)J/|GHN]_zX"I/as7lIΡYx7sMliǨΝ{z/I#G%~2S:[8C& q^d'/g4ُ2Vv$z;Ō!&@EtzL:ʰs΄i@(ŗ{&Qb .KEʓ ,gE@cFݢl< p6t(g2J>Sy?d4nO`07Q~%~v!!b]Cg1#|DR9&|2ql7h7QJEWw0F}31k(ml:wu{Ic'v$K9q~$KW2j~_Kye_3=J竘;GC^_L*;{Gil5RjOm(킘f;5Ցqh?պdzƕs- ;<ןmʕ x9RHq̭Oa|#ZT`W8Xօ"zvZnb=x0Ͼ30mn[?7wqtaZv T?ʕ9Qӛ.4{';Β߶̟/!/]=vf/ϣʬjʑsnfa/q~&D&^akt>_uq׭k{ya9Yg6\ umk-neRg==nO9s'OGɶ/xyc>MX1hbg}ɲ6L?#z.uAK'_&ݱo-)S TLoS]Xb-ߦKH/OL =%K1G[)}C-w~.d4eojo>#ZHc^|c#Xwb2Ws\)`;x=MIyOx2󄁊8owGj ?:j㫷>w{yպztW+@]<*wS]iwŶ"صdh{Nn>}l -Nձο8:Xu1<8 wO@'ؒu G2p Fe;Y* Ƹ;__E eDuQr}UN.!H`R>Z8(yQɿ^S({׳h- /!Lww]ŊMD̜u^hw[g{MY[yOI(,i!ĹQk^w17$75c [QzනnY4ˎBJ+@Y.ez)_jDs#46z\KH3P]Dy !py@"'2*ݾss(+>AaK~O"%LFаXIw1LMukJOwp"=;_Bu+֞tXwD{KgzJ]F ʎ~3>r*ܪ@.GeI9RHB]~>EέaRm໯`QbT|^6 US4%^OFǁKɔѷwQl!A Pi4 r#Mt2FL&UO]xٳMDy}\&Q1|"2*[+.bHf!j"*[;G@k{#~U<w l>IpO`|)E'`@eeV-]}93!x妩<*s?m4EmWE(T`?x䶶T&nճ8z0(z"0;cNA^)oߕis"MUd&t$KRkC扛 pb#PGyo;@mah=}rF[aChH2W4X9`Fk{}}#z~{yԨ_HȬԦ8u N۳zJݤZ 6NvETnuHC'}NtAAk>YqlF{?Ơi .3&;)HݻN׊̟{DPmhr&$V($(@p΍CiRč Q6l0q8i%dB!7|2rih Tɨc1̒<f #!D91dKi+XOfj5vaL0qԸ2v`l|"~7Y59x QˏlB]fY3dfbT~c=E- :rfO z.n {["b7ʝ8INcKNw20v`\+xYo3lL5MlSۜlKu>5l>XH]M X"<П>¾R9p5nL9] ?*ڹ2O%yʛG2jv%#mH}pf&q Øg=9VXUR4y)OϦuǏdlǷI7Q$9hZp'2eܕ IDAT-g6|sF˧b"7a#i";1]2JZ.϶xH{p~ULq^ ~C1E)qHdJdJN~zq_a3+ntdX#FgmXJ-֏>,&zmӥ۝7qj7@ol!7 DћfW[v~fG…edd~t «YG&؛kEPŎHOH3?|Ï[3֐|tB'o!##sEd ֜wsK<&B`}ي/ˣ̟ۆPZ au#=Tx:V-GӯvJOq?G2(Oc7;g*sx{bFkug&Vϰr:V-XD ЎxkW0GEe3cn8+@i;'ZǪ$ʲO]VZň Q1΢uZeu#-`P:ޚk/!Wַ;.O5FT/cua]Hԡ} 1l?ʻ'r }#./뗱ⓂdJ"Q2iDW K& =# tM&HWkzBRBP6>|_{yS 16dSXDvѶxmZ2 ‘qЎ,#uT uYqmo}+F>8{5:t+=n9Ql4dSa6vĦ!O8ēXvva`o捝Eh&26y/3OaՖ5ڦg@ '6[._˧Q:MpNfcB1g!wEɺw0oOLv*k;YXH+4BMϟr᛭]l$a##&ƾGWrn*/Gܗʑf-^͗ո/N$azm?( 4tVbPJ&:3@eFQRH8Hf(mc(Ԛ\Qhˣ=;wx+kή$d$*WE xgb37ϲE_7x6R:G{y[ulnwSbjw1L fE(uI~oِz<c)A> \T{0 vPtY ĉPMS~yo;{5\N M%Ե] UeV9Z˩4vhiU/-}8Q`6f oZIb r2 GarlҐ>KB&>/6 k 4PX< K}=M T.(\, ͒Hy2NL40 iҟ!jD)o [xw>*ҎPOHC^[Ɵen3I%qSgܯQqc+I objХ 9uoS=3''M-p rFv6g'Y}Pv3КPkr5:x,+6Jh:c h1oo\י/K߽`bt\ J JS)R3&rcp1DڹDhQAqRKȄ^NFY:4T diL LttKd*%D<*יLGJv뫅BF iVwJb|k(D'3;t]ZwCs N#(#9kcru`79aDe%Ou\S֘s'CUP@^" ȮXbݝ fBRzba7gZc&jgy&`v@и`8) ^n Bvt9 0q6oelХ%V|_.7.vPBS2;q MU5 i[=EFRB+pMᣙ9 Iԡ+cJ dԐ`"3'W=~2222ݡ= gyGYwhIo<z7M|ȼ cY&I.i(^IϿq|3SUo@i:P\/_XyRmfa] ;Yc dW- ZBYVV>MUUGCKbS+$5t(wh>k_'Sg !~̛"m9d_ubXbYeKig̪E$壦uMaa:,jZR7` Wѫh$l&*wsVfy/2s*7gMyJȬuOFFF(V^-< W[1o?i?AWwUjnsJï_@M烣mlMx38ĻcͿʎf!"tSL^BW58WU39_Md0Cn-h /js gwsJ1٣1 |sKtÈKZbOyd|J )=v.( ˥a*| k$YhI˖wEtQ8p1 G39#> %בu%j.ҧG=θc @aKp=,^w%r /Gjc)ٛn}8cب0۫_%j&xL)np_#ǎ~sȥ,I%hl5!nqBx`䇘gN8d)sOcɤJ~"X)Q"z2z\֐Y!}D,-uſT 9oAg4TlC4M^.u&bfKG;\2E{أ<<8Sqwpy?ªZ1`}ls-3M҅Qbώ/8D'_?} /vv-LO$rr;ѡ`87vsFbXӌ-'gM7Ub|q3UZ:9- zٱ|[՜3t':,(kN!*ǪZ)(K -mp[Bo ʓ_aMAُ|M䩈xh&q7 v)4y7㬪%3u%ˏ3h5?p3U_ƞm;wb21! u||p;zA"n:>͇WUh:z 7ņFr?r4B"HLf҂br_$÷#]y7uuX?«);8/l'-53R~^Wϴmb!RD) :NfLQ#5{[Sh>E—g}P1wgq=L, ĭD[KQrz<}ǃ,ڵ "}c|^äɿ?-q^"ޯ{7F }Ş&N FTƙHdN!y/%/՝+Bs2[3~1٪T{߽b*/*,6d.@Iq z+l@hȧڤ@)PO`Kex)RH*q̀}xZ8za,Mh7kA$*W[}VTo2AΎ((*J?АM?b)G^mk4h}9wM>Nm'sD |z:$n BaY6m-/Aj2Y+ʍ[>r`,' E6NïJa{ &+̭$)SOQh 4dSaT3ڵ[:*뺍8ҺTTk>W@S~r]L>v:+Bfq' |I1|^"I2dZ4)r,;c=cD0RqOaՖ5ڦgyOIՃtX6)nï֡X.qðRu>U?7[ "+9~G2oT Vk *DNնI64=Eu1R]Kv&OBC1SkFvL}wg}\VKߔ[ ]$s Q+K\ZvϫMƷ+>80[皏1@N@ j[*T{V A]$tQH|$}fsRhė~vO@aMЧy|;趰2?V*0hՙa*+4l-SSЈG{v'xWl]; 4I)U^ ~gz#oper~m,\(M}g3).:SqqGgIf%BÔKwvFhA')HzZ:lf0dTh3NRtz]Gu0&Da]d_8t >рz^s^@:F 8a}]v8Ogtym~j/v'ӏQXΔPYtS2jx\V5X|R[g&I\|NP#ǔBY~\M5Rq+RPO=`l#$7kEZh_; J19q͏w;\'B=eqm2Wm"H<}>O1ڟ+xJMPy3?h3@y7׫g)eOy/ў#,xasr3ݺSz _JF|>(n̤aIĭEzSFBd#Hi gW]FT;vSnA,Ƥ@&Bp8:I?B)XZh4tR\ =wKD=5GWZti3 7WW4]>>X;) Bɻ?"EM FP~്.ĽHC-ȄB('2.wEb(g@|OKZDRuъj/tәL^[^o;n^e<@a;Xd6űn Ņ ML  |'cg2gl,sƸ IDAT]wk$Th VjPM;tІ}NPaW:FU6d$wywU@=6r<$O /1oq< Da__TZlX oc*LS&06Sh,(R25Z  PO^1Z8vFiURohs<>FhNb2⇸a,¡.DA ~ܽ3;,aKJ' }>rIL"17vuE#{D1R]U > Z߆O`$5 XA;|Lh4:l tY/R%%R)Εםbv~(pFK?}Bcl" mp֖k)U cJ;-lMRv rqvS#}O"Jk0X KؗQ̼B*h] UfOcWz &S ׍[oQWmK^*mrO#:3WAwlL.e8#Y2h3OI Ix'J-P5.bǙ8t)ϲm cpm/9)6'KXZ7ZlPo]3K3Umi3oCM ͔кaV 6 {zf+ų1QBl(K+m W y|:^?p{Nf,IBPf)Ly##UYiT : } >>Ahn+S%?b6JP\\2R䣴(z#g0ۖw~T6BPfH6z_A#1@} R]S1G+06* Hȫb<\eJ*{y&BF7lX/@C۟Os·>>rv?*n6ȧa3jsVr۹/D}>KXGRLeOŒGYGXɏ@UfEo@Ht1`wR̋j%GO@e֝8A#-.`CU'|SE(QDC/NkFPZ(7j%6tSX1s ůSU cgz%׌}4S J=m%u-,`!ik&9a#I8)RIv3]#L8!`+i}OcWƩCOeCA r |QmI=ڽ/6(*fg}[%oh XLG2##:vh$!c=^c)Z}:r ˧OԔְ6>">k2J$vAkPTUݧ;S֭y>$"vk/,#g{M?75uP\.dC#$uoes/},?4&J6v.`ge?sobiWi8/acd/###seaŌV5q?5}S}sDKwWn žFr?a= ky{-7jC<|8O'Ώ.xqJ +z;Ć3?^+'? {9V˜4 W&Yr4]${[/*p y7'vSR/ZbV;ܻy-Bo?Bv ԔOSvo肞~** nWк}$ .6wrY6:Wr5RlKs!VyI'6.z\QTjaN>{̊XUœɥȧ+%9uEbLMO`rd7[> Lѓn3Гqڻ4,} mі`5~.}˻Rb^Th;fcM6g;=;J3)O I,vJm􅂝>Y&~KOvbv]a4 H[*gRs4*6xfeBZnגSDTr84/mq$]O\'d⻔[]?S8{+cZ9r_ОV`k 1Xǡs ؐ֨=\ǬI̚ En<+yk,׆* $ Abt%,)ƚZXx g+$4dfD(WnVPt3R٥ןQ.ιY] E 5HM4y. GϮrS13{NPr23E 4lh;aݓD}) 0U hт @{X=kR:cPWvTMTq’P%^HڼD NBnЎztc `bV(@/ڠ j_[w,)n|hS͜0=vHKuNTu֡O Ts7 3MƳf>#Jsc^ :,]ZU\'VxD# G;\ENDd9@A-1@S& XPi_w 27z<^\ɝG/ ffܵ΁LU; 4l!CP{hNnx 4!Ssv{Pct*[\u@[uG Z7lЪu05Iܤ c~T#u#s.~(59GXAnA"sĻ:)c9Rvmth]QB'v=IXy [oĀJ<m4:S@{ܙ) Dc>z>7̎teזq 6莝'ABNxyvHl^*HٵR!ИZ(ɡR:zӽ=2222 :ZAMX%5[QnEF[O%{ 6W`۬{V lfww#*#@5"|} j%눲k cǗS :=Po3e,]ٵ$74 aT-5a4`׃z˥y t fJȮ"'{KUFeddd.ހ4Ѵ+۹k-=nw KRdeh(vfw]GHy,Q#Xd :*NʧP%xYV3ݕS\տEBA]=l`_@Lvd5)3>&0ړ3婯&|ZQ0PYu'N(C4N®T8 wX[I;<+fOxP*}S-.`CU'|SE%Q/ϳ1sdv[Ç{#`s$Z^6"8z +fNu (LBFڪ~xnxӝ )JtZ֨z <-&Ds v'~!żul+eo;*r?Z,j9sZ̘1NIk̍P\JԒk(7vi ` Ӭ2r>Ci4m6?y蠗Rۭ:'F2222Wu <-Lvg[6aJ̻6v"_wiE(Y[ahih3.Zw޿{{RؽE+dddp"B˂҉PQr-^ez3"fЄx44 $[qddddd~W u0N3C${[di%= 6Tsk{{dL"o,E/eO][nERc_Ipe5,wKOEl9CN?!wXyN\@J@hK߾NJʨw8rn6=?s#$Mpf*-g'sc)pt ;Ba1[)14dˁ|_Xsα V^_)G+p y7'vK {u׶=^%(f5ٽýNtq^/u"6_qҝ1cfɠ0###>tyuty93 K~ /nV=u&~c}l?я{F>Iֳ}1'NV>^sdV]Ū,L.GcB\pu@ IDATқYh+Byxp4[`oGdiq~4 \Xo<)5h*zr%"1DڶS oW4 @w'] T t"O^7d{Xx/Tv$QNVdDq ]@L~EloԡlLځ?'d .0Ŋ ~acȟ$6<& J3 p_WV|pDi3GoS r:faEsc5/'F˃j~E0#Lɭ1n(;;:>Vh;fcM6cxl yy\viHF q+&[Z)-G{7fţ$t3υڼyyVJ̠1i\OXsp/V=s%RwPFNy 5UxOb P"Dyvdśq/c4FNe@]œ$8473Yzɒr7gx?_ O*cn !uڽ.Qzx/) *gPۍdvP v$5ye(!p'L,DzY}U jp4g*ӣmv~ J Lf,ϔؿp]p &޾_)0 EdkoQbk ^+v&Ѷ?l?qA81&bHyuqO7ūfWϳp e(&,1yStb5>;t jc*nzg~zvL)@>oQ(Q*59G1hL,aZh3@ܣZm5 $7'OHl)PQ+qu91KxmYJ)0LG',o0x fh0JfKo<V1^9v9}S@ &}K,;ռsp H|SlQmV)?;`T8yŋJ)0 >gR׉T#ɀxPT:)/TynPNZCAƆx{:vn"xM nyY(q%VH)6 E'xl5HImQ"}1O7I`OՍ~EuNAF .K5e.] o:qʝ ;9*`" iX*oqJ=+duɢcl9?WaCx07Ij &'*h#%@n 3`8ʫs+m#w VIB5[JڇjhΏY))`F2|⶛$;P*@m퍷M*GO=Lh27h2Ѿֻ}[ n_ϱlreQ+%L %vps f9<;1>uP8ƃ$WgQY#b'} 4-N:^@ er/b0jUS9BmYR1!6Ca-TRM[VEax; agRm jR:HBUE4f}!^+=j?G,$>l)~[+fIEww[NQ&f@eF)+̤ 6V62#Ll@ KFuKwؚ Eb(}2~CQ!ϟj@c *s+aO1Ԃ@;ON>զ QDǮb}?$pNǝ2ydnj"yJ!U^,يv7 3JAW%J8n:+vMb}U=1S8kJ8#P\ĬLihJwºs/*=Z`u=.L~ \PdV~'8&[h{OG,:kCtc}TL3ԦV8hZ,`i>!RBNu?z&bf3b Ʋ_kW19@F)T4zК0b@hO4"m4 m1Ql  AJrhjaD9jI? { `l3o`CԷ>\AO U+ +:zaMIә7RMp)AS7}@>_龯PE,vSkm*9Fr\ h1jk4hVӦIP1SX1ry).II TlUBX:㥈{D7* uf^a" mp֖k)JɎY"Oc*o;6 @hs(U"Y~P͹QʎX=?njw*Z`5 .a_F1F G9j1CTlGTȁJpun$z}^~tHK 5@犭jUUwA@놭Z.& fܵARgrl$x'oogd.-}i+-Hxx|U,i}C| Fv]Z7ZlPoIG 0SS%/~6 }|nʹ BMAzxAXpUߓXNQԎX+@u0֡pB p't:~X|nJ؃Py̶KcyYWZ1D:Y1j"7㧝T];T) O%}Wh]å@Jm'qJ+O׺a ,z YzЇVw)?.J !75N-АI K[ vQIiyveR\6:꬯'C7}FD.]l_?BbjL\#d"zPBORȩpz6 NLSbg!s!p&" g[LD8tZҊͳLiUօX$3na !aLuڷ`t&OD΂t,\KQ v͌@=3;^^/D':К ?؟X]j߶`lMJZ@>뚋+nW 幛9ؐOBA#Cg0?"=x&CCn M6g2/4Yb!#6S yL!;]ogr@KQkF N8X)* \^|9~|u>*Ϳ}!iGq‡ \[ؓb쫄A` [|bGlqLIu1G+ Ed? LrCu<_s|UrzfDCVogt+,M>ʢɣX= cmLE)o#wӂrq?m*#p0V4r$4@܉?ה^{'p-)8p0BX3O:+8t%6v1pJS \.?Vrl'!aX0$ cX;UƓα]&[L*Iڧ}@1 q!HJ8ZuIq@̤fҋҦݥR6.Jg(f4 GNYT d%Rew:#p 8.mV1{>uZ**Km<]Euu=mۮIc~6s3>I0$Rְd-!~} "Dz^Ƹ(<%>Dh+Hni%28A;|לji$LAVHyRW"?֬)sw~TdKFVN5V86R+J/ [)2.~d"BgkKzvN?8o*Wa$c|Xo4 UḮapmOe+wsW{5H!DzñGԵP]B& ńT~O%jvᴀT|cI[LԔ%Qg3CHlmo \3w |}:Ճ;gd\:m(3rhj-Eٛ{k0txʇ#O䗌s?@jUVi{gՂp]818ΒpoG*B[cf>bk0Cyk17c֠zQ1'm|5q8yHITZ u %7 u 9y;E(f. NnfD\ ]~JZJKôiD2&Me/@rm uE;2U1!c V|[y:F‰ r&ړz$:0pSc !ͻ_c0Gٱmk=rMTjI\d<|ݨJ5L&NnX7եvS(5}n#XDcef9+sRC3!lZz UwIy+zq9;ӹlpC#D ]W21Z(JwIC(f jiNX˜șĹCf8Do3P'Omv~dK-JK_@Ό}l??lTXlRZrU`(֭['6ډE % [@yڃ_o))l^;Lڶbn'J'f?<՟v[.uh: _Mo2˭r"vBuCC?WTj$_nRgRw1>e Ʃ6V,QK+ʡ V:_jV|4N3of=wt: 5q7HVuLqA8>Ff2~"XKΏ3ՙc-9t9QzQ&2 Pf,fFM6l ?gz䑟h 3X_"`d7Mp\ A#4pۯx{Os'ofʏb(8gOj7bFY>&7 `0G6`K=YG2=+(%qsy`AnJ竔{[?{acjLx%2Lb#xhJ Hٖ=8Q[uS97ɑß%Fy@1@94LrbVo!?]ІrƼ!&qդڅql8QRS>zsSõק{Prmsgx?wcRi[SJQ9K#G ~n,à< [klO:gK|qo__yoIEe1RBvcB[A Bz霈ώQld\d0WJ}H^{I![%eQ!ʜυ-S5-GeFæ:G7zS5>)6Ttٜ?Zkםk +mG h|Gp}Du=^\K?)p .w{@-fV0‘^T#WߘG@3↥v$ؾg1S8w 2vgs7'ȬJXh6>r-6c!MP:0x" =aN(&f7~VeR n --f՚L%jB$Mߍ]0֖5vϑ~Hi/Iezʖ)x`ͨ70;[Ng,|ffƎaBM}{sU k'15Ԛ[P"ή3Z-tdMQ nĀyK9zfR9+47 $O}ZDw׎Tb, ]p5>מGrC,Ky$uFLc#+ϊ+kz'ΦҲq.C06VGW)7J`3T;3q8HG,JPn+zd(0{JԞ*xl IDATKݵm}Jꀸ|k<*(UDU>W[xaw"U,I.E76Բ{3_:]wQU[~ !^HDEA޹^ "6PPQT@Dz B*L&# 5s&$>9sZk>KrpퟝtEI晝'bk.vVѽSu30Qe|}jib>/tXk8[JټŇ}ugP fDgRPi#΍D.Qqhg0Bj/[DA$Р{GgͣFL*-j#K5/+Fs70vN&kX` z G1a )q`(ba<<xBΞ,ygŪnldkTZ:9#Y]\Sbο!TIҮt\#s#lܧa_%ێ`Bѿ?#Ye>wS/z;7G7%vyXu()4җYGJuN⤠YmFgAOH 'e+}q]Oz]-N71 M)mזFF#Ze=^tALz ¸*-Hi^ה/u}qSx4Kva)='KvVP̖=%4ʎdz8dDi]:>o49qÉuOjfDʡ9Zu,tIIagIbks*S5.(T(Uf,-A֤GlclxbzkͿU-jU0RȯVG@ 7Ҹqvc/ rszz8(fƗV7'k?Pj 8jtfSO0,JT>}8HbbUmiXfE!T}0뫙1m4~&׿X]ǖ XR(HA,CS~I#aƠUl>x>h1WT+8ڗġnɦF4;J Hf]+#JGWZX_`֨Y4u!*OLu|Zx(xjMT7usc[bI%{vɳKVzZ7?F#Zlٴ-U99ghΨ:?#jc2h_I&ig`?ƅфŬB@/"hPq^׿vLXx%3b2f_=i{JFB՝I" W.!E<=T೟BZ#:%1{ԃu-EcʈJ ∦^j57R84RTkËUdԸУw~)(k{QKD۠texDFLyL |\B0LGk(̦ӕӂG#t{c};Tɥ?Y%}([78lS^]w1o4^N=)7:,(t-zj$ꃈ. _u/Nq-%GXac-Rꦽkh58)7pj~hL:E `nwPMM - 1@Db,=4j; El>+pS g]1c9<Mg/ =e.A,op$4b91v\.\yu=3=߾82Tή= vA8fkCe$ĸtHpK=S> 'YSԚl8tN`a7K1ӻM5s>?)'bDu9'vpuTӲ}+ld 󹶺׳<xJr%uD=G˺;㭷\Qb6vä|7Z_ک$pҽ,!BBJ./ةLb*agՌ)^߷쭫y UUx9Ю#[=nhWF-2oeh-^NZ^T͟o}קغ,=ӫH}Цz$6pКj}ktA<4f bc Ԯ;j2y%zOしPޟ6WtNO{)SrP'R~sAռ_:o$|`w]$p)*KEōSe˖-i羼x(??)dddddddd7YV`՜~YMg~TOpQNBinxk(t9N/edddddddd:0'I(########s!/ht ±?FÕA~hj2Ӿä-e #0\%+ KvfZE WM}Y^YBڐԮ#1&F@y&Vl5m;Ί6k'y~d)c~fKhdǜHyZܲlץ!,~ O|#q;N )24ڇyx:~}Unt"ռ B);GUZDC|'S>kdcgFGu0uax)kJwwoD9M/1zL9_8m_cz*ԑ22ID;&g䌋ƾv^{%+[˨0JO|u@MR^ULV Lz!*/{IZi?;yX{s9r=a@qQ1F=AQeP4.2&s%l1=M)]$+5׏@'AMK%{Rl{*E9 kq @p*@P? ?/zQMȀƉݯQU$3FM%J+}SutS4M#kXJbOH5™Ѩ|%[]Z’]q~GuGp FDٷ|r(xn w[_w]\"K @".I %y%IGu7Fn nk (P"c^N'0>FT Zt}+OS"McK? NXAZH)$و=2%j@Z}i6]@hCKs.Rl|)O76Y6RfH"& 30q~;P}7o~7xX&ƳV`̨+pC#@HS -dC󃜁<>s֏ZR 5?ݱ=[ Mdnx'OW%QU3q\BP#dOoY+l糍k(zr5_:i@°79:4 ;hBoNt@71`.J&FP0hyU00 `kiipRh0Yϒ;|;biZi͇9b=.68!$b69cT~N\~z_lI=kZ"͔cvƞ˗Nh 7Qn߾$ ڀ Js,^MdBtl殕44CxYS<yRR ee׿KN8HTky/8Ҳ\FV9aI"ҙxG.RF^"Mc 7N Μ-%O`g(_ړGM}rW[v'bAqAp v3uHeX©t*j1jfC Ud;^ͿFƵ(OEr^]9>frk3+LI`0-ۨhID[a,dN2+&3ķUR:Kɨ80|T/@M:e]R/,ٖDšjc"l9O]Y`S1)UOoP_͓GPA1s4*.4B}ՑLZh-N5f#.uL&m[4ηOGz㬀 ג/. nD$ԒƎhs]|I}׎'*53m'j;ӝKد~yR#]E`\dbdDERLDq\53b!$:t a 8+shoS3 C&tLH.6֔^4OI$zu')(Q+K7Qh7>vܾWG#t%o45WuQG_x* ("" @g8sq\慣ccr[T *!#X— {4y`R…Rʾ Df˨A71H)q"Y:Ntc Ji|}pXGD T nt8p c3uL4U\BǥPGnJIcT\lȳLmbgª}ȩ׉Lcpq,lvd׀xfgp3v1Kenl$^$}|n?+Pκ}_K'/v'Fij;R.S:oDH/F$>È^go{(Nyw'H;&練`Q2mCgYiZ]3[g1TlȗT oMT}&Q{*M?ͦ[G*5k5j˷"Oo7;DŽu|jyBg0y_ AwX,dǙMbd==Bc?Nmל3&-g9q҂u,;tE#sXObvYLÏ|t@G^6Srb  IDAT=mpU{R;Uw{Q ?=u&@HCq L 7ߊdv6p砱5a]VW$b!1j\k#oڭ|'{[Y!qiyJ4fۋ}iJnKXniE,Sa3zN~{R:0=L=mڝ3֑ ב8{)ۺ}~} g?EiV֩)²eyzئ4ؽ= vގȻ\Nt ='N*###B&Ʃ3>qt{e]l:>rDFFF p~7Ouʶ֨"}|4 jyNOFFFFKFFFFFFFFFF械hs :u ±?FÕA~hj2Ӿä-e #0\%+ KFq/@&=O2M)$]GpcMEM5;kڰh6D }tR6{IڛM[.i/ٱHyZܲ!,~ O|#k&#)m;RO{_Bw Toe/VEI'%Q _}[c"qxOeʁ;~|>BԠ`eǙ>#cR+,I/oǑo ?>Ֆjz ZK4q7K+|,d.7:qĕ عޟCP<'*<}>~%O-j.scP9q ՞4d҄z׿"ĜS )=B3[ەΥf5K[[2p pHz obOn =iIOOH^5y뷅kf 3*xW okP(s{Su=>>VIxҔβ_ui eCNCW$p6e%ena^_gUdwdl璲T={qD 2}+3Oo;pōўT~Ŧ`$6tx<;]e&wegE&֕euQjݸjH?BQ؈`S/gLFT-ϼڃ$4X?Jͪ<\ݡ:veQg1?cSL Nc?˚BM1<ߠQ058\ƻ;6P|wz|w{ 8Y|ʪC/ZխoRFDW-G>ǩU5J챀g_Q͂ϝmvׅE६!+ ݽPݳ>`0z@埼uhFPDVq*J5[ꄧx* Cr8iYm^WF S:C]鹃+FSw)ݟc#2.ҽO ?/YYɰ겝u6g8$G4b3 g^Dkeevdÿa}#}K3g>33] _+.\t>#GcTD]UXMA /c28{XsӔEB˾Rn~z$)"1j*Q_ygKϠ{c _m|_=kC.}^e/?_ G"pWd yd~\~QaA\BQ-@9bi]X'(5KR  `4w|җ0e&\Fv=qBG0(ȩdr*"z55ڂw1ԭ;gf8!-I]B6k7ox fZJ@x|!mϫ,I:Gy,6Dl-*|B(`CNQ ep,^-Y&K6s|[&٬X,/cJSK>ku'c%n#k~-?kѡ“`YK+V !+>L&p\m3űl9=<ѪH^v ڱI%ȑa󹹧;GƮ}8 mlam>mD &PWЯ-jJ\7.\tsD<ѻԮ勔L?J+SIZۗf "Z@Qʖ<)^Ʀ;׳tF,iZ$gwPf6RZU٧3 e)Œdzaa{y0[0gBc N)? r\2Si1Wxc*PO!rUh"s?q7Uݱ$Jj&KjrϘ9wŚ|q BOKGV4 H׳:G8M̀ 7BVI%*ȚtcbPI\B{j<8TmK^*ĞPO%ےBiv|,ՈR12p|'%.^m cM(q7ng&$eXO~i>z PO~i*-/6%\iv1ұ=6m[K:捙DA:F(yy+|ԤQzvD"g+8]̫1o|3^+ݪTPNAQ}6GFB͏JGX>#@~NXk$ $[/[uK,XǏ))btϛ 3]o!V5"%|IC]t&@ؼ[ 0շ3NAF#@M:jO@M: Xϼ)H\ f} "=&Tk >Z@WN쥩FD_H̍ejW4Ў~ώ^Id?0b}RH)`e{k$B8Dr}̃^糘@R΃*p׀sԑjW,6l<ƺRb.ZˮlPu[*[68J OlT@c0닩;,[6X+Uĵ.K2p#_pe:sÒDN3@]#I: [bS3 C&t4(xpCcKDɍ`cmh'_4}zN";Iź]3/Ցru8M!PQewpZ/5Q92jM RdHVܳԲwKH~!䳿n6j8O:_LlaJajC^z_6;ky<338x;SԘ%bdhKA %=Ӵ)5$crgw6lS2~BT,bg=Pj9k$튔 ?$J)~nL~g'n_ZTnt txс22e#\'2=:Q3jr+ٳT>i1#7Jg")AZe~Æ-OI F!9gk*?LCAN:.NC˽>ʕD85e[9I>J"͆J붗n$KƾPf*Bb5NsPY߰&#6.Mz\4mъ|Gc<9Vq\K|I uHekaGk_L`sUڌ5(/:Nk_^IbwOJO.IlPOQ8FB_ -v`Zc%%ˊ  ,[L7o_m{_Au`'+s*)Y󣬨6<SòYۑmx/5 .t5܍s<<fȴʢEu{T2222˨B$Ek-'eC9Kkɢb" mo&\{FI|wo|c9`*aIΓ@d"###8tzGv}ڱ*W3n"b('#s`·'*qiNj0M)wQz߰XU`".qҒrd sR%X22222222222y E4 =M_E#1UgX}R4a-3= zJnoR^CI,]ЃO mLS ^#7 ĺSJ~Johtcz>+S>~ j}|:.ɂm1޽A_]vRx0婩/ܼlE+8, 'cBXʏfmNc,18[}5Jn]|PcpoQJMγFk>/?q|Yo`գͿ=-:&M{Sp"nqmiM̛2ۄǐ~"S&%=&ۘAJEwq?Mc=rۑ6vSܱkɒ8j%4.[SJSYN sFbZŬtc؝ЭYT.,<1g|<ڃoVef,gѤ|7O+drLE;y&/ r(΋222=7ώ#ǀTu>j87 4$}rR@5K;gT i{R*kL'uXPVHõixuMiEO y~6om =]ؕSߴ߿o>dc}KPs2`ܕMߨ s'@7| 3*_3QE;=ƃ8_-C/Z(K[UA'oec#j ^̺uB-ְoy[޼R,73*.ttW4Q|㣆G+"|7_=K;8+ !э_ PƟSvkJAo#Q꘍zk,Ec[4.5>anLsEj2 WzRlç=z8 sϝu?+pz<;Z!t5^j2|fVc+FHSJ;RP2A^=bp)ʺ<°>lVOh e1z<,m8D *Ȼ5A=Fv#oBԁO"4}D`n՝S1Tx{1U |ڈf1G]J-YU#pg<{Gs%)23c(8DwYJ+uTTiwƊH'-=XC{t~AI1Q<up?[>Xc)Tp`-RM'ޖwS,6J9|+oԖ'BM[˛/M%⭥%%OaZ7?FV{E$򧷸ck~msKŞb³3FJDkzJKۻkyP8TNY|4%Xyb\[v#/]Ϥh͗ddd.)%tטI{3t2Qe|C\@46bRiQ J?.O?g݅Jb1~LQG{T.Wן1>Pm܀C2ywnhRaz RS8x7͎XLԋM]&,ѰBF6oȤ:Hg@0vK:QTO\2J'ŇK+3䋟-}m݅GKCJڗ?1_Gy'@j3:"ѨǤrhcl'VƇޕl=\Yԓm?9mV3:F벚O"qln*vh<@03ƪb$gX^Hs8W*v3&Tc. =%YN&Ӌ+4<ړm1)hJ|zu  IDATHk>(]V}IFF撢7V9j^Ӌc|ӳzs-E Pl+iB `#Аɛ7H)!W䅇IYݫdQwǧYyh4Oo ֧Yk;c7!ChrgC._Fn;//`,%?B"-HDPI"Ko7[%DLيPz-}lXf73rs3Q?9u}_׽߷.=u˾gu|ZCypx:D@@UC ɷt dn)[3| ׷`rB{Pۘ{8|cf7R[":w0(bFQNI*y'ɏ5FH+DdSX9x LjjcP9ʸitMTE(:) +%٢Rv_m+%{ꋂRD Gc }/6q_yEk,Uz4^- =y+OuIFF\Qg*잳- gs1VAdJ Ptr[&R&7tp<g:q%OB>OX1_M'[N jEiJ}@Z|/*<[1RSRX倡.;O3qX*ꒌL̶`)Z0nd]}ց+{ hXU*v=j32ЗJ; X+}n i6w?P>M:Ƣ{AMdT<_rfD/.JjtĴoIFQ# kθq(BrR:yN PZ%:J4pj ">}V2p{55ahJQtu_KhJ;L4Ղ*4tY_OzG/=ف6 ^2Βl,MBݶ Q^4N50Ҩ}U4~=Hz(5UZ55}ŝH%W6W驮.8.+7ࡦQڠ!gSl [PF+ J*@cGHg_1EʖU, Ô]Pv~n#q1%JJh]\,x3D/2=I_6vg.Uejrld USW=عmٞIYLʹEF C-Rp 7!""O;fc;iͬLȨ ݹ]l虓Ѹ܈ b%`zy<붓LeALFb=s"VmdYR!GfNjY0O عc\K̽o<d/x;S]*FP 0+iۿxhpo#'$qg4{U 5D$Ke!~KU,眵w;E+Wb7%.Nd֒ 7O+W@flqXGa&?E[D#1ng;lɥJߖ~kl|Q%B9J[I.x%ե̺PJ7k9Ȕ++V'N퐑{pW/B@SOkk}WFgj ~.s):)##S1q %!1 B7""wm@Q['N\(2KFFFF- ~ˆRhu J%DEھ]݇ -W߼_~cig~CFFF."o'[ddddddddddd)eKPSo^U0|_ckѕ0: ˀA`yb;/?BJ2wrGp͛L)YpxkƴAW1ˌRDt^.VQ@TL%ή-Λn)hujU't޴uuf]]q+c?2hfbP67ˇ/[[Hr*֟g;8b G]0im:-˷!S }{bsrlTs^~7pF`;Mx hs姼p$2CF1zyFO :\Q֢2:*OR9H2],qJaH*jhӧ?=)8=aUg壟w\\9w}]s$%1i'H2&~cѭY,}:'g֝ ]D|)svf1n V &,_&\¿&kuj1kVΫt13K9 ZjQPZT0gpQ2eJΙ[Iow:n]W}\Y;N愩^ %v|(__,FD$2e}5w m{0o]-KaI nu$^Kǹء~v\츫8C͇J98S-~ikCO M(EܳXeh{0wO71z(En]i}gϔHHFmZJ0X%ۢށvCoxkW%ȋL);K֗Kx|r@E'_1Dχ ywtЮMB[ΚW=]yR UJRmQfg@ٲ7 E+*g+z)UHMe]581V kLZ_/ߺJRU&($!nZRQ8+<)/\-!֖Ǝs ׏S'L\ɶymD=VNrP佗){2[N<,i>[ͫf0xOw|udM煏/Ph̿R2♇|s~bi{!*Gjcf1u/ӡJݍKml4<קU( N O{#ۃE CnϿ/2zaT]"JRvz&㷽\ܚV Ta@o[oE~hiĸVE&_.UϤҼǺT =ʛl@uumhz+6*-RuJ)X`/DZ2}:dO2n3q^;aևSzJo:WRk?!pӑӿF:[KYWAlۓU9bn|qŖ;`EIӆrhL~3dB TlWFsr.Ѯo"cUkCD .k}w#28gu_[|SY FF%2gUϚ.fKV@F zOOtXqtID˶ Qc99+0"+=ө`&&98f%Z90JӳDP= ?m:_m!~~rj5D[!kp㲱SyL阬Y<-+c-`M+D..>Bҵ2]"ZF ;%M\]4VJfkOgtI%Iz&<%B ;& dٙeVICΕGJ|j*W? BNK=Px00֘O݅}wF88%dJqmWٿ;oU`:Ljq\[}r- JqʢWh :1ۮ. N  L j !d_ʃ%JJr=HaU$+q lM %3ExyXpmL{>[#4<7Ç8fv ߡ¨6/ht,(/~7?.=u˾gu|YO>-1WL(th]& 51( Ne4O*΋ "F\ FlQW;/\_%⠯e$H)Opt9=ݐkS7 ^$p[.ς]Gu,a HiS]+DA@Ǚ6 ,di\?r~b̄"֊"|LI_ E!'θgL!uh`x3N ǓqvC }W$dJciSCE+N}G7#\MV~1!8z{l` ܀.$[R kIng ?ӯ1g=P,J952IlJxȎiXEM\n2w7 7~bOd,:W6n Xݚytuss`~E5[MEvk+T#t _ N * ŃAGhD,;J5/v|WTWOiXw,pJcz 77}yYz@=z*@BOD<)T4n@@EZsSHa j<5i1D6mL3ϹEN;Vg)KžD?^eP^2ռ{ZՅ\I--Ҩ1j+Ý'^t+7,CMZ.uiAvs9yW2ޱ%LkFۊ|nw|Ϸ& 4M)OWI>AuٿD^-AN$>ogq<;k@XY kθq(BrR:yNם]="Lۺ~(-mu`O#n] P #>_w_IaKSlɻѫRPSUS^竿KA.aTiNOuu!vI5n7ࡦQڠ!gol9Ȕ+hDIvhb zh~+6Hٲa}2 jNy<7w;DR voƒEg;-۳>)+!c5qE cт}wfϳKAKAK̉XfeIY}dW)$.vL962q &tY/f<-` ΂J]͔+.:g.z%'i'x.Am\49Ato Ѓc͏c=^pf㒽t~q:\V/'Еy~AG48I޷͑/~*KXĚOp bv".6ch\nD[qߒ`SiS{zt,}uѸ㜳nhUlzEɝZrA@_IbW-n+1,}ę'8_h裿Kwvx(#/Yp/L)`-X22222222222[Չ| _~O\U{'(A*4 wXk+4atؗMn%v>^~ɋ 50bp;+PUX/⋅8h\FW@T O52ө[M#*w.zD.=69(#Jkgy8Pz[68? AKH9(o2ϏՉe~2\l(p\7p 1YIxֿ U ';S­h sTv>[]G;9ţovmnyMNyWNy~ tyY-R2eddd :(ST{&iYpZ1(QOs߸nyָc)~䋼 6^ҭ:>#\)/å Qtȗ[GDL2Wz2^8G`]1g|W沶s̙r-;r$\Е\pOW*߽NPA@4z#w6q**65[O8!7ő*TPt+?g@{}B؏pu0mz ~xc157]PXv^-`"d]$>S"R mi={+ug cy{{0_F`hb,o…=׷.a ߰.W S7s"VMڶx4߳la y!J[3rg:x=,j,mo4Ќ/wu$DzB633DsqCZ8qRޥ?Ao˗2gg6N/vw`6sP";)=Dp{~ a =q,]O M(EܳXe\uywtЮMB[Κ6DsLh$Rͮ ܸIKLg$yL'd5ӟyzT(? E,9KvDx f! O2>:xEJ<)':?6 IDATn[.jڃ*; ;M[@Xe3HvjveI^bŊ=]+WADͼk#f)<KHכ>067 Lcx?o}]t&m2hE <ҎN}UYg$ T/`81#\-5౳͕t_3,-õnZ\B*BCOac$Pц'[g "iR0fy1N?UQDR>bږ|̛ˈ0g\GzGU. M01㴢*UaYKucȰQNl=S%nAO<ؙ‘J^dw^jٓ`-^ 0:6:^˷"SY|S<'Atx-k!1O gIK60y*{1ڿRv*i?ux~G>778G$}oP'n dפU_yky31xH>KD~hiY‡},Cn֝ToTO m6u+o\(^zʴgfKu-ZQ9+pgY`d[$eȔn= FG~R>".go&źhF wZP{jnoݾ BMܯaʿqjVox R]v(8@/ȁF0':"nDI(*8 DrA|; D JZjm&n>kv>p _OMJjD̤GgGˆ64)ն9n Am,ʃ=_d`]\=~ ),82s*)5eVqә>;"IgЅN ZOL˙Y[X#mh+ġs`͞?U u"jj8B3\ΦdcRc|œ>! Ih;qV<̥Ɩ;`EIӆP M+D..>B߭x//mYphuLdZǝ,ʙ\*ʃ%JJ¢f֣cA]܈B; u;0RBtɬ_& o~]z<}|AB+ag.N NeCSkЧ>Ӊ2fhх-%+2L yBPcT;0ۊ4M\|[qetAr5j'kWox/R"|ZҖN&J?Z{X9xJ q®ңrC ^iy]HLߗxgt{NM@EG(qSKR27}wG$~,;SN2eddd @D'n Z1(eb.5A"V}[CQ )9F577"YAd)[,YW9 ^fN=۽DMۣrL:*u+s\cZͷź:Ujyi&c&u2_&SbHX1ߤ4ԇBCVnZ=j[Q?3(}!}83}ʼn"GwXsbjnis[=ypTDAFQWAłTa2vh>4UxvXKrcKʻB ?u*N/8Rrqw˭xiw-j#40V+Y6-Ƣ4(p L3'DA*&B47fn7'V8X:Y7\/x L^s }lW$eȔnm& ~ P׮2T۪3|ukϗn6r){ᏎHDTM[TQ"59 !oY#[VGs n[t'h-Y,k-XxswOk@ PЮGmrv#MY0T㩡Oӷh@Pٴ12ϔdrQ0Uc 3ltv%xS5ttڋ{94<45 B5&< P)JjtĴoIFQ# kθq(BrR:yN BяjjڢoΒpY(DS]MFNcw@(F]/x[ߍqAC3}/A*&dq6bH)4<օ*~Xls +ݑ&|ka)L!dHD2'[nMaN;O3,oāН`f9ˍh .[8X/W;o7gv2~.l'fN$)ƃzk lmȻk٘|Kñ3"ZB ZgN4mޗ,Kai|7q9gUh+1,}ę'8_OWJOP] _R}_'N͘ *l[ޟ8rży(qur'\B]epRO>B k-^bݙ˼EU;5F\a'8kex3bdϽʻQD eȂ7-۳>[{R41=|4\)8YWlL$3y%D/2edddŠ+ĉvȔKTq1] h*֦s#;;] O- M> r<ݪvSmTFFFŌoKFF{(zCFLMdCIn/0gH >Gj9's넌L䞼.#s|r;36 .>,\m&_F22222y =Aނ%##########sO)-Xzj: .r.݀&N2iحdˏR|ƣ̝7D&S kPE b~/Y˺3D;K? C6܎F j֋b&ыoz r^H\֫{}6VE- viLq Zz'3mT u539X1kR?}`Y:߬mtawi8n b5 %$PŜUٷI)Td'c⛂ڏG+u9qik8\ RԳ+.)2hfbP67ˇ/[[Hr*֟g;8b G]0im:-˷!S }{bsrlTs^~7pFp;Mxkhs姼p$2CF1zyFO(#XyL;rc֒FWs=fHqD3ܽƋyfwJ qzF?o*h^nⵏ#5Vo~zgK{=kiꙌnz5K0%&ÿajH"65[O8CH%~^k-J/Sj!D*u7i:}38؃*n#Sۮ >{/ϙ/(CMt60<]խ> *4ch@MYM y罖sѱC5"^qWq_ #U uN=; փ \Yg;bGED[I[~ê^d (қ Q"g8ȶtv_@kIL a=]L1CPߨIh:~o [E/O܁SgT Єi[+ r9GZHPRݤ׈#X*=oz$ ~Z9"830m/\pjGj͜=7c[&6KEp-BW~姐%6 v.YAѡvqxN.9ö"woLHP;ȿ-&z~Ctў}Zܷ܅C~F~}GA^傺6v4=NU`2%ܳ>K8V}_fOgƒɶ_67Skf}8s%5,VCi=7 >?ߧbG$? PE=EOJ$UDоv>ceqoMjۈz7SÅ 6"Al[53ۙEC,|ߺ-oONTx=y 80k5ZON:E(kӛ1{fKH#;cu] Dl3mhFS2W%-####sR(n8I/YvVt[tRPs呒 ZauPtvJ_s|4p>!aƼ}.$K3=q)!S;ȟh ߱wбo?F~om=D͘8.wڻéO+ہQϴv KfdNX P)Th :1ۮ2\b -<8a<]" *LС_& o~]z<}|ܒ2uh̿> u(P~[4WEAPUlk} 7͜vvC$u`nR~K.NO1Dʧ}̞_O@4zië{B\(8L#*'L(j`2_&S( Ptv[&R&7tp<g:q%OB>OX16mj (pPl9d8dY|Iuy4PbwN_M76-ki4S/f1Vuݢzɻ&'Gvpgt`Oi;>&ҬTmKSupScF2![3ߍp ҧy/𵞉N jESX~[GD,|27U`ȺdW{Uz&ggķ>LN`}:q45jtb1rfTU ^=zfѫRPSUS^۲%RH}K(ԮMDZG:.RbiXZp{1ύݎ(QTB]d$zO;s*cWC܈+dg%;pj>;-۳>)+!c5qE cт}x$]b6ߙDOs7lfe@FMYY}dW)pdjv؛;60;˅D]l虓Ѹ܈ b% -UlzEɝZrXRy?πG+Zؓ>z&5<]zA9W@-h 1h9̶yݟ=#ܒ%˒J5gByK%yp *'&값{8Ȗ dƒ-wm D_ٗ,K2c>=O8StȣУJʖiaŊĉSj;dddd#?n'#####s_3cw, 222222222222WFFF.Rx7G{F٭jtͫ&pϳul`-2QF}4V2OlGH)GWQNbz)G}J[vM1?eݙR]XȟFF nG`J C|pMa}2\BwK6_{[h<* V$eF>\~&c&67<*h5b̴Q%oIjgyKŷV O5竒_VFFFe6D=~l~ok/: jї_lϱTխ?w2q2@ڏ|׻\aV+0tP["ToC5F1.٨%oUƏ7aU/#3Ε‘<\ZA9?|{;Ƈm1cCzk8 J*u aa 6eI^}GX1gʹ?h৳RQ/5RyP"Szyf222hnz5K0%&ÿajH"65[O8CH%~^k-J/S!D*u7i:}38؃*n#SC@ |^*(꘸r9s& |/@]#_F`hb,oe;pgʥ̎ Q<17Ercqī3)+.:P5aԎ\)+=- NgE'ǎ:9ajIa=Z݀ZU|*jĎqِYUIU>h`E+΋^ '|x\u2i(M[=aw]* )5-ߦ IDATmH1ngKBw^Pa&ҁhw;bT'.bEZO?-1fP(BIfM%ޝ ;KRi~k@A=xwU]~nM @)zh&(RD^ 6k>QAņ" R$BsvI!pI}ϳusgf̙kĽo Eno? +z?JH֞xksZsD|t+=cBpUY~/h%$$A4mE:Q,ǣk(>Iշ +p('麋t95!*}7/)I}>8_ ؕmQɬo .%Qrqi;yDVeq0ɃfcҠ6PnM >=Ӟ_eu@Հ wjE2{L4J Ȭ81neng^~"³X}Sg@ωxĦZ IHEaG^xe\$UkC'q_v^~]Mq܂NCAQ-J'Z9w.HɌ*G@GXp# < 39U8M/ˬ_eԩCPɆw0eF2pbVLQ)_ߔi ڋ;yxw9#'vlfβ7b,̆.{&^S6`,XYyl,f K9,OM滵/5Ȓ%?ƢyY2 Ⱥ"Dg,fߟ1)ŷ_ ]Oop*^>ѣ XNbǁ44d{doaK4슠p^qDn曮3y@X*ˏ|&Dɼ} ܂?e_9XR%R"ly$J?&U$ @i6_#Avl.A*~c `L@ҫt9-JMQe>QIeT3)T&}dzhϟ#Vx#m&˭ܝn<[A0p$)09̡J0w9[᭎"O4],u3KAIXP{6fS)1|<;z󠨝ASnS.8ӭw#1an y"ǥӃ,@̗_&fjZSS`m(%#KNd*kJfMȄMvo Polѧ {P;RAgY8+IӋ6'T"ҩƌc$w7Wsj!)όJPj﵏-eU5`4L @nk=?Cy l倱.0힤'8ĞI 1׷kK= QV('9[l+\3U3 8kwjL!()Sɸix_a˵`DTh^]VRPQ= cҠUڠ*2v*Uնʊ(a$tѩl2.f,HZ=.V`=Č3&]ꏶ0fT^p16Ŕf%$$%"DZ;G}O5ך_]%4/΋s+WVq.N%W(Pn⛃+r cb\>~h0-lV*"S pw`Qգ_4l:Q7cS(Y3Δ{Kg$ ;9W7a ;gl**=>-i;H8]BtiM=0<6o5T_,gCFm[@H{joχD|쬮prk;: Uw=z%5,vYci8s8֦ :Kn"e",XEA ZacU!9:VQ}&4?=j--<[O%$$,Vp*ܻ2?!]C!ǎ@[<ҕ#(<CH'*S }d]o %M[yaSATOf#2&LW4M0^AnrtX֍jvY`[(v;1I*i~(O6db$$0}6udF'jm6m8{Ȭ6a:d4_}6!7Ԩ(<:쌷 d>:NVZUb>oN';B[vx&ޝ:BOL Cˬhٻ#Սҥ}<H7.,HZ=m捍 myxOY@` d 0:K5Lޅٳig'lJ2a {Qݷi9ckk%]6>m2Z!-YѾR͢N(PP@a篯&ތLӘWV_§aA{HmDT< ;eOi}bV37<3EViæ%xoYxi=gRuO|NnźI<3gK.'Trnzv>0~}bxk!`_9`3YVW>:Q*-s7scڃ 8-lӣS+d2-Wnai'ShujĊ&c[ȉ;r y C썛,!ߨs,x{c;>v}1{>-'5kE0o)ž}sf9*[OL祐92Mqco\*suVU]i]|8^cX *E%wcel bHLZ^gZrNmf3(YlACA<{tfQU9_8iCBBBdk׮]p퐐{½9vw;D߂mkt pK$$$$$(:eSqqmς$2.C4ĿF8WBBBB߉9?Q\Q+O?pF%Xwi A`m:0t4E-_?P=ͮoJ];V#?fYIm=d!OЇa-Ytx GK لfw!lMsIэy^k9qSu79k9mE|/7maKwlʼuX` dbNhdW gY6܇_|:̋ 糧Lde"$McBd#?pqvOt艟mYv_ɯu)Xhgmf}mhq7ZM˞xK^} y+!!og@򁣇糓)?mQīKr'.E\Is< r6q"S }robC kv-,p g}M 1;wJig7YMm}Ob#P VXIh7݉".ػw?Wo-+4R[VpBcѸ7fHԗِưXrWL>ݶǎah:QbNV+اkLj!rScnիwp٬b܅8h zIHHhMn`;sN%<6<е=~^C@_(/'H尧u(uir)O}DxXOg30yQI} 8Aq_MKBhpjMSd8C^n;yL)d:f$.(m1:Eu#s/~Eh\ʉ_Ce8q%L}fp _ 3;ш!X9s */L p3S=ԝzimOr'>?s4^X~ر#KyL>uX+o$[YжtEc.!!e8W4@/FFU?}gsҏ>P I۶/{c@W{7p?ӣFr>ͤ*rr0gl9;Hyh;bkbHwuEE3td&PׇPysKl cu٩%J~fCL$ (xU/]M3 SsgGQCLqJ?ɐ`<H҄L1RM{soEWx#CH\w_Yz(f܈D#ސmqU*(k:vI L9k']M=2R# tLV^`eAkbf Evmq'%t?FzalHj}wu'rGNU!љTvJ8ʑ DgZ٬̤<jk5J*r#*)0KmeF0jJt1_ԝ",=V&rI8Au?H9;oYX]^Jc-8~= f{7rR<ر_7*!qwh R ;`jU|1Tv8RhGO,\ZD&.7 4d֮'gdɾ#_A L PQZ8$;/^hIMiI vr(7+* kB4kyo /Yְ.2+zU<:4WwفCԺPN $Jή| &͇ԫ?rJlg}XYh>"Igkh*P:=kCPpa%ʑP*ɡB-*q Bez@튭 J=gg̊0\}Qjw]exԧ3",jꁽ dꋿ1#)zԑpK)w ui0fb~ވɮ_h0AW*|: m#!a1 ݋AFGFQHUi\*D\c{ 1H±Hp!Ŀ .r+ !}r1&ecK>I-- 8OKLɔkΔ8jXC!ih>!-*)M/ɾԮӜ >Ͱ[c%T[NF/pC.qH(?H2 Tr@WA̍`__Ԁң-6c>'V X5ۍyW^gb;)I.d@HOz6a_=;cDhuE+gGU(}5 gpPvutͬc;QjVG>"7zM"6*vc q巬\L)w]HWfc@,?pia4+f}գ+ip#WJtc%G۝ɽ)bvP[OBu*/P(4|]pSq53T!PSڷoW_}O?\.uB$ Qz>aoЧ5e~ yk E|̊n4wpe4}~>m{Vg,CptfͤV(L۹f'MK3G]7DLۍE/qpvPߗǘԦ˩ŬUJ9iWud,tPb8yоL+30隆Y< ܸpg >=Sn*ӒgPB:.@Dט1Y_*oy8D\՗ k]t@2,TU/ igUE*uj;qr[NF:CBql@rCA~ Sy%PBVuR=g %C{n.]/ϕ:!wڵksm;$$$$${=@@?csՇ+4t;XZk ?h$55'''t͛y㍅R'D⎲x[p ߰ursלwIKtffٓ$$$L鈉a„ lذr/^*mLH&!!!!T)3SVĞ[ח 4)t::g2uTN /QZ'q琖`IHHHHHHH˩]%ɰXb_Z ht/~K_B^ ^cL4"qGh= r7a>MQ $Wi<Z@)V!k{ȏDe!OЇa-Ytx GKq* ;}/Cۛ璢z#טs ~C9l+S,}p0|~1X4f2Iup %j6m-],8z؆3?ub7[swCJf" -Yhdմ칌gnHS;/joͺ[X_cz<= 7廖zU52t^o܃gx (">;Zm:P'v2Y{td *1ӌ -;zN[Aj;#G[oh4 6oފL&QK4 ]t |(Q%f".$9k9vA 8[)>7g1!5ky3xbMl{߭mtq޽wC?ƇP#kΛÖ7Y#\a7YFoߎ܍n|#"M[O\ryӔlm Xz>mLj 4m2ϵiEQJ)Uy' }kɢHi\!GFk;O᝭oxf~Êҝ2nQ[o_khO%vm̍0Y%ϗSq gy܈wQ9Yn|!>>h17A{t"B+ؙs- )q䁮DuBF~=A,=Cy(;MKyz&ăPz=i`ԹU|sµK۔͹oГ|+"̎,) FKrpN8L P;A =Eg&Ŏ 2S:eCpZz{qUX%>aoƁ.CVb4Vo/{ f؅ nbޯ?beLSE|/Ɔh_4vcWhvK6Fh /(ʝ_2\7oŀPM7!K>yMxS*<=d 5Ğ݆x,ah3V/uDz ]G*!SXA\>O~&u96zENӢmƟxIihSM~H*A膏{{&YgC݋5+W~ʔ)38r4"(4W8q{~:w-lt wZ؁I(C.Qઋb/3ye,1_-"܅Ts86m |?> 9QTzJ;pT>1 N [Ĭ"|DEzZFGňXy4&9#S1H+|;|m wU_h:d,Ƶin~dUBk?/<[֬[ !=1/ahl|bN! WR5F2>Yq ז#E.L\@._wɝ AMڙe,;vy}3$Sw{A]Xk=|y !T.vTs~dֺfHK6ue}L\ޥ]ҋLȌ*-T \KAK{<<=`J9` FV.y$.b[|2΢j>td-A2&P +FyL"rHL},?lyyWgen"T%.2gBw #Ws~gRF1's[ϩtw,/8})\djk7/~Î3k 9~f%W8b2L[͇S$ҩ7܁tޔj1 >|۷3k,oر= 9'4= 2[Zuϫm@VfybAtBl1A%] `sk2\wZ`n L#{?wN_x[3baBQN1}e'31^j*3>bu=ё@_B9l-eH=` 'CL"({{ߏ#r(mdfDiӺ;<3/DRhfhִpvF O@I<:^>5}|6E*dm_F{y@6ɉ[S+ɴM.}ě54˞|S%?!&ta<תMT"jNUաp%DIxbPf9yiTYՖ&Go/c4+ p,(Lwe4tv6'JŐꊊ3b2'{ʚCė򡓇JvKa2S\bufM麮Qwjdd4aHdפI?-iD.#l=|h7+sjfJײda kGaߗL[HIxx87o槟~b̙ K e2=sݧĿX~BY϶@V 5YACAk˄L[)kAD.z.mOudlH K`(MCL- +<ȑҡ $U,"U֝`M 5 @G8S{Zd2d86܂sד@i֯|w#ji,BX,rk`*l1'U PnX 88c#+*)Am@@F/`D@P W_Yz(Re:zDQf`(ɅMU|||8vرٳgҚÇtDedHK$Ȱi/u BY\ed gqR i% 5ACfи>?| ';.LT>*R`JO(BmZ7&Ogd׾x={; r?T3 h+6BК RA:Tv S]G31`?o$JӾu3j(Vnɡ;ܬ|Tbc7^ls)R9_k̅r>;pZc*֪0=Jk퉣*RƊۓ թ+růiɏ͛s1N>Cpp3f`ŊZ dРaWgA$$ʽk(nqlN6 rc'ppWaBzEk;oRD&TWTy$&]ӧ#p ghk9A@ibugJ\ND50B4pepס{I Ž1+ dn=ޘ"jcY/?c%ZPX9a߾+I)! K# p֧qSqקp${ҳ+66 O{S8ENh+TݎLACTn= nKmp]4;3b(s>`pP " ˓6exnc.q<~'Y!x17wq#; P,6Jy$V[|M^li$/%',A2;|< l ;Hyx!ʴԗ+)Q00]T'J>g5uǯ29:pCK6N,oӧ2PPqsrk4'V{1}gPNt M?GBfyc'O}KDFFŔ)3X]fϞRdժU6m,Ff@tppO{yٱEB1".ͺLo/E b2M!n0(c۞8ː39Yux3bCvYISpho #M""z3>vke{'ń݃)B2e[Kpْ靧J$ƭ4T}PG+ʙt Tg ^AC @Z^צ*oy8D]cr \\ryLSD|RVeuWwzg\^"MMq<2Ke*~ )S[|m<*!{7g4,kCAvĈcLBjS;]MGP/)&ql O{I؜O2n .F|BLn p*;ѣ؟xykH%SG5գ#> }uь09Zvꓳԭ){l=V٘5k6j &?ӧ_r9Dll$%tLi퀫C:>sCX.]NͻMkYڇ/בG_8-KKdrtqvT5˿䛤{@ANp zeIxgNmnf}Ҙt` >O]3@-Gū|f4#?DٵL_zbvO;}xwf#%w#0J9ڌ6fKKj%$ 4XD| sǹzEs1|tgz͇N`!|\<+CҘxo< S}ǚ](r)OgY`νE2=&7g[T<:/u+~‘Sa8E;͢y(+m Q+2N*XvVIJ95I2%DZwzX2f#.Auit5/8ԅ#(4uV. 4F{kq_06r31,|;V ̂YH~k/)RDBÒT^q$E_$ǡ3*Hƍ^=&\=@1b43an vLa翣|keU@JSo×51IC^4B& Lv1a;_DH 2WJ+ٔ;7H_>y#dԴ l~axsC u#8O|Qǐa-b#=&w9PŮ7FN%~#^4H]:6gQjB(I<Η_lu|2ܙ25 2,\&tͲyy{/9ɹw>7#`zζ@SB;*@N.nHLl5.F`y.@BN1**va ,u~Ѝ 燗gت,&yqLԆ`w5TVPuoUi$d$M˽?f "w'S.XapO}ϴeWt?@ǸӃ+uWsbw;68Π=\Ɇw0eFKK( jm8+/i:nC^ Aʆ(#FxfsD]eңtO 3yvezkeҸfY'_X̳iұ95+99IJ9bz̚cxv^x~_v'0R<;/3d"imifټޗNn\k:! h Pz} kjڼJdֆ~ޤKE|嶠r!ȩʨ!= wjE2{L4Jm&i,*+E#+áu8cH2 Z!'MzzVLMSV^Ϗhl?.e¨Kla_92f5e?ٛwEP$XE_P6Utѝcd9-MN ǺN 0]Ϥ<ʼn= Qxw"/k1c0f=ᔺRly*,}=uK^|.Ur %r?˖GcBP?L;4/ ;d q11aIdƖRϦ22]>47dgcv>Lrnp~_ Xpk _!fW:ৎ#BKGTړrNK0WD_c%<:9i/ʠB%ҸH.y +XÍeLʀF'Ow<=*RX d,vHC#O'eEBBb"ǥӃ,@̗_&fc\PJF2T(uhͶ_&dBƽHk6=( p̳,FEdq [~AԌc1sV+:Wv.S{R>=^;90cߐҏgw%Y qNwAÙxjl;;u6#U0*º;JqB(vH=55:0 IDATQOFs6V8gΫg +q((1wnZJc:ʵ`DTh 5u.VzfdXꏶ0fTgãIHHtav׶ka׺`AE(({ޓ3GLB<.={y_c*÷~W{j?1f "uX Momv=ؗ@uzMᖝTMD"bq`aj,e~ iMHˢ:v4j.Sk}}En0ys'ּ6 dTй'~e֙Jü 7=?_eX**rO.CpMKy5^'2*ʦ-#\~+H?L⽜yxP6Lc+F3#gs4F'ZүKj:qMC7Џ^v$Oˁul;1d%Gx_ 9#BKԵG7O Пr7~5fNώoQVU׳×M؆\ycjf) 5k( WP\ƒ9mg֏g_W[{lٍD7Z̶j= cAzݱ\Ǝw_:=N_Y._Onm)WvV?j#s߭=ߏN [[г{taqnw*VW鍗 (/b1WSؑP;%p rI('v@?J~y-J#1Pt1&kqպ,cbYcH) [-É1Clxd-G_tďK|#̸f\/!il7Be 🄇3?7O(b~?&LL @gRѵ72%4w`C U2&ڐɛ"A6b7> -.MG!+1$4Ɵg#`6fm,oA۶%o_=uhIָXʀcžfv9UO51 ~ѭ/[r~8-N 3?dX,c_ࣈy)U5̺gEV }x=7VlYKpU]{:% 'V==fB9?VJW]w7s<<*u;yMy-Eۅ]Uƾ;Ypgrlj瑧fFU[p^ gR޼&k%f#3̏ovS|dIM}8/3"+F$6WK.1N=>={hc0~,+Vx͈8Zʎ|LHF=p{i*uU;Y#- 1f#ܿ`|vwP^k>fU?u}~¾ipO\+G#M8US5W-`om YK^O|[v[O:^ˆ 4j]_eJ)o`V$48jvd)R~vvl4G֜I )Oɮ~}OP) d1#֮~׷fzo+zwEw&yN&Mo䧉c;O~3[&E̠3[׾LJ\tywZ6Ÿpoϩ҄3.ƙ 3K>';^Y>^/6̱q30e}ªXF K#ly};2m3ܔ[yoU/upGOƉgw:N7?3 Nw9Dƞ&ۆ_58vۤz oN'%;nJG32*F;XY]$ϯ,?\CX<8l]KyYùS`cCSCuo&OAA`͡;H|կOg>7P? 8N ƈ|9K7njA=/ 1wÐ.P=o6kϡ>5ҿXMՑ jc;iq?D_zCU/=AɅ1= '(h^dd >c~zʲ͜| I)#=<~Ɉ̽$|][0DȿϝB+JΉ Ei2R9s-ߵ X)WϾz9oL "'r\* I)iL72K~49 yF> #o*dwBt0R( $w0ot|k,TʛKP?5l;VVY)GɫGYR2gJB2R^\JT8 I}uXQϾD˜ykt\ӌw|&_>A@V/} Mijey-7eu G\fҧEnYGMC{tCqn+څ[IHE\[<޲d\=xC -1FƏy BPQeXKP'&)3,J嘚$ۮcyK r,0>0?S*RX(_{Csvn G0W#2n1ށFTG>v;Rm[4n?̤\]S B9ucR6ovz$6Zr(h*@@2aM[WXYn{%5-%.g [| MzVQ x8YAH"7zUƞ&455.a*!w03d0Wv fyi]fr7&>Cr:p4>GWxeV{{vo9xq^\&#`& ykEl^YېFD=@@~;2ЄNԕec#3G\ƘB֧fO͉wT[{j^DwhSNѠ `f%ёvX-`2w+b 0Ayڰ% 3>Ϝ*<Ӌɼ%1,ʖsx&}2T$9̭{N ]k~^8x?TjIH]cFLNվY\.kğǩ Gp(Y̜|\J f-xNQ7Y=Hfj EUK4JE|P-G -7]zY˪yuv)do lLw-w(npQα# uY w}6{K1]B7e¸t5LTȷPHL1P5_Hg/1m$QY-Gyn[ҏ0k(w6Ir5]ĭ}{#Bz\?ڒ~|:s:nwqʅKZ-$_ -]_H8ҧڲ4v.tA@&’Bҥi&qAɓ;rg- [9fʢ2 RK&m5az%qիۂ;*+_[LѠ Ppn"%?o#jy%y5ĵXw-33aEæCRGW63¥8Ft'O,caf󣘒r ڥ}8(IIJ@Иסm;cn Ơ‡pԳ([}7sCCQkWPQ(3=Oq&G+` :Xu?LoB8.}A,?&͡ށ Zww0#bB&&a0JorPVG(CGL~\\:h-NcI1瓜 \]A$&2O:`eQL{)upۊmSbvj]mRAz zk: 1*] OjvZF7*N,.ZPmԸ1'6gǝX.MoQ{.*Q ғw8tѹɩjyuGR^щ$lߵ4X+ss3oBFŅcGp!PS87coNň#}]?S(!U-f95\1Kɬx֓]3P{4i|ã)jq]Dw%,>J X<MJۓ[R4[NIVe9/JRţp <+|g i=Pk@bҀ?X)ż*Qo9cw'}SyJCY))~mhf*'}BU?xۏz,?.*zg KXXgi֡m[Bq7سu2­P]7ϙ9zDz'_M/<c 1Hތ}m r2q4©dM؂SHN[?O1N q <طRB8I#鯅'"T%M?fp$X9srʴ~I0&dߞ4FN;(Ry!vvB^w ,t؛rQth9vk,d@.1A1>Wx E=G2nt:,l lNa;@AP}o^jʒU \yzJJdv4Ē oKT7ȩ3>s*o_N%L4> Kޞ: f(gRN3L',.i>涗Pߖ$ ~eN!ި@}[y\*W!x*āiӠbgI H”8:hqYL^޶C{¤e,3rU2F_J E%Rtiyl-vBdfbҠGבcyKYpdhX\ͬʤB@_,dod=^~N+Q5s}DRMG--ᖑN(zzXnDG0FyэoxvQ7nfxj%Ӌp;<;igIaoinwy~MBF_ʔqrN*:%M6{q[ # ,~ڸ>c'pثذVW3+[b+6<\-?>SG91 +pLa6>C0P}jo!c$ sw젚޳td֍$&}=/Kʾ/0W Xw7æ̬s{ÓZ[Ƨys#̽=Am |.HZwM%j9AKDz\2&n䛖 h^۶G+{5pgEa]ͨEKaiZv^Lz5K-8(rB׊S #)v'cmҶS5:sZ2TW2#CN5+xcwnx=Kр7y`y+zJJ(sUz߻BѺ}h@لRoO̼e !O` !L1n.֭{!瑄]YG%BtxBoz] y7I B!N Bsrx;z|{D!$B!BL3 Ƙ"޼ m賻?T+<6m_&c\]j!}ugu@cI}WM+ȯV#D{C7jjA1?vbU!BC_Sa4ܡ胬ĻM̷v[gUT.O6uv51Z _Sǣ病TB!B4qbx"M3=DF; Smgn_d9_vi6"px/"S5\U%HYs36>Dy憞Aܷ4 IDAT_337pJ8#\͑;mZƆѡ#gs7nUKC_krEbpY?K窯ÊɬrkpS6ڔV:h[$1R8a("T6l#X֒GCBB!BDc7' R{܇z=u(jC$OJ :RT3i]t2H> n%꣣j ok 2 bqFjHq]=.͑6emVZ[ݦs_Kz69%xx<^kB!d%n$}N D5 86?5ECc`if':67,OҺH ҐYvryT%;d"1091Ga7c>6BzJO3.(jYe5x rSWf36k-RZnN+ʢ UgUӲ Ԓ^~l{k$h =-Y]!Bap;~ufJql%bo a3.P] DA18{T5!f[]gB73q1`D1YR^uW%ZMP{E.pZ YHWȮ%3B!MA*4ê|ga$_%&EyK\u$C\^RC"Zh3Zί:xL/ܤld}Hz;!n-DQZok!B!w HtCT1= N3~KVME0nhTS[QxڃDODE+7:Ky7=[>N-Ya]qYNڲ >[nVU( <ޞe$` ӍLVȥ{E`ZW[ize:4TP*1QvtAͶWږsXP] `]M.]R;+tTMTx\=;kUPʝWn獯cfG?\#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!H"B! @B!>#B!g$B!B B!BD!B3!B!|F!B!b!B! dD!B3H{Cqh4ٿwx<]~"to+?>i;딧)ἕGď0gߧpY}X2 `w~> c?X90ӑGZ• ŸG>ZS봔#?K:w6ƕ~n9a>}m Y~!z@<]}vL 4{(p'_ND3eЄ @5Zڲy!ĭX 8-kȣ5\m%zq6:3;x}ƯC8 txu]|+nu$tSc(>Г\P}i6SACLC|=#]io>^w.ğq3t9Iim)L ͠#\>?et $@[&nn[} EoU3 ,řϚCv2!9`l9`U/a} g@@M^ /o4x -B1iy s,qpJ dµxjYh3jbqI?t1Xkصr+֞t>}sn(Bu.O۟QZ{ay/O62 gܐWgk 1pH{ s` U! V8\w,dÃFe8pfX̞v/Crhl_O8NjƑt7%Q0H ӡ֕ej~W؉G1/-eKQ.Eߌ%l::5kfU=k;WŢEUe9V:i<_P<&.3gZ fOSf)>ܘdžsMCm,`'hq9d$=HN~ ?aÖ T@ucҕp-(2$o#fJ9Mg$ 9F-bC)jR+*9*~tID,ع6DRC7;\'\s_QȁnKww!|uRĕu1|઴R[bt},PGșT[YLa$Y"Huav4 osR0MA21\,sp2ˡzX]feASxlL𳓞IS!x&Lp$Œǹ:-h&d=̝;HNі︡A/cx>a4c4Cۗ}H>I}fvB9V(7~g6,V'nֹϝ^a@ p| BPeW{3_ ^w.}gˣz˚8~y]dq % 6]0|tӖM zAi߹pܒd`ݙ' i##Qݫ?MCX~nړIT'h™2^ԯ %NT7mp(Mn3y*. QNuY5~v-f]+ge% Gr6G?$=<- &3d{J 8\ ]B/콦YڅiY q Zh!XpJ\eTT?b-|w[s8uSY=l26h鼵s~ة}eZ f w=oo&T6O1i_[2Bk0bVg?P{`z2#jfKHןÛiHWa3QLe䴶,F#O87ܝ̊99؁Pݑ]!\fߤ#m;mts_f'ήom50t0n 7s3} j@3$* :``w?jZ]5T/ŵ$f١ 4k=I 334liY.af9iNwVSݴ.+{ UΘ0Ɩu>o*tm&􀽱y0:BQ!B}e;T)C9ɛ%7O9Փ`/*?'d5ޮ xqDEqU ̠AP_"*ʱ"-I{A'2_{JAHg@>U-Dž<)݉DRkͯTjq#PkkE3$ bc;0MtT]T*SzXX~ 7b|@ lG&Vlb}ip7l1MC(7\ҕ|SFck4&3r9( ]˗o,gOFD0b`f8 /<%QF]C{hX?ۦ: ωRf@XQs,)~YẸ&^p|WNnV&I#өBL X0j0Cc-2D2jpp}}yM)yy쇊9Lb!+bmncA}謔p{urjt)Mr lh89؋X&pV|Vtz-7jAGn*l?1o%6(I͸WTaCۨhA4 R_M}9ړתu Czew 1:nFǗ gٟ+2Kn:ǂɽ S6ytݮa!~jNJx>J#1 '3 4v@G[.ҜyfԺ2N$&&LzBJ *y,ѱEXO&,mW=h#uXԵ_`3%8Ǡ- hu :8-}4krO;Jk;w(Kͬ+`0hqs/ޠEqq4Fr1kjjt!~ʴ!|z0sdWTIf'6A(#{~f/~ܰukރ8wЄDHZL 8& DE$rDVq\DֱXDck-S 8 VJpkb&@I6wIH! ٓw{y9GG>r2(ɦ"Yj4p2h!:Bᮧɬ8;`o,N7K`X&tszD<:m;kd?$l6m?1qlOz_#A]2 ucXbSN0|PP 1X@)̺s?]CÐUP&+Ά^) '`qx~Bǻ4oJ߳].f0+ Eww1,6"{c w:<%|v @/̟`kM-}҃}])iJkXOF#]TRF|9Xw$S1֚歹Ty0s]?h-u[SaĔ@bzBB Pm=GcgeF k`ld&Fpz} *qM$zHCKoIDATplfʰiw_3UI~܌exL;eMp{pOg,+ɛEg:i&B\`]Tv˯~ ӾϏgrW`,[+f#[_jnărzZ"O3+N*mVMx\ kq>[sy߮emD* GRKi 0'gjt G.{&TDZ_RӦE0) G(mǯ"9"yEB)2p>ΌdɊdZ'drz=i2~] Kho& 3L{S {gɊdL;*u?=Qci᪺G| iTaOI'RֽCD~ g$?e1d^H/ʟNbq<<5.vl!e ̯ $[`@qj~X"L#UaHUu7+mRQ\C*ҏsY4|4{G_gmH* ƼƣQpzˏfWһxpiU&8eU*/ggj$ZW]룱NBè;XsY JWFAcngin9IQRp⪯`1LƣO G!>̪--քT'ڣɼ}?c ;xvZU_x^vPUv~ɣܤDO~LX5:|I"bbq㼙ך IDATxw|ـw]%cM/'!Z @BJL $mp-Kze?$d<ں)ξ;8K.u._gh\`BP( b[YJtqܸqs=u;gΘ19* BPugq衇5{}{tӝv$zpqU׮]/^~AJeYi]P( bBzZ4 )eMwښQQQAYY ,k6©SvҧP( bcGVR),,ܭ{4=mʔ)H)ӚBP( H@Ӵ+R6mȔJM6Ls:  BP(Fׁϲ,eS(;{?_~0KY#At1=ם"(v)|G[%/iy6U}^_p FC*Tω:>Cb`(}[lVwu+Bλ@ӊw}7^[`ӶlL>MXf ---dee1qD,bѢE=ʕ)bywYSӆ74.מqZ<!t T7)9s%0^Iw~v2r 1HlJZ2ǛE ?åJӢrϳhu5!\Le'Lgly>nKuqgp _R~:׾%yQ~Iǔq*fx-q6\KAN!yoƩIOxo)XFv<2xgxW"ouFj=9R깻:T(:3_:7t{[چa D4&Nƍ)--M:-PJuwe#/~m8+[ SQj ?;Li=O7xd{=`>9׼xՅGܿ`ԆR2gtY%4}_틷m!OOLb|!4i,)iwԛ8cc}Csy?CH)$o'L:7NĔ|~ˆfy0Ncv>#S㌔zkVǎ}!T:O}i:oF|IW^IȤ%P(w9WG8f[ 7{IxG=C3o=b/bC7~&%^tsI747EzVW;no^2/$㏿{[ˀ˂gfU/ԟ:!NLg_XEXFoY |r­fX>+AouFZ=wl;# H*F<Xz `J n'`ng\Ӈa_!V @̬~ lAUđ]f265/7w<"VUƯِ 0Lb + G^9=j*^+ٯP(knz^$cyCF6O:kYb.?.n}/}iuy/u<;\[Lw=<w: Ktށ|^ōwʯxӸ ϛ [vWzv2];j,edagGz=KɰsW(]P㠃|>>>ٗwnqxmۙ2e BLO?e}EԩSzV.AS:r\u~lwZjWE''ä>}măh"U_ a"MH)9`Q 哙xL[2aA|@ev)% #߱EPb_,swNCJ3O !1[9v.K㢯lSSt:667b}GgYFr=wvFB=wUPGїa߮m7:ןt~oia 0 ^z%VZūi}U(xyN:#ɻ {nxh\{_/Nײ/?'^?=νp>~\n/g]3nr)9g)K |_긫4ٜ]j?3kO*֝}zpJ#/x=/عyn[U]#ճb%%|uEB{G&ܱ7ݾ[ڦiSO!dԨQQ]]O<w99+ļyG6BÉaǦi %ebDɹU>6nDJiG#Dut`i$iG7rP<::ׇSH+J '@67>Cz__y[1-Uuj83I75qyW꣗ms5JokcHuey')~fw{܎Mk:[= 2~n|/x~Mmk[*ySqL/qxuAVPf Ot˞Oyw G-?r5 9:0fsZ[FGLM&Gۗpى2fhv}UCZQq }!L`Y:\KjŝϥWә־ˉ`Ԙ);MXT\@Qqɠsߞ䘙cpTv4|L]D|_L&)gN`r3؇Qeu|T=_]1{7FŔ:glSJe/mGcS(+)uUΞ꿧ki[}oEwފK,[X-7{])%zpFO`yPGJɓZSxiR~x,i\?ACÌ6pǭ'i&6Ed-Q;0\h9~{j{z]]k_ٹP:$r˟/KnŰ n.zfr&_qu5˩Nu7O𬹬Xw<}N>]ye%Mt.eي93g~q7$O=PViL`s=/V,$T)?v^퇷Sp{~ʕqduz#,cPeL~#\Ʈ[kӮB6Ti:Svo[VW8v1-jq,sG繷]fG q#ab%^'|I%gY\8r=SbVMȤ5nlp'qpձ8so~|\˝-kճ۵*}o쩌 byC9׀%eT13Ylb揗DZ _:cYȏ =QQ&,QS!ꪍlN%@*s};'^^k*)uhz-*vRt|f=y Hٸai2W%e87,RRC#:{3vfN4rep3g?ʏk9zk(Nߡ(_WTݛDC!kw2B޾Gۙs픜-^/e"G}7ٙ҇K7?;1< Rl 뫫qةβ;}ٴ2n*߿(%ҸO?_UyO2wqrYZom0}Eof_e b{gO s'ܐO%/)..=p &Q N87"/l֝Z}o)i;G7z8:O|a?scG@LL;뭍*~0K˽ Y']͑Sxyu7ʪיyo|Sn_&8Ϝ|;K){Or\ZG.#{Jߔ*Y ^&*{rip<7v62KS sWpymazIL೫d 0<|L:"-uf R|ğN]ƭ/3oD-&>xAʫgk=UsS ݅KϚx1vY\,*VDN\t=Oo/8ңɱDWit%OorҲk7pW10uqrsAdZF28<]rކfr˧sڍK:{j_cpW῟ݣk?(q-l<1>kqO۪&˧sZIc}ng oCUT}7C%ksٙ{emOHk9)%c~| YCR߷S_޻C{t2K{yF"P\@oJL[[h6p06O7 V"0-T@ 0tBD"at]j},loXOFFvym IDATh)vNoL(s/ȏL%qnkXR?dvxRm~ְ_qo-~ơw"/ p8sna5|v:#$ bha_ӈK P⺟Dц5Pßpa /cD7H0!H}}[/ M(,̢o8˲  i6,dᲥ,]vIu˗b &ȨN02ve֮Y)SqB+|h&LHUUee\<_R6s-+ˊt -eI+dx^ /ÁaDфF8`e\͆9a #8.innpf0MiY-ŃA&ڹ74?XBAA5{߆dj]VTSZZBvV6.MУzýC݅/~_|q|xRmM;Z]ƕ}ҶJ*L c.{vaP_WG4g[SYL8 G  vMXx1?9t Q2'‹/t &+~c\.ƍ#`}sBP^QIٸѣGfj6mcs*KhI -&-i!-SBd8t?0hjݎ`4;o=D[i477e8]|a4qA<Olo(ͦ))NrO‚`yVT-fsRYYCj8p2dgzVP( p~{;dvN,\cXW(a}MqOEV: i[RAV\Y{b? 9:d$i[b#77+nl[iYCm64)(/O$N۱,H$n,BhiW^/\u sD0iDjkk8i"N ;jFw߱aWD؋2P[Ww@lss#nN4 D_lh6E6DN@o't8lP@ii)E4E4~?nP愀XYK1߂e:gH?["lwH |[Zkkk8`.EdddrHXA˅cڤr{sBm24þq_LL9Cwcwz-j|oQxwGh }4ѣ[NB{wGGulyI Hb#z7z+oVl6"!|\s5{h&MDmm ee%McS&JKoP5W^34>$@Z:4-F(emqXOcC-vˍimNlv{rvOH)(dgR[]p ˂)(,0 n6rrslbrAӚ Aa|QII%$iY999]>O>,$84 L#a8^NN͆@-Y._@s;pG VZP5b/9@bB L\`ҺZ['#SmR5ð=g4L#þ"SQ^NKk icFq\6m|\}{8].JKKC4 \ʫ̙r//-ܜrsG˜$6쫥ȶ&vbkt4 ˒lm\!4\.'i[KA~~bʤ׋".D;bJG|*@qqLLM7 H$2dn`zTFkFo!SBUM0^$d=&R r}57QQ[B0þl~"'r0m7y !c/u_Q)|{^gk<-VjK-W:, 2 B{~ǷهNK }"&ܨ$ m2q:ܴa1gYYۄ"   8BönwPPXB}] Y8NBv< J񷷑eHJVBtu@qdddHAa!mmmڂ㥱\Om-]ҢP84dn`zV0]$mrRKwW`m&':͊t0M7vPVˌjB3$þF\IJb :IO aY- 8}cYW=G)%~1DGo<`i2{}nddfK(}}?ϽKŘ FMmmMED"Q?zH)'^9H)ȠaSjm1tar mX|$RWWCfVNp8ǛEc.eXGFF&WLLӠ=@ HFFЬNN,s0ݧPM" ̬!s6w#5MYS3=8|㏚ƊmV>x?[VĔ+{[M^B14 gBŦu.:Zd|a|I2]tE"Qn9~֭[隊tp˯@.;_e"  uQ1MS_WO‰dԘ1ա4Z[ظn]+ȧ|̘Y].7+V @"{[4Z[ZcsdaHdUim<3v2d/x\k8氉8Δr,~~'i[=&n5y 04þɖmq$5@vx_&W_{/œ F,{Oۉ; #aU[JXj ̓˲,Lˤ-g~z<;*+'Þ|)hV #^~?{7cP^^Ά )/`͚լ܀h4q㩪bLERJ.\}m-::R.7/]p8(*E[[3u6鸻aDt]tfD:Ngz/䛅xacSh=moU Ezվ qEOZIuPkA/D!/{ c- rP(@]]5pplAR'|,]?9oPrYiD0 WW/Z0 L^ѣG~C%Tr}%^tRJ~!,K2~X6Tmh$ʳ6YmAdggcдXI:4MZ[[՗Zgv{l_^'!&Ff*oF<Ylڴ +n=/..= juidffX_O_۝[Evv6ᰋCɕ]I}Y4[L/]{W͌Μ*߭DVE}g6k_~te <ԟ8뜟mU E"'ϛvd /q/`Iqc'괶5 S{ y_s[$7l؀כر%eYXIkktQ]_ӥI DQZ[[M#/ )%_~zuuu5JUܳ΍M~~/xc&Ə&܌*놎iI("xc˺:em&eLׄFcc#^oFC}Zv1}ٲ(--p(iH)YWPW6;& 4AQaQ'qM{ p2ٸ&GTFoJ>쿜rq<ǿ2Pl ٰoçZLfI+9o]p!W S=;;Zbҥ8\1+E[[^qu}grz8 H6LL*  1i$V^ Yf *+9'c&`ӉSO +Q]gXآ"Zڒm `>@ Qظq#me$?C7m_,+a;#465ffC]Alᖮalv;i9Mڊe+(qdJѹ L%/C7‡_f |S 4 1i"UG\qE<2v!qP(Ni(/YĮKURXPdͫwdidddqIg$Áea۹4 fC7&N2caC6 KZ!v?.rEff&Bn7~l+xt=kv=?6B$ӇKߟШIn41roDӴs6UאE~^^r`0DI|87j@yɴ0 ֶ6u˅b n`eYDQ|67h6laav{|[U+kzp$B8&  x=yPO?{oƏ?˗BuWJIcc#^o+aGC)<b !(,,blX6mbԨQ1@ޔٗ‚bB7''I&tR8>N_Sv8zʢN0Lp8#++ ]p8e䐑aS^vv{ZvwF`YYY#67e0EbP$e1nHK4Mh8\.FncBӌTYC[[ib+(%=I=Ԛr'''yMM[UA#D|A Îi9pӱ}NANB^{T?% qŗ'}*m~ !ciL08_P6m%a'έ4Kox^fߠ$!zH/]L*--|~wdg'R M(,*"/?0I"p88eJc@ĕ1'70]y4M]5׾&Ua_9[o3 hEK)im)Gy$-⣏>ce}fB;b޼y#Ok%ݫ>fB=4;1 u&8/+eJmLi_sR(IUFZP(ҏz BP(v P( B?BP() BP@(OP( bB) BP(;v˖  BP(+n9 BP(CW_} BP(v$:?=\r( BP(xl!ޭXj,33ò{>DCP9t*{LGY͉=7j3v^~{ -},j3mڅ,Z?·~'U&Mࢌ^Dui+z4&6/)Ue V[$i̷qzܑW0#.1W @[pw`I_0N7r5 > ~ mx/AR[_Jr|z`i LTH!/)횴tQK[B,AyZ j->nZ-00ƫ1'GP.SQO6BGJ[-B |d Jzc8N`ZPjFO %S\H 4+CJ[:D-~Q/ҘGd}$jx.~`[\DF8-y;E>c-? ǣqw LS~-Gcrޕ;cO=-^G巳R i"?G v( CIb[o//%{+skܒ'wZ 寧>DJZ$%m89qK B1RHKT{: $Cf I5 .2hx&@c95n+Ը0fXjHPRqc-` tD`J0VSfekeX|g%Kmk 4 [&ag9 k*_$%1nl_aߴHډ@5~- XԸ;[,W· m=}k=vu < &75[> {/ tIG/4- %kܙG:SⱠdlA[%^%B).y>Uf:>#PӸ+(]<A|])Δ5Rn #0B&w%3cF/jqnl&%_F4`[㭐EU &Tw߲.bW.;%'%crKE$5#$^`_`)@baI y})@OI#+L[cN}~ɒ`CpCƞ}IqWPgbUX JKn9>GMpO0^Mt/) "=3SYW$"[?ӑ6 huHʎu~3GӰ$+Òyˀ3y%% k,@@Q]YDuX(crLGQXF;+6D%ˣ V~;C2? yb}D3S_ `qOߣ/%BA!ASB_?݀;%v\ܱ"4 IDAT+[%6IѸXX.dBMo$6H*-@qk(v ~)NgaKx}?;31͖Kh|hIE0S0n-x `ORo:sƹb~ e03z E9z=GbP,xe*|+J$2YS^>#xw2AW}Rf?0Y3w&/f愭M8΋r? <"ϗk@# n%K~%Z=XfTܒP+qhW>vmKJ:rkCźT0B3u|j*bE'nM|&rNvc|XPQJ\FEcסAx X=kШ<" ->3Vd4 5b_f,"a2s _b sUDpi[`SG~϶gjEsQzпmD`UP^^GH>Bxv C nҹs;GoүSҢ9u>]u'7;tZ m6'|*bO;r9sKc<P,k| glHqGZW+A|$wV* ՞`Τ,]ӻ%C9Sٴ+= o u5\v;O; -]Zu΂7\NWAgRSuT},>@SN|jveKx{WFQqz-Sm7LU~,`uAg}mftBhm z T<–_<gǑ 82c:]YgJ/\Seq?D;0v/tjIg &f-~N{AͪAkBC]vո|0WuUx][a%@^^8l4| vg 'W骝x:Ԋ`G^  `/C 0{c֜Jp'vIQSq•˙ tsJ3EZH&f_tzj_8|FfNI՚4¼3Kx 5n$}&o$QnPamtA`̺֬!Ŀl?L{#50$l`k߰7G;9vЛL8䗃h8y:ܝ SO_4ujnz9߲SO`S'1mf7ԢVm_jŝwcn}$kn^qxÝ䆴`{2!یVW lOM ` >A;}?1mޓ`/^> ۫l($NV4*V1emVTV[}TZ+`: s^`Gf.P6~QvՉ2x ǥeG@8q7Bժ)<4rN%$,wQF < ̓SҠuC8f'smԮܿoKYP?AD'|#H/?#@.׮MAj@ k-rbՅiuK:voGU\]׃G֦q.tOc4|niV 3Pu%?# C8ΕKjhּ.Az@IڵhP&UʵkѠvE15BZб?5FQ3EdۊI%kJ`Iڒ筼B >)lY{ԘsGheR9fqZܗ[ҿFH޾yJjv6MT9w~Ws¥'J;Xϲ:XVh sm1=:Φv }&ƞ&\fgX}'ih'&: gi?[Q̟-qSr cUONs6)}h01sTiJ8ݧTX1S?=OO!Dy9u\}>Ncܞ0_ g?v+A͎%r>\uuT;^dᇫ8e{۷5Fv37E5>ܗ:u24\'YNkϸCv&"E)]V^\]o]2Mo ֳv&a~/ܶsGxE_ONa5dp𗏘&:|[X9k6Jν똳46_MӧȨGj67G[nlj/ӮRnoױr|q:\à>|=i\\=kfs<- Mr9+f/>BKGl>T;Rd؝MrNVHQ@Yt_vgCq]]"Cat:_C̀*Q|j:ɔ7KwxalW!S@eխEs:O"jբA xJSv}]FOzQONk\lI'׀i?zE^Lk26!j*3g},jfXkŇyםf/dI!7).uQeкOB!ͭUTs{JĞ~s?ce=Lt4~-GҞf>akٛ^΂/l:UL'wEM2j|oZPT6~s7j[v`Z'^ >Vv,Żk o}P##;'[3=8 hKořiS2D7NmhGxQ}Q +_痐uK^ѯCb f !Oُl|Yp8=H&#Adl gjDҾ}u3|G,?jLc~Ʃ(Zi[w1iBjRؿ~+w6q«P+bcߡfD3Zc֔wr"Z5}7;|eHwiRS5̃6g:=&V\zmn C_1A: ?@ר(}m {3l#UG"kX͞rt,ӗwXsjq^uw#=60*ToR 6jF[V}>Zh-`c 4w4nrIɿ:'xz;f1p3 -:f@C:FgCݓf@ɠ+s9c񡓙؆g߂gV>XHwRG Yo[qOb}߭+bwsEø#ucGg;^[U٤e_i _G΍*y{}}PmBbKk~/uk1uL-ZO=èoԚ=`W&B]ɥ=>N@5z+~GW\,i<51`DS1czeP8'08S|u աq,Kl~spxy_gY_Җϙo1mC>u{=Ɍ?btdW,l,cJ։$XB9cbOdG9=`s,~N_֓Z%UM.R7/a~|&>3!!2k½u?{ǯ1>fC~AҜ*-<L>_:|%0SxqfXScHO$H6֔h & ucW]FRH}*^u0'TjGo|Ky\ql|$i6PziԵ{aSLK ]=Yg)l>wNHMd"7'f.K3K!.Qv}ؼo=݀McqTtj'Ǯ 6NG'F{nwN(uxyb፭=0|B2?OrdޚНC/BCZ|5-/baFLJs=9JN_ˌ=կu8vȶV/V|"/DΜTLlaCN{s9}lK=!.Ux_/nIw0OF7 (-:z'Z\6L ,.(ͻT@Eu9 HgOjn7˚ZBtoFSJRQ=8x#j!KÇ+4w lNϑ?,b{V᧯nҶ3oi>|>ZG),_ǔE$ؓ8Gpk MS|6E1cA -U:xzJ2U㊣3{^^KqcM]4+-gVKP(~$8nl-&ҵAӛYf齩 ^,d\v)e_}H{&kY/ҜlڐMj !tx"AiSFzgwӸ FV#4֫JY>ūG>", 7Kҙ7Ot@`C-ի^}#:n}8e3RדܗK0;6_zO-.Xޫ&[ {Zx+X*kd]J, pst.PLeo 4~ xT̜ժ;qcDGP$(kā`5ExP}kgi+z nҊиW/ōYfz4l1uؗ;:WC}@sƾ<}ϧ _@cPl<9?LҎ/%YXԩM9uZl1,?;9z cTokҩ3x*SCA~#(<;#Ŝ=w%bs1<7d _SQsOd&~M>g6}zZtƔF IDATތ&K<]].r1:^5!Jɳ./¿s#4b{O|uvXG1zÜyycV 0$yj~rGDOÒt36=N2l;Mߚ!fy6q.k-1ʩK|)OKp).L~ Ipm4)qԏV%H9Ͳi[IqpӼP&L5g ߑwgH޼"[Sk㦳8^#$-LqSvαW Ttj*=s:WӶZ Pܘe- -(É\v-ZHQ7 >C?}G|~]>/;'\Vw,[1tu&qW :Sd3 Ӆ uLR\pQQ.H [n2+~Ό|3f;iwi 9Am!%d ?|Ba⊽YB?+A_,Xh{`R'䲯B!\B!(bV'ì+.l k<6spR7Vç_sjq3mDT<,bETn)2%(JëfWF[LF'70L[YYŃ๻avqdWũotE͒+u-ҿUwa<Ϋ]5ί`8L 7p#MAjѨiL[Q|ItA-6-x:Y,3U+VC9'~)*w _8r j ǾsN*6'n{ne<8OaЎ zn2㞌b̽d(c͒bܾ k aT "b拇܊2Rǖ\@1R0׋ϭ(f)nd]Q[7Pg2[JOTH685r> k껓 t5gSGXጮW)Zg{rfkrfH>eE!ѷ-!8Ц$ʖv6t Y5{1J~N.fD> dzq"r{W&̬p^lf@7H/_qA Tdө(g:D,roJ+Ĝy_î[o7vi8b*<>eB2j|oZPT6~s7sZe !U})b'F^Jek@Vva2w~J,fX>3מj'~6#g<بѕ֯xuK^ѯCqm8^MyĞ }@ 2a`>siH (>uxxD_捷α5_0c];cm',ݟ楑j ?y8iū Cѫ.2gܟؓTq>Ƈd&UV`1w|ui*U% ~/twum316-<8Qڐq3;P;~;'ïA^}ndۧqʪy"ԔNn|XX1ZK04SNWx~wqissjq^uw#=60*ToR 6jF[V}>Zhboc 4w4nrIɿc"i mGu=ӽ9l]}ՓOņFצůoW&BޛއݺR!v7gl^4<;R1vxC~UlR2/>6[f1x?ؗ:Mg䵘:!E@٠vlcFO߄edu[!Yܲsj;<9n!r9nOW ?O z23fX:mxv-(`O&ROFwHo3PNw^GA5 gpQ$TAߐ8u{)U_Vi/aͧ+< Nh-{{Cu| mU95\##,;Cf3Y|'Rϙo1mC>u{=Ɍ?btdڭtAtmӉm:nr>Y~ HW^~<JSִ,H=x kj DƚM9ELtɚbW'2k_vr@ZvhB Q?1s^ү&X5Eйa|t'f/IӒ&P9u6"^KTITr곽-9O=jp$tjw'Y nEdCo*//\ Z.ch-_~?I}bq`|kWnbeΟ}xxo PXvbh 7NB 3^,YV#=gOBW.u[s_vhSUOƳEl3 ~0z'G[|A8iAş'[rI=կu8j6{VbX愭P7-8-N6DQ2U'{H;rˋ"ͭxyftfO <-~49U@Aoёx=-azT@Eu9ȳ~ zEf;秨T MG+>]̲}Yz ДOd_ΜTLTlILXZu]zܶ 񑑤jKX* %cv@3FfyZe%fǢc|ܳ|\x ר.Œ*+~ZX:|al6/d_l)r2[?8@Pp ^ZЭ4K}8\.N>TXEtZJY>&2,#$k䎙m5r< } bt~6M%sқv5S?5_\2נS\0;p:O3'jX8-l>-𲒪{Jib lt~z!i7[څFǑGJz,˦1s x*yZMz (XBO&!I,zw3ycU>ūG>bBV%$j%H fPLTmמ uf}tus3o%:6;ҁ*&#^oӬڤc&{%~a8+v- <% Gs##:] ~  , U - zG-P,5?.\?4=7?w_}sU<4gӷ }H;&Nz[{ǒp]ݏ[4{ӫWt>ګ3>6qՔU/W>Kڒ֟^2v-#siPO*Va%^RŕΖN4 a+nJXԩM9uZkMy❜]=OC1V5<)݂СǠF ?Ñb鞻9ҘqM2/'X22Qn9)ʥ#7f7YrqH39:V_,kZ~Hҹr`<&O X/b92Zc$g YO^t͛vx^m_S:scѩS@/jΞA#dMPޛ7غmx8~nfӟ?@QZ:b_5Vz-̛Qǡ>y8}=نgteD]͏G5̪a`$[O~5]ZF}?wQ}s&k+ѧ/R}-E.ep͸[,q[vp/⿉"LV(M^\mY3cw~$jq3l&nX_ ^}^͆iq\"BQnx{WvO!BPO Q4K];oBOO>>zB܈rw];BqS˾B!H3tg9r,^4DgќMՂht/&p\Β/r=!8Klu&Bܔ AfgLf;^N+'K>@!Z|z?$EtU["74}gpē&ÃIOg~o*x8  !7"?JvE*&iS7yTfÙˡqD`2Sg8kqfsSf:-3>Z0\RtL&{*t\Iil_N:!èѠ@zK") lBxxpƤ{e6haԪiDoRMbO'MGc& QpI`:=AR3&=Jj&O&)[Ehk'WeV3cphi<1Ť e7Zv u6v ݾaQ: ^.g.zP#6ipl8ĬX]B!\)zXًXZGE# 3_Ű'՟JDu=1i.|;T9-eCa8ڊ "/JR&;%aZ*kH;)NX%:f-T7YD$ϥ'S:rT|TIlv~OLԯE>'^-é2հ8NlmF8žK(-l4JV~LRd"XXgUĦ;g1I280-;xF Qɀ<0ge{ D"{rKY6iGp_ut!uyg0ӱ2v~Tޡ 'b8rUTj !Lr@\.X<L' #IU?=:8QT0LQeFf O ٓu]Fz>h'(8͟[UNpb$x1c.^{I 1Kq9]UIGƆ֙3_-ep/b'T!Ha` Stqaog}+z|6]@Bqù,SMҫƚhN%jR]nm< p#jRw seaOsഘ39^DiHO ){ o¼X5؉8/F#./ O3>}sW)Bqùl?S}V~aOz,9{`#8\ibljg_YiPsؽ=tz;Jtraa4 #.g`E*Rs+dž?oPNZLS]|ւ+Q8U 5H5džO@۪"YOu}zˠ]vb@fx]7bd'yt>fNsQk!Bx.dD_!F^xіǡ% d!5 fL(XKtZE?U܄\X=XO 4Vzv'y'c%ft~,XK^(WZ{:ͅ#%2 NL 4\v{RGWaWUr`{"GXIJ~+ۇRco4G dG^>OpA] [|&{M'ǩnBI,4ݧr[PʞCBfELfTv@#[Q-C $i=q1u-BtR9Ͽ" !B(݄B! B! B! B! B! B! B! B! B! B! B! B! ! gr: P!$B.Z6oGۑP!$Bԫì_p B7I'8Gnٌ))B'I'8ѣ<t/=;CB v8@>}^{IBH'8ncٻw/O>$<̰B! $Bp8l6vɨQ>?q' H'v~022'23yMI(7)΀rl8NV\IժUPgw9(9B!B˄ӽ{w~KF}Ao0t*Bd䲯*UDdd$>&/o//׫GzV96ҳڝt !F?!y+Wf֭?x$ Fff&g AA^o@ !F?!yl߹O?;3?g`00|*GDPV5\.ΦB  73csNR2d۷H'79 w#/2/ F#Nϻs>fҤI,_ yS!WI?!)3,@AQ{+#N$I8F_ ! ԋEQ/Cӱc"wEOfBBBE?t$B хB! B! B! B! B! B! B! B! B! B! B! B! B! B! B! B! B! B!/(+l9 IDAT'B!⚓?!Brp?~zC!Dv2?g!CBW~s E)ץuԷ݀MؑyvKBMM)E)U]j֋.qYUP,D9%=T{t'-X53KWP~.'ϺmC뽋#:q_~YulWI3| ޖJYOR˿J{Aw'Z :gt%t̫B8>ᒺe_:}fݏ@GV/xj,ɽ7@BB(&@~I|JBh!c[ȖlȖս}w~\ٖ$ˢ<~l=s3}xj"Q@|tf-$*gN:MS?AyFeaݮeܰ뱽̷03zo k-q0skXA[?Geh6L|*ab'!"zF49ϑI 90uz+X7LrM3an3N]bۼV4)t? C`ys1ڵV1y`UbPt]v˔4S0囗z[5sӮΣyzF˘ {1eT&V]`Tl9! ¥_Y`{p!ʚ sH۵BYm}C9% E~oXhyQP<;4O 7`74Ĺ(75p+K k*E'R4,2ݜmYVhCh.`{Neds-Vd޴!$Z Xψuȍ & ۋdZiTHܺ_lÄcxkLʬ`v1i: Fֱ>66jڛUF`TbMo89J~ȝnjlݭ0gx;>(lQ G~Ek `Y]ɣw]>jǟڠΰ1( 0(4Vbu{>p鄞,؇=nwPrQ_Ji5u^+I1x)*D tKG'Pv a\>g4P>=>EÒP:@֓!&nHJξ|1hGb,'h#ܨED ;ip$4,Sc,ޅu 6(;fEC]}Q5_8iLwQg|^b< }.aYVcW.!3ݾR(9"wDo0jJIu7,VR󷓔IU'_^eI7s*vNĊ|eeȧims]L l1W98G̭4Q~G\UdFRUo%u  Gxt6.HE5wᳱ=Z}ĵBTtKwF먘N QrN(f򷙾6>*ZGLkXx'؛3%Ӳ?5BS EQq,# &a 2wo:+3^UYk2񤦲]9=("LU`C4 .! C3zc!#f$ᣄ^$y6#('a^Bo H4f]9O!qk9@~zk ''1YۣGy"d{{aLa?P@8lzp%*$%QxJ$,ڵfw4<)F|&v'/{>,A/h`uǘ0ǥsGbQ`)^.h8Qg1LVbkJzN]L4&ġmgڏrrj48Zylu'v?fxcТL-P؋pѪwvu1}ld,*&< [V0{kbk[^%Sm*fog~!x4Y^2뽔tmQRZTV^nh)YRO`G Z^*(Cjyx?.**}jǯ&ӍkP?h NPScR a -/VkØSff?ٱɮ6גһQ:k=@qn*&h"&k1G.Mu,}wxB4 *1:l]#qRemH-4*=0PPUH1t&" 0RM]t + 0ܪ][E?/l덇'_.H|F lZ+$Y"Z">Qy[܋/ $Φ6Md); SMOŌSdrs$p`k9zqH= 8@`DS[nR)d:<#AiHo;ScZbU< {l Uƚs"3&Zhc BGfPL|&B6Ad'M1k÷OQ1a3Dֿd|~LލkR@t<^lIc bz]gOd$zvNo{zM/ ^\DF]*9hHw :- 'a*+")'㺴 Z׷0*"3lP\uk8j^Z^sАĨiQfMKaimѿR Phd PM͊܅aGU֍6(lfduA:{ߡUh-xHjD~6靐6l_Kag~Tv0aA=g!}Ybr| L{{KA~#SNӥIc:'(Nl#CP[M8sqmg}M69ɓ0aF: vI d v}>l+ȵdD׭gr |C tb2a,\Ϡd4F֤Z3.pS3rؑ&g_ql$LNA۵IxTЬ]G}J][&a0y#@" .Յ(INZ5.\ k1X:V+ . z^EeUasF߁dY:bR? uQMi8iT(T9͞9jU)8P!"S_6Oa` jVq9fPc)S9 )va%([ԟۧE&!Q͊ L/B @Rm$3dŌ=tĐv4Qw(c/tR3%GX:;LT*M !d¢Ul3CfCg{R"(ds2dI VB !hs2@h6&zzY|=e'&ȡKEQqF=׎mr/G#/\- \cP:~?6Xa0tjʦ.A7޾/:1\{#|-4xffJhz.f-9wLdH&3JVR[Ø[en8Ta&[ Y4jI,ɨ81՜ʰ~?ގ/@zZrqt߽Tݺc؀CD(lkr A#HE%_Q8O+WߪO \-\>?Wda݆3z%ǚ9&Og@ X8i?0,LU8J V}HA9Z߆)1Ǎ&a sP3 EќP`^=ș4GU)ԩ5M6BkdfV j[b;1/ HkΞtaEP0=ifN ,oJgvCaW':Ƨ&pn;X>22>tv=ᗰ q_Tq[hYM ;_^.ڂ;k5F\L' m\9T+ذ >R>.8 &~ul_Q2|xy8U8LdkA{g0il pxrCg^TjWKap ζA}HffC|Pص7k+k3CFRi1I")bԕ[*M7_J̎JnW{(bA|;ک+w?c&+ 6_n?t@0i5b5'oW>OjgbMf€ +=B)b~h`jXP\Bu0=Z"H$u(7Bۀzp7 l/;PDXgwГ0'QDj_P( C @="K8zHdWP(-JdYL8 Zj?~y t*)W;l4]L؜fZ(P˚6bKa֝S&ЖH$DPf {w($D"Hz}wH+H$D]B:D"H$!'H$D3C"H$D%pz?5L̕X?<7_9lqd؋^434l]NIOK7_=m2+H$ [-4j>3E@8؉|OȑcOtbBANJWGsq̹s0i+s_cH$D"Vp'TZ@K\Tkkgskq(E"CSyG_qwÜ֝[~6QI ䷏!v*ne}~ab&g8~0>dx@\~|j6zDʎLC6/7290[\Sgg~~;~-Q_,werZUP6_NtS>H$D}'|C1>؋wF=GF~^[Εz!6u!J*m=;u͆a.#t&L\& ۏPue*Tdŧ+ )&pxd6ȌK+YSM8̍LXS'?D"H$i66j@AL&Pׇ[&7Q4l?vY4 0/tNQ4iL,mQH@(Eΐsb?WIϱ־{@[H$Dm}.Rl*FY*$64@44 ,K4Vr۝ -&XqֈZ̘BAM: &K[ڧBB#3e+vMl~?_3S.ڜٓ_ד؜LR+) F%D!LA7F-ZF`F!yH=DUN4(fӣ-ZEd ӄQDkchfD"H$UbYڣ7gP;;[ !މ͒U7fYw{"/\|8ĥy<4=z- z={2g.Ku>|:174Y+\KxՋyL[־v۷h\zK.HD"H$o.ʛo)x?m$"H$D"`>#yD"H$w I$D"|ΟD"H$wI$D"|8 ]wޣl۹#t;w$"^~nQWtVb {r޺+q A\oLdr^l3[*w=6!6:+F1<7_ lnޝT&j\{:/>{!/?3;&F:ԁđc_߮Lngg]rY.bTbEj]͸fk[lh)H$_"gtT}1_峣a_ '{F~.ߝ}'ltyuzl?} lһbr'wS"H΂kXQ8+ FԪZB\yA=w.1i:o>x0W|m8Cs7lŒs3I]RDME=C0uCxJd7cv2%?<v7.daZj٠g{PVbg; k?vаl||Aс/X[gNg 7V{=eWR˄3bT3yaPdkhzo[~WZ$hH$S9fDHpr'q|T C'@˓R+]ۤ ECm]fv'!@QՉ;XQC]|"fպpgpn`u9/ㆻW1ԍAڱFwM=259jf(Ys\b^pjv_Rn{L3 ïʹ?"֑)5!/ӧsYz[_tȃbfF28{~~0i[ (G|nYOo_3ھZ[2?&O,Ɵ-/֐:<)r4`4hyEy`EqC>~J\zL l瞇p~ PR~"PɌweZ0ZE]`JN&eZn{%C];ۇe|Oz~ х ZV?ϖ1`z&}HiM#cc x|vxxW gxGBTۣӝ҆H$ə9eQM~O\s}uhpV+6ӷxlvzo?>njĘ nkf*@>.N4f]ػmDx`Of?ҢPDk<Y A ?;@]n7;cȚ:_p)ډ% }CZ\I%||0}-OWρr-1Ķ6τm @w{¦ ,ȴD=jb_?"$:^ag r5EyZhyEO+Bˆτzk1=U;}59& UQh#.F%? Ѐ_jwR%l_f{͆u.\FԺ7paPi?Grr߬6wVo ʧWarg'#E ۣ;eD"NszS&ꂀUU Ti4P{ގh`x*1#Νb` *FvU2)OՈ1gF/v,٭2|ݍ sTI'ыϒäk6#ci&boSt0*wG&dxzPh#$Z \h1-Ԣ5Z߯^Mn h$?p}y/yX+k[xoG =[Sl)$#mQ[s$4yFeC=@QQ=fCɌ5G$Zfi @`_hu5rȥIU n>S|"Oþ;yVW_D~-KQ0'yx _P: * |ӂl)?Mci]%X_KES[qCǧ8Fa']_! ^XM-M2LtQ1Fq`{i 45bkÎXzzW2)hWAD1gZCKeѣͩ$qOixe%mvvw׳[ROfA X]Lcw;=NԆH$o=KBc6<̾9>nfTS!1u?_,79?/)mt1lY@2T*W:jdg] *&zˢ[;W$pGp̙]Sʠ=q8!*;)]d X”i8Pl̺y8R偝<^Iֆ:JŚ=VΟ]-1;[)`ۃQS5-!Ja&grY4b Zl~q@()߀;l4/DOnS F F:7Q\ԃTa%+F8*mu:I#b04( 63Z=*ޢ䛒rlVEI沛GsE?kdE#}xU'i~a#;YE4u.S"|9 ڽ𙋇{yr/+|?*UeI\ϭʾ]Ǿ DlDm<0Vm㩚&>*,@?/Ǭj$9Ӆ^?s(bׂ+U9gBd{-Lĝ9|8}.{A~ep.j +OQ3GE,$Y5/7j^XF.?t;l9rf=È"?ĭk>|&8JMq0zj9g]D)?Al?#z>]e'yzǷP?ײ,7߶TƇ_p/ dG3Ŕi-<}Z k"}k0U[gJnQZa K{nVDD"9(ox׎  (qY,:6jmNFKɓ?e~ SF.: !,iLf͒j#Ǟ6'Y%仉=on*]gO/7($Fzmebs_B~_ߍy x ieïJ$vb[cP~;bAʿv'u<ͽ_Dڹ?>V:(H$|?x2^HN[oZUk!H$i|}%D"H$ȟb0x+>3%fΌ?e$dž;=#%1-/t~%/%0}kYәQx),Nfyac{vzLΘ1h4ʥextPz2$L8.e=%Qr8 (&N!xw~6.'϶&VANLx;kjh|gә0ﭧlbLI7լ{m+G=FnS*X2{0vV/2 Y#T^vSJk D"HZ4ϙo}nN} #sZRo7RpLlsݹD\Ubr|i754;r8y),z.>o3/xrqr >RlHN<🭣ZHރԇth8m{`Rf\7~%k)kr ^йpLv8ovk]ޘT&h-mdQȂ@\vq); 6(AZlnBol2h!dL@ ʍ`.(T P]zB ?d#pn*jW2fons[.[Ÿ2YbrD"sϯ>7C^Q܌y'UmVm fpVS7WX̷S[1A>X# 5+Syѹ>.avj#3ke$5\~ 7` b ݼ{Oe"z4L;PE9ރ6v49 As2 #?N.բ)1@"ttBb6;:cAPњPhe{wSEaq5=l勢a5 Ky8vnCI gp]ٛ9f2c g -sh$آRQ/y%iAnǛxg cx@ L40SCwIPzljV mTyl%iy"!WSbONZx"Yڮ[HU)n`۞VV/ j8iBBwQ)""XbA򠨨PQQQEz f!gg&w]L3sٙQ^>^#_A:1iQ/U! $'}jdiu+s߼K¹&Ra#9M$ILѴMW3.4^0DܭE8'rԽL>4lSfA=ka:AKBRco]T X ^#5naҾV\*3q1z@h5T, -JftiYڏhwyG50[Otrm2K,oyX@1QOqdMYes(&[!ZCj>ws@uX[xNv~]TꤵFNvF{r}'Y[+@.6u@On0Uoff[/hUXxn~&?;Ĵб [!Vʂ gg?oefu\쎧ǧPr?? tq$woG>*\/B'gݾ.:Ztv sE ZFE?!os?EB!vV/ !B˾ǣqB!˾ޙ{5F"[g첣;]B!-=9[b!,gM9O6N!B!D l>d"{}N-:a12ه ;F3;`+\bWX{$s -nY6)>.QQjӱn4=;cˌƢtހ=*.W[ mFZTEVPUTkB! =~_P&R0HE1_鍁B0AG̱fԮhQ^.h] 4쭫TxWoQL-[&V>}'>>tud96[dX%B!'9)бd @}D+`~bO&>,@|--M~u]$&i;o;8^Jխ}>.M(_q50aRkZG(phO@Iz|~K1ѳw< (HD6!B3{C7 @/wʢyqL۞Nˢ[ !BXB!W.;ނl3V6e*md0'%)ž@\\=%(M_o`k.H/9Mس_~8Dt-&;\w9,NtDhE}`7Zo69:͠ ]&'KB RO9(B!*_˾9v,}&FJV 'f0>٬I*}>brm,y'N=FǹTMl?TfPda6ҳU:E~ d#Zסpa+o"wj%h~5!ZWLZ?FDiL4ʩ_TxF2tEvaS!U))84:PߠҼK.$8lPh*<&( ͨzTãm}^˸,u|Ǟ>z-U vlS*yJCVy"p,[ןmgwr54?;ւtRl4hdEmbGIK#KS+j%P\{jh{]CZR?J@9BQB5( [7qVC|5݌kV|jdw[ld!{^l(^ncbq`M# -s (:Ыm}| Z*#Mޓ͚tt~}{?YF>9'i[p B> (B!*$tS3ŪJLm.r]Z' %I4Ӣ`kښ~@XZza (QpA1B~"H.M'[O'D~\pmC\M^jFMȘŴT-L4mEw27{[I?,[J;>^_=0c/ƢY%kQE2HJ7F~DD~;@D̻+HSK4u(|dr|W5 VQ9BQ!3hXB fW(Vtj2BU! $'}D5=8_'@TA}x{,neqiB Gϫ8ٚӃ@16ywaM"FoΫDץI5$arUTN!BT`ٙA5?oEyqbTë=$ ,ΟMsx,"q) ^.;fDzSm߁}KphӾǂ@n4=;cˌP{\s w(_Ȇkؖ}:pNpG .ַ;ZQP退Jos֐"4_:E~mУw-t*z6 .$ V, VoVQFrÍ-kT|Yh;BQaʱeZFGaB!?HY,@|--#4c/kklYNwshf~,neqiByg'S3U8CMУѤ6{"X=޺qvpKaa!׹$B!D xML۞NˢϘwƚt֌cUG6nsZ!HjL9Nۯ2keS_e*z`Y;38_?eа".Kf۠eLl^;KZ=XF0)?$0l|e1)c_vZTՌAзa$/~xn!/JDco-\֎^ Pcf-f}:?Gb0[~O|ZK&6ŜϳӻŸ捼z{.}mC:W\"P]B ]tK=~#0ټ,jv7'^R9,\t×h{8w<ΗgL!}ʣ7ڗ6b<#_;7_A33>)_bCKfV)K1]ԋ&zt,wM`':ֹ`E5L` h>?M!Jalr0{z$?8XәypnBNS,(ĜsHg̟H-W#~'0fi&=.?ba''/Phybz$wzY4^.{!cg24'Ga}}7~ .Jj65) <2rW: v1rӽ )x#XAֻcƎǍ#0+ ! :ti7It>xc.k}`7HD b 2& q -7xsRP?cgtxW]ƄA#雳q&p4djЛ˓zǽiL cW[K>!D5q+D6ÃgXzSz&PmK>no*y (?HDlUn5@?ȸuL+ 97?šgͽ҇.&Ӵ&i/K6*c&㌬u֎MN]*T=w/GrSf*;,Xkֱ~Z q咞E}]C"ItHdjVr`@ fl9è_8LuѢ?K(ѳu!< Ed#w~:q6+5|E1Ig[JFBl }'%Xi՟ׄQpĚ *~ض85E/98Mmptt+˸W]HcOb6yU@AoёՊ޽yS?`tW7 ޚrEu湗{/'LE:sFoS 7)L}HeӚ7}Lj-?ڏf=wf ֯?#e2>^#<h63ʍ E~SA1`qT58@9Xar=^F"M 8TDgܘ+y,w6g+#P'IB<8\4oը3C.V*XswɮT(7kgUﬤ>*_O?V_YїIv!>NۿyV1OI7.M8azRY==`U@ۉqϐF&dH hQr.vzi%>1ؚr׽ػๅJxscH/6ǚdz%ؗke 6&<=&bǀ;'IFW&M6 H}B_U2S!X mw̾_n>t?KLy%;>3e<`{yeV}b{? }|>]2ؾ6-GLUӵtYp=L~/~ׁF__i|OӛǶ?᩟[Gwgf~z/-bj|9lڞGL63tCZo9Vq=Sy#_'JQZ|r=ޟk23qVx/Ӧڿ9|B!D '?qCn9rqm:S;g+*_<3k'xg=ͳCSi*O;iҦB!q1^<:y Lz|ꏔq8I͋yelq46ޚk Pgp[߄B$ c?؟bs70gB!*\B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F$B!F*/]H^$d>*{ֶ`OR}6:(D664yTv꿳)T[+6Oi4eR*wTXp={<O7׀IZȞ~ī f ]žŅ;]M;d~r h,ߏ=燳oC)M\4oO:.U,ݹ{UO9YZO}vœ4J:,uHnmpJ2ݼTF4FIDN?~<`X=i(rX\1ľ1EQC.MC'״=#7o^m]iƵ8̩Xq'GҜTn.kb:iz2y]ŧZF$axǣi/#_EF?8k?߉;SϱzFCSܘfӑN͢ݧI[z0t{i6|?Eym)IIQGŒ0Ϻ-ՊM=j`ml\ .k1FW]\5CL}Uú`ԩxĞB;~`:dc%/ 's|171읷 BQqbŠzp~; h _wT[|g :m]7/F#DwPʯx}~}hݾf>eyxb~75!'1f'nA3TÔGt'xz/(۟#er;ZYg9Փ{)8zuc[HoZkآ( β/YG|(\/܍樦r, ['18pmgq86z`R a6U6D9{ v?b#Pfs1oUmd"b{<(D|9z-n}7{}ess{ 4)cͯ~`pƭ?tHSk\;>h͇igZ6Ԙ(kRǻr_z05#¯7wjg7 )S՚Z%h'ǗO@i]샶Yg9tx~e,ΈMSAlsa zuuЩvv@$ճ+3mKVB%W IĴ9^`B5xwchu4>/A]:6IUwQ;N" !ןxu0o!3v3?a*ܟPACHr5vQзN[5?|QZ}9$=xQ (庝4*,!S0\(?Ɨf1痓Ǯfw|j 6Bliېcy!nXB͇-;);?P"Z+9;O΀JKA滷x/y;+]Uyt_+2Q)4VL/{eNMu?ˁ(:*|+߁_|cT9OfF̗ b[z˿ PB}ZE`0;Bud0`FopsuԢ#Ƚ_#iti5g u6 Qu~}~PP,z#JB=_4χwO6Kkbp~E׹%#qFo| I ')t b5bhI<*K> ϱ>-Xz E{1^l/>^~?!~ 57H,!S|vlQ;"/Mx`A fq5oC`cWKк\5W5;%_ ~y|њEBYPx/V-< wފӃ?]ν^ލ{ _Ų.uP,11Bj^$u/kmY0[ .=HDtL>/ IT\ U [Jԥ룱G w9Z޼}d=:k=l V#}rDFG9@{ié%5fF+˂"?Kޕק1&BAǧ9wh| ǑM'9Bqn,캳kCV<'X4ʢ/ m]?sե͋k/LZJn|sp;H$Ǚּ:Nh`u7 ſ}mEwT;Zȧ} .Z}85δM]L3,Qe[:m(-YOc}]UV#baŀ.vš6scBAE>dk3fN#Ɖ5b^=Ӿ$ c!N{}%;] W^a p"M,bңi˪x)NDFcצ9KOlMCڳH35k'خ#864>nS*ΊT;5Hl23omqKV;'S@NIjl@,X-{|*7?xzO|vh NSyԚ+yzMlC `خ;ilj @f61Q[iV,~mD.!?#~>B?yK]"1j\%/hN\Eo rڑL-UsYSI y=}_rb gݻI_SN2^ĕ!C$蓖䕤z#p"6;);%cl#xL?MÁ Q+Ye7u"Gv9(98x7,k o'Ci}Q5k {PgT~gM=~|,O6W9+yM,"`qI)·&z2=6 =a][3?&>7#cyr$)B_F E/zMŠ|L%h6JDsjtNLY7:z%ч9Ӥ'Ma( spI6QixI"b@CKP]I;b4ss`RH o#Y3},Fk|t>8Nhcw4,Ga̕pC ')-BIn$OUB?vFUjFI|t?MhƘИGJRyC&?$qը@ O {xʂ gg?U0{v>({)_7Ư&:Sp|8bz~Mxc=n25mEN1P=:oa"hkѫ v,c֬liYOԊrvKL[z/nЯq@8חʬJ`iԏ_Isضr=Q=p VKѥNݫQKsMIVxw/3T<]}탗3U6 Qc҅0q:5~Y+sGt~he$rعf3G׶6D1~4dy.iWLc#DVmtnݞ'6f2<3qz# #eq #y/L7M @^%sɋL$VR;W?1FQygOg(m]hr>I|P'^K2Coj/O4?/uKֻmƎLI[&3q>:{k[RilDiHcp㏏`gX ꠳sѰ> =`"hgR9y.5O]P¬{J,Zm6<k1la*ʨ+hsѰK(7O~yݞZi1/㦟4rU m =i|hFؿgҀ#egqے2*ĘK6a<)rtBm1DKcXB3‡Rg-DCzqq׽MM,fA.=["6fe1'/ٯ{ '˖l%ׯ9/$ЭeaYvjұfnůz9z%{­{KmF14qApX)g&kfbC.~|cLCB&WXAfAfYsE9}).{ݷBAå #XU1$A!p+ECMȫwbIO7 /ę\):IijMQ$My>~P[tdl'2 :&/ pU'pnު?oJX#(&`3mQ>օo`i*&"KeSXTzc+Ƌg9+ =͸}dvpݷBAUO&[Tbs6!B̃_żiqy|ul;Z+3~'n 5"tPTrJGauRu$; ~S+0fT/E^#і:mv" /]U6L}Nulpfs](xk*ՙ^YJ:|\OHo$ҤCE{B{=m-2Fτ+7rߧʅ۷ZBw}IqJ!J[]B˜kteki݇(Bxls$ Zh7'QVOÿ'_Grqd"P,pP$ ]w:3>bA./Ѫ vUNi`E%Rrn Zm Q e f3c7sP^l4՝&#ktgѵM zD Xd%')Vt;M"pHN߾3aIÞZfUp ᨤ?=.'I1aɌqrX:ҽ]?%L+eoIJwۑCy ڴsvPLY7-s(}bra!wm70_ֲմd{-:ģ@׼ ymA Q=p]<YO6֛ÏIc]q5?XHD)~z """" NU16MLIDATŠZ$l&b  \2DTͪjr yy15xe{Is5ԆT$0 ͠vFkl0lb1x'"""ISIuFk)=1*ǟIC~>ceiA%;ZޯEI\d&ksHAxQ%:Rp^D~l 8vĩ*Kl {ͯku$N?3݋7n\;zE ٌc|Zʫ-C 8aZ;umr@7Q6_S!-=wυcF>~6HO!jyM3zHrsܹ aB́ΑJ6Kx{1JY_mafp/VU'5&nUjYm vSW%Y(?0A pd-><$I3ta-#*^ǪNwz0vR嵬Y軨U5A|"x"^Whj{VM|u `ڹ,&pNÀhS/ug00f7Cl $ " ]Ph?vMR2Ͼ.Xj?LG.SG_s :RZo\E3_zM%Ȅ`)צJlk8pq -ˮm7Ө=Dj3z|l`Z:+ жICI{:\_ $x/k |/7 OWwMi8|pbno{ǟ|: R־uz5.dOISئMG)sM):;."RQUvHjy5sfRTwV{#۟ͫ$3</xK~J/UhnyTG~1syJ+n]v_3_`Q-M_xbp޼iD^k}r|n\:yCHigQe1-o}Ȁ[V3MA њG2CO)3I->#?eo]GQsx ̵_f*cYDg_'6#qSp%zT 8W߇7giD8r)ٶcg:XMS\;:£Gxͳ:fo)(ͯMopA=rݩճ7LYfy1ƈsWapzv15=|Sg/KƑl6"wȕ3{*?hU3g qS~.HMI'|j~~ޅtDکf%UH]dIaCBå3bΜ?. s&\}Q,o|B s CFs7l %O<ԛIg:*%խJ&xdj {?t/~A@N]9a ko))cFgW 4!ִEhwE17su`}CG$$pƈ1c6RWx& (T.$\d]EQUS2b\ίM:0lN#ͯyAcliZ ZWV$==?A'E >.mU`yئ8:"ݰ [;ʶ1}W)}~Je?~xe&x5X&m; 'ǧ_"LiK:8.;"L)+8rɉY3C\wIDCXk;aa /^V +:gG_yӸx泍ڛdzlO/n/jYFXU_TEc{+KU yGM`/9!V c7.`ʹy<8֭`}l'Wڥu|\eT2wY՟Ϣ.8FwwO nouHTY[E4wY-񐃘{L#t zؖit*p`Ca/ZFϚMch:,9p]\{̦U4+I'v0D_,vi3^ֽ ]azzɄn iM=m(.p,NgH*ҵx*]xGO.û{ w(ۦM]=<(q VI˯>ʱGqk@66~uœOՉGS ?sȨײ$}{M2dgR^4:lz4NN wHh8-ߌ-~  BxPY4lZD9c7ŬQvui>:8NӠ1Kw*+д.hGq\l'f{}obLlۿOvz|^ ,R(/?Cǐ(~x-R儺 /f, _ 3k}զࠓƈyjVZjt e!<ӵcpRBҪ>Czc 5pMb}[~kXR1M)+Ikvn:IMˉڞ0g ̩muvS{±Ԛ_81 \Nl X54I}`azۦ 3+AN)n3qqbA0LmNȳ˛`RAY ]oLcʇYd-A3ߪ$#}5ά'[Q+h3_?ϓpɯ~8{51ME=.;]^z%xORiPxLqV5EW.zyj5m^#(W"BKͳINfQ \SQGl#f ZkpӨ0? 6Yw|mbFxpg+lhu0M(He 6O5uҮ8 Niw䏊p~/\|kXؤXiRO?hи;7tݾ?~NݾV04lPϽWe6fc>6WM'xcFd1G; wU ce2 |n,oSk>4?"M4y.$1Q/ڪ3RG] PȤGu3(ۊɤWnlusW583gY^Qmx5ছ~da̘1Þ|u򼻸[si|;~=J+.y|ZɝxK$:i7ٙ lk]y]۴ClK-n{BtKvph= :!?7'.Q^6;*m|j?Y|ojy)7y_l,67cly;}2]ϵy[Ov,L^ 5Cud]92hoQ[߿}UN268Ūj੼-Wt\7R]YZ4)UtȹPA<#CE,2k)ތ L\=79&щZZ&,U]TVg+MSBf/]{>9Qb0ư1-[A56lN9lirʫ,]uX>[wm=ًWo}V|-ˠ) d3,*bWgֱŻ桮l庨Ӫ.6[J=w,LK jlm{\oUdWN<\s{kԼۖXڍ8KNl k@6c/cyWVŬimUu`a'WO5ER`nC/){K-[ݏW@xvj:S"lXdunplh,9Z&gѶt}gU 5>#B$XFm `VϑT_"_pR1)6}gf^vUV&}7EVy1tYV[`x/Ki#OH7/@k]Ͷ-klHV:FA4tƹkalm)<=6疚8ugx"'>){ox4x.q;P g=F' \YX$gv;Rr=sƟ†dG;Bm26yx&}K,wz* BD13ŵ"g;XGbC,rTv6)AhN^ri5 \y\>.쪾Mg #ת.?P4u/ [B2atq2 {hsBwv;l쿝|VչCV>5ߦ5yA?<4j78hY u`S{+m:9)DWqW۞hd8wW ǦT)"WKM_w(fcmy,TV&$+kݝn[hjͤB'A٥M~h=l6 Pϰn[#>X̏ނaefo9 [=܅J`b cJ@BO& L}B J;f4fuJ|ogq9Qg`O5T İ3čSFNCa(\!0le^F#H6?؋# O)GY#CĜ:P_rGᱏr*mhy#!Y]R&ѯV@ #>@c_wfcD;qQF0 0R9V CB3j@7r_6m^ >$3N믿G_ĢMbY }tVXxg# 1}f:Fȁy~W~'FGDyHꔘ5>qL"—$9ikx捝: ܂ %dXxrջ-ќ5aU{Rgݨ*3+ @L`d"Яa ;HyD- 'aұXXHiI*VKbtE!ILdSxp=btu#;e!%.]G{VFfr#bʶ$M MdIRqB0"4dg[ 60f­|c&P@ѸeN:tDXʰf0:`BD,>Qg^xp{=Rk" :;AM0p50bŊԈ kt{BE7<(WC&,wM1c<<yٸ6>j~t۟e3KSvw*9;:=&>sW<0Vλ**KK\•4KN1ڻIp%y4ҔfR;Hq m< !bLh  f$#7s.KEC¥nvâ'G뤌Lb7<Q ;^ n0V#ǀjdȳaW2(Xq}(㦲PSltNU*ßzB<>耬\-@mVF MRԊY3xf[:s$$0`iF !ԁ<( ӥh..jk vļ21:vIFPQjz &q$jH NqxZ.:HHzYB2#nFˉF0!YDo}D ȱ+O3JK%ew)1t_#Yhҙ:[ȑةsOIb}AE͜0Or5@pw"3@=Ԙ }xG 6X|)Cv$뙫7jF;BP|S/f2WfJ`FMK4af.X ",/sBIT|d IDATxwdu{6}VUfy{@Q"@ER!J;349F~fwI wEQ(iHQH6F{_]Vz\^fU _ܸq\u\u@8zYn븎븎7rѧ&)=̱x*%!R"D4MG455T\ FK{㻢(,<ByS]R*:.꒴i7 !4)Bz~ Lβo:iUEaP%ݯ)%/l2E5X!%a.RJTU ItMEA]1t UQPUUU<TUEf?Ri_ϪuTŏ˯VkĢ|7+Fo6 cw Y Q)RD'/AiA4urW\WI?~MזGPAR6/t2@!D n;hJF\ᓁl IP? jY^U4E_FrRVL 6}b.m P$" s*pHM65gԛMBu١?SU0 6EJ0`pZs !u)]4Ha`;㸠k*蚎8'<vlƶm O\yx<QGm 膎+$ a8BQ@ Z 0t?B!<ϣV*BzJ,% j BJ*5byO2 TUq!k*'- GMU EЩױl2 TEb9.Nd*<*) _XQT 4UAT+AfTuq=Ay~i H]z:PWyAN@?{4 #}QCT͟ / IJf5&e#%kIͲl\E o% K2DJp<חZuᅩ>REx XC\®0MDzY6m7'wUQA񗧖p/$4_]m]UX](*aODU0 'A_4&l8iX+! !c211NVmOݲm:n ;Ľt_ L<̎j !)Hs<IJS7f6c#zoc0҉ H吋 At'RRoi &a\S5LD|E 8K0RVgqn6 LOf[$DUp=pȤ!q.,&;TP ȯ*i*?͐aI\FڶK30tkai,UUAkMF )RiY& 4t+Һ?fJ,nK4ھt}ЮFܚ )6td'5MCQ5(a6PȗV],JARDXd#- VRkQ CyJeH&TU!`xh'NέތawyxB044DkkKDbgbet)yHW*x4`9/ )5㓕S,"J Ž%lضE\"UCDnoFJGgSv-tvvYxy^^flcr1z )mTM 6|(0)Y5m_CLM%jF"_x6jyj.XK_[uE!2QT_7nS$YP5]R G^u[lJ,is]GPt][5E;sw8u ?qϵ _䳟Uef쥭- #)}kX\iX@<GubIPVIM\V(W*dyl!iI$囝k \7[q`5 n]i&Sse{9sEmGl 1LZF4_k2),ΩT5b@9hـ$-T#s(_*j5l!  /]>^/2u'rxOz)p V; seV2Y\Vr{{aߡص ACe6@SXhLp۶)J73q];v022B>bx'hXHu Z M²i?Pש6J\HV}qW纁SCU8)qf\( +3]m]"[4t]iBu.f̀h] M]X:_+|gEQ/>t3g.ҞnG:Ur2,F$N,|d$PY?ϕ "<0suiDYUa.EIR&L[0;=xͺmH&o6^l J(MBϦ0 _54O8du$@S dDLUp͠j>:ѐ,_5A%v+/Nr4,Oy%2YpxY/% \QZ˗zi{}I1ټ0 ~C5}|!ZIvNgg6'&Iwtyn8pb-D |G>Ag)::EHƢRJ$d) Hbnu=,",u_BJlhZ[)ae\(~0l&=FA*mOY0 y}p:y;vD:ڕxW|1Ƹ=W[H$1x$e;qڒ ]R]5MR[mT1P\pBhVދ ؼyضM$K :jH$.okmZlA4^5Ox H@S|o=7 v8?4JK|s,zQ0-뙟'qQUNS'D"3;;Úۘ@2ey z';k$ \:OނDb>+`زq'&8vy6k0/ uNY֬DgrR}Qnf:zLΗضygNg`=UOs xE3?5F"en>Ǧ-H.QÇyQ{:ɔ*WFFa󶝴%s |Go ;8.U4$,]W  NtM $a,RJ\qrF+~P#%0|J!Mat6CezErB@v>褒*WFuedmb <9fk.}} c;.*q{8bl!a&{"] (ӌ?qW۶299bvRc-=~jQ(HuVV$K<8:aq5g>EkKMk ]8d&[,**{9?trΝur}M1{ԩ39x.Nq3Ui G5M縂H$W24 U07?yKՔhz yj_җ~}lݲuMu@Jsy馥%JU\Sү/?\c@?-w!)T6՚ b:NPJ3`[vP4ڪR(,_K233V{`*I4-jEg:MOge5˦%zPMjEt!߷sR=.8{+IYA4vl߱DjƮXKKkbrV fbV\R5f:LeHA7tTUqҶ<:Zʢ['L& K% R\>]5H̠5fj-kW?yqKl'b ;bxd0t $ԯOZܵ6⹂BHYPkXN$![R %(JX&S)!;L2% ]Z4òèZC >t$rt{?9gre|ӿda۶Kھ JB 8~1f|w!i4U]D޳,0=LL^f~~)]gd$ǚ5_ 6nՉ+T*3OP֩Vyn IDAT'2uJ!Vyꡗ H* k:dl.Kwwҩ\H?NMs 'w#*.aWIĢ"Q˜u(Wt3]|īש?NH9ʛ78p`j9ſTUKKX)5+1:mϾx üD8jo&8U/wx+av&nJ2\EUzV_,"޵6m݄X\{gؖߑH$Ÿ)!Sru!2~罾q'3pQ9{<96lc*''|XlL"}ߕ@pr8l͖^~~><_i*7o]1i27m3gN׿lo - %,%X :h7\ϕ( fIضUr(Nb84Z}Iꋮ;,.BT /[~5n`:dOFth4F"qUud lY/}z'3 %Y/>WO50qiSa۴C0FgVh $R 164JKKX$-qʰt{7ȅ ^Pۻcϟǿ&T6o+W*~4K6,_Ea_]V:[JI6'̳ 8ӷcnذ;Xv<>yk׬4LdΕ~zBAWW=%\iYV}E b2F~ъCmtߴ(AJIR]qRR0vd ,PxKk낚`4EaӚ>уd22>0<4L^'{^v( Bk+[UH8L"VF<ǩiI& $RT,rBk|$)x)>񩟣:{Pz>y;15_`rw7ƣ7~ޮ4ݧiSFjrcw_ۯe-ٔf5qsL|v^W6 Bsd?"u]g}/:-ZW'Ԫz| 4hAۭ6-V2yx! $ݻE3V,qbk֬vDߊRnK:ml̹ D֕L TkWW76֭1Ux2ņ5}w;ؼF 邭[72=58v!}ǏNn4; ?/%z'^!ߏj:/VB-w/~~R6hH8L4#ՊiZ[iD׬ՃwS,W"az:@J_K2alAgry/cUJh(AfFun>0?7یW1LLt*I\O|/Y.#//tco!DJ//VVkky̋_Bղ^[/5k/7r8}ss9NO۪) Je5LWY⛟$R(T,.-)Zlng{{93()/=\.©:tm[7166x3͛7r¥ΪPLLN!d.8eǿ*UJ 7վVpo|뾅~T\븎3R4yB$pBTTh$8K WB©N'pB^j9@akuXVp7nj?`Y;Ǖfs|ufqNrc#M[K:qTMѣl{lN>#nR z6$,=Q‹I͊;2ds.n䩇R.:se:ԁmy̍ELV8eD*d ~sB)ٶ9JyN[8W#BXS/昜Vo%ڳr+H͠UՐH,!Ql.=cO?Cu| Q X"ǫՐ\!Ғwn. 7E)34|E]x؅;m\Ǎ7NK?{7gN\f=$oR؉tTܐP*ڍIޤz PIeEtouIzBt0w7im x~x׫#Tom{RlێB=a*tѿ&Ad괘 W {[l}?q;鐂cՈB,E]vM nݻbד؎Ck2AJurORKKlX$bCo*x}|+MY_D8SZ$QD+|lq/j`U,fdfLWg~gKsLMֈBdkhDU%UKf&! Ps5܊E?hEUP`f M,a`-tM'NvB-%ho7x6OG{iOghݘ7#41DJ6_&N3UYSs9Z[BSQL:<}f7wnY%i%^|@8RfgMO.榡5U XD%%]((:ژ]k.9{مg&}`.mpoKkb~H5"W_^^>]<W?!.^tZ{yvZ}5ī}(Ҵ[;mubS)owfDD=B2ЕfCrX0?c~fЙ͔(mtMe}_;p˷Oۏk#6w㭸VTs'ɶ$>LWG+[w`mWNi}?{ww$ة|s0(;hM'Zº[ع]obϦZ6pd:7ғ 1>YsiڊωDq=K5yGtB'!qp\ yu{]Os9>s%bz.Oҿrflo.B:m?I8"Bn~绸ƽؖW>1ٸq#?TO9C{:>0gΐ?o(q$MfJ澨w?-NnjCGsexw-~_|ɩ2u !?1=1\L|OBrн77߈y+?An9r `箝ܵm۷E650'ύK\2sÜ8JBrӏY|/3@#H3Dub@aJ\u?};8U72PTqFN_cu}ݻoTaZOaӆuJYdX&{ʥ {v }W E чyϽwC? ;_'n'92mDvvo!GI\>wER<\vԘͲ!2Scۢә߿k#Q_[nF[h~Ϗ113; H&'ǨT,茍btF9SkE2 rO;JOGѱ*x葧ƃ=AZ?x-d/~\>yd=QlΞnLq]A7'us0 ?}}ۗ䭐/ ҶT\jeɉ9 CUM '{gӭ=y]dPPP]|Yr>_๡c<}T=N*Bnf)\#LSg3d7߰'y0Ό3=W LĉŢlܱR$?.a槳 lS'Iwo` aAw|YPf_'Ks:t G^i{:9uk֮V-R{gp$3(;t3"[f9'q]$hOh>T a.W!ڒ1d))U]R$Sy`|rE5x HBѡk}LOyh4JwWbG9ھ^GǨjhxt'P i]=l2whmsy;Ғd~nm{eEy~R$Ė[0M (2CLL15>`24Ml $xeW|oףg^x|1" 6톍fu#eGF(p.U~b5_?'ݿl.˅ Ͽ(4kHhI^튵kV{}CCW}(ʊǼ~_{X]7,h4ʱxL}6:h˗< Vʥ/^60\m@\l6!-^̙/v^*dk{PUe䩩iFmhzW(ڽn3_k}{ANf{DLJhS,O7HL$2TV2G܃t*$64Wwa};ٿwLMJo +ptCs%FHC! hc1':,˲p~qNz(iz3c9/DӜˡ[WC Q IgSw;x^to︕Y<`,DM]i /h諎"S|A M]lgrzXl6O4T(Jger4t2stTςm۷279=Go'Z%!R2RbϏ[WELXbDulX`îݘ/IjMr)N|ny\$ k.b*L[fOLQSSM>opK2íiHMMi#gȢa Xf 'Hҕ9rpWKlffv1JtvPϫ_s7.U" YJ--=uyaiLP@% #óDnJ()]{̺ƊH60 E)M{glL!W˰ɥK&U17,+DҟӇ"Dkuf~zy eff&1œGM,$w19dtEI6xaKF@bj4ݭNn( d)dMGK,$ yQ[`:@,XK/< +v!#e˞E;MZiJvas}hmGo:W_|_.,dj҈ܼgxHcDB$Lh.llD[[8q"[ׯSPl%ne^];uZ%RHSs "'FAPkhJ-ܺu<4}}L>y鑰iF2e&Fش\dc?#wΊzXIԎ$TCyH$?Fvwr|/o|n+DADDQѽw4.2 #/-X1?W"PIKx nLɰxbˌ-\ȓNʐ˼rML.1aرOL3T/.&h߲( a6Bt&Cg)Uk.bR+cTDjˤȤZM59JlSg-$py՞M{!҉'9榧5!U`mG yHU-fpp7X2XmPL㏹HD=,^d||,gΞh@""q _8æę fS'3x\T y '(s K7s.2e!SFnn"W|X?uOv %Xlٵwb&Bl?u$ 4f`d5MYvR/*$T^6lݱڰy%wמ=\.|}Xms3'en4'')*\gM:Y_*Oh~(ܮo Hٳ7J%0|C5]uMwcrY={\>ҋM[ $8ryStuobqf?Ʒ $nϪ3?7b;J(_{7ǟzhv& $zQkz!ZQb~aWw2oëo};HWt;mer!CGTjDw.R$q ץa:p@}3o9|Oɷo鷑]F$ D&<6=];h@Pz{ڷ~!o}3|ߐ5w~wԇu%oo;H&"\@)5#0;W8l$DAdrzSRYxsڵYep|hH矿Ej|Y9}~6qw~ H-L Y8o3Ǿ(KU[[cT8}}i.37KMLR(Ky t^&& 7#wwsjm C9T=Uϑ(Ѫ SI{iN\m' P*;yy+eUhˢ_]'FZ7$B*AKH x7^Ta7lk$(BUmwt p(}o| "Z[^܀vSfudd ]7g-n4q|?:^R$ + N5B%,BUm(Lm,D6&Z(--e$eǩgZ/$p Xhy-ݳ;4Zƿ}|6 L#IipۑC45S.cYKyvCeE6g]|ߥy*׋2j"{=M|~udtmo埋^R!OegU4?rDb?{7/o/uMshN>v#I ѺFb躎e蚆hF 7/Zek0MʆL-ظI0I_U&5x|3-?s ]<|12@kS-}rd/}q>_糟G==g8uʡ}o*ɱGbm-$yN6B-:%b!_Yeq`ii~$Iuqyt\.(eb$ޜahsyTY6,sYAdzG$xzڻ?~~{a~ U*E4]=b=2n ~3tV/$s;7w^3+EX:EMM-۶n-V`$b0"2BQ>$U Hxήp-==i(ȲT执3>1Q.?:B`ll:EQՎ{0F9so<'{P*ql#SVGHhkKv:~" B޿m322m5U/0$-mR   'o޻"I" 5Ci02:(5>|>{=|g9HMU/e-4} oߊdە:HCoo>7T?\*fqyX=稩JGI.%yݜ}~f?!_HpF2 P_DV7mD>ajbXuSq4/;Vsg\[4 VT:lؾ /Q._Ȧ޵a3zٰq.^k1kFVu:ښt Ǻ"ĂCs:7tZc^0)C[[  P4lZ֊zb!/1np\ Hb:I)JD[PBn%P4V-B>gO_viKT7L n&ˤWm/#Q*Tk' Է[]/"E>BX԰VT N͠ 1 hB|L[`B~u_ׯ;BhhH4iضMP 2!F'nNyTXjP,`N.ǫk|lTZu1PM!>/r=NQD/] 3؅ao{I". {կ}:X):pꋲ{[NG$`rEr9&#ie\D&u5޼!_Iϩ3i"B/Rb87<~m N9tzϟ;΁s'ryt˄ΎvN÷011Y ,t`&Ft9qvq?`zz9y)o$cEll"^,12T]³µ.zҴnt.Mצyk2x:G-m>P1Ig-T/G.di$ݛf~6O2U\|nR2,LB4݅Gd ]Acu=Tɹ9d KsvO]$oc?xꚝU\:W*T9~3hL8).RG*>Vg=liihba~db?$SsD"aNrE8{ Ogb&K#<}4xTo^bͭ%C芀$].uoJhٽ] %Conug>B^gΝEصsC6b<[qGE D $Әlno[ک* Z&ZvmC;vHW⬲‘( "ո5ɉq e 7H"cH 0 a68?b2<\d+l jĢU|.G'ܫa4361Kc] i")F&/`i I۴.Q.x2.BmQt`D".(-$JeT@RDtHE|l5_Y.-<wPECvH@J"nty$t$?$J\Tmu[,!3GF^qE$v<հY͔oJ(`s1+xn&ş}sPr}'=$_9޵1fns5GoKK/nhgNzc^/׼yۛqL5Q+Lj:֢) 뙭Dk0I~칪&b7e>?A!k"{ff<"o CO&=@R.7,+/Ol+6[T#k{IedF6Ar$Ҫ!ˠyd 2_!=S Gr8Kf/ٹ㗋tpk x$vxSŃ$ " <$уT"梦EhӸɃ+$b",E.ܿ@NU]nj7, ^(ҼKj,G Sl+$jU^Jpp-1z,E}Q2c9FN:E~dM$gJ41{!C.(a&MkIiRض}˴"Qy&xxo lR3kUwOU=cpz&FH,Ya1ePSg%̤If$8sQ$O{[ #3X\6(,R0MTIbGE2eQVye-+kDlƲ2lDY6h$ bYWh!F"ic +F^.et]9,P΃DpIt,r1ןs<;$ I`Y eeumFB,F$q&*.dc6 se-<.PZk^r/ gpFy9i*\EϙyF(F5 +e`p0}ッn>HL_Xx|njjcqN:r^ܺUUII! "M;n7hP2;;=5 YvƸ%DžwK'بsi Ni3OT&P/ie)fC1a"RժcPY%p>Z@W혨AEX.rF*ٔVEe'l6F,Qqd̒q=)y 6.б sY6ir ؖ#Ipd,æ+,l{UۆQ16fəs)kaPumYe8mi. IDAT!P^PՉ HX TZ_fkh:>EtUSPD&vmi~_innd6`ᄑ{"g?1n} ~G+oz.ՑP>~4LZii*H D<{|_?.WT1f>:1˛ڱYڤ))0~:CI#~ى,5Uj>2Du&Ngk,c$^fi-x|YakbMf.hb ;}7F DluC 4/8yJMkTHʘ&Y' ur#ј<$%=ǿG/W(g /irxud% I>DeSm9BM. EM:#OQr\A$gT5i?h }f>Hp1x,^cbu{x trM#ՕX1l,{zsZ\ ]DTܪ¶ m<*z>{tΞH=c]aq]. /C6Yhn\ṷsqʝa6Kf`t|quWDZ -T!,b(U?RɌU:^W@BR.]2Q<2_ΔQ<.7`_ d*sedD]BR db=$^bB*p+)$ , ܬ(P*kT ip( d vIMoLAq(JG)Xeb"֮?#,TD$QDV),(fL$YQdPP_bq$֤ElQDH( #o1$JLMM19Gg6nۉ:m9TQh0amPS_ϥKԵ!_JOQ w7<ؓ>  axx99z/rÌ344RkZ 5ٵ3(&_؏eEXJ[b\(,<~N|mh#(FIa0LzilFurj !dmFIU1K.alFN\BBY8\p$f9F4aTRbJ2<\P]"IL$LVd J̥,Ŕ̷JGC~rY(d7T8v"(i; F 2&Ed-W-6fPurv֘7փ/ LӤՀiF (HTiR1t6ym[yg x$'9~$ܾ~XXy,ۏމٶE7o} ׳4gpiNJ?sed $.F A5e>e|=bh# viˁ̂"+JTn,e2;t7~&"XsGTDa\rDWQ/R.9Z]X2Y)}ӫrENJh; \J˔Cr%r%!55+&3prѦ3W(-`MV^ܪ)LɊw6*d}y˕ ܶ L(ڔQXWȉ:(m1 o~[JE~|s\"ҷåۄBێ0xCG = nU| lLS}USJ%b+=<475~mFyw3Z {m⭿n>?~y/"mm/<X+>TQ2M^\ۛx.~P}(u ?h75;{uծi޸޷VQō 59t6,kרvm6:kwyQ]Hv5'mj7ޜ %ŌyY_a;кiݬ{">3d\QYMeX]]u=T"n "54Uǭ\^'$}E1,v2"٢ͶD,:>-M+H3*FM8wy#cN%8q"Lo~[OMI~Wbd|mx:c3I޵M0["U t- .Q q#`3S(QUхJ%Xso&.?NE2,: #onte8)ZYr\'K]ˈKh~tU⋘@apE`9QBm` W9 MUx߻^M\W_{3'f'ҿ8>gOP |{h]LLp\9>,.r^}4~oȗ=#Q( |~EdxdxVLǃ.AL6[l8an O&aQX\nǞYPF2p i3ѓ"9kYnDW]5 }9qǫ |%+R0eѿL) ec:ofaqh>bڤ4($5ї}B>ZB ױdllN7v-]N]s|Y$_xO+I3깖P wZZbmȗ$";c!ů=V&t]CƦn5a"]]=w󭄂7/ ]mռ iHC~6W&j4a)etM z6UUqgC5w6QUC!mT_s&7j~xmrp"-W){*qnu`M.DQeA|_ӵ+Ӵˋ .Ҽ? M8*i@^{|\l3P9Nfu[=x2ݯ DP4+Q[I\:Vr?Ț!XXؔADeYEEEJ"tE4Qe ˲Hesx|n|>?X6U.Mmje\Lyuk$"*?8e fQ 5DdHQ J7jTD % oHŜEj970a%AL06j/c,Y`mQ}(6 Cri'[*[2_i!DA"J" *M(" {|R%Q%cXP(QS4,F9&Q6@NuJ%ܬ#*k]:^Z[83N>xIR+ $!EϡUV(hHM|lDrd-(ҺZE9P6!SrHkȕLM|^Ycjk4r~xU)e- DZuj)*v(XX%}6D7)eﭒB@N ee10zc/#ceU!-7r(CY:kSC#ހ㢮)'W؉L"-2ȕT]\J,qn1搟lmdQg1ۢ X_B!^$J(>|]bLԀYsY}QbC{IrO򶫫׃H (RGJ'B+~qn/ծ{ˠy$ ۞i7My_~TO4hz"j+󫯾|5"F$͍٘K)JIgH!:04WS(ׂ?NضP(4fKJ7P5˹VbMޣg]`iŊω롪$oa%**x⹵•B-1tjc 񛱅6c/-њ(xsq}qf|*Kil&>]۞_m׽m.٣/f T*ZXBY2M>Ӊ$+XQoc6^%YwSBl\6!R.IHXTyʕ ~w>̛0xx B4LD@E~7LW^<.,,a8M栏H46w8Ѹl?>eoO6/V(pji$c$WXH@JYE-Q+d e -4۶)jp_ޱnFD|.iQQJ*\sLm̿ߠ&6r^*#.Ӓ0ML*R@ qqСCy,//o7ò,& eDĶZ\ DU C[kXYRP Al0aWRm}=;xhCז.vnoS|Qq;ymia7o+/^g~N, bZpO"m+ Biu5ъ`^'(tOa@;ln>μ]˔$v#I"UlQ)f0M PS:u7~k+UAlareض(d3LF|)w]`{#6J ks|''tjL o'sYVV%֖uOsMי)E| ,ٷ z6Mv-)/ M'6&".(XؚN#^aoUTćvm򱸚OQPUsoo# Ưo,6zR~_~|| q.|x6 |N Bzq +I׽iu@(W_+4[BD'/ϩXiҠ I#JշiU C7(V5t(-٬Ē,Y;)V0:iDer^y{ƔA (BUnPMU5`G'(vSk7̖,A-wQR:l}P հe4h~i E1,7Ne}T~ ̍8vˁm@G{R^0jh aՠA۶ C7>?ircGOVzeQQeӾT_//<,UUgy%- O%)˸E(lDr{$9p08|x|~ZU&Ö~dYryimov!ZB>v$A CS| n.7QA,mik4ьpp8jiiC1c?mvG"~` P膉h\x583Iffc5 CܴU*im+֮oĘ0MIpX6%Ӭ&8t6H+NO*YAm47Ghjm3ĵzgj|{a\D!d9fggXZYTHgK-.p4 P,R3j2^&/i2i;X^1D:ؖ,|}{Q$h[d Cmۈ°-f刺}tnW>r' ɛ;5t:$;vt2̕y,%^atnVJlv+?6lo&M˼ʫoxeûW\eG^`q-sBM{嚟coƫ|jϙx沒9smcJs;unW_yec?319E"Y kOM8A"Nʼn=,7@QddY$m'\Y˿p lʐQ[i k +eKBSMZC -YZ*# 6,i=AUlJEknbN(h%VLCswg^Gq`YhkDIQ+N^5pdtDr)tX^(a[6jm IDEM,d̶-TE_ϳcwׯٶZhz(Nb,r0X6᠛|GFR$zrR}0n\N;rzՄNr:ɧ>;V5xU2:q-DmM$c"!B9UfC]2v?rUǩh"i2w۫^|Lҹ*[w0ĩghPe9^B~[`rD|?feHb.O8`lUq4H&Tm FWac TQ'ɯi^^[`tU%Rj[*i)Yƴ-|Kp8%$IV1-Z"GL2&ĖJd2tJlBg0X?@lfI2SH&]~\TW&X͌__*bV FIe*&2#m:F{PUC>IFV0+2OR.WxC\~ ȢC(:ebW[Mi JeHL&_@7 VtE C;Xеt,Լ#Tԉʴu.7UK:E>'fJl-y@lTApC3]f Q:bnt#K $(tE->ut:Kb Tdy9O̹ȗ*l]#8%ϜUt0(H(H/JfI$+B뼭KwO7Odۖe1r,S.OFv)K\ HgGY Ih2Y/pɔMLAw` eaerY$z1 b.G[]XP̥H2XLOLpm: XY^Vu'"i@RHP]XG[[ ziLޘ^?Fفi *ӧxwK#aU?"XEì_%)ԏAntBYҹL7Ibpi:iqYWW량dD*"G<^*Chsl\TYaXE%ڵzGIx=d*jUŴLVD"✸px}親 pMfE[d*_` _-KQ\.m/ٷg߆L |e,V9|[ˇC??g׮]oOD"PBqk0Nxc?{u K7L $´m2" b*I#.uv:#!{7^wػ [leE_q1/͑w#xƽi˟{fGo&/b|x"Paɲ!+uށ~ ܬ5=w>}=ȳ?}>0 - 0CKqL-ߕ I<=K|GQsq/qca ^z{z+i::ڈaed `nl᏾c YZg2G!>wNlϽ=i,<)I}1\$Slavng YJ sqKpeR$h;O'L~u`[Lghk K =xneLY,G~D-eß<3[ٶ}ۺD| \|~_o̱WgpO_|.:ƥi@O;^Zb,,-sλZrbqjt=#[9{XZc;%(8:Q+q9CE$/qeNpur[su|O l.{5@$Kq~i.\taQY2S7Hr|6p:PdCSpez?c& 7fX\!2E!ԅiUى㥣t|UmHb^7Ќh#CCDG%jfں:E!9q4vFO!;Ȳ(tm04M*S֙-ȲƧ&XZ\apK?^7VJYR,msm-Q-| 4$1v zM[SO_Y2u]8_A!~mQVVl몝wzvށK8뿓UEe0[h3+ܞ~8ydYFq(dbLLLc3$s(n_\:Q9:mae(( <?K/Q(ٱg;=ih榕 }πXt:H ]x|#%>6'E2e^}įnO8Ro/S\~kkaٶs4Pksq&S|>so >%|N^FQJbǸv4;^kбe q҉$M?Ehbun}ٽs+7Vy.~9aDIO>}}ȱY>K _\RYgnɖTˍaYn*:3+SFhkmӲ=Юy 27gH`ĒI:qY^Z@j8<>FFv6q%lIU4|ޮV֟!1w-jD&" ,b|-ضMZ`Q[c@M ˅ceySh'U [vi-(,jG4M~ß`:KO۶0 ,q#vCA (yg1 x Iz|>['VT.]0<nȱ ?T3CDDq(Tz۟: P ]NU*?{%L]( `Y6e6P ~)XMT"/ϔ - *D]`<^b^A(M3E(6p6MU%C7TԵ667uYUڰf ?tM斛n r>_.7c"W %!m0jHaȲi_n 4]WEm[ Gi+hic6i,WA9,+iZȊiZ,uc:H;ۇ1{*\ A?7skʻ"Kn|NRDIU^,Bg-#P,ӦT idk A?m#i2NͰUXvM&j:|R© 9tqq;3rA^htcoit: KYnDxf-nn%Cy pl'ܷ! +=D"{]x}7sӲ,Z5dxfP}U ]>KqnƔ)"_6p .:ZyP)f8r^VV3u'8pNI$g.Jnj3ϥHjF/~qΝ?O`Q*FپJ^I9̱r#¡BwlgGArxWȋO?)&&'ItZ|jͣ>ȖNÍk[,]#bb)+dL]Bv}}9uT-MݻE/uxP((;wfnn[4$E&ʓ4s:vs[JUE~K\xS Tx^VLzyq*k)i˱׎]\|<3\>IȞyQ+01@oo7WFk~ݴ-x`J"6i{HM2|aӾAׅbxxPxIS|˅ELAEbӴ:CE_oIKI:-Z71=/FuZ[ BP5LOmp(5r-mŚP[KS(4jw& )" Ԙ^q=<ضuC>+AwǫôAq?O=r80 cmhEG{PKp98(*sm۰ *j8 L\.e#KJ8mkx=nNݽ}DZ:;DZ۰m Yc6.']v bI j$j\efilK)"jx (A^_CfDAs8ToƵ9 ϕ:Uzz{I,,ȗ@Y)Rb:ժFO͑V"AER Ė:ۚ%X[$ CQ-OI% IDATݬt--H^0%UO[Zn304@ F/Oprtul]6M- ٵs{bYrMvw?LE즐1j~-Hnm3AEVjqxN^' P0;>Եi_u oMP(BwG?KjFMP\.VcqGsy>+x>24.^exXA4(lkAŴhiur8r.j`,lOWFTГa)zaS/3T.]p pXlX"E8kl+cA.=NRu{ו+uqu"NV3s?}(P5L ˱%g-/s ~WuM}WfEEٹN!Ul[f"J<]` c3\XNo7x [{w?הDMߖ^>!=׿B"O.W\.w:˓DݶkA(,J+Wj%rE/Q,-*Zftljrm}k5A35Y^bK)nd29]¶m.6V-3k7J67w 5M2Q7:VKf~a9}We%2q'b =74kb ֦I"Q-.׾ҙv#,sElfav̆Ks5kZY95l,HNaiEafrfr8yGi*E;O&_ڴrHL/V8=!gWHDc?ȷ2_ڴgò&fb S4~r3\y(fe457o=~l j[G1<"۹8zwlg~j޾%3en ~#_x?t?;i[عT+F3'R(ٽg/Y[yn ȲHi9utr#arE|Lss8Οx-'V, nwxYF{p:W ]{{f >""Jt͝5H?O>zw] &oF rߡUҩ4͑H-Aј]8Q9qu-\H)BOg 7ƹ ?xSo{Ǹ61 #w1Jx~2S :?j>p>DKtNb!ٱs'7&b4 y?cXNk pyb6~-!m]]̯ 1*Yfc>|٩INs[9],ܸF ؽS 64n==H:SE$Ia A\Skzc6*flR$^ѱXNZNb "l37z{5[ ,AT/EEIS3{GЌƔh vp..ͬ0?=M0b EQ(@ AU$Y05e(ueLٵYy'ab! kkm4a=sE>ėض رibD)f3 D:A$};9uDs0ș%vD[ yIC)gAANW ^!'H3 ]lmA-g:Gd2W?E4%!XXG:T-Uo\oAJU[vm1tŕ$j}zc`[ TUYhƶlTͻW2TqP­-41ssl҆&EYTՈ6<{zrfmmLOp"2'O#I ghJPT)k:ڍ~n'c#36`O._%J.򚠨Vʨ2`q}bD<#2InlXS4 9GkNݤR.3zmzø/M6J[ZЪ%F/# 277F>XmVEfn{R6LVYgAM.0ߴPUfnm"S񴻝T2ŪڍÙs,S9uڻIj1kXeBny~7$ݚֱ+L;_]wW󁚚3V'/ E^?^37hɮ.ԏ͵c:}O,~*Om')lʼn\^!#a^ܲD_G>宽;yh ۟!MMa "jE@i^h\5dj"Q jZՉ%b,so\شW_HfٹcS?<_!&b { ~~lۦOanzӉ뢜09JE cXfnd߉G;Mv,u:JKSp8GJ$ 7~~aYe&S^}b:'yL|^ZZjmbΝTUev`%ݐ|AO'phkkNN/$gb|}IƯ_GA nfa/?&1;CTFr$0(M>'.K\vG!;؎Et]#3S 2عGX rAO=8*3Г+tj^Y%` X c*J V|JQ X\})z V355r{n;AOjp;o2*W-1/o`jgs29jgV_wC\8{~ir)O2O<$i2=6}8Q]W7WU;gp$SLv5ܱ~); >=}}^w_mrBz Wgsm{ "L^~-Z5Vd %# :΅W^$Y^fp]l:=b{~\x(n(f/+c@'Y,o+y"ST0*-zai$.2^ qyqabt2 Efq#m|gc r$Z$z@oO (TK5zs)&[.::$arb QI%[ņu+|߹a0>$2]OM( %|i_V/`v!OL{mlۦZZHI@_ŎK[z+toݍXJ_oMeKV-8ylwLlY8n\8Ipμ|;21v#cQ'mzѪ"k cZhLeb"pe$l3dPA8w7]D$Qdp$xVZsk !}sdĒEtg/l+gY[˓f|jV$ ̟e;QӳZqJ2nJIdv3:cjǕ p`2Kc,6Zp8nbxC9UUA~Gulc4&ͶJI,u=2]Xf{#Ev& Axv|BJ7YLPN/QĢ+@?K 3vM6GғKLF x,/^"h-U64$Hh$ɫ+( l5L{3&l#lXr7m0}=DA#G^Fqt,뎍=r#&6Hs>|QIhCQDNL_$Jظxg :C,n'ضKHȦcmV 4&ˌM-01}k kz!O.rHӤ3ij6Zup+?ojX7ct5̶l2(v, WٿoIDQ O~ gN2sO>UV ض#47E:@$ghd; ]Y eP!\i?sh*˅ +h)lBm#4&.\fOuVMGp\r\ms#\=KoyByv Ɉ14#Pt" jhCft'?G:{z)ט^ûxŋ?Ah,DAW-PH#NUd%_fnim. {{ v I:Ͼx fh6/yeڬ \ňD#:xd2H]cfnAL7VT!STd4k%uBS4o0FqmC~;fl5[-,fjr\^`z~}1~cEE;dr]\-ҿw+S&_\P1imf k*b,D"fb@&\s;wRk^/^dj*D t^]wrub1+w\BIL\cP`#CƫLVET^^ K< F j5ݹ$F ѮS(6طg Q7۶H}+,-n&l"˥dffX!RI'M< o\bC`}~%F'ʷNQ,qYg(:ߝe6:\?Ãtu嘟cn̕^*7Wq0k)w~]7<=䧈 E)U+XC$@6Tiື[!ª][f-WH&4¡j攐N>בfES }˧#}=<G2vf/Ж!/Wyrmܶw; 6ŗ8m}G77m;n=yA@tDQ}2,c9.F +E6e?Y[-] "\ePa}AyFƯ\$&{rON NMʿjl%Mr<]LuO=4zX/IJ]ږI8dh6pNCou nLwt?7K9{3/~{} }Y7~`tMǴL4U4-t]G% 2Dޮ `1lGH·<dž7}$S33ca{0-!z2\}L 븈xC^zyI$ø.e0SS3뙚OncM~Efgݙ!~)J IDATiҲlbJu%q](!" =p Y)p}e$S/ CkkBk׮:VOO7Fg9zW''^ඃNExq6CoO/n_A@ZD#0"KyDQdkmo?J$G^ӗ0zJL:a/ ZΜP,.r I^yݱsǝY!J2\`)_ ZhT]CO6G@ *-FYEzdYuo2Y|$śnee ElӲ=v tMn]vC`cu2, '8ud;Ocfb};Gz]eumuch4(y DCe$ &ld!x,ћ{>=YʕMU# $ U\'"E^XehU hxY-RIB *cex. c-f'=R, Ad˺t 2 i Aml " +9.!M'߬ߓq\AA-:DF֕V6>i"224dY´mC!! ɐڇYY#I2aOhn )O/ "#|:, HHޢ;E|FBZҔu΍5HţDB:ETVfB"l>zIETM-;z2@&#a ,R(UIEѣ"KySaz$IdMwo/`[XN@"z:*-\ZU[_/[׫L/#" Ah ("n4]djm8HIBEW1>t(n' 𽀙y\jBO:i*n#tdҝQ)Ux'z2xtg(>bP8/&bZ&cK?r Ihl|$T-@OF6O_DVի=ʻUI`~ȂDWĠ+RlCE6!(6{7YR[ .΁06ċc_Y R,7e|~v.z<öp*(k2$X$M˧pxӝK}JevtRV"翃H iږH<lןwm33KA@6C[O00CwWwqH*8_wطFR=FRģa@wX2kmO^&nl'ɤLΐNDvmkd#}T+ha\bAFz{PL0==GZpn̜2~Oq^tRIRA;cy.eIo:JowK׷5mQ|u,,X/p8;yױM.p8l~Ļ?Ish7I0e J2}]Pf(^F:EVdvc6ڕ O>wKW˥2cHɮ=;H5{yÇ/^~j VSu$ErmWqc/c"OO}d/i9tX BQ>V *+Xt6F]U$ŵےawXXОݼxi۲16cxF CN(!:E QG 2(!UDEmUSYXc3p-|pR5eA'GѤ\QX,TjMh\6"jMCaۢj86B5BUl$Jd*x:~(ilĵmdUE& m_?=GnP\\6T 0ͼ#9~ Us/g^`T%|Q@D@@Sd0Ͽ|nCTX0)k/ %sGDL$bp#:[{vI&DNO3#K3Zc\G%!$IH; A:+FC[-U,NGEtܣO )YtPEui01C)c uEQHH[7+XOʹɄE"WE DIfq یwQo;l&NՅ V/|?]|O mP*GeTEEdlFD\#Qr*4*+OcH/]<] ɝQ?'>'?^ 't;{oG,8ql0.o&J[]Ǿ ?HĎ@94oF),:.~>~3\" unwػ:N2fPM왫8A@-JlDP42Z Q"shVN/َ$BVw,zfS鎝ɱ\YS \}EΌ/lR'$)8~t?K+yfׅ Hh"K-X&C=DqְMPEհ-z(QM6m6 M f0#c!Kh*=RN˲ql<>P\'OƒUlϣT(  ZFEV04H$D_p%DA`q~EHg;K_o*o;q)IETIWaaa3j\]fKkE U'`l?O-婗.؀j 0(uMe yg~'MjTC%%s1 CAؕ:/hJ*hqt]' Z&e,Zz(JFjBVEQ5TM#`XmT:XYF\k[s8Xʼnwǎ/0JX]twgI#VKEqصkZjtzKuzY]OI$.c|'ۻ^x\/[턣azm]H~a;OHR>aMٲJSoQ$[yXZz+/R;#β{PD> "*úH' -$Q5>Q"tzEibZY@HWq]vt٬s$ E 85=9A("iHrjk{t sL_:ף36_c'"v=eӤ2 ~S1œ/ga̱cIskL(1^i.;vq}A,OL'm[,o/o5|NN_sSdhлcRMo&bgZ@Us|2F`/w?N2:176] i*bЉ `gmUo&ECN|AF%;(Ac3Lʑ٪ssuX[fbjY dUGeeuqۆI&m E%yiٿw]#\}\Gwñ+'e4mCؿes⅗RP_XxjoLh$ x<;r{gl֩7H]<]8ř<'VBqIDAT]Zɯ1.iiK6"Ne kRf4 `e5$zۣΑ̕,f+ :bL$1tsYZ\ dp~?҅qHȊZL6EWWZjvhͶmCHB@PZ# YX"102HowB(p=E,d@`͑x&XZE4$)$c1R}g;*w;Wd %EiY& >N3gID5r1XTevE&-%a(R>}?cmET$fua,_c.uӤRoQkѫӼ{.UkJ 'e~qŹ5[hsK/AnKxRƥWS\+w_~7p 7Vm% CmQk6YPsDBmۨLtho`KsH&efG<+ Y" n[؍[M%^,>{'_Y)VdL=>_]qV*bc6J**>cw39.5 _3ӀJw;S~e=#;,k|(j uCq%@FYeYӵOaNN7d{BPxNip>\g [@.Rj٦&!5"!+L"dYHIVz0BuRJEiG"Xd+W+]WJEǓnhr@Be0Z Ό,X*qRjQJy[ȧIENDB`mu-1.6.10/www/mu4e-splitview.png000066400000000000000000010256301414367003600164630ustar00rootroot00000000000000PNG  IHDRgsRGBbKGD pHYs u N{MtIME /yxtEXtCommentCreated with GIMPW IDATxymYUk}}ޢ^"c8$AxƘQ0'0jFA $6B)( [_w֜vswZ3=>Y +VXbŊ+VXbŊ+VXbŊ+VXF+VXbŊ+VXb1LW* XbŊ+VXbŊ=`@0[XbŊ+VXb(|*L3c?}8vbŊ+VXbŊ+6de)m[XbŊ+VXb+_x_z׻"H{D!%Ɠ /߽"@ C~_z~ﲟ=ݳS~n"=tu0+?'C ΋x@T]?^R?G]+q5d=lRk;\{o7d|\;x̌KO~ \r8_-9t:kظMo:Jםk:fұΓMkCvn~H`(~kܳա 疎pJ)҈^{ckCs<~|8O{Zc`ǫʿT\uH# =޴ǦLlwըԙ3csi 5&݃5o=+:3;؍'vw ~dCV8wJ ~DxJjOVrcݟ1rw]qh-P_"UӠi,Vָ^)[H> K{Uc`4`浾=ZU{k8ߑ~G0S^5׿M=c&{Xo[IU-ϘCmPlAM )ޚӀ!y94 zl;qmѦ4t\8 fpI&A68VXŒ"ΡX[$ ZP7 N3{ZQGiyІvN `fuSZ&ui=d2(?^k\Ox-%"0m8&Eÿ;7T7*%3}#MglϚNJ"8Q T:B  ߑ5/+!@nv=cAбM~_wZ%* 9uк@͓ k8t%q`Elp}ُLl_wb-QwH9гrچF )w=NwZ~5, uغ-,N}QBnjZsWwmȂrIbCV:~uW 0T9qwAV:w~ݾ&(qó"J N vAPwSFT;wzkla &I*qCd\/u *ְkkE2B&] SƳ_ %z04>T) Rϝ B5XkD\i48umֺR1UuyGVFFwթ'Cy~}R癆й_Ê.&{"ޣMB}TZӦ5!81NtYT ˙?i  "@Z5M o*Yp=wvL%ҝy d[h71]Ӻk[2Y&|$[1H,fyx &`u :LDfnbnDG"c ,v>c7CXN2~Ϗ o|N~~\z~aQ,"rU)`8n;_w=f}qpb,8C@$X?xtoc aoOf֭!!P&þ``Ȥe,S~㊡Y굆8d@YI=竵& Z@=g3:LLwb:("V M× lO !{5cl,ҿ2!;;5xYeLP9h@E)"͔mZU66!uzs.[mH-l$lU7a0Pk7s]:6[7/΄3JmY12B1 X$cw!RstCs1#$ҽ)i:;]'9iFPh#"HX&8 ;ں5!X(К怬;IYl`䌁R-\([W}pݽ- :Y lEJ63~T0ai(аr'u^I<9 \GFߵ ,gC>q@8Ev2LK5{eM/G/`!~6y1d,(]KuYZnDž'PgItN[&NyT I-Rp|8ij쓟XVX!41-*PWnqw踋cxfWӮU6F6:68Rs7~vW\+l͜iw:G!N[)pj= xJI{zҏR:pQnQ?T54mZ0ŽZtTV5`XK[08 <0yb8O t!Qa1ثmkƘ?hQ ή$v6(c" Iʇ<2ԧ9)uCK?8')Tha;I+0s =7+X TJz R;@4q0VPiR 4%3Կ1Gk%+ D:9p-u< Z%/9s(irBYͲJ֘l}锊 2(Fi s"!ϲ)FkԆh4 RC2%2W)KW.c^+Hw ZkPG"G#YME,Aty u{.rK&y5(%O{B̡aSA` c^fu.koeL[4(E]ZxZj B DE1b6.kmtPkeIAe92Cu b̘kfݍe8 2׌(77/ BI O/\R̆%z ;!ϦqLwSA^e%eS9> #KbxwY5ݺ!py(XXK4[4M*`c;bƴbkݠDE V 8!3U *7!j:kDqvXU 1Xa/GA !imU ;SZ?R0XF aVU!zB̓]Yz|eE, :3xBY &.25tq;Rk9u(Y电벸\Djh M>g=Nv֝cp}1?ފQ;ml lLN\W:)c5cT<6#kbfjtBRCɡ1f%4e:J2Dmr,+sQxmL+*@im:P'Y+WK9DQ)#f|@YJ\ [Ծ.͢_X+ݣҌǴ>eωYMYC:!̌i|MjgTUƄʘ'{ X\n=RsACk1+,g6Z}!kg)Ww![wJAd4+z1e>1ZV@ݼ&{ƴo4qk5dwA~# L8EK^g0RZX^'ҲaOKWPԕ V+F3$#: զYz69`Bo]5}rc ,Q\澚q}eE)IŤZW`hKC_y=Y>5YAp\69C iNp'Y'ʔ5u(jV}t]%&m4[*Y5 MR)"V0!QRWbϢ+Q1L7P0gjzBsnr]bظEEBɗjR(iq䳉.NhO7j:R79!R\‚ܯ7@$wąhMqq[3֬3ɴndBv7^h%VIUѱ׺YKۦ x#ҩK3a-]xUŌO|2P w֌1 iXZ(7!g.t5eO}Ԉo'bG3! zF2lgk=z]t9 :DG\IxEmF<6k.^ S`4.:T! wX n0W9FeeWg̱+mo^Zc{6Xnh/| bMx:5՚  4H:yB;VQiyA h` q?9rBF+XT D6Wae{/#\U]RZxP=d "_x0F@%4`4A$X:RqB fNJ|ϙ T Ao}bgeM[O[U 40M;~i ( ֺZfZCu]CW >cl[4l%cfqȫrG"Hlp^6VKئ8D6B{P`ˠvR݁j@#[">4Fl w+{VJ%ƌ/uMA58~UTaW% mLzq'*M0:J$ V5鄎du*oV4HE9`Ȋk^ΐ8'vעjjO{ N_Ms]NBtucz+>]g6QQh "=wĔrJ!GN>͙}x&c=#!c T8.ulu;NC<`[kMDpBV=uŒy"Y,ul5dSZZLhV-RU,wH(vl˺^1cB=m# SA0ƜFI~,3 !duiiH!7͘8JFz_ZCV-gϡu2oNRG#+)a~rIǸ` !,ˤyFUvˉb1)־[H2.%,Z%Lh\$0Y1=ਅ^N7IX0ƒאa7tnY4zH]ypI3^R*]iĹal&`Sz حՓ ~88HV?-'ǰ (򢔈#XmJ҃-fA&VJEJP>3X*nO^Me d,vZp)km̈@  jH=sys׳۶h>|Rtl-l.H 0So'rSᶘYO?A J`pt\% IDATj&jL>:f^zh<ìL fƤڢj[xIPi5 8޾2 cR*,1#DȚbe78xkdm!Ӌ8Um .Fg}KZ2qܖ.SbF7aJ(L; eJQekc}qاҞ)(nL}&gzdqyI7[g9{> Lm(>㾬t 4{ke@H"F"Ո?LM:_fESdoz 4q+J]h/U%*R)%/+}m$w 蜓ڞFh_"A P#B'ϵ!(T *SiEOZcl}}h Y JF[Й5DZs4;PyqBZiOy,֕UH'|~3cZa\bZajS4En¸uU;_8!M ).DV^1e{ТC pNM2KN.( QaNV-}T2:ty")j-# :/X@Q>r̵LiK) "RJ`X{p>2eF?*4Y(]f`k *~̔8M5jͽv8nLfI6MaP2sKҀg9=۶.7%O$;Փ%u*sa {An;/ezu[JCbaqbVUzA(vTJ#C$wLV }PA])_Ky'j@F ,rr`DZ鱑7Wڌ =nP n՚;\ dYjȲD2.Є̱eXUF]7ޏq ٘Ɔ}uJU0Z@_56`jƜ$9O&'4WP'gwngjzZW o6 Vƶ<>1 ld*Lt0&s&*ճXq,q*".,5q$.~ұ@!MwuNJo%+Xt P[9'K.=^MIlN24R{v!p [a);7O JzԊxta m^5XQnLH66Dɂ8 8?Sm2Ae.. =}U: \7)5 úɜ8vv@-;dH [hhg!,'KQGgxi-Zx:du^AR5.IBE{SZ(@8|m˭jIg f1ƶ5ulN v,ffBHX,01MO-.#FV)V~`X"U]a+c 5-aʱYJ;ʎ֞:a)"%`_tR3,@S; ;s*y5;(ʧZ1Ng"TGOR犆m1t5WJz R}0w[MѡjҘSq΍-(b" XfG*j,bvL`*:I!Y=ܪ*6Ƴ6lTI;tr{J誂JZM ڻ +5JLS2NkX -m`ec-{#.ڑI ZezoPŸ7)>GcMTe5B+ks1A}L$ׯ==*0#NfH.pA۳UuS_{yǐsVA+W7Ǜr:ks6βRu*fouU)Z9ZNljn!S)d9Tkָ9@*RV\(~i`,ce-x8iCumXGUiǤ(ІٺPƯUj+('jk#46hMY]|j LuH}I$xKA}\nQ?nҒjcVjTVX`k(m;:ۖZjn뾛 )mM.)IIw^l4xITni6/޸d{k 8ĵЫhc( sکBc<)O|JgRt)句L5nKa 5n lRfFǩ Ze1(}ʁmۛ2!&-~XC3˖{)J)Tªrl`0gGb3!aJ+04X+oBl{:[oTRKZ[/2JZ/(.2Ɯon}QZ cӴ5KnO BB\OyP+nMsIЪro}^V eURjdeg5JuOʣ݀Zg8O@;ԡ7mgD $S'(g&9к$Pr]pi+xWK p tUQqh*h]b%MllyѨT'^ؗTP*P'ij1&J굝6TlQwFS̥juaSᇕc48pj (^ ɐݲ"c@Kza&3LF4>xM;e{A+'._Id, AsZꢤ0 uJ- =8q||zNjf%J݌@iE7fHKLR'jXi$hi -596XJHs[c .5H&(lOlkkiם$ 31RN2YkfXVZz${Pѹw6,YP2u@@{DYRiL^FE\A%JC+D$ekᲗ'R-@qPۖ֞q;>@U"x6q9/ㄸl'KH=C9:8 ,WXu`%*ӲAFИtQeƠO>aZ@JH(Ԋg[qL𪪲b4/it;s;V[qݓ%cu]C-UlYkƶ/T9hŐ9t8CW~iM:if8(JRī -'ɤD`X8kqUoJ$sBwk`R*au(5mQ{F@\3U"k_=Bb0mJh!Fu+I2oMif'o JW1(f*IPcx(Aк%A?uZOGWC3$Z0YGl%cI@on{#4Tgb S1H!V JKlWQBIb`Z lW)i dƆ> 32bX0}Z<`VU tgƮp>ftX./6O-LX5iU1-JBc DPv+iXNhݵ!Ae>tk4tacR{8PSU N ?}.Hk`kcc/c\Zi@t Ȅ hQ\xaGk\cRU @lb+X+2v\Mdrۂdr>DDyVK`;&9*6) #|76]N!$ZeIy*׉P[+@4z2[`Y,53vsk vaˋϐkH~-H["5\%[K#j|k<H蝞W3*]|<]#Ը چϟǣn#_X![V{`$5(L&թkr</7XbŊ+VoYk\pxtcLjӲR i`WwqD``}r 9k!ƃZzQ *m"v_*hkSI^sz}6D+gxdRFdbdn_=?߃O}>b2gt6ch\.q]wqG?e`{l}+f+VXb -7v> p^03CW&)fYҾ+J T-`fTa4k#GMoZYqd{;9ևA=5[+ڬgʅ@ۇo\b\b~-V~d2t6ra5g|䠚fbŊ+V؃ɔRv8::֕U Zҵo9)XD,f꺎B]U|NCvJ?ٜ(huj^eX!92UHh[ 2\@ DgL,V +5 T]U: +VXbŊ=Xd|m{k)SU5&Av@{0|er7M hq mE+Dqg= /e&:ϤC rg\mš|[̮ _E5\k6FTbŊ+VXWnZj_)b{t:RarV Ԯ{*F0|^R\_gg]>fjE!{^V5[<zUh]cԱXFk4곀z)'X6ƷUPZ^Y!Nƒ|Nj#+Lg؞V^![5 0ĖaibŊ{03+oPCbJwZNJB]O1% Ar`X(]6Mdk h( 00lR&&V&WUB+o hMk^iFe4z|' #V`bqRV :qzs5`{Z2WLEq[Xb/{W~ݟKXpΣkV~&|Ŋ}ZiW ^-!?$:BۣlZԨm Y0"kd2V@AO4f"10h q`;pN/aE8 dB9-L'@ځ_ H+Gfȩbg{ !qo{r)`{Pom-(w~|2ηXbMqO|ճp1?7gk?#;~ڿGUp ?W>abz0}S:wq_U'B)|`f!qf' t0#̏`i{l,~ߘm:/hl]T`Vs{4s9>fs,޵?D߸Es,}%̌.AD89>1l]ڒ+Vؘ}wovaRk|鳞/z^gs7? _ /ow G/Grg}s_5ޅ?~^?/xl2⎷ozG>ֹ?-_S{oO> ׾?{YǾwDo~?uu? /{7ܧk ^ٷ?1~ގX`G<} o W-؃^-͓tNsrǏ~YSe|5X- d3{Jc\bZՀfՀA`c`aƠUluAJWNHBX\6M 'lnvF!*Ⲿo RܤZ`4VxX⤱8Y>m,Xblg;Ӽ6rwg勲g;;8Y99gh5x`'?3}Cx IDAT`>6ȁ)Vus/u@rzn\Xu뎈 D1smWC7b|ݷ?}_ONX,w>x:^OeM'+"Я}?MO&=W:?'?˧?{?諾 w>>x~p >[w~=_t cuw??m ~g蟿ozOl|e (V/}VP_W^җ:x x;<'6Mo7x2֘T"Xf\<lcq#)7m/~}`/3p~*X.縻 ǟ՘7 xϰ D)~RPR)Oy 򔧴~8~UUϾG)[ouw14m0*!Ԥno{)̶qngwг'?듞sgvDOVG'xx{q||,={{ؙNUk3V/;i$SYa ZŶ*098<>'4&-u GU2LiEFΣMuxx #Ι}j @pυ{bVa9z7H%RPʘZQ^ug> koO=+0:୭9iFxc_zOoO?8Z4̮>5?yNrz\.Z6XW1L=Ko^BոW9O>. b&KK\__ WU//tkI |;ؘU^clmARZ\;m u`PӺvd lSvB e!{pZ"ִj`nI9P.2ڳ(/UM[1[_,8Y.X,{.a6bRWPD`jWVG ǚ[W{YoS;[.48:Y`o}Κg/37d+ ë~_z Owf89dޠޮW+ǎ<۳٩!"h+Vؘ5~Oŭ^! ,tx[{oWxQWxԬOs?s>Tr/}8 =a»q)`}!1_b/?z>??x/8S?bPO;Esc6~vгft ޻BHឋ3qk 2NNX5>z'lT̮n+J) ,IF>`7(7J7.za@]3cl,Nq\k=ag4Q=yYֳ3أ9N \xAW5fdŋs 6יm,ptp ^ n/bUma8^vJ;;"N21ԒKob zTG ^?|? ޺;>> I- vi{;o _:7l{x/ww+~E_Wݧ;#??xϕO-Cb2݋b7P,$>y>wZছnb_j'luAaNUak4\].i  FӬ ¹3X,X"ɕR)VTQQh#M$ JB$)Mp̙ߴ[2W3&6뎧s 3?il4~&|%OC+V󻟏Wm嗿u{{]{CǹY߅?Րze_a2r~r4/'ގW~?]s#s?&]s%/_גKv#~~` nyS@YRG _&"Wا=~UUx?7|s:[5 TU910̙=L ,ZWPZcZe䣴`V 5ל%*׮n2kϿ~-8J^N-VMb9NN899Ɨ<)xғY=YN,V쁶??_<zֳVXݶIGGGZM@;zI]A+-j>MήCfD=pդ-hnskL&5v:f@c -Vl{{Wiٳgq*}&~oqk|tL&5tgYL0m u]Z'%ظZ㺮Q-;ZZhM`2_>9#E#ѠE'N(? ^ @ ҵCRJ **xbWXqΖobŮާĬpbV{}>Z¬W7{:,h:M7t` y+Vlg+}P?[gqӍ7ÃX"iɬAa6x?1?Œ`+\wv'G==a|.68ZbRh~u;lVu&h)4hdr *>+i%[o)~abŊ+V؃vvvpYXkxeV >>tMq R8{Gbogs2mV+f K yGX, kL3{OA!5aec@0'Pxŭ"`6>u{Nvlbl]&Z!WYXվ[7HUXbŊ+`kwy'ӐdZ٫}|xOnpͷ`{:|~5U>Ilm>YӟvJ9ãc|OަiX>q<170 '>?(R1 }<]"4x50bi VlJObŮY[,KƷXbŊ+`w}7vvv0NsU3Wp|rZmmc\ҽ=^!y.q%c0la2Npt8מCvp|03*@Jῼ!{{ↇ^5?x  aX]uGAsDb0Ң(bae- 8+VJ)/8ܬ*XbŊ+jn {{{>K.½Gm';۸a8<>k-KOg7bg{( 1ݚPM'L8\.G#wΏ}}V0ƀZ]]݂WʄtBw6Hu[p2)x&r/e+v%'ҨQen+VXbŊ6ϱ//*7ۛflMkn|#g3`r{g?|c>{̦5mG?fGGvC[nƅKƴ@]qí\`+2@ؕ@x"܄nbŊ_ , ksbŊ+VXז%e]y;Ƭf7r+[\Z<ᱏ x w~쯡 ?KLJwa4,x>7p- ]s->;Oލ\N?2ㅧR"P>CKp=YR`%V`0&𲗽l\w0T3g\ ojJ-&bŊ+VXbxĥOx>tk;[.nwsKK\xZkLwp1~Z8Jkt]?}?f3r/\P9tõ89yUY6mȃԩ%h_QÙJ*ܧ=ie$+v4 Z[XbŊ+V4vU=.[ۘlmaw{zRd1T0G0,M8 +, j5in=Adrec?{ھəWi(Hϡ&`g޿Z*܀oU?ȃ>wU:1*6DM}FIFĂ5 b|-J R2Lgnzf3g朽9k>m2t=ѕqA J{A*}7^DDDDDDDDDD/H&FT]!x*x.f 4(WkKaR+q4@A,2 |קjxA@@X)ni&Xn!& EJ)1"lX O L+Pj$|Ga*,]T3NZՊ+zbչms>Z,+N|#CC* /"::wB=S7.? |wDѬ4BJ00b?m9=cmե'Y| %uq4+"fufOt*o YO =峽}Oetp|r|}"p"5cDDDsFBb!+0MqFW ! &A}ÆJ9qlݰPфX t]G(a{Sõݻ*/"뱜rnD%xխ岵[?PK% x!Dl_7r(w홾^4'쾏/ܻ:Wͥ~7&"3cٚs5'#ߌܴ(wϕυ1iy\yiҒk"fZCE TL$&X%lYz[T&W:R'lxR) |N"|5!q"wz9:lB P4@qbqQXe5͛zǹ~Z}eUR5 nyJ%<](71:6|.X{8ѹ<x]xw\y.+Gגº;~§2>|79;g)jA{_|}Gt|W$z=F(""""⏍b>s0b1bHa0IeZ 0 턁T4Th%li1sPENo)dO*)k ;RH!'JPw~^6^vYuo ['h}R_|2 |ecϋ̈8&X 7uik=,.#bv\WZn~뒕g_z;-ɨ ߿o׼ힰM;[b|ַ}w\N.z7k}/5N>DRCm9W~t ?$9}#iML^WWr5|yE(!Mx"N܊!tk Y&$ReW4~l &I6u4'uBas갮κ!Q7pC(<'z29>Pl|墫̵o*p\aH\|DG>_6s7n3m>Npt\!?{~ hn>t6yV0 T@Vf ub1+ 9, fbABI;B:8of  QhziӞN_Co()gi 4Do9_qX˯]_:6 >͂ $w C|]\/Н6.Mc6F;>KbOE޻×L~o__wD_i߁>?ϧx=VW77&`lp/c,M]pVUGr}?3>5s#c#Ivsi{gҏ^\pDs8q^"|F0 L+A2"f.R@Hꮇ=3JfX M&cuu]ǴtR>WJI,q> sTiFKЄy VBo$| ^kl IDAT!d k C Sw]lqi .℧V'p[z㖁xHXE\Wu߳qӧ>>7Jg5^W\k&w=ynǜ]^u3/u1>:;9+=l)77c:@ɼ8^O߷hibVX}ijخMZ!# |L#6z 4CJ+, :ӊ#~J WJL2J5sbX*$CHC 4fzV smlۦno{ȶ(Y#N_ԧpBzD9jM]=qwcVru,;Ny!B>ƛ=޸"7nluWno%GܶqNz|3|/;/9+y}R T5ۻ:;}>1:1%ιCeJ?;[|[ pUd;ٯq.?do. /~ݟ FYIxv(5=򽋚c\u}Eet7_9Ύedf u۟9/X,N2G_Q˜Ƶ8Ӽx6f3IL0 Pص2ڊaƨjd)V.]gˋN]k^B^xZBQ|mY.Ӌhc nR( oJ!%̛ü9Wj[DX$R S@SٜC!+PTj]אZ>'c. ߥ|wk|w{J~ g;f]3|ͨx+/|{ၫ9xy=Eۛ$W\ҿ~K:gq嗾ƹXݪA>K%'g|]mkY.p٥B翊/#nζ/㉽e{9#JxY| SY|'ȫ9B _^Cszuvf u۟9U*6X!K^.Ք=|.l\3g#B/YD"bxfejH!|X,a@Q/ I2tvI%Vk<&,+Ο%$?ꞏxkN?~j5Oy/nI&K.5t#FIv]\_$W!`.RBby;C64ru8ޤYn7=%}\nF^y_+_T)# Q׈hGDDD<۹~N.%HĖ4M&R4B)b,:ZhknV)xٺ}#c @H$IXh1nxHrz3WDKSG7

xŝԁP 1`{~qnCNXVUofyafaB:.aH hH TKMi2L$c&1ˈMj%ҩ'KR? nc;uï)c wjJlARD"m;Jz׮K$HII2Yۉnذi[ZؽoS#kTC4~qh%$LjN7uS˾R%ô'@KͱBz*".B&ѕq’޻93&37)Bn~wup55~&ѥ >f`P;+5ʕ:F624:ĝw݉Gyf @OI >DѼx.PJ"w}X4`=txaaaIP4BuD$8OqS8GA\0(8Tm{QV)%-Oτ=C$ FDDDDDDDDt2AKsMtWJm*'hjc;jZ8}۷eNVXJo|q I@Xı=lƲ,l;`X$DBhGPu):u}WxR5Wѥ$0el|@%'+-%/ۿa)[KZUsCFaG"``xӴhok#KӔJhm8n*t]44ܸr_ ܺ0$R), T4JSW0WqBzb S ]F\D>#iX&o$ 0ó&0hJEY  ‹:UPahdic_Q+WV|R㛚|}mEqo`V]DZcA,f&zLDZ=\*G:B Z&#c=G"qvE:Pgrnqo4 DMPpD" I)Ѵ0nC!TT I|M^Wa1SC(zLt"WbK_ҧ9r޹q'|[[%cFPO쎎Nnjj /$"""""""nA3,R4/2\K\6C>#JS*6ii7mfHӜM 'v!H$bxLM5Nϊ&nX'MigqQ(nTDK7u!BA>ayxzSLu"fTjQbĉd٧}|:Ʃ('v'ޟoDDDDDDDDĉG̊188LT&T 5)  #dx| RJ-&M344JJaT S&dI*OmLx|&|_?M&0 4&E阦9Y7u]jCݶq]? fB˰% @"  0 3fVtED"њE426?i ((%&mhUa` RGk̢y\Γuަ# ?͸m[%-[&EA4xW/VMއF\}ߝ`u7w j'?:kx!sDDDDDDDDL,^t:CKK \l6C*%ΑKI:b 3#L8nlcxhxԫ͙}{<({AM lQf˾ Xܑ`4g,pl4"NXG5\Ų&Mͭ4esc:M9,3N\&J&&hjnH)µǾ}"|ס\gdx!U=+A0J&$R Z)l 4.SГ?j{>'L(e;<'vcKSY)?,~mT'w&yqn{UWs~qp jZ&Blcc4#Bvp2W8sɺA4 X!p*SS|)8}V~w݊=y_ˊkXl۱f:} %B44ΙO?^4MMsUĢw+P@u%i9<@gg7x"PbqY?+x9cTG­H."rRKF'((̣\)366J>t}7on0^(̚5/n1>GYie]~ChI{s'g"qKy|0^stg,k&E E cn4òL1]hnhP W(G 3q)W6-x|$CCCTkޮn qkqߢ\?ÛJ Bk6:񘆮 j" |_h+GV'j|63ȷ;w+ߺwQs/?͸Y!ߜoXZ\:s'-JM\8㺿&e{m*|fΝuY\?9[gZg?)zz{y[N.LH&SÒ kA,ܮ`R"ʠ _wF(ވk20Ց{I4!j^xGǡPgujZ?h|=(Ra0h,m,9w?@*ӄվ 5\- F"TRP/8;5eLi4m:2E'њ;!`PG7u sKNoM}Ad80 ''a,?Wįj˲eÓ靿av ҙWt*m}v%|  >ۣ-5Q )mus ~T"NPB/P`ke;@ =|!Y3ADWZD,"8[oϕsJhVAٷof<(Kȶxۨ ~kMm_EN}F}--1onB0)/u]\u\ϥT*G&ݟg}{r_K,2M>k]9)~+ gf p"~_j[J:cc"N;]֔"!Ɖ#zH7[!cԊ0SˢQbu#}:;xhmm/Rt:CZix1mYYp\ {Qj*ޱ*P>*>xcZZc<9Cyϣ/^ɋfR$MM451 l6nJt! Tt=]ޜȋֹbteTXpu0wř н nͧ6m @_:)|Pa +fhiIάE)֪H>< oOeҥˆ'6'Yh ݽ=KZ%Yk֬a۶,[{`B\k}>-==,^ݻwMYj!ɤRH2$>Q*+B-;JT*;᫂^խQ5qǞ6݊'4yVA6Ų0wM>LhmmG6swe!>;' my$qrMMfXG LӤ3L0ltF)]H* J|B1F:یexlށ~ â{vYdRuy_[އWFgyC8 P0vŦ_^$bVfhT*UtMGJc=ƛ.KnO׿ւzT*5:5X ǢŋB>yXt {즧>W͍mXSMH)qۮN=t9mHvkO[A2={+s~{h޼Ufވ4:Z3lk|]U<{SAmZZZٻo/rT*\4)1 aV,_97PE·N:n c$'8NGKZOpMd3YdZ:JnM<5M-4Mglldf Z!59 F)$j ]ӐRchin]ӬӶm\/ !g]& >>RtdLX`>{7of׮]|$ݧJ)-:C{+f1?{+Ogx=`At?w{{1BA7b46U:˭eFKxJuzJR~?gr*?OB*voY϶}C}A+bc 3f ~oBfږQ꿝LEGݎs;g,ϓ;R}deґh+'fJhwK90NS<,_q2<YzHRX1k2":B(E2e0]}E?18a0::J^gp`t*M&Ed>dzr9 ۶)xGK )%zC I64M|ߧP(0׏\ڶMoG iuRIb\t"i %˻۩l(nޔ^*u ?tUS9quniPVL_"1t3vR _h~iu54̳<5of~ًEh>kc1aɬU(Bun'^&iim 3 Vh6 L>-RҥKGWgBJbӦM޵I&gExwoYг]پYGVNYY[A!q{7ވg[G/H/z$ ٳGmr?V>T,l6ܿ߱cgW];y]?.N! ,bkf-X]nI߶7-V1:6e'j&BH,Y"Lv@s5m^:6Th8"+4 0/`$O_w`j?O7i=|4&GS?D|WrR{ ԏı>pѬTU4)#Wtƶ7C.a;E6-̸m>F>obqK˴@iK.~ҧՖcGWwB(:{APL -$.{xE=tY޺om9p5;ΣncH ΋K̏Kq\|fF +rG#.AR+&hܝ[ޮگR + q٭wc۔[p0xΕ`B0",vFI:}>WY۹# LnJ*[v'2#)fj5\Բ|x?y Qj>滨nh"a-|36NߍP1 >O>He=5p8fŝ=cGq{ ﺚ.hQYov7=wAC+b5li߾vzzlٺfuv?gLGdd;ΣF9^un,gORGBxX~iή{IY0>(vz,_A@K=ǢuQa$ay#ĢQd2ۺX~3H8j|9zFG(L4fl(A466RQeQ!fc:6tf&J*iEǣ t ,G&SpQdL*q]5(.]QeM30LL$_rpc,\D7y5/@ý#{gʼn2Ko/R.הJcrMعt~s d9p>W*-޷o2;ﻅ[o;J6ѶP@Rܶm9ph )#Mc66@]x^ zn#'`yhܹ, >T28s.{9H4W匊/< \.:ZZ[hljDu9zhhfLW|N<䏣~̲u;ye>ߨy )6uP(hoiysQUhui pL:O&gÖt"dP$W#(VUel" #ATVT U=?^ _"p-F\l`%ySr<Zk=B$"DP@³ȲjHH%HpB\ P$U.U9S?{mO^mk}ٵȍtqWW[|;2,;SeQ++㒅@rAyGÓFiZ߰[nu--UgUoI8db>|օe0zG:!043>&DKoK;8f4Ȗkh+/6q3t}\5uxurciR8|&>>o# xxNAjEUvGk4Zٴ=VI &I U46a٥Qbr&3$9m}lxmS' Egsg$MbSHHȻugR?_|dZu̟;q6f:5:nBƍN!yK&4KضMOw/՝c{9\~߽vdY!N8_(qGKK tvn͛7I,iꢵ!{\OV-hlnql߶Zv9bZ'ڵk3XWrbyye 3T=D}_v*~ems뱶%ںUZfmvkjf~[I(>xJvi؇kĈDVk(KL Z@5/- 6,_ykӛA P2v&.ukW3RY)Sֆ$ I }GEuOə3DŽqjvvM\Vm7w?ǬYscd _]Lv_/=?_ĔTxy)WV'3Y۶eyeV*iXBqӵoo_>o \!=]Ies 9[0hm 004¦X:6u}I{`ttzZtvwa][BPb(y,KuPocXl/D$"RAUcϦIߎi<!ɚEsA$!Ex 4%hD 嬄L@*,1ru,;y?Ε\}'/ ~-N 8̋]]?zD~Ô2l7mV%瘟|3N1ll)i˗obNc 0Gq0a$F]~CdGzxr͝->,A9LMVgo̸<*8#cq, /<;;VIvdb{( 1h;.;լzu5=/|]L&C4q yBP*ɤ33gO&oR~@- ;Ecd&]oUXNIHN:\ݹ5,Ow?o>Oƅ[Rd[g7bcq6w( ϼT&CX$ɒH֢"C]S+}zY24m3MyR -Hʲ@2$# \?,+.`ӪIo RdasY(CE.#SJE,%[72Bg }B2]@U,'loYa_[?w'6]v;Iw~wmC!=n{>|瞛Y5R79GZper͝gS[|,hb2zoM3esfGu]WA3&joe٤3]H*2PxM+;$6}繤l zzw[HUUf͜EPLخ3+B^|N:$oNcclSXrO\i ng< w´/Wp]B#TUU(|hle4qPgw纀pG2Eǣ+ DOv (]˗rEh8}ui^t(/7z$Y@48 ROyVR1*zd2B0n0}p1ǡkcYVQ(Lt:{)̜162c l֎>\\ץP(:B;|~u/l3I.##`!+Vf͚1kJx ߽R \k/SU{$Nzi4d!P5 PNQ ˶QB!pehj$Uˊ>Kɚ S9m*x #>=UUU|ɸ;+I0MsD"A(faK%#,k5Mbsݔr}V9d0~"h cxl7RS]MoPSWG&'hBYd"_P@M"AX$4s K?mFdSD+V6헀csļQDT۲,L>Ky%CQ4\_I[['9PXv=z0EQh, ȊFw [{zNdK@׉VTx# q(Nm2Y.h٤2Cvrm)d»X# p vp@eέT4!꡿3 ogp cbK"+{ %X~8cM$*|vȊ *' Wg۶od, SqZiy?cu_$?>&Uy]ש?m<3 E>s!>o]6w ;G3 `\]HJL I@U0;(Y&|#Dx(Dbq#peiq@UhnmEe\IvrA!+e]}=$\sqgyTB̙9!"XBHC)QQXX YStZ)r!Yqhi@I?~7TZi`DS{Ck<~RzYr; di,;YΒ$QUUŜ9s1cEf FI MU.^#|CGy~{.HD(/T`0HUe!==<aP_׀$IS@4jÚ1˲p]]4mX4qJ$I1iB1Q=;C,ܫ6MT@M*m۶q'|SOrN稣`aM"Ϡ$XWǴm F$n 7QS R)l!_,Wc({uaBz * t] 4 DH < Mi3CndUY.Eˣ2k֮CQ$ UXEFaqwu #>^8Ni+Ni&x]s)L0\yl*\u]yo #bAiZ8W!R{DQBx"eYޫMx;qcLga$<}occ#=}Uf5WR2s6pQ/oĕ‘ :6ݍظY0 `(VU*h]בOP04P!Q,dm6 _M HxSTNUŃM#&Z4Yjҙ!H HƦv.IHm{8T1ɿSy|!w}t'~.J ]íyFD/ VU <{>χn=(qE ~scRBU)4yN.pi7% ٬J|Պ7l˵Lno^7aUyq勔c&S7>>>>>>>>o$k5X,H8CG#idY&`(4ו "826k38p?fVzWiRl⑩ xWTU-9cǒ dZpłpX +sĴ66v/S&vV-}-DqO !_(<M+8܉$ |+my;݇q_?x?'g?9g-}Mm_ɫorͽ".jb2i rbruykvF"0P C#꫓Ղ#D#UC$G'Ų,Ȥ8 L \&xI(^B8TB65Pn O@ɱL4lBajW|o[/|lo^,γ4 qɒ7?n*Iq  m f($h pȓqș:YU =Ӹi0ި-=1ɻOn6j_a-1ϞH&M֐H&$ kԵ XE)6%Hdx GR)TUX`A,OcA(]g$mR(9_?#|Eg2G 8q,sYU%m4 b躊8X%WUU!0A4B֟KbM͙K1? g7crb%?zD~Ô2l7mVw#^|4-oIp71YBao~w|y?'V^]ŇKwxoԖWL'AȎ/;WS[|&|>G.[D31cYIk̙9ac m[~;F 6̤{eQllCeB@QofxxTZ$Q(p1-slyƤkN&]vo`՜n͚_ ePeo}vicΉDb]}|||||||Ħm*8@6dQ N?b(,4EA:쉓?@@GuD!00"+zkUB4EAQpfH,LdݦP:k[E±̲ rBJ>BdkxغmXcrQ1MMd ! $_BAH f0BZ||&u5_ V{b_^q)nHO{ؠIo7|sk#>\!0Gmԇu].MS?^ϓO܄iKaѧ)&8P뽿,,m|?/7p#N(n 7)߰uyWc?b||YW "%x瑏xʨ$Kͦ;Xᄓi e'._xnEփ-4h(22E^+&ܞSTDTTE ޲[X8e?wp2HjLQ&16ޱmE γP( x0 d-]*O^ >g7 @P1[=D ;Wl#pTT(O ǝLkHmoLń#3[)YNE@o6\!$}pZ҃A0^kQms(8px@! ~}4W?]Zo,KYxS?7/;XTVN!fk?l< ' kxckWfݎ;װOp9Mlb}Լzt UTkuGǑJ 9G1" I <{׌w}{N+A]bײB'x9A\_QEp*y=yOℋ1w<;@u~kZ>xZN~>;fNuHM<GCG LU.U9S?cJzz$]׾@{݇Wnom_N$Q])ϛ$vuXZTdduS0dp@%̢>O\+mឍx Q 7A>[9>Oy>^];x}5,[x߅ɲg#l,z1*f5oyE/=Z]}x $8yf_`I}h9'Tqh<8*xgwEj g^xeh,/`jWWűbC,MI%V4S";2Ppm H@ъ(B\.C:fNńO'c8B=/ sD!;pe C#dyq"|ʚ38f "SPe4 ^\-h pȓqșNa8x(¦u:nb~wuYdIs߫>#i /@zx{?&Vcj* lLrvAݳU6}퐽k;`y~is{W|>5<`lzx/GXtB#Nlfޢaލ/ā/ykK~r Ki>^=MLJAZ;:ѳC, ^ &+2}Z\ؤizŝE~5<UPTtYpCXp&C}<_ ?{gfU>ϩ۵] 6 J(b؊b`7"_n1E \`aٮ{(?ٻK Νwf3sfwʏtK/vM'#'E(AAVvDGJwA$ 8э:\vH Sy\Ȓ.Kt#;"B d. `ke%H0 EK/.e7zC=y .ڇg¬ψL$?BׂlDZf>9fy7=7{?z2fx#GLjB/n⼿0>~:N| m(6:8T"Ӟ wn|ѷ|{& b;rd>'eUjm 'IuW&1I ;3Rʝ{tsא#8Nd,;~a ]_Fmju3%{wJz(yKW|c-L*6z^pU݉UXj~,{s5'R8#)`h*eKZrzߖN]?5+U|> )5O՝d4Jagw # jRceo!6?+0ք[l+X{I>0 H* $a !(NZי!woV6w,yc '͆0-dvq2eda_CS Ht8((z([_6ݹt{?ҳIh xi Cثg7~A" TUyk9SG\ϽΪp|N.<չYM;Z2i膁hB!œys1%GVA,i"H"b")"NNEm(#J&Fб: rȴj6p)Z+SxKu~M'= KxmaKʜ~o$ycshHU} 6xGK__ʚs.-|p >_STaz\AM'TSCTdT-w|AF Z)߷&rnWPD ŗ1`?G_7&j$ίZpYXl Od-dV۲w{dF}U=kVlptAwu( /ZE܂Ҳ21E~S]lrB5 :/G=h,|y*v;X =k^{$*+YK,A$Ias" d˾ ijPUns"&xd}Ǵŋ؁Pe%4' ()昣]E2t }+.!k8N2tܞ;4.'`8h2s0eOl7TʊLՐUR) _Y$dfhw #hHsp{WNN(&G tj]>HSP0$)a~o?7nXE%«tRz_b#p |_x0gY}nJbyPqXEԖӮsG=%ɠcvԑEKAV F6P^^NUMP~Wm.( "PWWUq&߇Z"> _MSNqPB(>n'##ںI2Ns((I4X潯搓ݻgLw%8%ӗA,YsXWRAY͎9]nYEH1ԄFu )+gO{=%D"7e%Ԇ"hh$pX,'m[1`u&ԗ7C|,cO?ˎ |Ӝs)ò;n?9^$I$'nc{z&2mvDaǜNioz K.]—/f-?oqq@M:g3ᦓot:,%Rfp%WUf~)>x-3\ǽ3֤̏ͦǑ읈HaT ']F{ ɤJ(%/3}|5bڻe| ߼u'H[SF0EמGnqwY4nGZ,3yޓd'v5qOQ{ wS3 ~\ɻwLj}KKq ¼Zln{΁}OG^`vI$& ڵΥ{BJC,) (%` n`(LmMߏðKfMS4IƓhIu;gj܏)y'<aHPtv,UXp` _iLd/-\{Q,'NR48ɭKSLGٍA;RۑRU$Q ӹRe.;g|~pƷ\7Ž3Ont7ۆZGC.sO!TYVXk_5E%(_ 31x쓵Yү ~Ⓡ`lTJF<|kd[PT2y $36ݙy?GzE:;ΣX~ؔ2w>wA&@!bЃ<3\1\qsOJgSX[P梾 瞽xAtpԗ8 HM]8D{+sǞJ]/o̡xH6+u*CTu(.*=nlr^ xy&z 9֊ٳB@Úy5qY7(\{SHQhw1]!ϯzYn1+6,&*X5|-6z~?=O>d÷H4_[ۮ֖i6;vaP%hAFV&n1ǟX]o}ߩ Ѯ}kۓX(Jyu (Zc _1UB4r2@2!C+ˆD"ɤCqеS{EHT#S6bqd)/MU-ԃj囵;83xw&HȎdg[4$%_=f. Is-t k*эp˴sUQcKQc*Gwh#ږǪēēhTg6߁/Gb#p$dzݺ(!"n'uL l&+ÉP14xp"MVzbˍW4ԅD"Oٰ+}a݇5S | DԓJxem۔(YD>:J {###`mjc8[,lוۏ'onau_KhܔՔT4ͷ/go-b -G2*"M uya}:>4voκ_q"h݅d8x<8Tp]{H1M7>)ŵ1SΝmY tǸ/`߶}u IDAT(HqOz=}= Yи0̘gRl>Te\إd~a IuM:D/`5LE\ͽ3(ݝ7M}=ϔ s΃pfSck?HCh2}]IugA}wuҫc8U

̜Ɏ-_z^~~{uAV@4TUEm oחQOb<v6EC${#;uDɁөWU` g{-B-qCgZ6N$?.jqkA6h-3O4p3exagCT&T~5.;4v) u|D&V%ga!v댉84.o @I3/އ')ޝM~JnpQm" nmn8NMW|vioLTsv̭C2t `1l;sy71uk_c;qts2IZ6ÆiDXS-^FnYўUSΣ2K.2Om޽|0 hmJFSDi bu1n]Q|qd#*vGcSe㷗nwm@0 o;f;c8%]▣sEdm,\nޡo(\'x5ϰ&!o@M^bN}CIMIQ R&Cӹ6BiE%Z6lUy]evj_@},Q^Y-vMX,?͋ vBDqD$ Tz^Pv 7m<$3aZt%w^~.ہvgl6V7I |e^o=uq޸t>kwOഫ'2*/qB߁}8v(O-1yGgKH^*&} ,Z I0h׺`~D#@xzy9GX0&V=~]b6Lߊ#̯n۟H XF? (mEqTH imFna(*i!:JG0 Cd1~nYyxʽnrRrw]D[˺c9c4\4f`$U|3:ny r:0+$I ɷ7GSޫ&={ǯ;{>D&ْES.;㖵-;/o i PGN޹Uƻv0&}0pu~GxN=Gg0utbS+PUSl2^Ǎ +t@>7@dE2Xj1ʪ*VXÝbp8YYZuWQxU,Y ~D5n$%@|jG^EU.~I%rxb- MׇbQIzz,48l~-Y!ut~Z@eknFX|9#}D@tP\\BMuA9弳fI Ā0߅7.!JOO=$)g|wnE\qy|l^9yyDIN2*{tuLB.7%aHh,)j 4IKOG DQ"P"=ݫ#2}gNfOj>wN [|@殡Σ>XJPQYQWW ʲU3Z%XQFF ݎ LRj ͫjO,_ӣg/ߗP_UNJ5%TT\uDQunG$P Ht*Tw~Ro_j)lOWqܼާ\NZTcQ$IQB$ -(ԅCA$E&m)*ȲHvJ"؅Ӽ{'1IV˨ b ejy-,,,,vk5;X,J ̌ưhc(BZfօ8}ih̬~|ztJ7@z`w {}jFMD! d3E(,;b0KϽa(Xʢ$:YYY>n\vNn{uuۆSU]ϊV)aMq kŰh.hZIw!:`b*`\АM EQ0AT0$YUS-,##2Yʰh]|>H2vC.GI L|v$۰c: 33uլl6t/y&^)ӻg/inʫ݄jC,YcO(OiVVV*dС]>#H&UuAqX,F6m0 $ED%40LLS@7u $44@"IplG^VM؎h)9Uw^ám(.xbv_/g?r=:/+cmh!e%ݧ_e|5޶l*k/̚5R?tcVmβ+!K]_=9+jswP\,x;ymz6:RDD/p&9}q5 N'%?$!H"YǗd2(6JTKjB.]!NnP 1BK8^Ҳ@%DAG5D4 _ Il,/YnSkM<\NITJ>-Ɲw$߼y7OH[8": ֋ {O! Hf*Jo[d6Oцڇ9YMg%w)0)<މͿЛ˓F0!̭<N郆mӳ[a<[-2oK|Q̫I veh{ʽ\W;n)ܱ꟟㚟,[qGm|ߞUkh)3. {|-8]? ޹~o?W6Zi*ц{wߋtA!";7l#K*j4z-/v&Jԅ`E%%tkH8i3iG FpЁL}]cI4XRAClxh"oSDPUFq*(# :lG@ttDYܼ,,,˪>=wJzzb-olub?!*Wl~LxWϒ{@NI%]e2STz"|ʎ6DnW%SnY?;3O`q'P-F_8|fe63}4`1|A ذ)f/7O LuO=v)ٰgp8M XYǜAI3*.t@CK?–Ҟ;^yZ `m'q>g9M;cMLNA1ry>.KUS~#;Důq4[g3v"l/_ArsCƛasmw^Գ~x~ا 9֊ٳpLat3e}QܝmC,}ZnC986csL [v (nٺ+ 򦦠1LP0HB՛l\n7к ,cs(tlZB$ SrbE]UC!ύ7'M>G9Ji~&(n$$ 膀DlIp. 8.슂4"蚌ls7Xx|]0~|tze6vjl)/MFQ.&6Ù|Vor5\{u~Ozڒr^x5]u>fe6$#ar:~Ֆ4oƠ3xw~+4;%66{yx8~_I!7#'0༷ C[Ǟq GgsuYgV?f~c-6 YNN(nz{4κwޑ|];|~Ϟj6_?|9 4ggT:Uݝ0| @Q\ǓnkQj̨^淟lliwEh_36[v睷柆\t.d?f+MAi7i&]Cwl4PS՗SL`ƍFxHXHkʒ9&-2z\kد~Onn)`}cSlw銤H.dAҊr\n7~ b" *1:,<.'ZQ-F #S0id QR%Eo"v _-9YL'kJh*$#+#/9QQdYSVC"@DĤ١ 3]#Ѵv؎۽Hm[~/Η1\澻:yesy$| ]ݳ,zAO>~uoĨGVeғHg?4//o57둡l^/y}O5K!9h'ng}3Ɠ$@_Lz=ϑwetǘ+O}49-^gƳ/dz_㵙 RB{AY.| ">/"]j 1w7-y}-/t proI7 ĸ_#,㨫{{vjJϢsfŕD ONm-@M&ͺ2Nl PiU؊pšI2|>g#ڐ?PZ8$I$ PCOFRvMJ0tU W*-i4#'P IZz6yYԚX,I2.KhB"#I$_b$1g؎Y_iGvމ鲻3p覧I˩KDEqvE,f :lATVf*tdŬ'p!0E2S律bA,j|ĸwئ|e| 3$מ5ECWrf˨EaW^8_eą8OeFp9RX70eg4lOͯ n]۴>M{>}6.,Aᷟf(^z~}pGsvb*UwL]Q)-z.<9L 7&Pœ,LA ixTQ& l8%oH =:~_L^=1TRh4ANԅBV`a(Z>ݺD%^w5|?m%ՆK.T}ue9fn>nSm̦p,/2zEmYnB*c]@ķGOn8h./AGfITÑUV5+9m, {L;Di*kjWN8ZEvxLv?ڧsՁY3/!V|Em,s!+b-6urT{*ׄx+(l؋IS3e6nOi;^7UwAKg}Oiu׋n@RqB(5MiHt}>n ^W˺56<.'dD2!ZXީ#9.&rt+XyWGXyGu8sа#fh*m{#zi z]۬' DQ IDATb*r7#SP2h׶EdZrMO3gB3K!5Y,M<~/x/nQzuSuWCEpx7=7ߞNiY-ne5U9nIoK"4ѷԷyNncx\H"[ÿ-{q w PU[a 2t ̝?*r1y=Y" as:1EL uK)f8( 5tԙL;w|'4Uӹ^ vh!j+Bjc35jkH1q:~CN;p1v'+J ]dp̱d^SBv|[c"nG|pÈ$Շ%5 G\"i-صx*o:ldkt 9OO8j(ӦMC!Rpؚyh?8+jqewENcakK/)\٧l֢{<6~\z߿ug%4Oұquŋpxi(vI3iHtE). -b~P/@CҤHeKWgs>7#4a&SHb&c`˜ $8@pÿ:~~x*?Qdg`dTIB$LAQ i覉&ಙ >KZ(}*H6~_V.[Ayu5.";$XXl'N(&f6_oᑫ |EV3z 9cX GԴT|2QES~6*e c#fׁN?}H­EexeUkSSY)xB MGMH8bXD ]T54 mՒDVVĤ4XO$!r8INL"THI3GCq٭ĸMcrV galצ]~|q?SXTыfIO #+QS]˫S/VPj!"%#ɢE߱r21z8#qp͉Tc|;ې_CC7uQe%x\.rҲ\ ,7ȵ!}ظ [ gdRPTQ5tMUili-Ph5_ >S|ߢ]e1gN5l>e,ٓL#M8&&&&&&&&&defpIKK'#3||>/|9'Sxϥp[ 7=dYDADtL6nE+9K_"ah+* KJ[R]KnJ2^5E hFt3h(}HIAtiAl%87;%$A >>@$BQqs{˶Cp헲{rNEytxMdey[$,|FN`q‡ ]#;$ڹ5h8ӯL>[,ف;NznQX5F_W2Lw&){yl~Aٳ|3KʖB/k{FЍgtl%?=)?eZ wWCx=Ϙ;lҒz_Ɲve'w_rs?W0mm~xvаڝ~3Xvsh2lj߰zb6n]e:oOy;ǃ2x}zuFt$Y=m^6)Iagcs#""vt"~RHCW7SZQJ=Y5V2w,>E!)ٱ "(6ka$"#c(b.$I"rڑDEe2R"ʽd9閟DLb ܎(jTaӆ$&%_B)\~.fr4beDh_o%l9"̦-!%0|L>) =A7`{{x1.PV5;CaJMvޙe 09P]% tU䃇9Qvd?(R g0ڂ9hcՑIOB1xS9h[|)o.f e;{4|<8a>R8 bs[Yd e_/[$\h׽vZ>!U$\4n`%t{Ǟ)6f}?8Q-,_t{Cxbyr̅ڔ'Rs63Nњ¯AiԌfۋՁ;>)VƆ&rgexwՒQ4Fo#)iOym='f!KD!V;`Aq{TaÖ "2` ".SVX@q6RR-0*FVկYfFT ." Be}=qJ0gnQU^Nmm=9Ao^Ic]I-V 64m'.%ŕN*@sqaH33ilEA~gۢ)ZbGkvLpy#9u}w~Y [ɢfTuwɬ 6 fJhO]i[O7wvIת ռϹAx䟽I x=&soEbym_sBk1wCcΜwYOp}diڧ.gUdx o>}/ϴ<|-O}׆%zffcG@}3M }+Zd} l:|Wi,;] @͊w(FWӘ]Xţ=i|ˇ0f` ktMMVXkv}3Nt>z:#nYp%W4ks叏"="ԫ'߈hܱg!n-&?E}k2=v$_5KaE6'Hu}5+6lYaۈжM)Iv67z5D,(Y0AP zkz+nG|l5^_-z:+J+|>:͡"$Q)* hzvah!@'@TU|aTEG54wȣK>؟gmeI}$~ F~ H3g=;n` Kb{Z}vfrHYb5I &'^;m ߒg-ح thdhyR4H9c&]4(+ݥwsL0t~?ܳ̽<_Noz$oxf|:K`89lV .')1 b8+~| gZ[Zz<Ϻ\%Or~-f< .Nm`) ͯwz䛦:/:e w;xa R[\6o5̘ŬO{5o _Av2_Dhl+L[ip[Dn]ZÈ ]y5-j/Wwcw}G֤ŵuD*)J%+*xbIMItT-۶e*D1BeE2s2[iNZf.ymIMKIY;5\]#$ A1@"$ |>m/bzu大8+^j73oC!cCr8 25[y:,=+$ %;=$Yՠk8!OCp!cl]359$#/;):yTүaae@X{2θ~ŗSIלRxM5wl ~CȞzv=ضEl )R[d,j*<3boA1V>kݬŌNw}</kQ#LurٞWxw\q7  ݢ|SC~.:>>;CxL3{Iϥ=zUt-yEE6ۙ?JFtd-A]}-յn:ah:!M!m'ГN򈉍') J4o}#^%!.[ʵdw$lӺp6NDvc u9u9Q"QRbwd'Ҿ];ӹwOnlEHZg ӎf@T lpеStM75RG_Tq$F5%T0Gtwgo7|8Dߤrŵ|05&.S8BP5W-s~Wv#9ފ @4雍U;Y[Y@^va)O$2pկ-_òfnmϸgO@kN8=>Fb|ޢH]9TR?P-ao2gOK'ZQ$'ʏ~$nr kٓP:=^~s3L|,؋?"xѢ H5]P<ۇ1h)n 4ڧg-[Ǘc(]TH$$Y M5NS`9 -|h[% Z9N?/9cN:lq{yy;:О׷";C ;`*" Vy/_.#Qkh܃`Imn]kϛS3H9w]9i'KvOOTpǕ2gϔ%NVUH|y|hĦ^挽)O=œ Rx;TҬ$YD^ 0+?|o8wӪ~%7M`C2(SaO[e&ig,7/m߷{#q|)Bd6θ/i6ħ>ᱹoPtl[\[$7x]ApXme[_]ssgPV^Ǐwݾk,oߚĞ~TLKq ngGs+x~\{Z2̿l|l2@yxA ʪq.ӥ}>NF2ns2)tN' ]pV}zI=q8lZ^}zuDRj\'%@p+- ݻ tCY} uu MKGuR >7$ Zl]# U((JX0 * W_}9a6%a<!7c mZ迏>(Y̢K.֌hhhD745ӧ#'ߜy1117]7yp&⻏'ѻ9aL`.vRֺ\v-k}c(5ҩSGRX~=qI񤦤iX6,v7nǃ/Cj7\T|55ddP]YMPm%ƃA.d5&'/XD `c5ՕxQR"[VPHcG]阛OYYO=I"d=h5u|b]>cbD"A2RHOHUp H2ae V]׈f!"*(c(:EGS DA"fJV߈lҥ]i C O}ă_'W >N(&f0ip(0ԫ#YYV+"2+eZ A(툡]8mUA'!/˖eެϑX- *ޚ2*3)ItjCZUf=A}RVUl`04I H8B(r!KbM5MDQ$@tTMC 4C5 4C5[!sv|h I1LLL9x30`bb.(!q11d<ԴdrDBL~a o& )) Ӊ&A@Ӹhg&GHKOGtG.˹ 'R[QN0rJ8tsߜGSdп9 H[KmM=:ttQQ4YhLШnB7DT@5DQD4,z[ȳb IDAT1tCבd!Uꢷ>ʉxO{ig$' n;6 Ӕ rNѮ=zs$A w{E;[, &ϖfG쥿ah9)f29,5xQ jD1abbbbbbbbo]7@MkW{v %!$Bv343'x4wߙ~.;y7 p;VEi,ylԢMfʀ[37u>L<'k ]vvfŒk~5DuuV-KF3cyg } M# t˦ %[*IB;ގ=6 }{t[\H57mCĥe`qd> ?/^]:ѵgOJnIg8=ڵ1G$ڦh*"D5$$0r S^^Jrr E"bi EB ;4gE)"3-p{L[XdPLy,BoSo ֘;!U$\4nE2]xrxul]2~˿& <rlhͦ5ǖ.|qV1vH>O,Oov6oy1d;_?}++$;$N1X~m1@PZ٭9$04 "^NPKk7o)ry]2zJW,OF^x|4b6rb(e ĺhE?%?P~qֽom-tVW6 laSQmPuo=i Q:uNl\ NHzjH"VICAA DBD(QJʧ?-%wV&ېՠH~>zjigu' &U|4|4]+H]uҽOHRvNuSV!VDɂvg ꟟ݱ.u,-J[rUT/)cm-Rݜ &M(dwHk ,Ϲ;`uߓLAtzz[Fܤ,(ӟ8Knj:os˒- Gx>4{cɿ>hrjV˕@1n~jSh^=,%8Y?bő~<.[TOOyDsi18*K.)ܯiquьT+4͝ă.77[euNGzy(\p:{Z|_E+ Y Okt ~{qᯮ!MVb6>  B(u銿r;,V,m<Ĺll+,$1-~FME5im~Vu(x!t VP4ł*p9T~d&?z8uE *!EGTQ! QtCշDyλYuk.0re?d0,O>TZ>5`Ti W'¶ؒSڰ{E I"u^$I::-\ւ?AaLJ}L>̃<{7FYhʉ-{bٌ̂wsL0t~?~t2!}w'7h<3_׍gswr s`89lVfQh-vop9I >]X@$:e w;xa Rtv쬵_mq=vgKkv8X(||S @ d=n:ҿ}0ZG@ :Ѿg?bp] #*,L}.O>!eim3qHxdgsam} @-  -$zpIM٢*D"*~6v3>qg^j e Cz>KboIqG *z^ aS9uܕTZ[w<(E"q\$??W1ByEr2[O>g. /UYW찰2`qtB mQwg\{g8cweq75@ _NiTߧ̝ly}<"לְYs͡p-~[˵D)Ig3ah1ٟ"~IZ3Ï/Fc峆o9fJMf>p!`a#ڎ\weT+?W=vO} ݥ>Ѝyv`ogvw|u ,X4َ(bR;lȒ ͆ $ɡq}  G"mV\V@v6k6`;?>G\.]Jyikmc@peDtgjү{V $9-,IGT]CuI04YĤHJp!^lREz^ycz>5k6":{>nia>X뫙g+({ Xռvl ~=sחs.Y]e9Lk7/j||4R2tzM|EAaUյ|x.(D&/>C UaMڞq9UlQ.rQ|5G۠L{N){&n)os_Ef̫a{J49,卞2a͏L˳M.V?3wG-LIKz+/ bd&_ݢMfyxuj)+'~!s_v,"o{UY=궾̺b1׏DKirtsݽ$7x]ApXm-1?2'TÖ%̯HcW]8dmp?`^I}xܞCz0,'6b'݉*V !bӶvΧâ!.K~dUZ‚<ګDQn:gnڷS?w~qP!8IIUn͎7؈$ID洓GZj5ڤDac] םX> pPX\馱n鱌*?n?:ʛ1c6cn/YsfѥCjkk{pHVQ>}: 4hvĤuXbanbGv5h}')1idx^;, IMˋ׮]Kaz>=Ajz;4tB~^&r;$6Q&]. "l; >  vLhTs^{ShxilEW]a za;~ޑOehƎg|(Qkj'`$첄1DI tQаQm˱[d %Kiҕ.΢՛X@0LL!pb4abbbbbbbrLu]5 /DQ=pI]nVPрa5|nhESDӓHHAA' CBdzwD/A0TWQ &Dn+k e͚u|ӓSJ"aDu@rb$T*kUܜ6tRHKI! "GeE-" ;E6Jw_3q&cΒMiy%I1LLLLLLLL1DQ$5)u끢9[CUdQ);),:aSUSGqI n%ңmN88<25ul޲ U3lӆܬ ]d_X[R/؈ndgsr|=N>VFYM .tmH'\Ol!J$!1M>.[*:u\"l7[!ч7GW5&8̇V&&N:4AeI]@LS@ lo`SA=Uut=/%3_mdC61n,ի, +w>P}{ҥC{3$WnUu|WoLJ|9l..%.CjR*nEp:haM!t۱X:QIֵ=ш&P0tPm+'wwlY%b6 #Bˆnᓌ˃ǔα}I}?/y;컥틌C}@o`_FKk]W`p;3ǴۯkymQ45111111117l!XWӦM[ۤP%+6vQSS3s=b%jj14lAs "j;dxn/fKQ}Y7 "l :EP(iI ! EFwq E0"$]3NlA54A:d bs]0jW{~r)mIjCoԬx^tӮiǴ}1t\gJ z70ew5{!Z.&Oy o<kw}_ﯨ? 9?!ѧ1(E(OXT>jšCkjsJ=7[׬6;1k8=:vECe=,%XL,55^*}<ADp2EG%SLO_aSa 5^/FLCrr"KWa5Ÿ1?8XpdY az뀈(tmiҒ( Ȣ磼 _0H]TY0'PS~g|M2-7k{]xDt_?dkO1$ Fժ9I>R\S=HH_Է7ɹNi@^ @D!PA`E9Y1Ag RQUGRE9(}'_&OtJ=qg:&&&&&&.Hc}+7l5<VÀX5L @3tID_cw kRM}>`E+6rqFr:qpA G[Uf6)IDUAEtC@JW#Iq1!"i)H( (޸EՐ% Aq "2U" b ՘˿YЙp}foC>}S1;JӕzrOx2bh>pv䧯fӘ|E3m2qh5ը ..&NBIQW.P#L(prڨǝϷtIqpYݧ,X4liyh5);9ܓ DHӠ=d('" GwSfe Ў(R7?nMf9I@?5MߕՇb,V re 7^w&.sbDv;|-am$N=sMc>ظOg6kۻ-vsh琹7O>䫚>31111119zQA ߾:dp;7 i:^e&PX #q޹琒!)/…O V_/N:? iCAkhfzv[$9Tգi)IɄtACbPZU۞ Hmm=l,`kv!`i'߆EjՖnr@4^/O={`9@m &b@َX#8;H<:Kr=Y"J$ H(j-2Pgã*;u{6=APJ/\; X *(6,W E;*E.U)ӳnsc $=O)3̻3μ.Obwp:)|>GtJmsZQH:²U $01u~W/6o޽i Ȗ$3â߾#--tSC ώ̌m)2my!`Y8ncjg]qhJgò{7wV9oMbp|vB^:S7V+px7ɋC =o/S,%ʂm"tt:Y۽ʻq]Is+dջ4ܕudznQq\4Nf My7y6666666`YEʰGi5M\ˡLAh۶-l-(ĥ;P*$!`6S_I!lDQZe+h21 ҺE6mۈqDT(}cy=Iڹ{"ݳSʾ숣emcccccc<b ۣ@~B2Ȟh׹?B48Z.+|OvјzUN*;$_:ہEetҊ UW1VYIn^cz_թG!ptx CpPP\!ϩSQYA0@ q9Qĭ8` haIEshfha ! Ӎ~s}2o" qg=2㗏wnDm9cJо-ٮsnƠLȈv}y<{:Z;rCaljc g[ Ʊ<ydTa@K}{Iů[ `2C~QnIb,E'X  Qjcȟ #th*&akLsWSYi"mFMto{tt3ҳA(ӣ$SKt4݉eU e5U|7##hF$Zd85y[,R\ZNE4Jy%c_}+| 6#>Z@WZNv?qҸ]ux9ፉu^{d7^n^2^{)t9|ӹֺlrEUe=Ɔ3Z8.8+)ڰM)=7W9p 4?b?3cXodo17 VrUظ/m=+CCed^.]v!}ʷ%;:A{k:V=u :@y gcccccc%ѻk6osNњҊ "sѺE&,BalV̷vcko`„>|xtޜ'1bAV/e_7}trWrK){P\\̃h4HVz*g{>SLAAbϵ]K6qҥ^DaZSXE2q8$v: ?!)) ˃&Xu]GUU|eB<:oE4acccccccC' n? K.aY&(*D2 +*P E+7i~rZEFDZ2-i"L3B*<^SӉx.4LĒ,Hld]hh4*d&!~ˊk|-"ccbeWAo:{nGR?n\ 7ꨧo3v8nw$FUMZiXj~ƈajTZc<Nt+Ob \/ ~)2em[mRSma۱yODQ.]Ln=8u 0+$/ʉhLzF*I>TUt]Ȓ D& +faq @b(!2aI)~?ID()~/BAxEK(J+w0_wmHOI((gՊ5x=֙),Zju1+UMnnFY'jYlۮ,ݳ(CW0)E1}ΥcEXƿ_۫x⑳#hb^"C=`'P⦬W @2kwwsزa#N ΡŸ޶̄U8qMہ+gSOh?t8Dc /(ݺjTzSXHf+iɸ\NIN]Q$Jy *CQF#GT٠Z@QdEFd$,B5$!\Wq9 i@^:kV p4  ֲnߎ^#i xļ^Zn-"rhO&0?`q-[eitk;I %ϊk)mC@‚QTM+)#f] H\ýaq }OIwy`' 8|b@!`Z,lllllllljʯ4 ׿i%I!%#t|^fp0,@%bPdYƲbH2L0W;rU $55,P d R<Βl>B7mez:AY0竫Y8^8\Q|eZdfԱ wJvm)-.k(.`[QeKWи05c 3W^& ཀྵ#6s1te-sr}o1}sIv_o0MI4x[] :kt__S'0r~`fӥ2,/xs/7CgkNT82aٔG tt*$fj$IGE/_w?ȥGs[(DVf:MY00QQ σѐ\!4 Iޝ4ȲeY(BJRf-D`bBDL4L`8@0BdR}~23R8T>B1GҥC;j*WFgor3cTEuҵK{\:+ˉ〉7 ^HnP&o=JQf"W0 [[F?9x^7Hy8‰/G3_{:M9m6]>wLiڷ%#6U߼I$X'Ӟ`җ[ q״L/+;yńOshN&%|v'rd;_C*J_@UwTU&%:XH<'!pݸU4Vr!!Ljۥ$Жi $ YH(("aj Ye&X31b&ZѲM*N$ŔBBWb9S0f6Y$;VID&XJAA0Ta,L^~f<ÇoV;c_*$KB)5i~ӧO we. dz뽷ڹ+L4h2.)S쫨`0llllllllWǒ|LӤO>Q@MJ`ҥ\旓݂.v&''mINBC*)ETɉ@5L0@4> J¤ٴvp\%ؾ,I/s>"@R`}tq̚U9?wg(`uZ*:FXRزn3Vn kd8pȥff2ƿ)^ˌvϫ(#f1-^ъK'Yg^6ˮyk <0 v`׸0,ۭ2=J $[Mey+~ϿHNM#93={c$;5b!KlڴxYH -- p\8Zb;@Ӵ*IFl^^7dD0 .a#)d2YY6h y"iz? y1ҽi Yix=nJJ+aƣ(ao|1L۱MPZVh,f  YNb;+BVv _rӉPRZJQ~!q` x)a(BwIuiNNt:iIx|x^ܩXX JJv# 4Q:e9t⛑u( PM_rݻw#)%-7Y)FZj:X0aeV] 2&AlbJQ"!_\rS E* UTƦA–H")ǡ]KqeZXm (--aڍXٳ* Vp&qMN٤z=x=i߆-[вER5ˈ`@yҲ֫@F]E0PNQ&>vN 7Ce:v)d@r mC ѾKgˌ" MX7֭@iiYiXb]lliO!p9 2r-W666666666.>&s<#!J$eIσB4[pdT@H ʉ]@J% ]18t}EddӶu.f>ѥS;2;u#3XB?pQ4ălH,Rp&-9 .IKEs'.-ai֋պ C8XIi~ I ur.苙!ᬧaT$iY7?mɝ$Cx{;bxA 82v`U7Z}tL8ڦl׏1A=7)÷S[᭦i6FMߺǒp?xFv6y/SPTF4MVF|k}=#cZ2ڝ?{L\|ALX-goР\#@?d;2[1ۨ)]s;qQGRI{]2;.˹_qcy<>6F&mMQQ߳׭߀@w ~foQ 片oInɉ] sĚqPIy]+| '͍)`P=_L$euJbgm;f8İv[rO]y6&C*vxiXvvg{pX&!,Aنw8=t ]θۦq:/^'.~ޞ|u}E!%3GT.Y ,K5M2MVd]QEIXqEò FPaQ4͉Bn.lz"nʼnVD"SRVg{`3N9ӎKIy%r,Jzf+$,YEvjh._U0$ XH,dDU$ɒ@T6w1+UMnnFY'jYlۮ,ݳ(CW0)E1}ΥcEXƿ_۫x⑳#8a+/NA۲BW!Tk& IDATx7p$ɤQE {,pȎl(IqSVP+ͅ<,i.m[|LXWf~"EATF,R‡ k7O |m8=; !{q%db2ң߬jwQO<ZLN:k}~9;N*+~u}3# W̺Js?`Bve6][N3,;+YK21MjC@14[G%,x{L}כJ8dȽ ᆑ_c!'yK{Ɔf++h“NRo\#`!Œ#, L9K9Wg5PSm᠋;]+v8vI6*(糃8κkOexfk){BH(RdX >o_pКU.Y\IX,K` !DbEXd )2e daLT]&X"7PU0TY&)^:O6k%U =1 anWzRryqL+F4"``X I8PyT[oڨ4'8/K\=HJM(]Vv^|Lo3:x¬w'/"o:^9}Wnʠ{`mg1;ʃO_s^gAGfK&;pz#KJr}unru;K}2<]01+θ|I7OIJFbηvFx4w^5L "N17Λ²ʄ+zYO>G󷳻k!PR#YO簣&;@X\xz~]5_іp0,,Wψ vYX⢱C٤Jtڹuֱ7ҧC:lQ^k|/^ #`˓:gsyt?7H47, 1'y2CW1ͅ*f<{nd=-*YI.*Ky;YPϥ#y:fw|$RB<{nQ̐x~O-tÏmMĔnɘ_˓D|{<;oA]vY#};$\“Z?P?.7,24&=rX +٭R_ܲ4_Ӊ}_s3SSoefsϭf\y;8u(VyZtMy/?r{jH~7zG#JSnnal-KHs#DH~+8Ԗ|2zו>}?<}OG_`[n^5f)_8S{o -yۧ8ןJ,[6|!/'9^?JH>KzOӕ λvU0G]p4wƨۿg+CO>1IN3Gk·|ȃ|XEjO_ogӻʥ>h<^{;x SyU;)L[ԁY\)ߚu K`<:S>൚Ux5iʶyND9ߩL|@%m!aqeieɞ v9 5+0H9<$dAI~>mx<i>]Gol܈BQe2b`1}.zIxN] F$я<ii ˔gƐ.ó#0,<đ$f,Q_5c\ڳ12 0DMi"KMMSP՟Megj۴+F݀!d _. $VW7q#q}V}IO"+?HiGv}&5ʽj ̸fwW0)//oYHk0Ϥs*UPﳲH%8QY=X??g97oSo%{%릠ɛF i᥍A71I:S"a*YMnT{ Vŝ|r/'T&MpgR8DZ5 C{aI)vmS:Bu{?v`&3YGCRy<7!]>m0ݝ\mW #2_uMťre'>?bA 8(?RéNeG p$5In;y4)> qI ݣw8$#Y`F.UTr894?>B,j>:Q G%~YZּIEXt栔IJ@M!0AF_;( uW ,2;daA YAdY/HU|C%nr#0.9JvÐ54e(FԈQdF} eT2[aۚe2doGP0QncO8|qToK9r";_(W)}"t验&/ hw-2 Y}<0cc{Ñi^EefA^2ʕTl݄jr_jX(@Gš$2]6uu"HTI";"9ox p5=op_y=~z4oZ>X!w[om߫ܓyiqːk(${-/1=XFաQ~+^9 ^ϙ'cM"=^jżb,S/hBlHG^9P|c$\j2kÓO繱[i [gkoo b<pߍK @Rwi5)hswm=ك/S:״=`G(9 [԰RBqj|[5y'=-#vt-„b[FH'U5.<f8;WlO2 ,-MZ|7 _fm+c}yh2,dm19vi~etK&㙍s?~:7L{| Fr>Kfrur۸H;RT;2W,蟤q{qwMJfnr:V]חg&dm켮0[bgmnI}k%dI6I\Wa [BAO6ٻ`i;eKXf?pcX$+drQ=bh5DS38 }͑"'Ӷ0Re#7tqu\ͷfwJiIcOßem7}eU9W|7MCٴ*@~9mZgBiݎ=N]0EHl$uV9C*]@SDٰ~#9[ 8>d3s/ +Knq^7&rnҽpݼeS<_Ψ.+$:N:uk~E{'Ogds;.O[U^⭇_Vh O{uK,XJ0kx`Óp_(aD,Ю/ |c+SK)$Ç_q8q8.TL nJثTo_ DHB?r9Uʪ%v5 Vtk^YIsіv\}W^p|oDΡ7% S(- 0/y#LvG]x>dJCA^69ŀ=J>,B-&;ŏWvr0|f+9OcĂ^6ˮ9<VSR)@[j 3G>I>}:+sd%[E](..s.x,d6n?_:EfԻv [ 6Ӆ昽Ui_+6˙ǣO'^=Czaт)gךt8Gi,‚5xytNοVҮҠέ&\S_loْ`4vgp\]dK7< ZUtG׳."sB;̇S~Bw\:%Ѹw&`AsJZK@-TBG(\7_Z1=X<㾾zCf|KxnT1o M#߆5M?],Z{w ?7bwpOi7qW6N{cDx#ώvd+y Hc1x_-a$';֊²"Y! D6 H"r+(MGbKڒ/cьew0+rǤTf\@,}u5waAi0ݛQV;q=IXY_w*ˉ"& ,~՝% Tzv3aKa& g-CIyhcDF~9(+oq&Th d{ =מX*gvcrE)>ylx^ LT'[ D*#L ׽9pV;.EJw0{A*;يݦL娤ǶSM4>s/ i]2nI6a.'? 8\4=N?6}ȵtv`"%t%NI>;\7'_[7.؄sGOFhm.6-z^y02=٢trig0{@ۚ[[6$dQ/7 Fm@$bye,7]BߑbSq{a?)i'1igŴpD\7>E6 Е؛9S r7CώrΒ :nj^=J2o^ě$Mр,˔½xԽRD 쳣IBQh֍۩K) |PM, ѕ'97~BQpXMU 1i ٢Pi Uz5&y[pĀg{\Ŭ<-3B#U<3%_l<Đt_L`NMɝX8qw%ILn Oꁐ$: ֤ֆhiǿA[=:qخ !cgۋ7X^5ڟȸ$/`Q!sي;Б7ˊqmŏp8Te'LuN>9 ZgJm/_ѯ=7¶szo`J*Kn\ϥN"\ZJ(+PxHpu |sZ:Nz0}yS;0bBU@kYdWԥ^lgڿl(Shghf.qeg(ltl]RNfݧ =hU爐kF>(3~x )PycZg8b Sр !O͕V#s J\eJ uBW{[cxucתq03r"~_cѦ}33г{WG-[CJJ e72Ц}Gy9CP_H/nhr% iBp*K_Yz II{1$K똼u@|ip K~A7? oEa+ Trf,iGt͈ś{x^ V"7Ե^ X Pw[$kiA,jم yI+^['L&)A k"My%H&6Ap0~**J\cd݌+7EkCCi0} *aZ͟H}eA4?݉vl.?9=5D/@^ 4S|6{ڷ" OrITI̭fW+ e]rs۝Ye>k6pJǫ"u)_۳=_~ r|Uf }6>Qҁh9=tK`_7j`5>·h&ve%29$&&o.Kn8 88X~!#+\ۻ77na.DӉj!.mh?u5,toxF=p.|& Et:g!7)f}]!ptJ2kvt /-rv %\(y`t͒ t7Ϻt @aU$!!tд F;՛p 6'%.-mQ|@V ֎]! a{][B!}o؄l}d3A_Pswhk )2sՃҟe,,oG(ZJwlk7gC,eivN=G2 "݌"!q(/E>fPIIruqve.o;Y- 3qtnWx|?)}صbm xS=zƶ붱ts!R'rʈ.}GQUth:[b>Xy>ho੸\|clb˚ ,q&[7oc-絉km$RM$) -{1t ,ksĴ4ʊKشa %%-nș^Fw7f?5Y~|?_o%Ylo4?, ?b~id5z3~.9<3Mߟ6[x&յsF:)HO@%~ʥQWw< wM9%}V,Gti="Toa'oqWfr,Wp'A=!nmc!Mwʽ\Mk;?{ (t*;nG:O/pq5?FnW)zQ瀶jZ %ѮUƷ%~&׃_Q[tMcg[ysK_Hpt%K;|b?ֱf"Ve[6M.oNsGʴN#6d~Mǧ J;~{! |r6ckY]|kr}|aZu KK4_QǶ6PX6k^#bJJ[kuqV-$xO44M8Z/M "?ۑu?dĢu̒j!.# [͘šK)/ 0AS[j!rB0_ upf@>]uӢy zӦ7oCcxm뼧vyxW?/gݼ:i}!2}jzq"<(*/byJQ$I=͐5m7~H=" ]PʘL=z1Nx>Icrޜgwd+Pg樚m~1C'WRk~q=¯2ivdfb)Q#Έ6'|q+~U>֏Iʓ*. d3ƷF|,}W+f ݸpe{=Mn;6Ov dΜ\ߖk87f'9w)xx7(ߥ>uEouŢ`{;kу\8 7ܩ#fgop1d<߹3^=h66zD3Hgg,( &i ʸNs"Ye$I!za ڈZ+]V2:eܩ#rz( L"fO^۲i:aMŬzX[?B|OyPLOo?h ]0x'᠛sWI*J q.V-olܺkEe}W9VTTĕW]a31~sM:q)`AݳsNКpu[ ))2ZH#\~d``5%{~qqqȲbiqÆ ]Ӊ$6.Aݎjlb6+L*Q[lmsm\Xo{{" \JҊW"_0^/ pX쨺FjaOHq{ٗGF 2}Ğ>$"M&d2 ڱX,B*O?vVc& ZDZOE%V‚F@ a-:0Lfn/MLЛm(/s<|rc+GiɄ>J]͏A[ۑ*]i)&`ŋF] h|>/ UWArRmJGRN *VUZsWPZZʮݙHQTUCzIgKz۝N8ۃ 4aBrHLS 2/%$35/lI XY1v;M蔞QW AJrBE8 ^!%I *շ:7$+_Ze6[ѥ[gJ]hzAI!X3 xrr/p8LP!͌df~d㘶iYVm_`Y恫`wn>W3<}W il\7?:xGxqU%F_큚Π*&جuҢz5tIr A8X?;*l2 R ]ksu[ۻs .n+A>|b6*|Ր Æ,˄!0@ۍN;an!RXLfa Y"Iڰ}{x}Zwj ĝʿl{vk\uç>KR; k J Wݷ|T%īSyƷh_aڶW0:h'S-wu݌E .ɿ ϫ|;zp[ _kzgkbzZ\e՗@9XߗpqJfs}ΜZ֌xλ/$ d}E?zK]2 ITZueX}حU6 ]C1f3m۷CZp "hzxG,6ł(Hh($BHeI`dJJ1& +&dSY{;pM}I>dKiw|O@؝_,X.2/$tiFXdY.b{2Ħ+ i@-"fV9yV,~ɜrc2<{ np#7s}}Y27 EW^Solg].EDXs(ݑƴ_!h{V29 ?EU?j 5IW3h>i\2h:}ζvwFe"!*Dm-kn}a%nuFZJ;:έ4U`Nk *:XLIu߅5$Yl$p躊Ic0d4]Pbhld'O\hFFgH) I3ۏ .1^ 4 WWCL<ϸنL-Rb&5 np'?$E!$"]xq:0었W8a6KgŠdC֩kCezyz)2e#<~?ٖhn ̩ 5 gn`mY|z5L Q]x]\"%aʇwc> {-j_%i|I, {V(l{q6rw݀] )~}3ݎ׶'LuN>9Z4qQ(Z u=M7\0%%7i'.-%(:' [UyEz']3sFc``pH?~_~U?_ N?6WߙCWjDDU"XT9ϒjE}*DBYĚST :P4UQHJJsF&łjf)'af-UIa؉IjCHڕ]Z1?ԗ=˱g+HRW֭^BRR^|g ;:&*`ݪ 7P/_{?ҟ_dOCB- c[v>VńfkrSB}ȪY4?>iZN]*ɹ#IJ:HfA_( Yӫi` (!wbl M;йtjއT 4S,\请hl#;ܫaIh^Gm^9=tK`_CLJb ,ZP(Ԭ$sZ{ee@edYcGmYC֭6D0xRۤS3!- 66؄xZF1IƐܦ~‚fYHNih/ jc鼟(/+'9ށE=ZG Ca+|>%0s&K6I(VlΧ9佉O1xȤtJ2kvt /;{'&{|6L"uJJKZܺKgv/+!*eЁlb  N:ѐO8 HXh,ߵArP;G綸%v,׷H5_ao93t = IDATwmBٌ.D֪g2J 3($9Ђ:8;FǽosHK-͟A%z=״,AX.9kWLއ精O-,bl;;gbHOoœ{5ODt^F+gW;d<[ymߛUZ'A+[X.{=hS1x!X®Rڎlü:]uzؒw1ͪkCeس-2pF:NĚ-[f/$1:]nx9SwbV/d5xi'˓l.j4Osƭ٧1$Bغi/۩r3gG*WAVfm2?ZʅϦS {=q_] ?/{ ̼&\sKX1ywڢ2ճkz ֆN#Sy=d1wF{^qVh:@a((2C4 )uͻ)#AjJjۋEuo _T}iU⵾8uCqUj|H_װ8djKb=@HXJA\9(a0jh_|.Ec1T|rڴHZ[2IЅQ\\LK֬3隟/gכ60|l_=5͵Ç7r#;"b|ȅjDmppq8XJ;}jzq"<(*/buDFtf~JZOgQe䔢0e [\:'blw? z$DnOZVƴg"'?A;4g{ 9֊]\1nƾ{;e.z+]GX߫k&{\&5.]M쁎B7n(\YjkI';r?@d2gN j=--畽n:fS{Í3p|nSGުr\Z?f')Ovƫ,([W=h6'tUe߀v\/ vʼ1;J-Huޢ{zFcyƏaM׸肋r)(* 5eXԎ[q?Y;缪yo<ېFz] H̿#˟q\I,]sfeŊ=#g?%ƙJS/{$Xi">k^z J+ $nwپi>׎|创Oz L4`Tz>Bp:ƶjy'O`֍\s5-*ᄁW)"fNB秼>7|J\}nr'kF6 $9t^=o1GNκ--G.|R~Rf4h``p%+encCp:TNsgӣk$Æ XvNXXbvl6ٌɤ`25<3~-k;گE Ϻӣ |pU+J+_s|!"ʕ*GQ-F]AjJ,X,3;R.!Sl۸qo}n=6SRMYS^Nc7>-ͪXlJ\-<5Dfo ZT㴳5:}`eUт!S;]yuRA X:bQ5^kDmPFӫou~k/ ݄!JƷף}q_4t`OZTvd呛FR̘6{阖FIy;e+;QumZPudLރšܲ,Z;h,*2hP?|#iÞ:fF~Ti````p՞煀jgwӭOVt~Uh]zq!~_~QrD$U&&JrJ? !5H $OmcHM=Mf>r ]*aMM[6{1yn6o_K/d6V"&fUql>h4"|fVpvW$d$]F:r5( junm[_ [6*|V6i{rhtI c!/޺o.49{{W&܇bORǓ`iF] kvG,Vu\ 8]1݂tqk㹷ѻ{Ns OA5-g|l:iH=^yZ$9 ,cܟ#:<~"qv F aR0fd*"M UĊ]P#QmWnmчUȎ?We!++g׾6gaWVqRJs-'&+˗!7s75]cĠpҾc;uJuĝʿl{vukv>_!&F{lꂷq-UT/-ot1Bpgqm߳}p&| =Yd65;oJZɠUA!fUѽ/"A-^*쐑@vs dqtb2/ʦ4>K: 73Ci|S0&boѷ;NIےp( `],I``````pCQLN#l1i:+Yk ךVgr|T~Lʎ}%L^ SldKiw|O@؝_,X.2/$tiFXdY.b{2Ħ+ ih*B`oA Exu{rNsFL_n?\Q۰|+H v(K4q7\BW0jb3o<ΠiemXm-~̙Mu:c;A!",D^G~JFMnŠyB 3ɧ$_څa=kJ<$_G W?/#֓UaJ/MQy߅똜 2s%]!%ęI6,B}2H7GV^~~\kY[e,1ϸpngvcrE)>yj)Wbdpb>x0"JS;%p&~1r)9[SxN$w6Fƥpw%?:%8`܆K=T@˸ B6[GI~!oLHR%T-!!aMhb9]UUDlsBL&SzMN(Ģ TUkc)+%-#=dO=d7HJ .΁*n_-( 6rw9aViLYc1m,$3&>V/6 F9M1t%%u|A<3fN"lIcꫜd:ï[WϹ埌a+Qa~^V>vGxM&h@eJK#vp RG 1U!A/}]ܬ] =dS&+˚?3ԕ9Ϡ k\|(& Hdy̞"zRtaX';ΪΟ2Ni ɢ@&M=mE<{Otfr Ci,݃#z&L_3 |zڤ|?oK^ô;v5)_ όvQyMn!vSshºFX0,!t=¨Q=Qunu pm$IJ8Y̯}X .ɉ 9bN)-.07}{1F5 `s ꀄ I@2G|9𖸂 ,+e%$%%6wƐ,c֭ZpX'jh_48?Ƞ aP_W BAqF<ȃI ]Q3ڵk#fsq\5gvbaRtAdY$Ll(",!#! D7hB-5h?u5,t5Io{|V%$+6чd2>DYczݘԬf|%g>||`.?2@giU]8s^n{yf̛?m=MktRRۑf?JϽ4K4x3}Es[ܕfo wM9%}V,GD|ra 3^;Ja=8w)??Y0f5"쬶ªp8b9 :cdڤv-$td5!H՟BWf5 v4rM'Ck7ĝ矽]j%E~m"̸ir>v.)itέG${Y~/wK>F!L&Ny,7)7HPb{FJĸ FCa o(@ l`BXS9(ԪjJDat]Ca0Pp8\=iaAm^tYm2iaBa5d@BQPLLd@UX,f¡e% _d`?/bm-nȬ3隟/gכ60|l_=5͵ÇG嬷׆hmn牻nz<{p !`]3y+aGBG雮kK/-n>7gȼ`MZ^]”+1$9y}!ۆϊ8zjKOtMfQrӟ+9'"$2:H@+윙F!ӓS”/d}xzژvyͅ=/eN0 Ǵk:JL ]ف+Ā fPM{jC;$Y8vW.nU t`9fѨ{u $A,Ȳj v#%#I&rVjάtTUCZuk.~+ FQ!TU̺*QR\ihPU\$I9K*_w63Lđ S_JlZdQQ1x'~Mv+Gd{x\xlgaj;̍\޴: yqb @ ̢+7fq[\E3̽?F[yN=+^Z{77+ws6Kri~횰+U6&:qketY#t*eTiV?+DYrV᛿;Ibh2mb9kM"{o>ZCqi9v_WеjCbYr^U`a )- KxPZ##ogq:TBa/6C%tIG/½7Kb,I,ia`c+YQX*RGZ!aq:r.E TvoN0oaƵ<-WGyi!a{l"odc:L׍N8Wpd`TrIaȺD8#q;̟s\~\%at]ֶV>W[}ks۞W'#46QZdYPKlccE9 KaImJ0m:aqagoV6vc2(97w#CT" FӪ(]uUw] . 5s!ܷo:3='|x'qalh> X, %$%b0 ƍ}8nx!hFjUCќ\۳ֺ,owCNd(VJ4T*|o?|'I8sm-msYEǐHdFP}z6raӑ,]|9[?jq`_|ֻRb~/x3 tP`ky||v娋egrၢ6[mTgd:j4V[}2fh.w,QXabX,e'F#@J@rB8j VCwV|߯ !Kٿߦ&R^`cG.!#ڛR)q:^Ƿ2vdKsl.XA@bguLM 'qs V(w雏{}Sn=9u;P' }s\@S|q\8O&&5rsϧVS?b_&ɬr IDATRX,Õ $J( ʕ }(b/$@)85+n5ּ<=Dzݯ߉&1fi8Sfa.<֭gh8\/q\ 4F*0R%0Im0zRĨ ;vNO)ךի6?3Tb| %tp#_~;oyLoϡR_[_3~LLeu4}]|C`f0N! , >Iy>ip)Z-b(bPD&&!qR$Fqf eAi + \QA& _YYp!&Yp19c?K>]J.q1" .ؔ@ ) l'McPFBcpl:CSX|ʳ<_NyGc5_}Ve^0ꚪ\L9v /xCp{x~ť.Yn^ҏ? #{{ xɧ1E]U屿|ʷWW:Uks4\<%@RbAGڨs$qc ~rMn\fy gt.}Οw?M!q*VיzdN}[(pu'V2iNZsy{+,aew_`hJՑp\|3c06H!Jި _=R:c<i~L}N>+z!٥1}_~t_[w2&R#߭H8W55ub]˿L3y-j֯s<^2o맃]:xaePtNY c-7fL2>`6}J.c^^-j&_FyH|m\#w ML/f|7#SKFcd-eՔ,G`6 s'Q*;[eY,by _ 6rpOTFq$&Ґ LH4P!ra5Q",cÐJR h5hM$h0 뺵4FC&r® *>:>ڑAFW "E*RM1=ȄλN}+ZKJj-1q0Q0 x oBfpޥzo?,@éӏW,~o; ~~~C쫺low*|`Sgw\MLkx&Iʇ1[ٳT^ ^MWTN >seZd 8qK=+hj8nX~s]<9~1 I}FP^Y\C搖aO-8ىjv( bX,˫OHRG&s Gt$qUXI#^:KEJ%(:R)rLqJBE5kp"`T*W7Wa J$%c13'LP}+$_!2-m~z`&n#4qdH&*FĸPԯ=OqݻQ 巶.)^~{6opvկqn3|[绻c{a|_yH踺?qpV˦`tTwoO.r4 _s:AGȵq@&P|Z0gVu:e2v!sw~Ay76$A$/|:/~y9ߚć/%Ym?.lgh~o+}w bX,˫ D0U242# <'A2 تZt1DQCV]!`4exxbKQ \*A#$N`(сSp4F@PQ"1I$=NTJ% az],] tɏ^q7^YL#berA]7RnޛzSvdzЏY39ggnN8i]u޼LTEB~ۭe].ѩsF[e3\íڈbX^}UJ$Y!%)%i!E"PA5}}}CMؘ=(j"q>jhmjmR0Ҕ+>2G %'2HG+ič#dWAx^r[Q_i|O}?gc#=VBT2EC0^S>.I\2k'ۍbX^}$)%)QWiH(FhSz4ښ5r j*ŠWmf ) NqgFud2P BtdD R%1Ba})TȠ@"rF(Hbcu/"}Y,b\Z}X ~{ʊnh~n{",bj(0 451 |D6IӦ2 7D]=%(`UUdٚȍFk]CVaqjw·X,n"EI&2BGgtKiDI&=R$A G)aCgWf3n]i("aiy_/ߕ>ĵX,+1v:ɓ6t.EZJPjrDD(&B5 UT:\.S*B UJR < ־QR455w[ǫg\[GR"B:F+8ÈBH ҡP 1!aYT+bEVV[Y,bXvcN;R *&bժ$]HdiH'Dp$RZ*c reݚL\uy,2߯U;j B~XDHaH{ W*rzBP.ycu0OOOAdq A,ʑ#Q l2APPTe()rɼc!|+vS,|Mw->UkoS;yק97Q=9na:ּ Hfyיq8R7O̓#~{Bvuq%]51wG{#3q4*m>cg̺EVjfSsmX,˫ #2z3Yښs,X2}$0 @!ܗ7RJb#1f!jǩ7޻pDSp e|? k@q=R^Zښ9MGl(B8 1a[I8a%1nL;cJXFRcB~Ze~Cr:yL<0Ʀi\S9S)?Ln?$>__uuqzԓ3xәq`@\~v;i}*usSW> mE/w͑?c޵\#yDΰ'cw}rvoJ#~ط6|ɗd*Y ebymR_[gܗ5+ Id4AM:(E5.DuB8y^MJ)QJn-fDo2A)Iԉ1!J 9䈃9崓=E8B$RJrFrY֤{Lp 8bX|e<` @Oj.5Ƅs߻=G͏:/?&}_'~5n\øjrޙE~uLaƤ%KնR n9#)\6|CwMQa?X•;ؓ#]3q|{877T}Ky|Cò;7ww]^ bX^q59`M%ˢCM2#p11&$Lբ2@UJڞ^#731$7gH78TGaQ],MXnv"(1Q#=ˢ7tR;J1"4[dJ!vY,FyKfӅ5#s(s?Q/ǝ6.!a}]>;}9Hmhη|Yy-ws9W0~m'L1|GXx=^7;{~C[|l<7惘7sw\Y 9fNPn],؋bX^sZ3~B+m 8$uTF_uI7I*#Dc,PZk^_]Fq<,UM$ϨaJ&i?ή>J+ TD&@HͲKY6XJ/QǴyvi:~zb0rI$svY,HUlf1&O>f7=03wZcOL8yEtiK  *&.wL朋b.mqEdMeYW4&@o`xѪxU^='g:~m|- so^ǼN_ڴ]by){'IO~xBLfϐIeH%$\8&cV6 Y)eU |y}!vw^>|-MCGRKf]}F%\GZTbtH\wx(T"x !\ztbXR5o2U~mT%XoಉYj\u/h@6Qw,B>/S a,3X̂[ޖ/wTh| Owq]w{uqmiwxNKrσ2oh#ݳղ8^ʟyyrOęT{2T4z{'$bX,M:a@.=DK{t xLR*FNAԬ"AK6`4F R~uuTJ8R"1$Dǚ0@B0C9崷ԃM`jюc~zcFJb0nEㄆtS]ihq߹o}_8^Sqաc{*tŚ\y@ 7tzgRĝ=Á[:DϜ5/竂ML2W;;}S9h<|n[tbۿR6Gbwۓ6}{9S;~P x=Scw\&%s{rp|mޚOwE+Y?Nф> /b&k#N3 G9 7D$x& ٍuT[KYW7MQ1ֵ$tXF1Ƹ$Iy^xf.)#b@ 01Dt"ɀJqƠvY,лe=Ollђ9ɋ-vgǣh{S}7'tNcs_ ~~mluGm>wM]p,vgQo?}W.{ޚ]MX,J.s.~1$N!(D)IVݛ]?wH ͏m%&`b^@_?i"ȈBBr~Ćпf ^BA &\C%S$RIR T.bt㺸2}q,QpۄbX,U@|dci TxI +>!%Z\'QO)Zg6[-=$7h)vs kV``Ҁ!F ģӮ4eto7X,bXvر8tV.}<u_24mЄea bТ[ễeWwo_E`$Mp:.ch4|5+WTT00d ,]BbCxYkJXFQʬ EjyG z9Wt8}|S89ö=jsos^)B3sYPw~:ɼ$F)* _;*qՓ+# g1'mXkd|V:=ANWٙ7RH\>H:[/Ƀv|wu^,b̤@cK;xiKP_Ok:G4k}LbnC9{E|aDpsޡ;}u9rNDy51ذx!|/:ĎD!Fz 6&s( tl$,q4ػeL rF,WSv޶iORҚUj~\vTFx4NQঋWhF\wŹcnU^ Wz+Tk_IbX,QJ,uh!yaho#"\ҒriN7MD:F)UsqR\nx6#+lR'"tc ʐkqoMo:\S+Fh:zl+\7QͬE _ &$Cɋ~lWŲ~7{Vss_]wY|dv'JR}6]W%Xl}\x̚(wy.^v;]zɢ>2ɰ4y,scʊ/vO.7_-_៞f#|>[+7'^6y8}ggUQ- ;JbX^/ 4!&xxB "T T*Nhk"xHۼl(یh4m*auPɈRhJ%~EX.+i_O)1L馈bJ_"#Ba>4jrJXFWnH]S6olGȜ#J 2rKHup-lw֯ue0k hC2M.??Zyg|pAymLx߮'.,.0Yx9x́(=wbX^/[9h[KuV5S$ƃ稷8o,y~r4t.8(O]~+K FV?WX,zocT||DI_T1غzێ1p1KVjvHJ)km ۡ[:ow ?Wx!*HV;И#B0!I&8F HLpB!Hەfj}Cf$ 5K -3qr)F>֚WQDݱ ćLQ:Ƶ1#wMM?2tK%{ƅKBOOV@GVq~XqN~~CC#8D c%m~Peps7r_@H:{zI(E{k41` X|e,R:$~X nCם-M% z7nDJCK[A6%37搡O&Yi3Hs( 8@!l1c%/!F(JXFQ{=k-?)gYa,}7*DGіH[(| R8c0B ECהE0z#^||PgW7LL!?%(J9X\.`SG J%D*_(DFubXjX,mqYgq'rWGwvzq1+_& "S-4Zkƍm'zHmuy!:"7t`Z,7O@T,EGO@8$hI%WDՋ0"!%Ug1h aα78ֆ/᯳ݫqo"Ajt(1a)tZƴSH/"4C.fE!Hԣ# ?:XFُbX, 駟+_ _WyoZ,C-^Pױ1$,tUʬ>fbCu:f x`l4Zk8{)QB\I2IRd岅lXfVܹW7׫\CRaC_:<9_ibFC\B&<XL> 'Oc ±b bX,3$zOJeZ & 8"Og2ZGH I R?)A.ٕf5V[,eDDc=5\5\CXGby8._9c57r$ŒOe0eS|_};Id["H5RB4`DqL:dقg~y THxyqTK*q<mP+  JV(G>&)Bt$đOΤ;n,/>?|)-bX,!߇~d2O~.R|I+~->8Y*2g* J:OJg7bB3 N& ĘVq@I]ōcٺ<=>F&8wWm7CƵ(R1]+W" )%PH:2X!CQL7V֭1Bb],b5A1v'O\u馛 ydY,q0=$-ichNH@w>@skz)FN$!%P5jmD8Dzwk̸1)}DA+;P3sCd˔p":P!@J "A"cv_ȼ &M%JXX,>f̘1y~7ᓟ$g}vp1(2kX^㄁ƍ4t2Mwi!u98tzGhdnhUh p=ʓPHR4LôFraқ/u3i.T0 HnB`( Pb 3P*⺊TO;_zIM8F]\Jc0--$HGB)a$: Q(A]]]i(^f&MMaX,U&NȓO>ɜ9s8'>u] 7-8 VZ,#s{I|_VR,&֒8L3{g?CۛNu]¨Ge>@NfD~@0N@ww'RƓͥɤ{u|#T ML0EG#Z +8 9;_ R$h(`b-%RIrѮ4e4_X,˶xy駹K/87p&MC -1tO<ϝf΢TEʥMM:y|bnwUߙrݻ&M/JRHE&E}՗"6D"-Ԅl)l!rwl~>ə;gs3g3+*!TvLŶm 3E,v] .ꉲx :::dRhhӴI ix5EplCm#T=,Ĵ-,7M3qkq2fH%1;|W-5k~[}'W$I4|/]v^W^yn.bYz5su]JEd2lkIq(LQO0J$ ! Yy3ۻ;;0 (k6pV E޾(RSSC*um0;v7WԹx"C5%.qIQUGQi:0AWᆯ3FѲ}ۺz_I/鯻|}#pOsI$I#o|Ϸ>ޛn;;'rM7 %IaTvCč,Bڪ"#Fd&Q]Ө@im5Qi{N_5㌳F`㲣PT!k)++aZI| f$ɤx,=Q:AuM4Ml`/HA>@xOUr/u>fNoDڔ&I#=ȞםϏ=G^ ސEi%Ia?^}ǤU?<]tGqwٺu+{|%?cȘBTi`gw7#ܣ0vR Wgs?1'%J {aE D5&Oa6a*T_gx'LQT'їɒN%Pmh<dt*L"T/W#5ɶ%Mp|r$!'FqIűKjW4IA[w3#xǷW.v/+s/ zpo>7>r(I5=!^p!o1#7$%B7dI_۰(:FH+3!9b>u:;-tLӠQ_UUt݅_ߪ%ݍ&xxqTC>LLpL DrOa!#ݼlmnXmmL9 m4;~0g}=ˌ &+Nctċ麨2E$IFp#$I`&m6%ГTF Q|A4b%PuT70 k 9VT2($RIV]Ǹq x|~6mNڿs L|Ϝ7Wm#?/HWG+A&e`@ \T:;Ѽ G8*`;%&5RS]L}]III4IѼwչWͳWחjً$I$I>BT݃)x|>:qtU2/DmyBBT6]P>~e`\430׌pp#̸8q(AaT=UYbI|'DiJoaUgڄѴ M#\W|C~,kL}>nkN_W*Յ͸ 4BYa>RZR$#MFG((l$I$IIgUeL]%*J.ɤ6$F/Ͻ(*s?h\1p Z!fM` E!`;&~5U}9ߚxW鎓u4 TU(xY#M:--q=PSÌI)yg/-YMseIg#y? -4h?z?=qZ?[nfFH߳L3}A<\zXI$I$ H1q\B&Xa Auy44]%ؙGT*;p5(BltUC2b\O\۴1,Z7.;pX#LE0ߞ۷1|,Y|^0eNغa5|Rں1T*ݝG ɯ6::eIH{{淿~o0c7sƒoO^s-K\eqOˤW$I$i__ח)̣CqqpT(+cTG[~>C"E.XYFVp]QKUI8i󑈧8Ut3H"'Σfq-](+ٵPT?^-yO,sGNz IDAT8"6w:O(:K4 էc !I$I˶ tTڒI|8/"FUS ^oކs, fibɤqRkbMV݈? R\*xua&M/n"iYD +@QZ0SRS!)HIU~T!=z4?ZwqσOP_HI4mFcQR$U2$i?pݏw<,I$I,{Q*GL*P]T)$in I&{R̝r08i^uuAȷ m`:.$l'0Xy3J$}FF=$P8 ͶXljh:+0Yd"5Mea0zu!ޓ1jK [SWU+_F$whWnNI$Ikk9rJQlh;{I'(Pp!'qع}# Pu.ExPt hfmzqZQ<z Cx4%eS~͇ kx7mfQP><@<7 aƥ9=x:Q7ئ ӛdI҈I$I$I#GUT8PxX8TPZ:zhmˡ R|^P\aDK+.ly\+G 4-_̮-V2Bh-A:k9 >S".J~o7bb6!J*kЗLPMr!cg!m!{Ν(jQx*~jxå( D ۑ,I#ʴW$I$cxϤՍF5ޙhZE]L($1U!R./e'+".D6vl\O{fz;0L=ec*ZXl Y$OoJW5|>I3rp\ c P=(M*4hDćM%+fpg.[ZȚiThw-ֱeC3ӎ,I#E$I$IJIa ]],xun**y3umtҝJ(6eLoOe-yĻ[y񩿳s*>"ŔjdS1j GC8W@EDlECc֣7UUE_Y!5BAUTU  qpa:Yl{` "\ E<Fr^y=Sf/3sT"IFN!WC+7$I$IǍ('yٌIc]k)󣸂T:MOL>jǝq&6lbi4PQ1*`eu Ӂp\bk6%j\T>drz=(( M,$I#vW,K,]-t[ɟ] h_<&cj9jvu z\$I$I$I@ȧ䳳xFPӈ^0uAL+Ɲ,w3e+I$I$It c),]ôJ^|PU$wxM[{):77N&\X@Ok n* ?@IHϏ6p1"zw#lco{:F%m$2&~tRY(*{JðcxK;(B52hG`z=dYi4\1p˄B$I$I*E%m;$ !TR[4aݖX1¡ r K2pj;)%7*&P*U4 $W \}t/%J AjWŲU\=f޾6\1qT%6uiDTנd:Iiq9%K68_IY{&7J$I$ITR[_ɘbB~EGOwh:Sz*>1 b`r*I$kgkoǖBq KCwKe Hǘ1He1iס7ŢVP[[O}Y!ysNB棧JJ`8D^A1a7C&&I$I$ItP^Q0 6"ޛċMESDTD2͆]tE tWd1c}8F: \<~?Voˣ#@1+>;7AcC)Ed.dtu;hwuR_ȸVmn/MiΧt*FW_/]}(C2&>P@F$I$I$IcԄD~!4%8tdTkun'enҽ1|y $MZD"APXe^ẸqF3sgW]L3Or~]lhogWO§PVP{}D KDuLT/ ]ݤS4MCp-d:E<&I#}OI$I$I;0\A~0a[lP_UQDJV^|mz]G$`ۺ>Ϋs\1U@Q(B tߋl+Q0'h@u'.224utI~LSdzh᳦aZ6;vaX&%O=_^I$I$IX<` Rʋ) ҟҴe3>y4TP;{^to^GY@a!~(>=]*gMbW֋ڑ*/G%d<ĵ,\EMP\9Ѻmc/#ϧPSUN6Gǎ ,}%J+M\wϰ_ '@A^~2H!iq,JH/SDKaM!I$I梨F6eiS TRp\ Bg3OatlEdW2G{2᳦vK 0} M;c_ۆI&i8 #ր/A:3}'4%X5Mb1V~uF4N~IHe>}:?7- F}]'1Xē-ʫH%tuuy2$I@K$I$IH,dm_vFs?y_#a:Kݝ̞=UX76ma'O8 U iK ׶"EB4h/2BG5l%xQY3&e&eraWUsWz\u ~K+ ZH{g7kWog: jt.>3'P).ȣ?@xCGg;B~,įeI0AߛU¸cشnkwlSW?fy֌y=ߺ#fPֽx|IlN?#[' _ƄlWJaZ/T{ ?)Q+o%;x-WDrд4wԖN+NDh w},9$Iҿ M[+0cL'I "NyY!`)^E[;x'=DB>">jٱ -d]WxKqQǣ0yB=Oߏ 30}$Ok^]=~ Yj#E%rF<5EDOOƺi6~]ql.,l:HOzwiv'Eg}[mXCmH&_KCJ|44& 3mfg~/=y w]ǒ9߬o2hnN>{2'~ݳY'~-3gn&;O$I>t̚.; J >"J"bCkU$8дe'Vr}ll?oD E⠩SpLiHzL5`T̹? '\S͂o&fqOqy ܳ+z #ƍwV~ko m)d?8S^XiStv/^ˏm gKс Ӿ<}~ yXt񑦜lٝ_ ˖Er!W̓KB?jg{3Xh1w~{aSy%>`SQ;߭|z2 +R[[.x֗ŧ8S$IG۸Qx}uXcYɌM)œiDqcz1i6nlvsfOaʄFMJ6|#MUq1[q@C^x3]aʋ"l3X$1y,b!3%0(<:i Z[q]׶Jٵgc`14glB=]W~偽f 8m)N׽\|ν@27q#gl`<ײaa%3ƉmDOp2~\C|p-`2%.e[zzxꐖ jp!eܹƣ/@u}|O.@x:{C/L\l~=vK2$I-TWLYlf6_() q,v` R MSc'oeQ]\{wh5NˤcbQclZ:l E1́9.uphps|mnh9E8c KKI8&7mV~<Y 7s }LWuQeCj_e~&ļݻ{ ;XmGՋkh}GkB:}BN8g94kpۗ^dC'|m˵̇Z\>H/x8p򨓾D[-?8f_lZ/x>Ԑ:lkzSL^:]Ͼg޼CyT]I$0OΜƬ񍘶łUiji' U25Si<&J \u\}ܼ~SWضP|$,wo=Ϗ=3;v+#B qwGz{^s~jewW}Z\䖛׿ 3}A}Ӻ/F6G6kڶLڢ8D?Ddٺm>s2M[wֆi4M2<Ƚ\zޟ X:.{`]:0Nc瞴&ɻ~sNAf{N$mQ}ެ$*='I$%x8֮]PSKrQ_džvfOi`V}"N?Pl+PvUAm/_S|p9} ^]%k[ɣ]So2@H?]bg2p~u#n6 ʸCf,Gu{Jz[x:*0q e%d2is_Xhk+ ۱QȰmy*\F\Z/j}*I$IŒIjhF \Q5eLjehc*3y ]B^A>a[LT 8 JagqҤ#oFGh" rxt6ݯ˙ߙtQl]*7O̙ԎO#e/Dgg- =diLA5T]#kr4 ؖG(8Iw:_qYYX{ $I$IO|V0B,eGk;+Wo|D7ɸ8P,T\g`Uk](7bNúUZdA<(:N\ݏ(Iq{^ ;R".$Ou],^ 7k33H(I5ؙ'j.`!6!t6C?΍4m1<pW ͮ^*DqsGngΠv)|<YL}f݇#' MBGsbz_3w=@yap0e_!ǟ;gyH,j=_B~@E0YV=| 5STļsFSZ!x` /4qG| IIe2U@"R@w?/H[xg(m:j+Ҽ<|f6q5W5 YSǰ],k4b qG?'`"WپPKk0]ֹ`;$uS :Zūb p<{,bߎvٗ}ޡl܁Mc2,\cJ̿o\8M,h3#ޜN?M/b+5gMbN-oN%0}sMK{,?ϸ>0&#yKM]MoKo&xTe<bӮ]s)M$'8 +/e3q+gYs;8d\=?ΨjtvMx4Qt dr/g,\˫0糖H]mѰm'T#4lʤt{'+^yIsrXP2E[w{U9iǝJQ(q}}X 6IrJЧ`^ǟ\N]`eߝٟ5lAߚDӂ4%ݟ?m`}{ИJNWIaXCU#ۗ.32s ݞ{ Hۻر^xw:.hu>v8xb/ge_|"pM+kIfeGrCpT5+3`yiQ2gY eݼ?=1o8mZ}H6x924omUXX~ȵ.bpu.C3>4pI;M-O %5@ķt +WT}8 N[dc0Y\cK_7є /,k9[Rΰϝ̤ *^;x4ʃqcGQSZLD ,[v*^,]D]QJ( .r&Q`5ࢳ`E33'M{gZc- ɴMaO0a3ӿ!ǝyMѷm;7`X#dIq]c5QN+Y+̥_cis`,|Q/[O1J6lf?|IPtNQ2C*|DJCOɥ u˴ 6>%O pye}W70e,v*B0 x%IqUs~o٘s;;JU vr%{_\k ݞwىkDk7X 7垁ߙuER劗mi| ؚVU n 2+`e-[ !_f͜\~,tgiz1y> Z'uرA@dz+uj3>{V|T7_vjg<a!2P|)cCPH8 KXnDVpasxđƵkXR8F88/^4(iP 抗m7TUcplL3~f&lf0?_Yq'WSUAU@є!aL+V2Ն}qs9Uh韬=  [FwkAz17/Ic3-k:3db) B44xپ4wػp#.≣8<>uI}jr}zZdbfaCk2$ҿ,[Bʂ~.RB]Q)k::ٖTV 0pMۨ.p'rIS3n,D/ۖ,?9}]&73fEx|T.xunײAiQSUTD&ѳ&qKۻ9]MW̦*H^ ͛hY""McAtLA d+I#Gap*ؽ!'/!ܽPz /W?;4AEl?=6l|| -9rw۱Oe186 /,fp${}Yk(9;ܸΟȧ $v4{^h;|pODp̿i="Wg<?5.RӺoG,}K|4/l<)f\ֳSE!S$DU i[3Q%EDiublY3#+S梹ӿeæMt91cWBV^F1iϠ6%HyAd蚊"T\ab&EUULf^<SF0E&sJB/~o_\5p2,vNвc+2$iNz?N."]sA-1]tx"6b`sU%xjE meᲸ͢*2l+muzofޱ7[M&+Rv/JuX<_f; _o^DێrK\em%W劗{?=W|w-m28Hp?ҽu/{.FpC%`oEX=AU;۲D&ZBO@,ƫjbuЪyk}e>$Wd/G{Z6R3u{{?ЈP:j\r|}PufUM4#|vˠ6x|'00J9녪.NLUIZLs 72+bawå۶%{( - "EHU"(EA H E$T-$r)\{m?.D.wI|s<3μ3J@YimPQZDfk 5Mm|1,#B4T-Q2$bS]k# =KQ;S 17H5&X*̼lIau$yD9:VQdN< < D^n-3cxГY6gXj*i)$cD#_Os1{1<%eD<7U>c;4ueky;nNPOzTOyew-_p8#{Ӹgmv|k|/]^}㧖p13Y2ڶUDc9 DcWn l XG|Dtt.됷mëo5sEz$tnӸ%V$Y娟8ܨ'jG!0Lozb y幆:{O|tUnE#r78kМmfї,X+W3ehy>d>+V,aC&2'Ӳv*tˉשP,ܚKe33\xNE+(+ vSeQRTH#ˡ2 t}g,\ʠCU- 9ds|UZ#2SWёoO>\MAm[< صo̙\iLۡf͞ȡ#;ﺷ1_|>f̘$I]nxH?~p{D@XB֦\4}`s Vy {:;g~cK~5~iVXS%1l𸙳5f#I`#uWb}G2O=ld#OZʂ>V_˨7b05te mtt#Mߒb"-<<@mc@ng-lI}uӛa0 B>Zԯ/4ҝ+*"-|lc Wd|1GjTuk"W%ee!() O%8z|ٲArx7_G@ U~{mmCA*V͏~rW:bań8e,P9a6 I)L"1zH_ qh(?w>]T.GA@N!@Awޚa_+?)l"X*13`O~.\B{ n a!%hln EsSU!6m.O<%3Okh%ɪ CHvc9|.MX@Уt{ۓdOYtuZExu}^@dgAhEK[AׇGVb[_BU] ,~ sc9Q= _@u]}B>./#m+dL`r:0imk7R5I =Ba8L<©$~GG*I"d*jԿU8Fj/*Xn"+ *#T$N Y\./VOqj* Vl(#PUQVK!L4MRi#- ffTRS*NZ1h K7$ ]]756JK+M( or1x|n-]I#MlTfqP.%)&.<Ƿ8ǥrΉ?dkoat ̑H'()(97 I*(Z/Z!fsmz'h{Ms4͛8dalA!@ l?_؏_UI$R|aўba#I,a& bD $Rq(Fw ?êwDVu̧SRۯ/$aʥ7A,6-ʋɧuSD8iU L/ɤ W1cFRW_ϒe@ `aY+7ԑMgѰ(x)tY1c&vb . ded,"Vz\<&p5o =':%TFM\x5eotNgǟ aʤ,Xacc  t B^|=ƍ`}m Dξ @狨"?8,amA!۟@ @) S¸F֣yLo8G~M>êPLt{ 7( Ee}hH$#Bi`<G cN$L.ٌ$Dc&¥m7ާoE%eC1,%a~pГ7n(>6D͙tX 67@ X%Hg38㎝Jcs4G΀Wf$@ %-RsPU64lE5'6?qcB(5i IDATs@.JGF.T'G}8BGF%dKGUl[_r Qn#fuosk+E0ö$Az XYɺMup_ 3O>hGqÇr×dtIFsYYz EQaZ=LKbX ^ mk!pQ[D&e׏[s~c-Vő2hP?\rJG I 3EYU0,KH5Fc: jZ2.A6gb˽kyxK>E>B~rKSIYxIhN-mm^~@ aBGQPd 7 6:ط ۟qqGS dȁe$Z(^lBHH XD )2K#j.v9=x|^$IPId3 (_y Ē%$r 2(躎$簑1#VdE0 2 t U  u;=KgK5Fϗ.es{Ny۪@ߕ,n~ˎ3] vdYzook|rs<53qrkkݟ}zA.+- زM }\N.‚r,G!յ1>x̘eW¯.? 8!P @Ұ- I(΂lR&Mms eȻ8#ȑN8NaC \,ea7b9KrQVRB4 6PU2l ۶,l SL ķ_+8uVi_~>8[^sM;U3*/_F&[lGleٝLe6gkm`NIj1`Re2054B*rjh^ !CƲy|6tJK;v,CGE8\Hae_$9۪8l S>j$4!`]QhG C˺~&m!K&e!* ~E˖cJ2CEFt\+ N҆J Kz؂1gp񽠓y /kIW)((v}Wq?ۯc섃¹s[QVDt[C{Ew=/nʙ u[mO\Fv=WYEڐlŏv}Ld|3_u{;knJ芓N7uWv8g q*ުMn)o7Jd 5yu֕>'6s* wsrf'd>=7=fm'eɧ ʏ'F+|M{.ӣ8{wr0LFImKҸޥ]w+/_uW;Bwڽ;l~g]ϖ{|68JO= g YS UCKLśI$xdq}00l,DA0Mt3K&g`9d4T|.w<U^gwaC"M!$0.H=1f*;CϚeۏ򕗯;ޝ|6_Gv z= ߄K 9(H}| "chJ8nR$) q䤽[Ͼ%s4ܵ{cr5?G4|=} ySWhiogTS܌&kHvd*C{4`H6I㨆? z{_\##3g1*mu%Fp\0<ĩ/Wo}#4/Y@‘!~7o^)I2i`hdʼn ;mY#,;GVFkʳ6n^lsBZɗo p?+fZ?ꪼl_W]^wRwu6/nUŖɘ:^0ZIdLlG cdP[PONxw1+Bkѓnâʥf~sO^G&:HJFd号R0c}`jIkcA[߻=w8S5m~3_ mζu+~vn./] ]v7@eqXbAu )SEBBmd TE%Jĉ$::OYx%brq0~}*7~MŤ} зiSf [-ڕڶnh oYEFϑHm2diV^Gyi9ߟ%Rt(vcYlӲ  u;3\rOvx`Lpc3D&-p}weyvm ~x򞳘0+AR|2v0WH;'8%BYԷ^u :q&䐩~\J}'~9bO_\+^8SŖU%7/.mZWyv{^Os$nlގ#mYLKxr_ogqלF!\zG/﹃ٗ(.zGQvDגV޿vn./_#6(nZWb5I2`KHe[HXE(4$aY S&a4܎*KX:hjmfx4g>H$,NER$,Xd^|wRVki<W{~y(%eEȲLk,mضCs:i"X C --1d"ۤZ0-g[,3@ @ U WNF7tW%&b*˲p(*Υ\[hq $#2bwʒ&dY6ll$ 4Eڰ, Mv]tVl4 .'ht\C#́cƲט!x|wh5Vl/ &@ 2g`C,Y|XB6!I$A8vlI) `&8!K֐–TE%glUlHqrH$}(B<W%d-fSFeH$~Hk"_nj-i=x~}J0$IA*ն_Ux}lBM$˦p8Hk[zz"1**T Ĉ>` ze,0`ann,K Z)Kԣ\yOؐ5?ۯ3g^#wr;N[ha+83t0gsU ̦Y/ﰜxng:}6Jj#_G;w \]f.Rsah ĚѱQ]}qK2NyOY¹s[QVD{]ŭ/n[~0wϸP/"g}Ld|3_ԥAwڶl?ȟ (?ô-5^^%ts;܏\qA'ݎ`\A|/gk/浱9[8e|önۏސ^O/~&}I~x wþM#0Z>n'ikb$mTpghIbz^ԓ.CrJ$)D7&QDȴ1mXg/.x SômTBm<ͫQ\XDA(DG*aضDYYٮ |NC N5 \N( aRഩ^d\:ǟ% dX]KYR I D7f|z= vN=휯]oElgz\'85vV|&.\KSpCY18zDi{cTx%Ow5opg[fr=79M2ͽ_ğ?O28 'QvgD;j߹9ymiLo|4n}lμj6v67~B>0txGs{OCOooxK9{%ߵmR;0o}r4,Jħwa;$)A+9eGQuN8 zڗ=ť=_WLX8:3_˗n?zCn>{G> ne/ 0O+<tRDk^m E$$qbӦ@ͼ1o92i(.Oh$EJB&Ԭ\GL~r&M110qY`~Ev$1,Y1lѫE[kЧ/76t$I%u5C^V3dLA83@I9^z#lKBU4ƒ@wOSGr)|m}m㷼OzYz}Aro s$I2[>6/Y@Ō ~ m)k =4r2D趍,;ޯ ~>[кe(pvוձQ햳^]ۖ-YF?MsR.~Kò\0<ĩ/Wo}z4zT۪C>9|;|>7d?vw.}]DWyu\9,;SJxQIG6Gqː$;Ep-/|Ke*HבYdC FVu1dx>%hRX STҶN) (6Ւge#ܼ_d Kd,NÆ##cʵhLָ'rLnpE4֯#1FtC&"ca`#"_`OoBeRr%|.-7k7O~k'ᰟȜO1RNb .ͱVA; Y~xڵr-T_7Sm}_29]T4"/1',UGdRqgY. 񝳙Ds[?_6Z|_Kӱ]~s9{*λd=]⽋ ?cǻ@峥T q)uY^G=ӏ<%ӘĕߺZ_%go뮮wfy]Qw}֎|wYwmww&H~LXatU1q8fVodZ.َ4K6a:]X+ )PI,dK{'^O0oU _N3GGG TɐY ̹W 7p--Kn^e=,`Ο9g_| 7s/Q25L=;g6_ @=\_^m/pOp^K/$YG"M%a+榿ܡOu4Imq1ەocƽ3;ʺyϲp;f+zKo}V[3z[+x ="ˬf3!LW:oH?*{ Ǿuz3v>7m[麫(o[Uugugݵɗw&rΒ[Tpg84Rh/ 'IN*5Ah2Ed,[-?O&ZTij#+Aۖ0M#I}aނޫz>Gf|ю, DڦV sȁh:VD0㐉FӴKvk7~,d0LL61N .3ˡߣ͜9kV2i;׬ٳ9t$mmmL9W 3f@:QG" [|xqm;| LxP䇼=[y}s3y1%M I7ƒ_{+V.%sRZXDƶ>CԷĩiF_aT1phTQܧ"do HzQYm3 LIb ޿0ld6G:c07>r@H /DĖe jw{__\*NEkG]aX:exhjogjn? vdiW Xa=@%gt q@ H_=%ؕt2>5 AtI|z廽\|2A3"P>Ty^Z70ǍB-.F&%i3fmO0ځPd7U=CSt3mX,˴s| \HiQK8 H,-R bb6ө(2 ipk\.`9LsZ[{**>8鄎C__#Pyĕq^HoB@'|me:xwxN7*Jk2q:$I4Y2m8.*++Yluo3յbY7>EQx^&ƚY&7b, r ee EQMsSz[FMI,u@ ]*KMY|p}vÅ'x]LLuVc r:JmjשúvlEBS;AD%9ġ ༁K0xWظlFOEF v3d`?6mB2 k֬9J0T@J4MC4$d@"u90  3WIf+gMl468;q/mZ8njo[h.o<޴ m}]fnzz~Fdz뛔Gqi^G/./U ʪ/7Q\\f,l[`60ũȒj(2,f|ݎߟ:'L3w_~>};gmY*^/2zv7>kCG_fLf:.z_. @  (B,XaJ2>}sj[y(ed2} ndsSomSAQic'劫.WE+q:\(XFsSdƲm$Y ||WߙǐŜ;8)gw{;FMlcM&M4* Xv (%Qc ҋ zV+~zdzS>|i]_īcܗyszwǰ,,'pQaalܺ u TG#G($$3sQ;9XOR0'?!ae.ghz5i#E]%$$7rO~ ׏9xv֓,bƯM u6 ޹ d̨depvC mY=z@b$i&^RUj AnCVTIWtaꃊˆqʺ6}fA5·78gx)&?UZ㈋Pj[/'w@a4onfY3o#O%wռ9m8?9h4كp=趭h4M2L0ɷ,6l  }ABO=\V֭e|VH8s_OÌֳfDʶTLCF6q] Qҝۺo}Zwp< E#c~ŗ]YgK~y?)ZINNEA ,um"8TWWQW_PJ!L84)>z>jq@:x¤;di\Pѡ32^u-Ջ/LQ)g7s]7v~tN?}ҕֳGq#Yw n{sfr3{s_Wߦ kԧ%(%Sr(wFqa2ƞo?ijv8LyC|35f8rF> ȴNڝVf02'@e=k4FӅi 551@#m -,_;oHlxqj{I6(,CbIO)8#3L0٥u|#f,*BvIEeSy)?9?3+og}&AJH:qFHx3h~uX.57/gGXC2 /F[v!Mߺ,_Ɣiw0eLfSd_}1%?\l<.zpaeIkb82o¾%BC9`/X;. t= k*Ck<"5\1=fwHesGU0Nf*N( RSiټ9Ӣj4FӅH۠8/LIn.y0gJ23 I8ds!{ryG[拯ai?Dm>+#Xy#;4xfS JKK)uoq񩩫WDcދ8;y"''7P^^Am]`H W$=7ݖ(׋5NGڻy"pgQ|=݉/㵋OaRTY_ X|˅|0p_N;^>a8GyJȟYϳ 37ok,²g2뒽]/{77.BVeh9]W/WbW3'%/'U GOȪ`lD h4+٣oOr)umK6bY/PFG!fTWis%oEa I0=٭_o~^~S*j#xB`g$,&Htc.pOV^C`6 MQ+ң ?˒\;~ y{ p6mb֍QK>}}0p[U*uЇ[i4vMWqv< )xu%FZ:g`=@Ɂt,B[oZܫ~Ovu0 ;Eq?m^҅xqmG^zlHOXZߪ q>"[|gE8Kúex{ΞPi4FijC98Ns9~ʖFTxPS>yٙ8<09`A;$\~w>`OL5Ib[e)յ5df1D%‚{ʗvK´gusEyX2D^^N,XR߰4uע!%M(WH9dLф'Ytci!wY e4r$9~M'N`e]̾Xaķ0 $0N0 h߸՟Fh40$1@ Q$\B"8wУ=뗯`%b*K$ L$6Iz>![+{B"}T>o6X55ɶ:kX{$ XBVRi&hKlw%3I^),."q**Kխ\/[Ml8`j%8ι瞻ӂ* kv1YYYԩSX*F4Ci͜=aCPSSè_C8qbj{A++"h͏Ch4?cgrJf\1p&r”d2e>b AJ+ۯs!ccE A!B^n&1漱d1Ս6h }`[e4ijR]WO]}#e@oH-ᖽig|?~w/8۲OV8Mi7H& ĝd+ 5Fh4Fӕ S%kjln9CC8KfVJ)Lƙr`UQKvf7ܤ&Zلly3-||ʷE"Rإu|F5MjpG)`pٳ'Ńܿ%KcmLu]K$'+N(S.q!H<~t2+5Fh48hr]PHP(\bIF TUWC 1ψEb]_|d& UL*AM{1ׇp8啻>x 5lj6Bc%,$C;Q7Fx+.D&c sֽ'x@&;3'F˩zFtVzFh4͏x$xP|PJ! Is\qF=X{zl,&3+hC3~'dC(S" S)Pnݺ/pP)o e1ո df6ۋY`5466"<=4 GHtUnSFh4o.v1@H< i.T lm8ٸzS=GEu#Q[*MG{@q'hxI,L}C%R:pqhK!A'p䗔\5uq0HaA1x jqp`xe i4Fh4.bxgoh1 &BICQQMQlވeɔʠJSzFh4lgSi%qGq>#RW t/̣nhb',YwWބs%R%JxʗضMϞ%4El8%{wѳo?,ˢI @EcySd.6MAum-4VDZi^I%rzVFh4Fhh-#i B_O<J7o%+$ܲ7^JzfQTKmQ>mcP L  egҮ/k5Yz y9M2.1 TՖRjP§/fH&Sv57DZh !R4ELh4FѴp|O !D))SU0p⸱fr-J8wGq̨ϰb٫痀)0U>az jR:΢AۼX,ASS=7l  f`e)$iںjzW>䄳hTIRH\<,D)Ї[i4Fh4.c靓Ò$={4JS.EYXū$ٽ<>d6>]t$1-Xu]4H:$s|/cUd` ;/E;{ѽw } a_aXP^YASSyBa6B5PR4DBg~5Fh4m!HrV*(!q1(PSWz.Ƿ[Ԕ7Sv(n1dP q+H'QZZJM,R*&%!}Ї:k4Fh4.2MIR !@D]$>$]{xǴ  $+W.5U^`&݋ 6|5;Bk4FF)}Ni/z@L|{XWwNA9ʦeRM=8#9~)S y0zI|% C0I4&x@(eu|i_߰Py(CԦgLc00MIFP55u45ԓE(..b+a  8ضM"jKh4Fh4]DIn4IzM(<@ +4l,Z|_6|0}LяfV/i { @UTx\ߧ^`HlaI8'=8Tkʹ 2LFð0M a-W(aװzr-ZDsp#l6W8,﮴߿eUN;5kogGdhvvWE~}_۟3sn`@p_ZזLt)T߈/ѤD@J2 %\%o%xĝfŅi_00M41 ÒIJTe9Jb @JZDij`u4"X:H8NYw_O>ߟ|&c'R߈iU/~Ҵ߹z̭ߙvwΔoW賽:|6Ƀ<;ggqkm~m4M籾Qn1]||PxOqQ>?ޟAydk{l~@qGw9&2-XmkP$R(s-(aH8TgHy)O>`vJ`Tjf8Ha'q]۶qf|G;p?}YrOKnϤd=;>doq7~G ;n 2r}34[ML^1m^O1zE\ raq(\,kj|'\~'ӝoM?UdnBJP2?4TcOfkOf{ٞZY{uN-_H̕7K_zv5ПPw_^m5ta#.3?JHHa3nY{JƌHVVy o7$;$d _|Ntadh-ysjh!E2-q)퐝}v#er̈́__t}A:,^k.t6h[-W b R(|a"&LF>ʲ 1 8rj"&@!9%Ki`gpˇU?JMS9ܩ;]&d3^o??IwӸjMCyge8ڴy.{e"'L̈́A%=&n#NOwl%䗼;nV{cx&NiNh4۫ךګtnn={g:qp0;<A3i4VKZ{8mB^9e*J9<9[g*g~tN?}jGdOG: KVv&C{m̡ 77|ݖN|_ϭ $E;sO^?nIU!k*hS9#e9"/uNk]A:h4-!*C D W!Q ".?wwD߇]% jKy}Rd9l(#1z>'H:xD,aaJP-.r| ၧfv%Ԟ_J}'<?rl CXO]m=yY ח¼\P8$xU0KP6W͝`c8%|.8,Ӄ*i_5x[˅gCO=ϥeEGi~k[/r! ܗξOp[@L_keIj~'_k¸ ,-zKkmd}-.:6wjKq>d{W}rL/)fcmu|?)Ga45h"֮o|DF Ȱ}8_RẢb>\Jn%ضM* L;l;"-%}%?2ҮE ,BP`&Ӷ CB<ƆLՇmBoj"-@s]LHIh4?hۢٯNN8{.g{;g\K7l:xٔtNvn5Fi qh7yɝ\w@jp')oNv&Lfvuaam~S?4n^:nc;gztΟ~3&<`ΘK+{&KT<͙JiY-^s%Ss5L+Ɲ?úN'3BK?Pgɿ֧Wu>ۛ_:uEԭ06uپ̹/^eeƸ~~̃m/]XhxE?OQƗ73&-ۊ1O:J<Ҩ& H[iM7]; dʹ_[mg]UΎeܭ<6=_xl>]h:e(#TjrR({Ha ([8";3LUu?Î-/R -}P@ H0dh}w}$azΠsiAX]MVVV7uTV}'uʙg2l0jjj8∣>2q=l짏D"'w<}xqo~4}ƚubǍ xQcKn?v6+Wײx[=kD /𕤠[6p!~~^{α>>zSPMr@d(‹|{aV^CyuYazv/|Hq D:"-ߋsHMZoMm>`OR}<7Hq<'@!<wQ(H!H)C('PH)FC =FE 0)5x KVh4]yyǧ\eoWh` J-RS#+R&/X%e 7o t+Cc~qE瓪~NF8̊Or~Ov|i_ qJ!pI.A)aM +`8͛B<?a6BV Ա؆Dh:-`ۄsӈEl}^O|74{ߟVFii#$ Lk7K"}\OQEΦ8#Azүw/z:K+G~~.e[YdqʗW`&a# (^3~2w*l֯\Ev0y7O80ʨqI/RKRC0 Oa7ԣu1"8(#D[C|_& rQJIx4#|ZĢ5OVVR*gze̮=/URJl ҉fRjjA"(dP8P(DϞ޽rkk`ix.!F헔h4Fh4) V+ mrr1 A<qCNN6Mf)p|jV%AB$-QJTMg$I<./3bƤ{ytyUq}`F&99Ȍ `! <ߧfu_cr),n}Dh4_!rz² Z(ba}(际aaYlm,i&Aq<>x84&"4cQ8k K l۴v[z1>[G4'Rأ3#H\Q[!J%vm!$cM~aamuSeLx:4WҪ^ۥ.י:@{+tݑ|_8YTŃpϿc_pЎg+s/NЖO⛸ϓpg\uM?>Khԟ*OA0dSƐ )| #lR ܱrWJӰ0M0RW&Cb,Dn#/RWI!Ov8>qX,A,} 7A<02$׮clڴ4, !PJa[&4|si4]A;V:g=R{q_Ng䧘{񟇽;.OM&rd6geGgά'3knS~-8_w73ʫ@SdFxv֓<;I#^g0xSx\u6K@>3mΚsO^YVL3o6DaθpәS̸6ۄ9'>ʋLeΜrTך>s/y%Ӧ=ܹM8ϭ%ecseSsE#i~'77QKxg6Zvr9좽zh3jWNB;3%kI8m L:c.nUCd;tm:mcAF'@ (*̦O t\,$1lI$k9@J@"¶ 2 !pp!+DжMSXw x8NncΤѼ[IwӸjMCyge8VKy}V2K~ɻsnϠvS\tS,[<ߟ|R9rTJ7s[Ng'L̈́A%=&N[tmgDw<29Jȧ.K+gCG>ηl'M/VRך>u~ok=~&Զ[+K:;;ml nrYmmrVο oSvD>F\߅8dO)Zrq_uk ߭徵 V}8$xt.w}jr꨽v6h4?|!<A,Gy$NnExuRYYB}! al.J`ZH$$!'A*$b$3lP a!$]~|Eݷz.6pӼs]֦;uO|]\/޺m-xtH')eȷi-y vI j4>5MQ, MP.!E,'3dO:66HxHS!@"(0B;[PxHD#L Avf;AqYfcʗvwrr3q]h,yҫh"U9xQ 0׉ĢdAp=< #)rq{o>=Zmiv,=[l @ h|.8,vqga E۵1qev)[X b٢:/_}fDYz"e Y8-}9 x)m#U9NDYhlp9jszَO)\rL;_5uOZmpExu²Mqv[kDxM4~<ķE}̵sN;mIհ9;͎6h4?kpmr231AM]M͵^>пG1C-bfwa!۷/ HRHZB>JlD;ޖu>٢Bd˳m3yeW=`sqOfmo<:g`WWo6Kǣk{`QKú]-7;; ׾Ve2HϰEEkc.GwPr9m.K:;(c+ wS:syyf?D[\= +Ϗ9u:4Kt[;ULܜǥ[ZU?ʍ'i@$osly&~;~ئ k/;k j4z2{!6ᐍdlˈg؋wpu;Dz2<) &ҰJy445PW[ ^30fθ[==3KK;[, IDATuz6ӉxeTlW8D ++*bDb)*($DuE5$DP0~.6MmincSt7\|3Zˌq( 5rwsmv60҉s q%sO3Qd;;~i7?xLe&=sfP_ĶyWmNg\-b #qOP$0N0 h߸;Aan:lAlwEy Z^8 HLkLDû,مL{2l1O:J<ҨӦic`fgXiKtxM[wfmii+mmP8tٲd 2 Il|h(Le K8 UC"P&XVD"Lb{3#閛Old4%ft|+lҲ3qO,Lz&O}&?0ʷqƱ?afc74 ؏ܜ|)oZE6(i|>h4IȎmZH0XqɕJ~a(y[s+ o?]"faW>($1j2xX4J$E5[yc$1)uoD4Uõ=2 %+ˤ`3\ Yь`݈d?V-Vn"I k|U݋mmg?s%L#Q)0q ϠBB27CP&2zX‘iDR&ȑĢ! < i0ĭs3Zħ:Dl#…_:uc 7sF]XK/}PLHXZ$YO > /JU±@$T<Meq&YK ({rzʢpo" EC|K|k%jŚ tϕ'ç("ɸfEP$LoK "aW= ײ+JMEO!Mx߼rDk"ZA1A-Esm#A"'Sy>F9/4o9xɘM0Y2mLz.矧hϫ b9u/5%iG Zǧ);/]x v/m$-P*Q<\DQT\f:Fi"_=EBt,Ǥt7ʘF4ݏ/ql4U$0y#AAa[M=3񈂚SQ@ }iէzzX{ᑇ`5z4&D?kpÕW˾Ov<( yk<<(*HG$  `K\qedRB$4EEUdY+&]'ˢ`1I %Y~}El3qW +)?G:KA/\E,jlbiR #tMFuwҵzY ~vq%;KCS3jF$iYM˗a*1ʬ璟zO1Fn_/_  `4BQ471~L#Xxɧh!)^ TT*E{{;3p<4C\S%|N,&C*2ROkV8 />$AA]K7fҚ6J9ﻧ0qP< U@Ved˘2u !؁v,>ƚ4P[SGKWf) yP4e2\t./"l?g樣/3~P,ʦ^mƎUhnM>WȘtK@plUIԤǺu¶m4E8'ScVoim&$瀃ϸ' c2itYߺ6-S0o{Ҝ8s )_~mȥםĈ sяeveƳ{Y\ۯ>s~cQ^jpyK2FH? pu[gG8SNɌ):  ؾF֬MoZN_?-S]י(5}w̆;5p$5-t:cG.$4U=MkW6efdjɫ_oW>=8_Gg8oSY6SfšC(mǎ<8-}ռ~IhEj}g_4貓n~~>gso|c'I7`y]0P|dN>yЂ  ?>]E$U0  ['Ig:CUxq\|f|dj|B*g)cXCKK7ZYcjy}C;Sٛ8;z{,{9i%=FU_!6#3vcQuKEZ;$|.hQR55T,^Lį딊EBA婉DOmW.,:ǞmKwϏ9~{bez/u\>o]@?j $ߘͯ;-T[vG.LoNfo}[yŦ⢳bl<n:$sD_A}їE4<nOaNrجx§9m f*yss:xh{YchFT䅥obttґ⸴|s |ƕs%cG2.{?'񨟚Lkw=]ijp+00a[Vm#F!)˨ʔ =MěsoW~#oy~Smy|IjH ym*롟fpigS9m.Av: qt[L-?3x;?xy8oQH? DAGR,WvMTUu05%q'3!UOrǼc?zx#okdkP EB E!,|}L;j$ʚ{mۤ6! *DQ|~l>GWWdSAUr!h(x[`A EQdC6b&Ch˴VV'~旹2M_~u|NʑǮ᾿_ɓGS=}4Z,7?Sx{5\(Mjto~>X@].Kk3Nrŋ&:m*]>lqAAi&O{J%Xv--ZIwsr& m%U_1s&c؃u+Vyw+T1u* _^GoH Y\> JMNjwآ 0N\5]|5xD$itw DBd;bY"܄ 4M$|>m~FTc28$I PYs.a6sۮ;f4O^Fk>ɭ'QIJ+@ĵ:^-w<|=4~-7>3>eVpVJ]16Y]\ͥg}* Opo9MǷwߏ~^J>y[_h3AA:6p 1uU,7~JvnKqc $ j4gNǴK[Ś 똿%^]Jog3%IVԊe9$aR(1@"`[X&XO{WLa9x=cӟ饯7m;HʀG&câ 6Op%  $ I1@$ɪ>J/JTGT EqxHj+}Vn  Q[di8dV㦻mUt$S,v^;e d jY|@:݋exNP Y۶!^#YL] GPtYS[4AR{E+  Γ6O $2**P.3ylӤXH%!t]BӂXCTDWU %Gn}k׬GUU>s?Bs}-+׮=KW;/=͚upl{%.ُhBCT\F@T.P(5͍ԥjظiE֑$E4Aڡz KA飒8 Ea e`H< Or- Orq̜1==$!0"Rq<邢bX1s$NG[1|&>"($xy86$bqGX5LkBϕdR@X$  (Vh"Cb"{N  m\l)@Ar37&]uMVtF64~S^_ÚneFj.N2#!S4lLa;%R 1s$Ǎa6'˧3'W ^.6VI|Ó%<Ǯa㳎'h'xe*xb<r-p($.E!&PZA_~{ʵ]MWVގuqmGb?𵑡5 ;>aAEV4$Yd5-ɔ= Dž~:{3lQ(VlZZxmz_? oݧE^ƔS펿ϯhͰW"Ix(+) yZ ۭPg- vNw0z8 =Dm$.$`hL4OwO\EQK&Y_j$y`KR*39ͦPvy\s@[5u$p p%iZE( ض) :paƍe 474Op+fM3EEr,WLn%Ck_3_:Db]cwLe#2r|WpWrz)?OtWݷp39`i:Z-8K;1sU+o3p|jbXznMb \r|,jpXsbUfk3宗BL*߹>_fd )[9$~,wg{V`A\ʽ}a.Eݦ}k9.Μ=p8Yߺ6-S0;xuT+ڲjRw\u=c >ݮ0rn?_|;.z3%>8Wlm Aޣ)Au!0 -|кή> K,)T~b4z8ʖK&/SP0T$pDu)f=5h$H<尃C}I|eٷ9vq%< Ipjەp=pG6'J*7\>Uy꒵h3 + saL1k}b+lh!pO{)WrͯyGuj^$~"?5/tjiLx8ܚW߼eKǞZ܃G.2<.>d,o|9X@v.+Q.>\崫ۇ$j<_ Is6*o箇3'sfsʬCXSv%w~om%jm].ʫZorg๞2FLv4ZLwR_\Sܳ"x-c bnO~J, K>J~^ii/GC2N8)AS]bر,Y: 5(Kbѿk6az8HeVF $u۰]H`v\ Uö]\Yqe|r$E@Of+}H$"46&}|J C\$ 劃3< Yw^k{6%Is%<ɕm>yH&k蚌ZT*~?$QT$IQp)KJ>Y5lW\+װ۾ ܇݌ ϏSgho$gx*О1;ÛwvsNEm i,[Đ fp.5<=Y}YdTIQP<0:n're\IlT\ّ EhF<&Hţ$"1|$ۢ?OGwXT<ʸi YjICFAe\ޒ{}<\Y܁Ӣ-ס6UOӈQm;xGٴHiζSaWPh{GMaBʽ`(>8eZ?<|ǵwL}8cH2/CB_[/; @l ;f)ѷf1U$5U+oTkǩu8Ty3v]wh0M{$q(F&#?P(Wb20w 3yқ?pxmZ.Ӊ~{/d_ӯCږeVigF=LI*Һjξ*oG\p|~{{YrsGζ7jλa~uѺ ?/{K3{JiVWhr D\ ɯsu?7X]GR_nXf?`_r~w}mVV޶cnjBSÒa>\_ GJ^7bU4,ē=Ml&R,tO@\D_\Dɢ3K_+Nx,|2Ve6'.2"I*.26Fg_~O/@H”SI$d$,u6Q )*|_V+ ,͙3+s1{ͽg.Sv@OOzooi˽.vrߪ^<Х5s b_r%j^[H, Lh$L8b`->Z7>F7$iIP\lCu* lh'G`%<,&gڏSt3ff|@ \~ʛ׳8浴od#2"6 HQQ[6b1'dEP%& rhCTDd$ GA75Ӗ@ 5☝QAN $1I45Ȗc%ApĂWrqT}=V!1' (4Mad]dB$/ӗ)ݗ%W,=<{Ǭi]t,\9U[nOSU< 2RT"g\,k,3a$5@\>Z88WAp rwP[RwhAa+ J86!Y*8APu+70>Ək& x6P@ OWDBH.!LHGscʔL_*yx'd?hLűlqə2%)֢h:nfڵtꚉ'hPeQ'? ®O(Cc韮S  ZVvknd„1g l߈ kqLseM Kgo ߏ߯ hc6cyik }Vmc+|hchWdOF|j"ˬpW{K^] *Dd4Uu]:;$i   ;IGC>A8#̾gU5 DuƏgdf tttsd $IJKWSFqpd N:cepЈ'|jgtc# xxfE 3v/>E[wQvPXH()G{gɸi0$  6wc>u2ُx,d.nE&ů+$C~fz0{~7l&NDet.[)U*%AAAvdnz䟌m,cG0~FP-|&m'RDc!A?>]' QoB&g "AY/GK[7vɗi&^=ֵu,]XeIsaȽy2qWAAܘ[ 0zt*YЏcõ}$ʮCG><#7MFIƢȲ,yD!5Aj$'ѓ΢ a3*f|sSFҗ-ϝ+E|&1C+wmlO7_U-ĄcgbCctJML.a]Y1ESDO!N{AAa[էF0y LߗhH#v~ݭ̝3s>5Bi? &Ҵui~ S[4xz1쑌Y<3+i֭ZɽKW#eVm褵= /H"ˤ ϗ¤{2Ky֬[M&@ "mJG&!z 1qWAAx/&:ѠHejB&e6'"biKX\6MۖK&}44MЏNceh̰D$+}:K|ZZ;i2)U0+..2}ɌnE%*EWG+u uuuް.ERNۛ&ylhK4A  tɳ7R&(RqT`.I1?u1zGԑ+MtO\t_$3߻Bbml,Tpdu|D6W&'u-d T*E%Tlth8dCW4O)CCB,  lz7EP'~lDM pr_T"L<C"0KF$ g||Ξ =}92劇VѐqcF6j]Yrey.|n%Ǎ%Q.U0#*"]gcI"d 6,(1~nte ke@FF5\<]e"RAӄY۵~.CUkSK$  6쓺i8Ls2e+"ztw FgH%4%uBh^bQ w"fɤֶ.6vٷ_.Ie0ZMWͭEC3!Y|Of2],HT4  ݊=y2эL>5 J`YyIƫxdswvc'ID8ΆbÄC!J lG"gJF1- 9އ|5,;LwEkhjLOcⲵ<x3{HJ؎M8CqQ)W*q=Pdh$J]N4  濴4%SHV*tBAF5  ϛ%FV4\$Jeb{ndb(8i>2<2W dpX!R5>O=n9耏=M8>V3Tҡ3.rc5_Ed\PPOm}-6l\EFTτa,SU`Os$1Y  Ck2b:ѰAafL@2yzbZ'C7 "#Kb>IdlڴC ܾȴ ˶I)i0v{=v-yfP`ْg:}k^ru׼k,Ֆ#~Ku̽|{]:՗>(ϟx?\}(l+j=z{/ayhB0Qu,W,kؚ,xK.zʦE,.wf>=&.*a;&74\ z9A$ MmkӗG"et]e( %$ñ=r,|: C<4Is<:6NP_[,+ )蚆W:U6R|{p|S>mp޹s}sߗKn;K}'ڲ/"mڃt~Zsmvᙗ>! sg+Krl<Et$\\CB'_IzՅ*kF".Im2JЯG 8eKdQtCEr]$`It2e:nT `&YFBAj1U1"Notffu(I$k %Ism$ˆ }5!'I∯ i?0 éG%L'Mp}H9o~Ir,zOMLK޶ޖ:>yJΟYߺ6-S0KL=K\崫۷y-PmYZ]k܆9|sLjQnG>j[kƐJp˜_c#ߦuk>nc)S函OvtOÑ$ddR%UaF\zy f$)(ǚmj'؎nݽL̦4᧟p>O[G7|1<]&[MZsX% APP̋~&CVz͓Ed)謣7<Ũۦv>+>+[ƭw[e X;b <,p3%~<3ksٰ#I̕nخվD 5O|P=alT7|vko]vّ=U-;c X7u|k92M@jyl`eGzdo=1fAc \p\8͍IK/KO_tLHTUep]0 JΊU+X$)Xʤ{ ttU TK|MɄ1c- UX:^{c5ZXMڙC0 {"޾幻姝ΒC 2 :H2caP.q\LШMG xI4=@6K[W~zz3XS$Oq=۶q ϓ6[;X9UVG8_qgO +Eԧ"\x>i 4S)P%bcWC3clG,C6 |z߶Te^z чֽsyr/N$GSHٻ0ɪ2scyBOb Ay\ kZDEEYEt#ɀHPT T0 St|,3q*gv݃]=i=+]Wn?LWLm+uq⹎=-}c;q߳Izw~tcL}w<î|vt;ֹڏr[Nѯw"[OQvP'#aDA w%薉/$# ahd:k7lZw ۈX<š,/ܟ\91u]_7H=`],ZQ6>M{w?#h!K:f¹y6$U6Ҭ77DF` 3y|v"e ?2ttMMn(%IG3.t-[ﻑ;&_xxSwd"O<}w򍛮bp?uLMtig󅋿čKtg g=ǡ)q;.itvw;w_[?vx]~]p3_N[xgD]<rGЕsiR"Js%m~z_h[n:YvY.l}B>tS?KMVC\{ sOb^g?gUteֻRg'/O{֭g}Hx~Ӎ!3B|d)ΜvIc&" 8hΡc[v,i,_׃l&FW@w)Kò,T"#B -קqоm&'44Nb[#eAkPEQN+:#!ò6Wmk Q({Y?>Rc(3lZu7=ĬAsnO'8ma;> vJ0,B% z66[,Z,]3;o%fZĬ\32² ZNt\Ly=vȈ@בRRVV+i\4 # (L3/D ;7?\Mc}l({Y?>Rc(3WM>&a:R(~oʗ8#-eȽXa@4[-$!&-dYXhDJ֤ tt t!NM-d~ýx%Nўp0uC$l#_=~4[.Fch:B"I:7Ӷ3VӣZu@ yQ(isqEQEQ} qo]$N@$)fFdŪ!,x{[p5<&&$cI!> @Tj 6d2&2ĜM|YŁKIg3ؖë 32^nLg;GyK<2qbTB!zv]t]G(3J~do9M\(HQEQeyE>cKV$Hi& =ھb` 'I⸕6mbb6a0$KWLav'0:`x-l:9;"\_&!s{D9`)fnȼe(f,cI \ˆJm6uj uhJQf&}~ϐNW((̲(D5j$014f4kNSȣE.<3w^7v#u6l`~)űtA.i^_r$K{',OTd~:`) _,cN_/\ Alذn4EC8` 4Ey3ޏJG8ȅt|{?ɥ|3YрW}Sxu 9Wfm~pxSsڴeӭ;;iG-"ok`EQEQ瘔:/bdbD"IR@w)O2Ԛ-6nZ& V"":S·,[d2IDì^?B*e[*ߓ#L3Zi׾f:>zk,-c@.gtl ]7wXUΤh{Ln#ÈZF$ϓ+ID0 x<"MQ" =/:.=z<~s 4Gç^NJN|Һ+nVP0|'Qu(( B#"<_" C&%-Fqʓ5F+]7ٲmQ BMpMOӑ^7kItwI%g6'6Meܿ1RijBgŪ 5:nSb&\L6h7 zAF:ҏqjtX,FD]h*e/GmRS헣ٯk=~)yoR((24!,!N)@haFS$Qы -h31٠RQln{ekxK.;4RNf5^[mvxl# ЄM²d*A4NRIOw]Y6m݊@SEJu#MJuM|ٽ@ ׅwqxxglEQEQfMIXi _t'~Ra8I.W`^_Bvǥ4mrmC!yZ TqÈvDNo1K2Bf**^ B1s`ZPk'kJP AaSɩ {-?+څutQ^ "t9SLe;[t̑i`:c*UEQEQE-&/b`:4#(Wϒͤd TR1C_fˡZoh:M (W+m0HCr\l7B 8֣ټq+u.Čٶmd"N{4۳|MwLth62R BhV 1-SETG)(tdh؅ 4MȈ14:hCh$bqr ݅,\]H'c$~1" ] C)%/oah4:MLmBy|6}+NEl[ZX8>lX':BQESuA2sZ9dS)8*EQEQEQfz2>`~.x 0t ! Cn ۦ90<:i[q2$taer&RJzKyz2,[Gܤ\i1<:NǃPCӠAj^`INJAMt`hԝ<EQEQEQm](F|"xE($2׋mƘORd$#$Aۥ0>VH.e||YHO,OF+r |b*IO6?/^DZIa`ZR@h[-It"Cw@i96l! B,BO;m @40nO:Bi(((D1>^wch^l?x!hB20Pd.mdZA8V"I$$a$N6hnnlux|#ߣg^oߴooo/BHlhUXiئ΂y}$4aGVm̛kp,94T*e:bQ$}i(((D:b.cFx*Ʉe*e)RMFLL6Qi"^RQ!ɐJ d@lڼR Njشe ]W(((2[. ( ?0R8R#DbC!:D|6C>'еuN )|Mr¶1Nnbpb lqzs,7x,@w?pFGCCt d"exx+v&ud>(((2[WNౕC"$HNM&a#8~]bx>ϧRnJ_v[#I%sTF')qz ]i-`ʕ&bq.eq{I&.Y2@ׯôtj tuT<@MD4VC*#Jy4EQEQEQYNE# !$$akx"K0G7 ܩj ӡliRJb1v:B 4 Mzy$9y >̙6=c-AZGm6m#z9:C:&YL',l;Fjʼߴg4}74$b1o"2N#DBD$Bb8+V{l sY&>+=aʹ,Ē)<)!80)vj MJ4"S(((2Kz8O:4H%bt ~@fpn/F>QB3pÈz!r n|P6tˤ@Ow O=6]?#w=ͣ_8W}#> " )Vqf鴚dK,1e=ciA %MEE^^n/e;O|_r5_w'/\ɧt{E_bz 0ۦS?zJIjޫ.;y_ex;_Ïx{eߎ2#󯼖7]qg7lLv=|.J0zNw)^/n ~'㋊ EQ tu tM hĝVH&cpn/AtHll]0Q Z$QЈlp_Mŭ[5hߴG|( 幉o[/6} ]^qy<\Qa_x Trry'z ,;JGǟ~oh*)xg%7c]ĥg]ަpGN[+Szʓ?NK˄&>Kk?6/xl1dfž/*&E[uSSפ + ƫMR\t2F& 3(e t7ZT-vǡxzAmt\<}d`x|Kvl_N>$3i]{wWw8EYv;mͣwpi/Y=霻ssg0ז퍜ϳQ9~rԞ#g|݋xhLW62rә0]乎 EQ5:.#"0(fǧQD,nSɤRdquڄQD+H%\ep3>Y 8aD' (n^!Z? ,Z 7@ t: :I$D>aB$:i&"@Jt]seysw|}1rN˦spc;Ƀ즫8Q^z!.>Fy> O:_*9!|_"w˙Oמ34FKK̫Nqcskw\{#|ʦC[n:}_TL(nI&.6hSoA6dL*mMuA!!=cU-ʕI|d"bEka1\/`-XmS[5Z-~σlCub"953 SL55^F>~tſso(kifkϽeW^}W^u=߶$,DZ?Yq'znb#;r>0oKnrtʀ2X4}/< N)xq3֟?B/qG=VZB;Ws*G?/ 1Pe!#[`|A5Z UjG|B::/?0VRIW0Â9ZkJ1G[GL6-DH0D_E٫lvC.c괷lʞow9e/|?-z!:p{ܵuI-s_t/Og< ɊK}=Fq÷>T'I3ֵտwxyoQv^i:z`<>wTP3`߸C-3 ٞgh}gي'NbSֲGH_J=ag;_CL}ݧʉvr`酟GUWOth˝qm|k,7zߍ1]z?+q\xᡟ^ipip/k>4|J~~zVpas.C3%>ǿ$7[sr}8o Xݿ1W;sg>;MWmݟ:GZ;O;/\%n<];[8|cbr{uL7AƦ[j>0]yP&aiR 66&Э*m$(RXI"i$Ro4[..R@'֐IB47d(##$IOh˖;>Lb6ne!VE=y BfvD<{XfJA-:t/N@@Ɉ_jN9gPu:2sjF\.sS˸Kӟ^l6UG?.veMD`pg87zsKK^/Fs_+oz;w=IrO>^olrC/*& ({L?\w`UB"huD K]ʓI'Ӝ !4 l6Hy#IR7(Z*&^! t|v'@m_kn}9?lrN 3~W|{04_0MZӥ֜Md*$Gic؆E$5X(ߣò6WmVOzzy)||ʳw~pVݞ}\qOxQ1(y8BB¶Da!4"V+8G:$M8 I.@_0 8JZjF٤b\# ,(8K1_p1ھ/"X@_W/63=[ ?h8nFI0@J5*#p5%\>j޿Yrn}V/*&v9&E#ehF݊l&A*$ZdEF 7ru0ٰq+00~0k7Qȥ)22Ir$}z󄡤nQֹ랉I|!#} ӡ:ČgӖ2+k|?1Meh o>hEF&''\|GVm[G$ 5S=<^jEQEQf_g8"-?4TܤI:mZL"`c?kw(W'Y,۠eт8xRBMjB95uAFe'[V6:[9l`ͦC(ظyZ +Qm D(p CE(((,I&,z$Dlm0YoI&Hmd"˲D#f~&ibYChQHۋ;>nbtNa$|jdy~}W"E# jeavGW4"MQEQEQeV ObrA,#J$-l& I7q8t2&=cr4m8V ʕIu6mF+Y:o&glݳh6f@LNM128Bdvж\B^_/`KվED N +\988㜿W`hcPVoPFc,)ɵF:tҜٹJTqCAxą)%y [&cѩii6`blb _KLPNbGgD$O-;vls7}.3.qqqBb%4FTL)A0WfpB FB٢jIr>Jf,RXk:'4qP cN=ٷ~z T˔K1R(<-kֱ m߹}{~g 135#(HNеiVRQN61z]SS C]s4y ?pqy"R =rh2-E=*=*8Dt*trErV`_ eFS&i0ҒYzzqsE/] )pFd-qP&&Z(P9Hmʥ V/q߸o돎09P/KWm~)kV_ȼaZ7qqq|&±F+r(R!QJ1lGu BG>a>'\ Z>4 cdF f#.(eiwR$9o\,qd(A1ҏg?|3({԰}b|0-aY2>{+䢥Fr$ܻ/}C69k=}7|;wc DE7ž3ޢz_}L+Bqq!u3Tzd!sa W cinmD%c* xP_Z"}E A:L7i&dy\@yK|w, zPxa4mK3;dӆ]|֯dRu\{5hŰ5-ֳ}^v;!E㜓uO~2Q/~s>qq9ykXS.Q*ɕl't4XXMo"JaVz+!+1FK q2{'Vf+N%}=}OwGj{[ گ_+:YƶMn2vYYx \9=ua91AtvKx_z$w/|7t$W_jĿ|wx߮)o}}<㙯w~#ۨ,%>=dr̫Gǽ88-k5hmr2gNQo mFhX#Xˀb1f%oiF?J2PUkCtBO[N=׏/q{#s([w~6י:LkA3x1}}\\ͲKVa.,`_o!c |M`}o;j7w~9gO)Ǘ9~͟coI>_sE)@f5n)j؋W4FooeO-\4{{/?qq|!ZV43aH!XJ9\𢡄9vN$I207y'ʁ jxs\9ıuտW?O{s03%L߿'˼?'o3ކߞC1_Vp §[mˇ}?sqy1`玽ںuvsMP )N,#UV]K/k /A ɡlؼ[x`;B T]:f-pfSixY'[^+LF??\y%ook^ gEa|6?\#V3em[j8qq񚘞X_r*cZV~=zcljwH3HrÓQSDAE>^QPjա*+{dbjŲnPtPbtZ4K\8Sg2oϺ{VJ-X)V?2KGW1x}U&k}_[߻?MH\dي%`5 V+q΢u]ZZǶ/[}[yxY9^_2=$yo}x5Sw|dz7f[Gؖ9sqq7=Zk'$Z = U$INk~NKCfrV O!zhmHM؜/ٗ_Iw;ږ9 +O;_?ozO rqj`B IDAT+hsbcRQ !INVRkk,F@;4[DQ)SuZR@KU< d)F/-R׋b\6 /˖g\Iw%fO3>YR)Q = DCEIȲ[$?9Z'\:Yt&Sqڻy~a|7/uwlS׾??^'WvmˏCo KzqyRRFC0RR-Ai3n'tRK*X1 *7L6i;q5iQ U JX<&cp$;tn:xߊ~P@K(J\#$gHtVv;ٜ$Rr D33="q'E?#w qyRl[zszI1.FRbS.P)ȴ(6NBDQ@B0$M2:ʂ֖z;ejơ#!brJ@J߾]')ΩTYcSOJ,Fk)VFAI5`2 HBlќ$49g.8$Com088d| J5ILE}UqDxa@EGRDQ.c( X2Bhwh4X+ig96>ke/}`J\*W!*B2[a%bڝIf;)!TNW*4320i\xe<+\9sڬuY88C}Ob  N'G_Gc~'0F$5vJ!>q!"2U#M4[)FfΊ%#Ih,Ȝ:D:1MiϺrOwIazirMg֪{FO/^:~%88O8BIJR9Bv'Óߗ 2RJ,×1q9`W4 vv+diTsL|oa…[p$-J-<OFx Msfg4 &NJ(!h$B n7㜃L;;Cq'Hgn?N1^VX7zy88<-}n23Mk<'(mHT-Z[XkBP),UxFDF@_ Rt)TLZT !BZlL|?}mLLt@Dđ #Q %AZqfk-N-F% <1ha -RX,YDk._q&!ȹ0oo:+wnz-}??C˟zw88“fqՈh41`=E"Ks:yFgX+I5b\2eȴK-Ld bL\VJAW]yiZ>՞kXc1֢2AxODQDQwwN|^;8㜦!&'{wm OzT{+Hc0Ƣ3l DaHE/ |^ݖbkKHa1zk,R+C; !iPX?0LFibtzi0k"W4j e X`!/~Ypf(enLB|c4bjE<ęN@$qc|qq')%=Bcրȕ@K'Ih9\!< Гbi=R rc}h')ҳhcֆ( F+lK/\T}w޽;Tٙ&="rCd*Gxen4餉49Ix& XQ9W]uRJv=qqqT>я׽X,`B'48ɕ& 4 ȕB$B(S&|,jPJqRJqH&Sݖ`.g5 g^O٬X|EV=J|Xw6> F\8 t҄#b O : V{\;I5# ػwo7'v883gŊ^C"֠AKI|'\K5Qe,?7Ëq*1g]rn>Dس1U ZL qɥܵ#N8gڹ|F[r0`&#A{&@C4Zk(<6^k-AѢ)JNRzH |0_+J z=ظy;q(X2,mOpB`ŗ#$R)l&\.& `KFʬ_g^64_W}fm ☾d쳌[ `dggNd8g398W4wo>޽iy\xCOtVc]g-^KZc֤lzkچ$q.n.Z#)<} ( V  [D/CZ^N>`5q`1,;ŭ $/\?(GȬ 0{γݹ VCjQbclBzqܽ~ *M(>V9 4)\L@nٿ>|޳|OHR_8Hmnr^!UcUiķj nҡyW-G BtLGYs_x%Go/]!ZH1@ ȳvpOqb\`I~!] wA4ĥ++'léַhr5=[\9Ú&5Y9qy"~KVPS8D}.wrYTaQ&3k%>4 ҂Q'~wr=d:Ufu;gzq,];p<,W_4Bf'# \1w>!];BY!,/S|Xw/ vM;EP\Ӭ-iT-5 brBq΄9b"kZF9<)H\u|Ov(8~ >aؽ65PBFwWy1 AA Zks{q[x#;1DA$uo@o?ҭ|ӟλS`Xx;[¯<}4jSB_ظe_vú_l90˚Ջ9|+l>`"eTc"Σ ќbׁswVMv=Hjl䴎_!|(?ӗS؂UKǾ;=|6';6 ۲=+Y34̶%c:6y~DEdG{6^W4s{7ф/=V1:ʺM{Xca1H炤6n"=|ɵW_L80ō۩.#Or%;ludz5ӏͺI,[]G= +>[늑%ƶ^6 Uф >'[:ҟ8sfm? zr F[N! \w1>֬#\ͷ>3u*2Zg+n-'rM[ kN~[ӏ=x_RgkWmsr=[IP۱lǙYV]Tl饀<>"IIV2 {q\~ -Zqמj5v%;>p|{ӏfftUVRy-i}dJ1h+,d_l\P8㜕Zh&)IiDԝHssSfcai2qK0!,҃r!>oiǹkl yOsϾBɭӜNPe֍޵/&"en\t%3/,'T;mg.< +{Cdz$闹2zBKQ<<]vwqc[BI`WQ9Wi_S \8y2!Eh:S$4 e QPΓ/Og'=HS1'U 4{vraU|vQ0*zJ}>aȗ?-cJdĸC k1i\4Ëhvl}:|f'PG?.p-}_w'Q*cq })֡5cl;Wl:TcϬH<'Qf`R_EhѢss<5Mch.So?q!|qH߃-ز= ftJ,TŖ}\/rrkiF~1c^-:Ȧ}sќ+Fb ٱ;=~YS<~+/^aS*,EC$,2|ȓ}q-K,dlyH ZW%qv}^6OŁG<^{,Icxki zDtc'E1MƠpK1ɦS\-Tq)qz73-318s6߹ڠsM&R٭y~g<:f {}h={MGBƴ &@k.# Hcٴ}|cMoz29WR9wyK^M||;3ѹVLItLȐ+Xʞw31+rٸaYqfŊ\oYv5e ژe^GJu>Ga!=(Q}D iX!=ڭV+e; Hjsfٹk=/[.[U?$(-[NKy~E7a#:׿7piN̲jZδk2tzPڰt0׷Aaqsv/qaM['⒅l|%ys?d}88|vmX@HIKXmGMwNe,[\sP) P/|op cLO53>uBR<3u1HCXonZHA_"iBWsY!E_/_+<8OF+Vx&_Üe)_ q}{߉wX5xR^7%Zֶb2`sX1iRoQqsc{nw&GK '7D"B<ח>v|wS4E$X4?0ġR E5< KEZkW\9K|E4si%F/P~@hBV6J[D[HRE'Ii"}?08DᲶ0q_< mUdu0tg +vss VX"Ċ|OZU]14Mut~`888s2<8¢e?X%Bf9Zل4M3zx^'$2@c_kPJ)& $i4H2K̵aPAz8޹>>ՃC!>}=%#EX0F`"ȭE7cCT`̛7ȚKVyN֮X߿.l9ֲ{88[ [XÑr1BXrc;3uOqtF[#ם}@>_9V@ԭ sE8OoZ!܅uwz bE lNfÊKXl{lpwv]s`:a .w8gK+ ]W qq78Ã#'^?Y''9q)dýH {{6e~°;vחB0c R<# < qHjϙp-!~ < Yz+>h6sTn$U)0&Ajer ._əI0v(m{zN'%O[kZ44T`tҭ;88Oa?:-MTQ,0o|zzE4є Z,`lxc0֐fym)ۢ0 Bx^cM4J):YRWB+,Y"LMΰddg]# G%:kq8dvCk'ViZiOh .ZLOEERH,EW%qqG;1} h|.^҅!{LAƀ'}^{߷h aya4N9`eT _9>m: ''iz/o Np'8 ^$!;7Hc!~~^Q:C(53{AE% "qUUH !qu珌aDe-Q0#7%@YYZuxijY]lЈ@m N-u)ˊdBC9 ~ڐ;'8 Np'g&1@$*ʒ, o_VVC1F@R'@id`,ZhST`*EU*bɄ,p fj|<řWrz6`ݤVD:l(5waLAoa0eՏ:'8 Np|\~PRj$굙nȋ*ISk28uC)ғ\kQR"ښ8k}0MCFt8l6y*=.xbKpj63K!EiiL5 *{![{dadaM(0 bfrB|Op"* = Np'ؐ^5T Tٙ6 s=f{D8%S&S9*ΗiK쬡ޯ`m֚0 }>X)o}6pA@=8 O O\Xrnn痻\<3@1gm{r9L.DRӋW3ܺɃCSS[=EU?{ ~79[9cB'8 Np|LH/pji^  ?ho2hy퓑{GDV/~~RR Np'8 N8$IHcI󔝃X!$SF*϶sQeJRM l]>ZHdAA s\U/Ȳ Ӵ'*IH9j70/1L ~ً<gsxv1\'縼`nş|_`)t_8w~w~&_җZ{tz> Np'8"ؙpۧ0$RedYA hZjX|Mie- HY$mn7A MP(n)X, *8Kkzp!$&M VYD8 KXclﲳHQl,9<8Zh4& 4GF"4[dY'^~vpN053W-Lzc2n3aiY[[ MJ3 cҏ0DTSlmz4k9?D3(-[{?NZ!FҟYf<UTxQVXkxxl8g:`~ĒXJcXbqa &ʔ*@+t *9[=FL1[a!RʲBJdz>Edk!NrҌ$wv^G% yQrƻoQZwlS\OH,{HZIR!GVJkPB}0"/ :nYYIjZ~SFyFU:$ ΁THhtU2D/L^\Au@G~~2\1tFX)@J@m׷9 Z)pRS3ssW: wnp<98<{$ӬY!5 aPCXs<SB#UaUDU8q HK Y0`=gTz:h~]i9{c+(ޜEhᬟ+Sm5280(4nk[l1΁giD!VsZ:a~3XDZEdyA*@$NK˄q8q7ys Aâ"7p!7MBϷ}Ksf:;GyKY!=hbQYCQDa@XC*0QVc,ZX[E{n Q/GkNjt&m=ZģƙPuՈ6"2Fiʤ4'ÀNg UQD,XLQ1 $F-%ZɄ4Ɋ ~9Y^0Js>a(uJNZ2):qD" 5X*cFh5x̨?$K$!i5 %J iVMݢ4u; ɳ Z>X:FfPlgUNZNζ"rSd~Y0h #LV6#V/^) vnz?'_;wnΧy嗹rFs (hE<0sBYqmyf24$S3.v>y\3D3>Ź{s9:bPP O^^`3Nqi&X~/upl߿F^s+5d5z<.wǕhzb{p`$؉ )sdžAjj+quP e)IhsTEIUX<$R=X;p&JE.  )iXTPXc(`IB T)1T|c}P }`u@FJAfy&ɺh ; 5i0Cd\j(YVqawsÔ1Cۥ!`fsO^56v~88p@ݡPyIP{@#acs,b2fBg]YdmcpLTEEVt-&6R f/mqșiT8>xϽƻrA8sf+s%֒2cmDù9P|w$$Q<+M2$4 SBpCW趚iJQ9x]fmqp0cJ\8೟|S|RX HA"DQDh4DAQ2pBb$ q TـVHF RV?H^vwiB8t#aaqxB{G#rqn9ݙ3C%}oRD.D$WVPIJЊBJcAV"hU ! њI^(IhuTeFb4B*SUFV!Rg>b:`:FkL4 d&V?Hh5\edlefC`z7g~=rQ k[czagl#pEpBק05= eȩ%nZ]%Yg?' 4tsL]2ܸFZ5=_!O" ǰ8 IqZJZ [5$"J6bzGu ф}͘888HZ͘v#a1i&Vͻ 2 A#1N+,:CbbbFg}{R(58&'Lj>kQ2'xEM0$ #QJL 4E"EJ+**T0 1eF*9K*QYOQٟqW$Qub>=F֓hOlB< & EY21!PXPZʼ@8A4AX"2琶UΔg} b #H, 3L2>q-ţ{:&YrDPJҒ8it:md Gec5 6 آ*K "iuALlh6@ks @:"ax,زĚ $BiԘv,ML24#gSynaTueeA(Il23fצ* 4coaۙ"*1Ec硪cϩ17 ld &߻Wg/_GNY*SЌN4X^npcv3Ɯ?ˋ}{C曂..>[x[;ʷy-{c~o{V{gCWV޽ |e6_][&/J ufx9=>NڇM `@ IZbG ОPklQ^&g}MHYum1 @)BMXLUz닩xC83HbJ{Yt1UUQog-:*["ZDDI5UU'qOh`vT,x5/VU(4CO \\KC&YZݘfG4N-\><3?ڈl KVF\箞c01u&A,R c]8CM}g V[)VlnֻBj6 u@LNyp/h51N:?Gh\Q)Bx3AP)l>RС&jvZkw) T1faes( D(@t198OE*80A"\O`H[ $* [SWGFʜz9}V[]g{sg O=qf2P)~?X} ,'J6V|(l1_aensӌ'f۠-^yG'7x8KXJ"0J)hNda(!0* V,h컜HY`IwDZCáeU$P'W-{HJc]>5̘9[i50@'ƪ:(s~sϽz,8L_l1N-)ZiV"b7!{;$4lsE~טp%)(`J0ͷ֘z%lY2}:$N)͉UMH!jCJ޻w4_>Yͮ&~{ZJuZ& ($ƫ>Ja@$Q+=94|7X+{:|KE "%YYUtm9qZ"tҌ4 ^e~M6FĈܺJ BzL?"vq>+BPJ> 0I0!2)*85ΡL 8I4$ƙ[eT~3RUj bTxղuJ Ss$hD<-|bw*Q/$NOqfRZV "M-%aAԈҌp V iQ.R dH9EhF_ckp[9E6U^ IDAT&"*se"!aPu[1jH{8贚Lw,$=J V HWӔ&8(* g}Tķh1MlOX7!>-Z*ɯ|*.>n(nRs+<}a/>1˯Ċ+v&?@lر*jl}} ޻q'u(.e|ΘZkْhBӤnc(?j_iÑI+ 3!5Κs)(:4{ VU~^ VB"@V(ۢ(*xE~rJ#F?݋Z9YfcLnqdk*C&WPXR::{G9eI"x}Uܼ}|aK,ϐ%7w92N **ڭ.טjZɀVKgЁl (Ph3ňbklfNatr<27u޼v"h$ wgqɋڳJAUߺ~]$LcqG-}~aF1v'/x[`,;Keo{1>tHO(*K(F@)qL $"**k8|R֩#=fg~{[ǒGv(h$ZIʓ_d@#I@ 4ϟ' n|xLj-HV*F"7=VՂPKx%8/ yejs ug̳OnZ`}}=F iyn+2(AAzl1DZCUfhaqmI0?q zGtZ Ny?LYg)dB&#IK:!lJIV:%Â0iZ —5a@ Q4A cLYS9(cVWZ!a([pz{dLYEgD#ʢ*+_lEԖlܣ:)u(RV;)z;E|O>w )|KS+FBhfbQljˬ-H}pR!rMmq *V8e?os:orvh:!$KE j 1kk;Ҕi>'y0yA6D1_5Ξ^mT8aIӜ.N+zϾm `)K\|ƇP\VQyJe  DPR(k)M5IYiu0t!B "€ W-3}o@idR0(+6x(74HiDh1kw*7޺;L:,9wtE[ ̑V~V]6=3Z*d)VHI+9k9Pf5EG!th24㲠'&F /pO?#},7uۛMBZFxbY֒S\Iև+7n(FJ0Fqc{T)֓%|aANPգ4F,QFi:f,8bqiUi*;=搻Gjyפԥ?Wn G1v,BP+dmVZ [V`,F ѡf|x;ز}͘.FLJ,- -,2$ՊPy%^f%ehVNCeHKSSW:qKko?eEMh4Y\g~a$n1jNp XC>0U_XA!) AE>P%[!+9R""C(BHAYiNGfF1:PRCcx ߺEs턪U\uU뵪 uC8$CYu opsn<;/~%g_$B$ 2);r8DO88Q7Rlm޻~w :KM*Sϼ,w3ΝYfݽhͱ.ffaS%tkzD'93]w ~M_Ouh5Gc2 i"D,9doqo |?ܥi)i:ƚ$iq¡ɑ"l\%!$`j/NqKVuAbaEew7wɲy48')woݻ;#4.qT@8k7qc+槧csi,#/ugט9{6FB "gQ:t0FL2mΞoLupiF!!?7-;DZ^; }5fr7 ?cX:B|gޛ᛫D+HB&XPBiAVn&B4f4@I :͘=Xuúo}ۼɟU?ۿƩ|p/_"PEhUP2rIm"n\>!M'LB:6xJ6q`.ӧf`«`N`mEX$M3b8Ɋ:, H)oG_7/@2*|A' xpXG|m`s$zgWΰA9aF$;ٯ].Y[[;<'Gio甂$J2k0ӣ4C@ь٧.clRY^env[fldnv MM 7޹ç^{_}DUx T gYu34␗? opkÝ*k9Sq j;q@ / G}ލdk^%?*5UihȆ" ^gܹ}V#" `f C8@HpgL2C#iY͓RDQ@:ɨdmJF0t;lݺ9ڳS12esk  F)ZGK\}<7j?{뮃D z4! Iآbs}|1ksJ`1 JZ7ۤv Ui Lu-&iFiD+ hjn#b{oe!tK#zi[@Ji@L:J:(YcfK%h8egsK\vd#>`@Ru:^P;ɀlg2g{atTFR9 fnEWj"ЊH0"Vppa%#FR4diALOuj'-ڝV }hu)j%QD*T*ZK 4ZO"DJo_+C:eݣ KYik>U ڠ9=J}+/\X"ӌ6"kw]6Sܸ?툣2?peƽ{>E5a:z ʅ]p:kgՈ7og.39o {..Bu޼;!Y@IG޺8~P\ogxݷx ܽw~uM\m4 aI4ȺC[y[sR 42ڪ(Q: i4ChBU1n& $AjҊi!T,,7qwH(k= }Fہ|pgkY:i:EAY^+)J*@+eQQ\||p{}o5yO}K c\!LT;&߸Ev{-οpq̥U>s|݇|o F(-HWϟ{:=3޼v|VyKOހFw?mr)fht"VϞFm3?9,1dݤ7f4ȹv ?nH"_ۅAuڌ!/y>/QɊLEQ)w!%|W_|n-py\wvε8'uDq@Y)&eTa!qkiM K3=gjf`0Hq&IHT] '1kQ'.1ӌy[<8Oi5X^m3q7x˜=@eKcVZqŕhQo,-gtgh]Zsd^ݝZNA C 3&{ sι}{}!AQ$l#[e[U'TIj&#x*-gr\-idk%Q(R\@Ď׷?DT&**tƻ,AIN.p۵JrLy`ˣۂg% yaVa<$ 8ɓ g?Y~9?O~?G9o}^ iЁŃɄ 1RdI#KsFjXB=q -C( }T1F^ݦ$|=f"D)"Pb]Ӎaף**\pymrt:q3Ǐ#+Uhk\c,U浛l U3⥕\.dz:B z @ {Ec]Bk.sgkӓ8EuZ4OIx^r&7nm`)t]rG]l\@-֮cYtLӧe*]+3E$q'%(DqSt,i"q]$#Itr [Hñ%~R{l8XEaY&G6AZ[dY6]<צ?|, BA--av67÷ycGqs3⩳h5+!QFH,$ .u˦g?<~#':?TJm1Zr=+{ V`bf6;Ø+}@( Q`iTTN\F"((3I*Rm[<$Ѹ08s-IJ7mTE1i-?>Tw3=Bٳ7̨lj-y~~~ ˿6S"V(鬡 SUPH~C(;a2,hW7n/]dwb8Whֽ^+3e %%%ww@`{0bmg@:=R!QjU,Y%2KC'qjڑX_~=5n\±UOһxM,+\d(zqwtD;;_߳RZ焮GG)[;]HlBE*#m\aӜi?N֚'3DB'R(9o}yg~| NϣýGGξp_f?!EaNB@[׆\z}{,1RIZ%\((0) X 1*{wwo\{i,לJ BQPz4!P}M[`qK2 'l#&Q6y I`glhJ%SQhto-I֚4)XYŷm(\^k;cNNˑ̤T,Hlowy)&z`DT 㲾 ϿVzF7G5CUayKi7qlh1RƣqIlF1$µ,5% Z,Gy:#W |jnwc(+߷xX(T>)\u|ߡV Bt)l:j%7K \:MesvoDIB8fkS(h^oD:2+)ʲp~Bݭ.~8JRxTk!FVF^¸교0kj,Kʸ)pHC彿0 z-WM:G.r`iC[[134i^re*1suMEeC)Mj8~}VEF{bjTT9y˯Jk #׏k-5Z|_Uv1?l5.]zŠ>%?:e|Ϟ՗"PyoR J\]Avl0d[JvwЪ$*KOe)3Ӫq`ɰ?be}$u4qIa&]Jl$'Og}8xn1?|O졍&s$ÑJ%DfN'^$h8pAv}[;l",ۧ^1GE;65.oQsH 1PjedQHaɴF%) J`;Qb`Z i|곾#+1u q vZBW Z*Wwtf÷m<Ñ5:!9EG~V+Wt&?w],µ7$Q\E2IV]P".^& \0s]@kMoEF3Xv/E`#NO2.8%CaDdY+Ȳ p(ZuS_˖8 ׆m71n\N)NݳunkkF-V*@$+{1LlgK($2Aǖ<ҲSA9׷Glx:nsu8uN,F(1(댭.{]|C6fzΠ#촩Yb߾}[\9/Ѩ vť|Z9QF8BzawkpDdnCYDzu-t2\W`ϖZ=eȳ϶Jʴe#풬lMwf"/ayeF&*01ZSm67}*$QB8 Rv0&e}hf.@jt4$IB-+|g$[dqD^d0DL{mՐf5ٮ1ӪlTh+4UI.@!"()ͮ;WKnc;6ұQiHۥJʢe;ϓ)^x2<'L؎bF!VyJ>I?כLc(b :/h%+E9BHqaII @#!]A%,jr8EX唇kBSe ӵјV"- l.Q-'J ߖf<.2[6aQzpRE.v<ɣ;یhcYe3R)'"-j3M\URM,瘢;G~fnʨp$ e:*lt3#C\ףUT=<0uXiuѢן02Qxk5vI ._gm04B9ߑ$@UIĸף7`$*EJ3MwoD;fyuwh6*v;54$b;$I jՐy-lNu-`RtVE h)xBSaͩD#:2Gy?6:}Fg? P81fx<}|_npiш;ʏ◟ɧgs}[(b sY,GlE&pw1?<Ͼ|_b߾E8.}FH)]dLsa_vT9DrU.S*~RsWO]Vi^BY;aƏĻM>Kϼo[9Gނg+_qcyAoLٿ$Qo[R p<ӧ򽏟F#*Az5B^̰,/>u={0+UtdI˖¶mPٔ},SqrU6SL92(,!&yNkFÔNѐ=4ŋXE##<dy?ۤZ((lvi v[mM$5Q)Hsm\=] +:'O3̖IH3lu|hKEH!TkT5ݒĘ clAYƔ:/'*㠄db4:/w| Q 굡נU͋qF3,(gӌav=B{L_WYؖ8;6/ڒ1N>ϼǹzk=8O>QF >ip79{:aD=zx۹ `ey}{N坏?=ibQhTi<,P zy^7.c܀Q޾+ \xk~9J<ؼEJjL, dߵ%Xoc0*dZ!JR|[ >q2S,}Z&!RJ°'o9ҁ9, #l5zɈBw8NMF`WY=z*GN|'Dw4!G>Znp4nÍgY[Ʊ}Wl߉cx?}*ŧ_fww[8^ϼΓgo9gNpa#F!3ݺ~')'u.4LLjSB,[ I.SۧLlYY0e.QSnIc)TD%48Hꞏ#db K(fsϾzT[2Oƽ%O.tx*__|4cv j@AYa )0 "k c8BF4$iʤxZYז|c PV~ԵX)(;L€89{cg{g]DO9Uom!sl*Wt>ySkp,v!~FgM b۳}ױ5>%JA^`;.Y&%*KR4òmlŲJd2)5$CZv FEJe4c/%0(uH !BUH]@1YNFnr-ZZ%UYyT1.)t,!`FsԿYc.^KDevDm,8fnn?.3|;3/_@*[ #(,Aj/̱Xe2/=/\FOA:sg8phW_gs{Ѩ`{RUh,Y:~y8uOҘW9R)VկzWRQ?xÏVZ[gv^+O>/5*ϋ3i,AX)(<ڕA p}ހKqj)x(eن8$Z֬lM wstK㩗VขNrvJ1 х@HT*_y>ʍ|?y}?gڭO3 &i Q IiU Cѓă ^:KU.]䛗M$ͥ})87] s33(G07['#m<6ý`TvwmUAK(oU~DaJprl\!sXBP R>8f Fa)%Ġ-r)JSAa [BЬԨ4vgpd|-:C,zo}1~n&ɣO ]ꕀa_oo|*?mԸ/bHE* .q1BOZyPzDUy`ti3um8NI\`r1Dqh7e ܔJ2ۨөVn^UG%sµ$IVoǣy# 8Mb&IStL# `Ah Eu,ROؙis~̵i.Qy I-nMbqF;H9v=N\`{d ؖĵI!Ҹ ~%ph5jX"l[)ZjCՕO%2U 4uϷT|<ϝ6噫lz癞 ʔ%3}lh1;JSUld{=&uW73C֦^ pmɸ7d{tz# $+4aY`MB!Ђ\,KRkw`6O`KK4BSmff KXģ1VM>e mayԥʳ^5,7 |sKPmCwxDۥfAG谆lT븮CTpQ'd EBH-pEAW6C4+|mGx{'ni7LF\pqۯм^gێ?w+% mvbؠeXma5nhyg)b]a.MRb8Ha9;~?ȑش//\Qxbs?w}>A~g~w_6g_L|%| 2;Dbb܂b/lf!2ohI Hj& Om3}?޸x1BaTc*%!iȲQf6zTS➾@^# =W[2ID4=Z _k5f4g˄n| y{ p2Y2єx7oCn4o3#,1vKr9^_q`&VDQ9ڰ/Ҵ7o*܈*2ot7E-\i*We){v9 IF}[|kOoNd:!IFoR.XkXP*W =KuN3,u8ϯ<:TƨhZz]*iQLq]!ls\[oǽu׭|?U#P kHEq޷䙧;gX75&M3ygGd|#d0/|~7F22L-pO~Ǒɟ|G*<$g_$6;u}PQd ÁCiK<|BZհ㲰4glG!~-vMT$d(-n>W摁C6Yo)Gjd8`jBOR{8z^kKM H:q`4} ? '_ٻw8tq9Z^NwgɑC7sۑyt.f1I?͠W&*9 ULTffK+z*a hje|-_NJcU[FE1ASյN<#!|:E)rgpq3V;.&P=*^v昮*$q! y%ϣf}Iq|M71J2- Nz+7$) CR}ƫ1^o,Vbc%l;|5fj k}` }*EJ))6y,u.__#cs|nd{_ A':%r̞[p hyu0 Kwdkz49ZG2ސNҧ?= P"/n n皭U4E!ӵ4`!A6<3 m{rߣr{ؿk sdK+V[\z__ev.>|?~3N`WO3s;'&ÇuǷs_gWغm3Z3ދ^dj)&G>$5̰Hs"a-4w'qюǴhm .$WEMJ |*]c,W$YNQ \iBWj#Y%ϔmĕx>sG$#B%63ʏ^X4Z"ߧ^/nn@Q\ A3 z{@ThKa$Jv:4%<ǥ{Ď$uQu6y`mdun{{պ6m#J8iDu!ͬℜ 'W ľxT4k<qFq:Cɿ1Y_^+x]K[n0Y:x׏Ӕ2V/)a9ZC>?wڧx ?y;SG "OH:mT)Een)}c{(&Hk[kqHȲ-&;olʱ1pl]+]lƬVaHku]A}zo{N<>ǗU^;yWVWXo!ȇǯyI#gip=(G"8N,\F}v{oXL0gj QXůM[Ͷ-JeD^j+~tg ѐ-vGY.]F䄮?yr2Q}j |%\]kA/x>n3R]Zeۮm+s<1@Jwu¥)MLη6Hm.|׃č .s^΃1?>_dǦM֨brTɋ\_(q~ϙsH R #"sWg@q'aLJqL=$Q9R:dYFX߿ځC8gN_=(MA3@k/r9F`W8.µE4 dB#,9&cm{'.ӬyaJ*p|=1à?djgi%aadrz^ /(K@@) (>n}N,ٵE\)K!q`fbv/X[ZB'Qڅe%claB"ǮԭRIB`r m|@7$MRVvp4FeU"[אx ƑxAGg1+ր8R#<zPׯr'yk4V,Somv*lٹf{ Up3Γ.w*qA+ ?F { d w$H)uHcҡ6! ȋzHkx$굂ZNo@?IQF#AHG{sGZo+$]ڽ.QJ#x)!D)MѨG(euL "w4ZanZ(=&>k>:Mwi yN{E{yRAEܺamسc ʑ%\A{7De"H\OVs: $>DZ>AdZ#pk(+ Vv{ Zzh TEFQrZS)qzMqpO:xR縖 AaH"S֛ضB [8.wutG}V݀} zm{ﺋ{G\]pD &%]r|GDA@$w ,y!e!7yW8E IjGDžbmPq;}v IDATWHzK# dh ?|El"R/IC56^T>޲oǯpim3w< g3V& |G߶vOŹ5 9G1v/oe}Gސ&d&G 0)z6Et jT4hrH$p..p]7q}!e4R_Dž!ri=74a'c7M4ٿsi2BV;I dLOobyenE{mZB]j'c~(BMTir1A9 H(bb iUֻbL}yEz.1w5N;ω..yշzrYѬNh/6yl<|Ó^Ehv$bFHxE T0 0Y^[g4Ș4εI!\*n`@21IBm  <(ͳO<uN*#4зG8 |;瓟NO!2&|H c&#P')#YXXZIɔxߧ\w9|=yπ҉׹BTvq6i~ssC~}9p0wkm̑Sl޶4K&C*&{ ?:=ͨ#]p_wc?+9 W8)p5:'ص}x9=ÁO_ee'%(Tn=YV9&UW2\ dΛ.<'7,Ϭ٬ŢtP<b%F %bB_bnmJ3QoR[ hFCv ]x< 7,eyEӧ9df0V,"X!2VPUȜ-ny=߱1y`8$ ؊f_EڐfAX+q໖- [m\QH},"t\@אA21HR|Zrr]и ˓11]x}pl23wQy &W,KZZ8aiqkW[ncR^i;sfo1ޤ\N8“ף'$Qk2TN8IzVG$n:>N}u{#rzF٤٨3լѨl<%bߣ$ 5:ap$js,JBF p@kesq}(G!cccUH_2-$9(В_DnIqݓo&ш$m[fb,&|zE**# [-Zy^yJ$҅ЧVo軍^"sr,v8oH Ӹ0<E Jes S<=w-wҳ8uOzt#!$4K,,_Y%v4ˈk.^Pf,K:Qh_on;9)QM̶@!is/=ٻe/az5V#JSiL4 \d4gd R"#-f%~ћ=qsG7?+^iђEٗ=[u8P#|Y \&%XY(Í\j*FZ9b8ngaWS %Y眼0Oz/퉭T^4h4aqXX!r%\\e>(cܰsV~lmV{ |Z܈UNxL7f)zEvxxm̆ߴ3HY[kc Xn.q,Sd} `@j2zոA⠔úA#F+yMveW2wmn h*q^§?߶[ @uN][jt :C,^}Oډdjڝ>gO`{8ha>J[WnDe#L4Iؽukz 'nҌBHCRPu114E( %`0ؠV"7+#r@Qda);Msz*[##Fy99ibdJ'$b8a!/.d ÍיκaVJٻnQ1v鮯tY ϩTjTe$aGӥ7 KÐ*kFi|"Äj\,<Zo\Ae Cdy }S9TJG(qđ7< a$&Ϭ Oݰi@$M)U*?4IeATvo15`YZ.QI9\:uF !\*S nZ-+|s8w:?2֘19Es[eVf7ofllRa/O(ϧ~+9fg# 2u\SCA縞&a{LlJ2:>=w_ ǎӄ]*ܲA2Qm9u.f!B:4ƪdJ3h VA #J6&7Oy|cg#g~O~}s|꓿k3p$`(#0c%T(xJ8"\(89F{g ǮO\ <%I4;7ۡqeF9j$#sZR$K$雚Rq۵Nk)>D'G>C__$kuzvZ _xn Д+rAbe%aSҀB"PCkpd*ڽ(6IA R 5V[C~4$e[qĆ q< m;w|P"q9Wzcv̶m9sRG')VOLƵ+,:&tJ^̵O(ou?o{{GnǫЃ5H,!xh ULoꬡLΨaݣ4hmӹQ1E rCՓ+$yL$ƹ9:c=g_DQÏώ_[g^dqye$yEZrHU[5gia1ؿISvsdu0u|J2n@;'MS4(G菆HϧѬ39>F\"K*:O +7⡊Ө҅,\ \ ǨsRMt;`2? d9^%rw-m5f \6!z)f7wvvn\ (98zD:#Y Èx2`8ah 3^P0Q-35Vgjj/ר>j/%OМԦLИ`rzZ)u$*,ԑ.hh )TyF2t"N6x=٨ JZ19Qcbbh7W:"V-lhp=Q1ȓ 3T.1dt}۸9wË? _y=wtzufvr,N{/c_#;yiLcvN-^3smeZXMy'RQb|qLӦ 'F$xeUB-Ab(*nz; IDATyÛ?:㎙]Yvp,C+ /_8˥!\M|IZ|6;6M™++Cur4Kw_+طk8f,3ػ{o'][+ ,WbD J0B3 <-o%Pr0!˯rszq•c77@R8('\[`,A +} ϵQHYT̤rT$(^ɺr1.m^prPˆR+kkq,,ƘpU^17"mǞl27hmm3 ֖{1ͮ?¨&)[~tbpK\9v]y֞ H癢D,JfKdٲI;QҩtwRuҊزeIeILI)H  \y8L{^{u%W*s{['h|*((]()y$6r.<O<#?{wr7˦f$#{o߲,Q̝"v},({K6 "+5~P6D,[ۍ$(rCnD$Q> q38öݽh j@VIz, kBfԛjŜJ-m"$H^~R R iخo|(J회ni% hl"cnfW^| 6GhZ4`jEkI-8Ml45Z j#LnDY' *Bt XS|a24~JeUBRHIQA~Q}/ɐJ6Z8Okl3AkԦ j*cU~"zk+tWY^찲rM8*hwzqBP㌍GilNb@X"]v~]"wc2F^TRe.U]SHzII=׸si~ m]}&%}ڝU\奧_Ef1ݠٳ{+wT;vsP{8vq໎v6R)m6m)]ߛ%qm-!k!j-N6s;yߑnn{/x[ܥ5괕(MGXHer 㓣 _?gQ.,S(pTkgGH 5DZ6BBb6qh@2j]v4}rw"l2Si^#r$*EQu1YRna|aFS |<"aT$?(PQLHg5EV8M|$qLe8gm8̆ z"̖a#P`ik#OYpm鸸ă.ХMUc;x"Y\rQ:IQξ2s囸f^>r|A_sõyyX,/u0m7]BӍbݿ~m<+F}ml iE5-s\gMFԜ5zy2ĦfǴ{dcqrixKM/$F[PqAq>#Mpo|/Pm)\ ʒٕE<ò%ZṖe(eEق|BKJ#R֚uA?f%%ALZ<5Ï_AUk/=7nu?Z@ROX=<6v3K=o ٠ڶcKi|nLKطxϽ?oGx/op\A(!f=̙ї^BN&[cn 2KgaRQBS]/VC?(͖[`FЬ7V:l޼sgΚ =wws}aK|EMju:ZiPL-a|Q^ec'( Y $]w >;q],3$2:W l5{^9)$e^ KI^ /,ٺw֨s]tnYL%y&]X]/Bh\AcUm ϗ6[k;}_]h33]{.wla tBr~'<3'f)DH01}t{|n~Wy_?m'^i& CS-_b>?g2ҧFB~>έ~E[GXQnNt+ o&=ŏřڧGxx9q+eTaa9i7uz4}ů}^Ч3h"y>刯>OVk^ 3\Xi3aF&L<^~ymOl*23&N T%*)_^ZX>/4TKK-) Ŏv(%)Asʜ󺠔ܐ ht32[^|Q" y䡇y⧏ ~md=w|C~irH͐ A4Eȍg 49ب0K77 ~Fݡް%+*ǎǬD a d*<'/Ǎ;6H-5M#H0QDHJP(3aGN{q fXN©xCf 5Z 34+!;J3&\'ogrj;vL3F6Pr_43%J8C¥glYBQmI` ,ʲk2=HFr=+Q IyF'Z8~Ny+y-x|'A 70bL kH]L R;xfd6n{c4yu,;dMuY)u3w܁Mʩ8`sf+>J]DW=tJ6ia'tr,Vbau@=WyO] 7^ ɤض*T40rL̍=ȰCoD$y&Ӻ$ {$Nm ȡ(u n/GNFBg9BUZQ9qNw`?HQR{SVgjj1&ƚl2AXR -<]w66K)EF6ut-.1=3G7іp|6JA+ X]-MfV$4*)*Ȳ jUS YFq\Z> \GٕXA2A~!)2j v&6IgU(`!T\6Vsh˟}<5ܷˎхFjKY;+$ Ԧv2},)vLm6$F6J +wq BKL 3[?.֭S/t lPW%L#d^.<󎃯q'_Uy',;[$9?͠5p +>FGH{:?K^h*m$-dh&t2-pA5tK844j.ܸ}7aX JN<WzͪoxE"o/c!x9~X}WQXnSiR`ڵ͓SX6>Nn..,=p<6/o[?~T>o`a ²@8~8˼|U;a XB5'ٺIBoin4ߏvHRX -Z0$Ej,ˡYoRoˮ-}2:2sgr b(@Ӌ ){ojlxK(ƶWk8.-l4<ϡ,eQK X#nR,C`&%juHhU dIQgbj.;`0@+A>qz5Tnd(#ym/j K!ʍ]Cp&AePZ 4g(Y؊|7 O3rU!yiH[mÖ/%)e*D r, ʳI*i ˰`Uc(Nõ\g> |g~{6lfFּ{r;7oY퓤%iRml۳/Jg<OM2*~]t>^>|GGkzۿbC?yhC<5DVyI.96]hւS3}vngS-w=V]{KF{In}kxc&B,UfD/hfߘ$K"~^[BS1Һ47emBGy e TӋ؎+sqCi"Cu~[ǡ3OwLmY%"S6j hi]rKH-%]9u{35* uk{vq{<^!]]_}_y(ǵ O[XFTMF] TY9P/ QRpY#&cbKM7#mȴTaXrBr,l7M ?6mXynš5B{2gB7nXEl'KrT35`|cc$7MӪQS%RZXız25֭Ji :ߵ0a^}uHiQ <"#O2Z2>dlA%c0Ҧ(]R,A RJlעY9iFij*Vj'SΝaz~JJբi3۷ Vp,MR$" H - \̇w7ݱ7T5lۀx@mZX-[TA2  {.#Q&-Pʌ`~~'c\>aU:49e%vP1)2QTV& u-H}6mUȰX_"9.d1(mj.kiNx芏#"KI>* ^ $yOpo|w>,߼ ]sj'/i*OxΪHEQH.:I_] IDAT E*5 G^e ؽc''Ocff昍 ʺWE9iŐcS@i[Z?F62(rk$g#nO?|/=xg~i4+dYF~O.9nW=FR%PiTDmjstګ|EQEJG0ᶷ}36RC ڼ|OxW83K:]뮜;^Sw_xcQq\2G3v4 z!>>} XZHm]<%/sdQUХRT]JlrPPd˖iLRFGo[t4wkwx聟8;pT^9*cl۾j+06RQ]$K/.]~^4H-^z:/K?auhSȻ'i?,/? >̫g-Llta3;wK8!XZ&O *oRTZ}NŠKv\z(Ta\aFŐv>q=#Et__ d^/e! x=&YL8mxɚ\ӛmJh~]*~dWB\cyn %0w7b-w=RnR838c+V xN`)^g EmlxuT*XCr[H94#sSlv \w7Jj-V;$IfLP2P( ]d96dEUdj5:(aǖQz'h6X5rygOs#b7_l?N~ پ{_u$Ks >z}ӧ.s[?qgg8{>8iTt"ڽA9:B؊];G _w84.y|呇'^?z-|o!߇;v;ǹ$ILEU7FQ&͕Q)_P۲{\]o?H^MHK1>>2q%X9pYb"{Sϒ>(# db; ",,!emK褠9HVc 7B i\p+hDk|J!xcSGﺝ*ܽ珟 (fz)\pڐfaC_h. I&;smWt.0J@pjٔ%K`kRb tNIvx;ضy"/V~( ^bjP4ţ'p ,/h:?7xEfWssc;O̰kfzU ՚BP6\o%s f1X˸0DϳiNfe%FI %5ټTIhw&Tp;hSG{ǒ"(9-4 Ox>ni?R"fFwsd-| tsCv0CrUE+ЊcRvХQq46)%4Gk J'pV\}?rgzS z}ANHs]4pm\}`d S's/E\gV9BcyF&X%R>j[XiFPw:,^:6aIa;z`ee@NiY6yȓv#co9u~A8w|3콒[|(;.ȵDDz׫X@IwfJ] I㛦?5lK=7^.b;y|WyI],/dT;o-&!sg97C/̒f6&xYh\Bc"_g?pᅦ?+4ª3Rk 3.RFoQP0u]\$ h8T+JqIӔ(Y]ը32=HQ% Kk+s"zcȆR2BCg/&K?yw}#Xv8=ϼDp Q4|dgNpݐo Ŗ+ )W8KVV9gsWh"cmu4əS.PA#6D4_5 /E 6&6t= q-/b0 /IȜeYBZ8悤mͦMS\EZdILE @IF^Mn6#SSj/~ؼe*=QN?S{)~䫼4_Է[[sgi,w3rװ8?O){e7oJlxY2F8!Vq533{jVcI;bVR"[b{w^k>>$cҸ=W"{EXڱʪ%J^7dǖ)yi9xՕmx[8Wﲼ֥U(X؎Ɠ=0[)Aͭ7$En|{^k$(B+Kj͗ʂ9]#-4[c:e $C,YB^8ZYj˜AQ\nb eѠPuܳ|2QcQ~'N2ԥR ɕ@h*6$GۀUggٺ ؞kvbw_QJbLQ, #FHJX)^8rT~-l,Gp .̵y׸n+\U5iNLM'TEd26 Ft(ݡHjU3\G?7iGH9Vݣ?IrtlB'Q kk<h@\p2n%>P.%()J hXҤymi>bB҈eE;S/B%c!Rۛo(M&pw]z\h5A]m Ufjc&:peqBRå6* K2z }[B\ h4!N p iqf"7M948TBZJ5 |.jȋvg n_bff'fXr `dlFGUzxxAJFqS[&vhuoxDBQ`KJ¼BQR˂,MQYB4^Xv\8fei%..d%a7fPp()ҌAGEt,Iټev`;:9ڝkk=XFiB{J8NɳLt NDHF[Z BסڡaqMen~z-VPWk8s"s* e\Ϧ^ q , '? ~ͼY<}PBfW('W+x-]8buu]@=z>dsǏ~³yVTƛھ[}{vYziΟ!bbA‰>K’9@%@7^Nr}_ݚ+P ض۱]XRQ9(ڀCRA4|ߢQi2ǫ$N W"qwD-HӔ%ӍXY0Ơ1U]ia2.]lo[?-|M£5I#;6 28f:TŰ.؉P B,e`d%\Ke)6}*RBf\[U3ېߋwȳ(!8^v˲gH`Hj4ul7]hn̓8u]%JIs\-NhdiA ("$<,g +6q1s|И{_P)n&wu n$QLۈpɾCʡqp&BQ"YI!Ξ |xxlD=~ȇh(Kh5]ͯ}8qqG˿)yN{3\x|z/Y4I'wPi ѪU_o뜫'^W *x n%bxoקxWXXS]Y}w;8'nwKR  Z$ ?{+ BV@PI4NlvR{~=+?3xNJ x^ь,9|σ}_vs3Oȅ0ysh^2a0}'s勜>u/? s3kýSS=0Aԉx5  /Kzho=>y,/>#8dX%PKk8*O^!2 {Q惏AMUFFntG>ȃI}|G_\`va'NJm1ŤcvzS,|zŽE |nvߥz89[ĚE`|XCz%RܑG9b04z:SGu gxe6ʛIGiiI!aP'2Kj|49]iX)0еˁ/Jm'&*̮wMn=BO积Os}X P`b0Ѩ9oPsL^{s}i3i^oQ;w+>WEVZ^wm\^X vM\HjDM\,nJ3/-stXR|4n0\O[dDG<>t]#;4#}Ta(t}H4 -NgBED2Ǖ0B+8u.zQ? aj&IMIȌM>ЃxϟO- x* v.G4}}z/kMZzJqm{&ǙE>XRWj"( TaF҉? ;1'+-aLe{6Ŷ n0bjNHdbjFar& Jcʌsx!K+\[ͅ&+D~EIC487nǙkP*lXkxJVR)SoTXstG켍ʘ:~NKG =g}F&"#o0 b88Xԣ(39q&ٳkBUNm%}*H'bkhı zhzkl,d٣9 0Z![*(jPS(=p8D7osw|cW\#_(Oӧi4Mul=MeUIUS|5=SSznR%WchV DZQ$n İt4[ݸٞA JQY6%oGl 1:D1蚆AD4^cTey|-.pKG>i>x9^z :\HkEoСoSqq~01{x_-rV s~>ྻbbLo p50C:(ŽcFXhz[Z2t0D|?hԁ*kG^C"0u4E>6Aqv7 X8N#˯!g.q ,-bcG+F>.]ltq]C%8iaX3У0ғǑ""(Ban`0x=G(:eZs4#EC d i#]:!? \2,5R5=Fs="6z=:]gzKKu "I$Hm.Ғa7Fk`l8m|ߜݛ, Y)ˑY^^#F v(ڨquz5 `0`Zzzs/VNBi'/766 p]ma9nH{:FrfTjdC@*z-˺q,oB@b4&{}FN˲eш8LmZZX1.^q\Re!F{#?ihŨ0@#^?_9u=1r?3k`^쓓T_du׾C1<{䍙i'IbF? /?o‡N<t:.ˁ].]fz~WT]"6,t-?پåy*L`dԍEiFi+fE1/_"gTK69dbOע6{7Qa@}| j,-,ƞSC)ʥc3 iۄApcXLr.4d/ pzHhLsQPETl1F?מ=ϋW;6FO鄺ߏ0to^I.Ϯ\779ihͭr,^#JêyG峟 ._s(Mz(y){ z!!W-1"'p"GGsjodZ*8ئt#›{6d$‘Ա#\]jo(tfX >hq5 \~dWAC3ш]?'xWbRUnIő?p='ؿ{g;0 $ (b4,sm} uRja鸣ח:4C =خz5{vMY\i% 1dɳ5OF rE#uNkp@xء<O0!~tAn;B C|-&4P:AmyW)}K~͢* #(d8&/HÓ89Hi'b%rjMӰl; j0<(d~19NV̽fd+Cz>&3AKVۄw~6f/&I+{!^za&fxB!B9+7;$ͫgo%N{|ldBocs@j+X!B!$ɒ[fn\gcmBoM8؈[Ic+!B!;٭CnީM uǼLݮ_!B![iĭݝwڛmvu]ֲs6ƙf&T+B!"TwfWoV߭|)iopqbnz% !B!]] ~f v}e>nunۻ^!B!ۅl #9g67{U*|B!B)nF:v˜p>V˼PlJBB!Bۅߘ' tlmNt|&j[>OBB!Bw~qEі1:5gV!B!)n}vw|`NeWBB!Bw~Z ~ۡS6B!B!v 1p*]!B![B/\կ{ !B!$ vB!BO`!B!B!B!g7uT IENDB`mu-1.6.10/www/mu4e.md000066400000000000000000000035061414367003600142500ustar00rootroot00000000000000--- layout: default permalink: /code/mu/mu4e.html --- Starting with version 0.9.8, [mu](http://www.djcbsoftware.nl/code/mu) provides an emacs-based e-mail client which uses `mu` as its back-end: *mu4e*. Through `mu`, `mu4e` sits on top of your Maildir (which you update with e.g. [`offlineimap`](http://offlineimap.org/), [`mbsync`](http://isync.sourceforge.net) or [`fetchmail`](http://www.fetchmail.info/)). `mu4e` is designed to enable super-efficient handling of e-mail; searching, reading, replying, moving, deleting. The overall 'feel' is a bit of a mix of [`dired`](http://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html) and [Wanderlust](http://www.gohome.org/wl/). Features include: - Fully search-based: there are no folders, only queries - UI optimized for speed with quick key strokes for common actions - Fully documented, with example configurations - Asynchronous: heavy actions never block emacs - Write rich-text e-mails using /org-mode/ (experimental) - Address auto-completion based on your messages -- no need for managing address books - Extendable in many places using custom actions For all the details, please see the [manual](mu4e/), or check the screenshots below. `mu4e` is part of the normal [mu source package](http://code.google.com/p/mu0/downloads/list) and also [available on Github](https://github.com/djcb/mu). # Screenshots ## The main view ## The headers view ## The message view ## The message/headers split view (0.9.8.4) The message/headers split view, and speedbar support. ## View message as pdf (0.9.8.4) ## License & Copyright *mu4e* was designed and implemented by Dirk-Jan C. Binnema, and is Free Software, licensed under the GNU GPLv3 mu-1.6.10/www/mu4egraph.png000066400000000000000000013135021414367003600154570ustar00rootroot00000000000000PNG  IHDRpHv@sRGBbKGD pHYs u N{MtIME 4RtEXtCommentCreated with GIMPW IDATxy]Gy{[%nΎv! @ 1°abd# 2Ll! `klJI}=,|s{j*ѣG=zѣG=zѣǡѣG=z"ܙLdG=z㠒"sۓ=zѣGGEݣG=zqw"iaE"Yb &V!p=ѣG=zm.A8*#2()RR<+oJe8 ,!{L&Xk )%,c2eJ*wxGgǃ(֒ g覉1`}!8)3$S7)B蘖( )%r@ML`e RJDLeXAJ4T"sx! !` ZkFy膪b(s㳕RdDW*^ P<29( <z]|h6ܼy ZyR"O u J2xok ; p.Og9!{نx caM'SɸB2 >P!$OyΆ lܴ{p<(Nm% [7mׯ#ea kcC<:@&q#yr소3y4' BJ4oz~pN~&*g@d2E99~qN"2c b%Z>|ٽ1qkCj?¶҅@!!u4MMBc―),{6cv+/{G=2dRD $"Ʉ(RGG) eQD2SHR,F1=vv+ |ee9o@^d4Z}X@@|Ig E<.>/rɔ,1b^;0z(}m!{ۆFȤ" cH&} Ƙԟe"dRJsHRtu*@k4{b]WIy(rRXk#)3@=eYXH)){>`Ed دnjHIQ{TܴfL]eˑ3ѐbkZmÆ s(vXKgdJ~,rA ѐQav,W,z`u΢ FqsyH =y1M6ƾXg9yQ㘍u3BxP()P ̸i" |5Yt]Q5KmytSy)'0|4jPirAP;`VˉR w㢋!sUՔ< EQ\"^2ZGh<#@^ &Zi 0\a"YYEZM]Gr5Yǎ\Qa ipEB"%XMYy0D$⭣(Ktt8k!Dj6} "ZjdE|"- U]5(F3O8㏘TIG V.=1LX R?|WZˏDNZ^ \wY}Ѕ"Irm#y֚Ak:ШD5*{MI2xH4>଍UI5# ;7QI LJBJHSYՖn"l\RR =d*?X/Ϣ)ȤLfcM͒"7L\.85TftZE=)ƙH*@(q!RXy hkɅPȬ5 %3F3(M)u$Rr@S1lkl(wK!ȓպE{>VFpޥKV=LmY #˳x,:e,QY36vZm k-Z]y9./m^TOA%ri:Y3lT \GTr6M(cZ]ɤdN+a4AYRq6< &. !΢Kje$klSwiji,C" PDEGH.6]RrƆ檮A%Qja xΖ`LR咛\p.)PSZdf1! BTZ7,a-{N,{'0St*s)i3:2u<DrThdF-O8H]'eZun4BH*$+H*M0*6Tu0! s>)$DgyT bT%7ofnvCT{u4wy77R%V<T{(c0,9O9zLj'9Hu$>"{TҧTRKErCKDe0P%U]QxBHAB.kzmt$qLw.dUTE B#uhkhNDD803,,gnvg-jDU;˙R9h͛ԇzx\i=W, ߻&żq6zd`0bӦ#;wda~l {M6sћ8)spX2367))s8%՘6ofnT %NjȬ[1D"?@=IruI~Bg25gEl!6ц)!U5VTTH*ԦaIȕJ|:-*INR* &!xP75"OM^IbL$REQD& OOò,X(RD9I\ +6NcmTF$5EBOVwtyFIxCnC)w& Rl.&7p8LHV5Lر@DdNtdaaaMv`lr!ۂO.X!x}"g,u5XRszb3|R"<͑&\ym߹BC ]ާ8]8<Nn)V(m}͓1D@ve5|RϙVS<ȊDiN&ɍ󤭧؎i,d j63*#jB]( mjWQ̳N.1 !6 4:ARJDTc")CqNJ*Cy ࢒kNnHZ rX.\hx=NR:,KT1FQZױW*4pxh6[oFkCtڦ(n|#x"3Ɇf RRRHl˴aY+dF,"/ȅ۰MQ[dG0 W#bh(ϱ6ƌ뷳mטy+;?(P"zvq0*6!m8nGq/ 8솎I}ӱ/? IV0-YNV:rPꪆ@$Eu6>nHBöʥݚp1N{(uSEc0b @JEQi"ىIˈ~I109lI%t=&[ T>ʵ%٤$ 5uQɈjot<~Y$D)p!S_bYR}պYF#h;plltZwU,ƎR\g8L0M\-Jgku3Nc\XhYŻy8wBu2cykUmcm Dcͮݻ=kL/F71ZhMgC}hIOS,VkTuՔA9ޟ&i*[(LJHx@/TD0m Uuڪ)޻fM㟔5@QF򩵡/RҸ)@4ktdzp7ඝN,,,}`0D"Z7Tuvl="Qƹf156D*wq<^ƱᨭgM23;Gb(Oq[xk)%1`aqs5m)߽FqM&7;(33%r}O礭䢛XK1lBDBÂ#=cVW.[m@!drkxmYD1F7`ZJ)LjTV`_;ÚFe]QY%B ME,HGS 80gDSfpjjΦi0ɝb6x>]|nKt Ew#4JDFDL 6`̪Qj؉/ϓm{Q#,j/$6ZgbV6Ae24 E^t=!&;בP6"iq]0RP18$UyF]McL"nU҄+SɌgZOBg 1 d BR*]|ĠD+06\dXn)FS)X ";DYb?9"=u.s=NŎɥL#m AQc)n T2`C]tp6]#XV#p *ng`T32%0xXk*"ݦDkInVz &<ǧHuDZLIif3)c)\ôΦwifT- IDATI;[/xI$ǘCkNo4yB+BRQD="ϨS :!i u"nyFe%%T$'6+EY&7KM\1 qɔ8CxtbqT&jKh;iU4X.sqΔ!$u31$z!UU-Jn ͬs̎撛╀]ڙnֶ>NQ!nD>N(%t̡IؤG7C$ILF2EHLәLƋ!cA4"HID6@%MUR̤L*w=U)16QD׶1TЅZHJXF(VL3}W_:VO] v<"N:NRb[D"#Tq CH3\2웺‰t38b(Q A6*a {=(`S|4>z͝@zջٹ4pJSOQ*& uqm4F 2$y(a]|7r͍ !2/n,d̩'8(rD`nfm o bsqBFwK( MpB#L0r)g2ܐhD !e(YF2sk'{:D2X k[K=\˗riXuR]6YEtAO(rzɜz㙛Ij=zѣ2ĐKN9O90]~FO/I"˴Ve^yAaeXA -s{feĭUꖫob= ܞSWŠ,%Xyx3{9yb=zѣ۸kywP{饗_YZn0w4p \{u8?D5| w2()1aI]Ou-\-qˈc $ cl_ D\~6Mh/3WzѣG?h'LRJN:$;8+0PCl!12# l&.-W^j p8\_Sfy7J3w3x-[=z,G}߸ g"ːS12 ]yK[~kgjV԰n "pHi5<''jGɩ<3ױ;XϽ׷~@qy_y99," &p˷[AkMSU Amo6HǸڅs H۞;2,?֥­JWqmظ[!sУxۋ^ٯv~ E]Co_7qyj֗ÝU>?hw8h_yCW\qVJq)%W_}5wfÆ vix+W}/8v1΃iٴq3r o"m셸!pdwR!mNN"UWQ"g8\?惟/0r y?GlcO>9=n[t"tE#ιoBdqxyͫc?̟?koBn8yS+ǟʫQQrsyC}g^sٿ~~eow|Wx:|Z;RbsIg}qu#)y ۇZuͧt_/óL'ű'p+Uj׭uH]NocVl""˩X[liԾԶ5cVmׁek7!oj]^//g̣o~z퇸og*np/| Lv^Gҕg _R>ۏK/~eWumfQO~] !p+߽|wSo."m$$7-^>wrv'v8pٗgnۼo{U~ysI\x }@hq3_e_o}uk_y2=tm?n-Az|gw׺1]N:餎~3{]N;w[wr]L `ujpV,'pjM0Tm뽣H낣inޒq0?&3= G{s_S2o=t[)fxGG[ϺQ1#Alcvַpն1#wuw~Ec/|ݗ} O3wV}ֿ77u/{`z+a}\{ޫP:O`4u]L*/ëL{1%7Pݾ>S9;+pz,{6_qYqWPw7133hfedYmdq<^N31a͉ q( yὥ{%PWNa'ܲLy;canF>dlw}sJ5w|+=Rt[U^swuǟs^[q:^?§`)=}1}pj#q~'Ǭ{({e~4* 28ZUkz\r}^eڻP\{;玸Pu{qYgaVx=vܹ F!;U„a^g {!nb_XpQBkL {7+$=Ξɸ|Q2g WVtV:]|wjx>3~dTXA왌,j:3V{/gt?.7Ժt!@N rvԋ۽C. xn{<5Xp0V?:g+?*ӵk=/ëL{wp־G߾~^vezQz~Zβ3<!93?3!8묳W%y&Q3 nu7eQpQlo*~n+ҷxT1#shmi]>Y} \U/3^ֿ[v|틟/}>XB`%]۝_ofϼ5xkeWn']kG?v]L&as9!_o6 v.=]mzx{?州8:s_]NcW:gS*+}]kBݗjڱ\%۟kݻ븻|ӟ??g?s~wڽX1,㩥jvh"ˋCJWU¹)R9BiI~j6>)'xS.&8~^k_{k_| ,>{?<ED_'}xwa)%?zHd2Yq0o=q^o_wy_^W2˴P5 Zx`L6L<эXB`}_L>:V~UZzx9{/ 亲2]/}ؗaX{+Oub\M;o}4Ư@4) `aZ#CV!)qRD&]=z'}9077ǦMZT.q(ܡ7nj,N B@$,S(yH$[FJI(к~j=z#<뮻pޣ9,ApͶܶ8>_}q7@*#uB_ZFG=z,Zff8zFLagFJE9:WJYu:k֣G=z BH$jCnڹ b4͛:FkR!jc0&n+p=zѣGR x.n=f8;ܑ3$KER(`ZK'n=zѣGss4r-RU=EU#`U)B2p֢"/Vw7u]zt__O?+=?OG{~1C^Tu 4uScvvu:Nk!;{v/Վ+mw۾uCz]quֽsfz;'~eχco=dxݺ} [ܼ㦽~>\ŝQ=u: W]y~:z+o猯~ԓ9{||P]2i4 4u0^\`2un80;;<>m`M-s?Dy¯oU?x ϙ?8~_=y?@6ǚB`kJ㞟S/yc=d[:W~[~C]z [9SH.?7e$cO ~\swxϿ8.8}+|i}ء]w5/zٺDC'~jk|;}{JG=Y6L&S<چ@$|U ֚npB@H*Sikueb—}~ l:[T> ny[#Um>߿jO=9?W3z?/n^qoonr޽Xݦ^揯x_w~>г_Iws>2+Kg/fӷrPOg1o׼f4-kՑ?oqm4}k͝U{kk玛sӶG__[j;Cm: < WΌ* 3,|yGw͟y?}1}[%߾}w ۾`Aw>7=~hu۷c%32FCu )\ !)EUM1pxc惎98nھ<|1;=oe?zg??O&'W߹VokH}Xo{pɥ__,4 1LHÙ 7n!Χs10]nΫu@[IC:V_F{ c {dYd2p@ M_ɻOW|[o&~g}|'G u-)u\u-ΨԛNkdNxs.-heV;_eOx7?]?논叼 }xzVYN 7Sw滮'?OdNaSF">%of-_p O{?x^NڧDo9[Olw]o~@6G/Zw9\O^'|o뎿s;8z\ IDATϳ>I&{Ҥ{KZ),.E^eU+}AA7 *(Kini$d&>1KNftyޯWdfys9{zb/ZSV.^| d*RJU#Jm}UU1|0 hx8?|a::އ-ztޕ\ڟ{"OtYwW\ͱ߈ݟ+e/}ۭpOVS.x"'Kgt,M-cy|y5nrGǸrΑ gҵ;į>s3?h 5mpg~ɧks߳~ /~w(wǿϹE_νoW_$^BmV4͠momw=VZwnO~{fZ!>c,՜xUz\]^/݇L^9ȏ7?_|7 }+_7nnO.^| L}ؑgm`Z,GʙTWבMܔ޿Ϻ8klDZm2ml6C.?s5wleO!W;[Z'm=<*É!R4tAຸS|NJ9D4,)q]uM $L&0- GJ|~UZ6 SO`8p6B7MMSt \‡xxxxxxx쓤Id: iDU(A:A6 8( B(@m(epMB %SRĶQͦA)Jr 4 MSQ]QU+A}ߏ % .V.c;6峔ePmG"MY&\]S4\FJ+A}l.F4DMR1NN 2 a&qKk> @XBC8a6OK.ulebr cDJ8m7xJ ?L0N 8Ƕ,+TMEJuo*Tes[md)k `H4B}m@iLJG*dm㾗ϲy'yst_#ߋjkjW_ᨣF|hp̣J(>DOd2I*aTT9B"`p-$x+6XݝBsy_{g浐tlTMM~=@@7ef"Ic[`Sâczj9%a&aXiī+vA׉}ŷ{x[p$,FUU+D"ioW;7m7Vp~{KӘ5流-}5HMUU PH`:PȳNmd3Y.u]x"idYT*lVɑTHdՖAls͛7W\Uͭ]ÊIcz޶x do߷ug̣&{ c/CtT]C5tM! O.(B) ~SX)6䈥dsR`u !3|̝܆8fi2C%p/KU$MO?O{lw~ ef]bu486( R-W,桤 UXJ-oQ47!!`9&T* g$ûs8p EPe(LXl6G*"XF$ jjjPTl:S1g96unZrYC؎]`Hr,eٹ85U&6%ŵC|/3?Z˻ZxGx|s=Acm3GyN^97>#ļ>c/sV t19zz}SF{  I. kFGΉ't:52۶J#)Gy\˶p_ͽFTo ! aΨG}cy{!JA e6假~M]Ri=g0B4UC4444UAU5Pѳ+q]t$VcŊW*;"N~Ldž ,9HNy)Att~ifsϣM7 |>날J Pt}~bqm1ft JJƉFkf&X P΍pdb 3Q9[d/-ťrdiA~V0__|q\8NוҲ1/D$BQTL3[g!2rTC}#?%ۏ[D}!]}I^z1|7w|Ԅ << Pp]Bخ@UuM%FOP SLA.[{N۶KSZF Lo%ЂH)$`piUUVES]AB8.U9~}K__ȀFYW[.d64stuB}Cu%5k6d]47`Db+D/@|u99a?2h$J&7/?ϗ}'7/9]q{hhh TTLGiY&t,S 4ET\Wp6Y˕ 1z=vJ^ M3u!̜7,K$Rܹ;RfKgY?@E TC9ϏQ²-,IJ-yKӪO8-̛7aprUהp1pmbKUU6n i'%}$EAJںb+R|mWxFF5Mck/|fU/ϭvrN=~',ݟXmPXXm ]7hlj&}U|V埯ṕMxx騪F'`MUQu#@k.'N-tN%01_Bu:)EWoB7L1Bof1逓"={Igc;l2@:?M__bBc( QByA\APLEɗӋ`ū+8CY0>eb Vs6˲HӸ~䠃bu,XM̚=!ݱke/m ΛGgg'--*8%)(ϏP;p|>tCGuJVtEV[ߌض" CљA*#5RE ]Ƕ-Qepp #kҶ ؀8H%l嵝TU(s) F.unT݊TVM|kpԊ.}VuI焥DU ߏ(XU|q̋oӏv6Op{BUCiA ʡB4c(0f.JQň8umlgs*2:͞W6$*_,lY :-g&ٶmz{1gy>7M0 *1=pT*j(/̇0㟏>{䷿ u uXM*.߫$?e-|>fϞCOO7sA݊vϟǦM̘1kְys/]s]=E؝㗭"?`pAv5me&4M2shaZ ꅃc[ eV0auB0$5m"ILUUH45V/)`?# /,Twp/MD .Ż߾ںZ %v&B jc5 Xڴ?/Z{?u5 4h7j$6H!qkƋ@Q)TgQjW_K WGF,=ڊx(U,xDEMM-n9.PH3hpu\|rga\%ay\~ee\0ګnE"?o==̛?YfEKK+o7nګ-;qg=B;;۝_ٺA%Sä4uATU4^ntێ(*RL<>kE"aarFZinn&ڊi(B2W  vJԶ6uuBLI9ʨm=AeT284ÞZf̙@8B"B,/7Y7&ǞJs]#kD>|N}]"~ 8hhmhVF-%RƝbjA `]m ?@za2=@a%ebK2^X.{ݻN8˴P bW\ICYU͎8UU#ˁt*S2U kYv2ϧ&w3-tnګk2;KW7K,V( n꣦eB2 "MӪ4]jjZiGD2QMX?py:l}T6ӝNilhK\ % >81v:u]]#v'#Y۶1 u4UE(>AQM BjF6k"\AycϦ:' Q  H:PtjUL. SQJB-L RFjQj^7B{µ$6IH%Dt_*Q8bCsUv UT:E.ö,qЁa;6e8c a|)tU>Pke/hnnEQc=6l=pxZMM GZu\s;c  S 34|F0``CQ*)+(!Ulul8555 +@(j~۸#f1! * 7}_.g+yY~;r=>/|!x(8Iy\%ON0e ܸF>6qF[%W^[>3gٙmىƦNf͞D]WѰ\*bp l& P,l"s2Mǖdқ Noop$B $f8)%LP(=93[3;n6m@_o@ɺa:s&(B">ĦK:VWK̙|~VZU2.( x~hA= >P؏PTG !e-"]TE!@o'qBZQmcddg+A<W&ޟi=T~s?aկ?089N6u=?)J=;EQvÈ$InYUgΤ7ڵkб!c9tvv2)%,뮹➸bWToB_'w: $nL0z) ma9,garf U0M OK-^$SWW[' H&hhl, ʟEsϳ'm-֯k;oSTEcŕhaOFŏ{6*ܹ< +Con ESϯᅕ`*3w/n{ QTZP.vI j`Y&UQqUU4β.TiB%UPƈ|'y)RSbI2]d鑅Ų XյU>&/D"A6ö ÈRMHNe>@KK 3f`XAdž>~!+3{;7֎3{K+L*TUEQu!Ok=qZ~[l["@ pG(F|XaZҽ@ @$a͸/vcc#}XA²,DC 22}n+dlG@?-ҎBLۙ'Hۮ?n 3" ]ӊ#D>ٔaSr%6O5w| <GJ~ yϽ)(D|u8p]t#]l,GUƍT@X;z)Ũ"qJ#+#fϚiZ[HY>A&GjJk6nH0e֬p',ᣫ{n9 IDAT0"`iDZ_J3< '^Z[g\ /|۸9eܹ< '1*i˶p\uȤ3R)B`~xY5r ̋tE((  lmDŋ1|>~ɂHqinnfl&8rH)y[1WN, !aBTжݙ'%o^G^|)jFV*RV# V9S'?p*ݴwӋR'B( PWhj>w!Pu iؖtPT+kn3cPǟtq ALtH-je~3?6/vIƈgߊrG.t"D"A0g3{esc0t>i7/N:8S 0|VYͼXv-;:8?q4a ~{Ӳ?/نD&- d[#T*Ekk+6mbxx0{Kζl;}Gix](z [AT#U4UߌdYlUӰL'c6 #;vޙ#o#6dwHӎg?cϭϮUys5HI{sԳx!F|gbxq'vńwQL\1 1]8{wo;poXI&&҂kb1q0\DQr,H)34p4B׆E$2Mc)O΄D[QUL&C`j#MicG<#Ofd9,3G6˯;>QTUO~ ~5kb478 TUTDpEU|d2UC:DEQʟjY-K|xX:uC9*2oxN*(gqNIlJp]M EQNpiQ8:S:u !ZTWj|DQ"B~?dت4,+Ru~?Ph M,ut/v}}}$R>D-*yVM0چ|~|>|b jTUWHUXt[:05Z[_OoW DT۶'TbX5fʉ12Yh&p NV^ K4YW |lYia[&\l&K.%xߩqH&Hy%̙=,K/œo{肐H7δ 8)pl"T&LǕ34 MBt]K"v4U%VWౝj#_+ ']F>XqYlٲK4^L6 躆趍k#Y0|>?ӹG{L:åW~cO2sHHE@qy2q(##U(T>X,~ 1obFCBkZILvI~HǵAZr̤_QCW aDi|h_{}ĨQ]UU. jkmTUuTu-\X vźr!Fy&/Ռ mtLŽn]Ԃmm~ ,+H 9%'x"/?y)-GV%ˍl!%R!P'+p 0%TT=@ا!vΜ>'4qQAH'cZxxxxxxxxsdmx*ˠd$0lJN_G0MwS7@˶Q4tU T[8Jk嫯x%0N2~0+J TWaX?}n믁 GdY~_d ,y ^zxxSMA^^xx|!-BAr% >!Z6E*ҴuI!-MHEQB(! {ᱏa6"Tm!xb5n³P]7G21iPZ|Pe#.,&^?ǖ8x&o9MEcs/U'%y+ύ/^sh1[ >/$~6ƝA*q}_g{]騊M`8yj"'T X3H)\ש 5'Nۥ'a{3{xHx4xס0 ̃O*s/;aNԣzZ#?w5~y6<8Y} %dףi+鎁u_g{ҕ,B7~"(gfokEFhP K v25^R5_BӒ(Ï ty,G/zS缅ՐM3 lƽHaJo\~*&f^HA깗yXɋXr/$ ?8t:G 9 zH jZYBͼd6#hJ (软#gֻ^1#w$a׏kzԷo~{>A;8Ln׺Wyxdp#s=pBke=em#_C׿6=ؽ-quߠ:@/1TE1jB¡ ݩ񤸩 ~xē/;C 0̃i?uּdͷΥ +EbKMcΟeGc;7{M2>.T'YqKڋw l Q2Yp@PV,ڜ~,})0j]x,vI׽܃h}qX+K B W7Swh~( Qڰ$~|Ӹm5=K 3_^{_m܃h=Pڞ`lYOw9;)3 @DST4]'L2M4N:ݛijGU֬YʒnjKyjT@_:A$"r4jsG21"g OJ+}^&? b۲)GÌfj1z3OW,vl~zom^8/u Z~/GE{,Ъ^Omf4֊ rAֺLmwu,Я"b.G'vaD~ew<3N lYOw94'Iؐ+Gf3 Uql!!c 0|SDsL _@U֮[1G/!mb͚)\ǤX9yWж/fdjޫ*o{ nOS/;#O?V-N>0l;-㏆F;{.?q<2d^]ÆWא4Zgڕ۴=]c|LGHW:tWsLjxl>N1M/Ho?TٳfbZ&km*@Oj,SsbpMze?%RLΏXkfd=uY=-$\$=,66-:'*Wt^@t>8fT7`8H~11ﵜv d9jv)Kφ,`s 0T:5,>=\yr IZCfG[YEъޢnkVYS{:~>}uigMyKhZ*H ϤNh<</>=Uzoj/Yݷ^[KIKh9*B\ڄ=[~0}/R{Q>g r왇c8@860P"RUC&6o (Kzbv``ar7hl ~*_YU;ao@EuI.af}{)g?Ɓ;>r7M1EhA3è:s?/G|)kތT4Pg nCA }룀 d PgdsuR!c)ԜiI$iҿeǞ#-ҩ+ͽMjhT=<<<<<<ry" lATDO938E,4 8Dz0s94T\!X&:EPu+U}7E5rwۏ؟ԞZv մHulPA }]C ">z|yF88[ِsJݷ p'weW}r{N#o\{Ŝwɕܱ|nYя?d.J.2ι3<6 E_X)~ӻw.ry3U]tSۛ;鬋;\˯.;+Y9Gݷq%Wpq5_7Wt>ur26x?U0աM1 j./ŁhLmٲ.4EH!\Ԡ 0t?kWob Q3cNpq2NH}ѩxw;^OcWο|r@jruxq_0Mb6cιNXpM|]Qu=wGv_ ^ܯSΤegsOؾdOaۀ?LK:4l\E ~ '+nvw"{k}J/LU~"j6Z4pxו8 `rM%$2s)֥XyOsg0_y;(w?X_S6WS %ā vS,Yg|'_J\!p]?']zgZ[}$xE_wJ/~׆,'Y<'ύfي-cF|i3\]6o×aڵ#7]S㊛>^.Q.-ܼ "!sT,?wwN x7qIre\KKv9n"J˂wsw}u'̉62]]L>fO>8[_8fҭN![kcg;Sٗ e\^OT;0\S'9RT{/W'&SUF3; 7~a;NfS[85QWEçc* 1jt7v Bd|7ב(J MMSdS3yɘ^M[W:?/\^5,G=~~mڟSڏ\[>_N8]ޝǿOvbq{>.L{ g/QFқʕ?y::LosϯgdF 'LD+Q7yI#=O%7?~ }<r=GO%52u\izO^k[&W?'*۝iʯ\Z~T}7|ou=??0"5NLw#8g:=z2*jϻn9/Y[W>՟dVjTG}D#a H0}tc\bXMX.m*!@ $*K1>uWz։_Y,9/y?_~~yQn|o~vҋT9Q;x!,=(5{·bhgDe;}[i$?>Cخ&zPck[/^\r{+L4SWw־LԎ%';['^WF󗛆-M4y!.YoNgnCa@0t?hP8+誆aLF fqr 4MW8E ʺz*>}OkʭPgefqHJ\4trN8v c G_{P)es*~bNq봫S팇󟽓w}ǼoA I5L>Q Mq oP)fōq+elW:wIҶ}*rk/>˓ y?u-.cTrmQzS/~[cqjn.n1Q2w-a'mٶSlֶҹaD:H7 TMGu4MA:.#:6IQwQTmlK( ""6,/`EEQDzU4 IDAT)AR+bW v}DQ)RC!uw}ldg7[ rf<9y̙mz4 ݎlw"#目b 8z*7V=Y*صn%mv6Kcy&Zf}pȊE/+^\\ʌFO1wlX!)zXѩU us.%L_ 5dYd'>Eh(AJ}Ųx@D5]AtwaBx9"jFc?T;?ٲaY {s.Et|f`%CF֎@%q*bF۳Ƶ?׌cQ-YZr !˲fGEl6. Y#gqN% ;so(b$Ս]oO#"{*Z 6w.o1|6r"]?.&mڇ}Rt( fIZeaVߙn;^%q:ez}ǎ('1OƨHHp2cVi}dT^UJv7AwW8NckȚJ&]^3R9N;tʮzLh_qӿgohߟ܂3r>DIICHMM<3lkh,0%=RO3\WBPlŒmγ#RrKxi$222鲣oNP/O-rbiRb(ÜVdbIHnWZ9ي)Iam$%&OH$..\N;N5kx7 ~/]N߹ :O +1x(|v|IB}ZZ¢{` O & Li9Ŋ +&@(Q׋7j}D$#!:񐥅EY NW.j9d+&]WPuш=Tr,.WJ, Mz?YXX{#`1N7 D@) 3ƆdH@L<߭XA0 8?gq{0@DD*x!oIT@nN p!Envi[bgK>begJm_5.*7VQi_Vmh?>)"vy"Ý32CP0 I𽂓 Q~g!;}MaO-ruTc)yiEl-/CY?y8Aż(WbP3AcVѤ}OPBlqU/Bu ]2?)kӎmĩ?lWe}P,}Y,KzT;I4D(F-TS1j5Nx\xE吗(^SN-,, }؝r5V'c$$fBJ$&$GLL,apΎ=,PuU׉E0 232|w(紨ҽOJlR#H ͎$8NdY*9ިo~ UP2:6I`{m*YXXXXӢ̒ N.s}:RTA%IdN A ^j.ۈlw~.ai:9YXXO^D$ Q%6YDKiNbl1p:(ndzPu$g[b͎(&i"j^l63r%a:= /߃. 6,ۑ.-1s[ >_9n=ҭGw[t$\ ~O?˓cjon.+"]8m;Cg'v}?CgV")Xlmڣi/EZƙ?y0*1n`TUEtD7GrMn:=׻oy%ύ,N O>:Fwx,{d'{t<6O}b),ʈro7exJUfb[>3VѺNes/=kDZh{#-| pl+;T.=Z'"NߴCr󫃬Kspty4 S[[*K %an>1!o4`Cuߔr1#fjԿ@y0|W_B(xML}Lí g4g.7~Ϡpf¥@RǙ Hl!I6ij ܤӨU&YMvzl%fW^gy_Οvh'lֆ@,{ZAl>0L߱h~7N//J`<ؼ5FUegi2ZXc#9t(w*[؛l<A뿾`:0v*].FLgF!XqML/m)Y2G3l: \0B䳒6 ] %%O-:ۜm jAq1{D dD"BevA0#D6lhAK7ßv!% tz@o?fmF+< zspX%LXE'NNⶵXAn[yUcdXiP'~&#dH<BX+WB=9,t>jN W{ DƱ ּj a 2 a "(#FnmDdc^]Q6RzD]1?hS1mJ6#TeTA!tiWx4|^ӛ+7w?xp~79A5~Ƙ6@/2Ct,>oeo\`PƻiEa1<|V9[^B,4V%bovZ DwED Q%"\^bٟbkIJ.HY5t@(8r/EG& LXEŋ3%IYݗS-a.&1}֢ݾ'71hV_4:C/떾%h4ՎcaAlXV˃шG:\‹+~Mv FPyQV|{߼BūuuytNPTY@թT!\הj5>69bXb綘}-}}zC0䴚a6'm @!Տ^XN 6 7ku<{~F|Ǻ'rK@}n8ώHT5M`4Yvg^zQͧ]݂ fGوt[-06RCikqgBiib\r[QTDC@˧i6r2q{88/8EoK\BW21$$r~̃Cѫ-D/$%%D||kj'E>ɦ3o{W8W `Bz_=uY4Zc0& IߏO7z,qPVshC[Iu |1@&5@1t4R@En#'nٟbxƠ~h=vAׅ;ٸRSΚ>߉pyi< nH[Rlq*>PL`rEhQ(f_B@atDr|o>׃XjWWqPBr m1{)Q Fspʼn UL9B'S骎x:fݡ!!jSpڹG'&ޒ}};<ߚ$D'>Hj"5ߞt}SGТ+8&F|L%}:bu楿I2˔D1}C)<7;3%oRT-ޤÐ|IwG⭥a>?ƌANwάY~ }Vri# r 0%Ν}Wƌ~Qv~Z;?AlK\2-ڭݑoLr9L*Iϔyo#a1dޚ8tCd޽xܮtmEm>ЬLsO%ϼY1d4VTt9$ɠ'oO7J*1\2]f2~||˃|Vrd|y'e2ILgM59t\|}5ߊ!a J `:ldenA+f_#VhyሽGKp,i 8*e%g a-׷'7qGMVFo:M}T嚮<V@ڴ1-n&A=AXd.?O;>u)se͛D}Ж@>c$/>uu9nװb6]pywdfYټUu> @ >+O9I><{}Oˮ@&]3 t UEtt]CSU4[;[8Q!l81zTEGuD$Er"j%f }~v1Nz&TH .$}`F:Eܬot?2>Ӛ13b}k$g}g`ow;>\>$,9k\>Ҿw( S:S ()/fbWH']kO"!qk蚆iQ5ܓDE4]Gu4M4D =K?qފw3fīE*؁ʌ8Y tjC(BaPpYTyF\خc$26}̶C=Yj,p#,%ř5]AtwӾ&`wq舿W7i=:ⶬ&A%Z^XӇ~p6dy\ADYEE$Ll_3EY 5<}W". ڔcur x: M-vICHMMC<383X3vcHtݗ'zc}e2[jC>ѻ7;|o o}1}qe/ ˓cjon;yWhC%X,߷EttO}nm}'r5{&rH,prrn$Ix^y,rs.6!74:бcG*T׍`fa7F̐B 3wDKfL+v'> 0C[ѱ7eҜK]fboSgfl ` ׍/ fy,^=3=eﺥ9+W܆%|ס? 7Y>K75;粼UY᳞ b~=_ʒɅ xnTU*1NUjLo!)1z.ѕ8x {˖m#f%fr.Wl ̎ S;շ??;vlV䡊[O 2̖@_/R8k٪ wFj΁GYzljsK)_Xp*?|⽘]ȨAϟ]mU=j?ԡ,=W@՟pri+`((8E!ENPTK14T"qԨY3:oXXb禘}!ޟV[M@;W,y5*nmf?QHS톾;yjq5L />./x7?3[ {gFsLz%xdpbW FNTQ&`b7avbf󙙯:Z PK _g89KnH@pw'p ';tHJJ@dٟb|>RK&mg(*N [HtMZ'bj|#\ϖ@ޡYتE׽oyV(U| \Ƣ-<{E:O > uL?d,17+t83ݎh}򽹈M)>S`IM㖘-f./?C1r3z7PU<DZԼ.x7+fޡƙ(E7tu8e5ϋw0:R)g`^Z;_̃P\^e$nǛ&>&UQl`$ӞQu]Ր͎l1<Ξ#f%f~< #F~Q3%n]j DgyWrD3ԾxU;7ܭKt╂|h20u( h:8V~h39sfuA@Qhv#gqˮ䯿@whaHHz#%fٗDy]о+N=տ?g}!ʉ,DOڟy,x7%3BGDt>tdjwc\zՊ סn4RE#:FN.P^5 @%$IlÁn(ʈj1ek=ۧ, O-,, }qB([(póŖ3DIb5I\N6;q8b\#fNBB<(MY6cիH {[yܙ蟲OV6wtc9Lch;4 CE M.IQt6 Qj: qe[gaaaQΉřǙtKf94 (6 0HLLM'pYq.`5ŋnhVZXX+ۧ@m8]8q\ǡMF;ޥ4-7PVm tMEU9(8aaaaaaaaQz(qηpbs؉ tC&0YEJYm壨~GԘ12r׽.rE>*]`AD>y+VGn+cR˦miWfeaV_!?CY?qCYόc?dWr p{jYzy3vzi2r)o m-[-SXgҾP#dsR_t):>5GYZ\/eaVAʂH̘"vzbt)GAQ^ ͳwIr|}5K:ǨԸF?coLOY2}^Rok#{{gx4&2cdfL @+z|53H}noϴRinYR/(8NbDMm+ѽjqcx ']D@dH>ubt2FoZ/[1JsERyfa|wf4ì>ϧj723cKJ hҔkMfvlՆ*vtK[fvH;9~:oډSN7}rT [2'η{xL>/qpeMYpM,,RG(6ɦI9Li[Ǵ}Y;WW5 3[ QrfU?̟3v1$;S{y5/Gj߇ӁWsv)}QT1KQ( ]Xw1kt>߰brǃꀶqa YQi !X2k3'.2=KJ __)\gs^@~ ^4͕W۞PgbG_ gqmbY}S_6?u,&4`X~I /`[sJ(%Cĺb"U8C DUp瓟^^4fYTOmUд/h~(+|<2wdڽi3gSf47z߱v2wPs5϶3NY'V27o}ʟrg<7InD]c7ύL3Nekbkč_=̛Azg+̢[8vjԽ۾] sp+nK\wc5H`@B~!u#K9cd7xjN>zo~kgpZHQxC&==KcxwHrV$O>;9l͠a=X2koۄqSyTĨgRKW_U#H<}sdn46(Pˑ,l1NCMW"ńY\7 ] XG_lmaGld Qa8lvdI"..>ytN >1A@|7Y@N`BTĒ;SQ+(?f,PU0+3ԸcVze kѯ燠a2īf0yƢc:\~[ W%=EdQ =>k=rL=Oh| }~ꝑ" o‶~F =MmGd*)Q׆P}f&[RܬfqmY <ͨ8{L҇ W@ igq8 膀MAtE z<`Ȳ UW9h Mxa`t22ԭo$]VW_fKχ)}ow=׬`5MˌF4hÈmh!f2P&piu#+~\R/ p?+āpT ~u7{6xaT4ig`gf @\ִמFɫEN@\p!?W9dgwKeQh؟9ϸQ$;>R1 c?%J(yKfHш3s/?MW{F-Tj2 ߲|ZG:k)ilQŵ?[©_Luc%;\V6}Go|+'#E$9E&a&$HHGP ^6ժVEdQ}h}$cf6n:0#T#*V/Fd:l/ߟ<2^viɘW_T\iPgFY%i=qc-o3Ϲϼ9.mY/M"{;(cl%|}(`}ݮmx]e#w6y3g0jyCzH92ߢf/ [y͚=^RI.>܊mܵ+>j_#xkb:_DƟ|h/nFFh IqJ6l1R(%N,k؛'&\ݿ>EcҺ&G݀= ]DA> 6nTt^fsou,ӷqo;}P.0 Oj'da˶ԭw1ţ޸E3𚬅ED8h!Gw˴ZB݋p8%9)x2sQ=]*|2~x>sGdVʴb7H CX`#?Ĉ'˪s[&yeaV@Lόfs33X^Z0S-%Y 3c-ox|̟GS6o5;M^4^ '3\x9b:{#>ux߿Qʤiԭs_}>b_Ђ©Gl(+trdO\5WTfz.FOYtoVgo +8 f\Ql$%o;fKgisquF(lՆqd3"qt <ê/2=0ڪq&=S%0f՗[>+b{glx6qwItXؼãYpdocyY,)8avΏFMX~e`l {f~ ]7>ɢ'}a읔$=27fq)%={-jeOtf̠b̟f}Q's͠<8 e0w8bc}̠x=MU@S V|?y9gofn!'8_edy{`=×Ӎu^Dڔ1`x#VsJ/gGmhݫo*~2U[zcsEy5+wϊX\?ugܞ`J ko妾\ӕq=IuҦ-5iqu,=9!jΨ䐿3Y4_2 w-<9z\U&]+C{'=Jm#gwH {0q_ILęǝxUŝr)v4.KQsm:Aۋ IDAT4|n7Æl+J< ͚sq\+msYej=878>ʬ;z9Bnmn=p!dѴhWqWqpOeo0i*3G"aׂ SQJ%|0Q0;{[~׿KU!EǞ9;&-е,nĠ|)ܖ *bכO(aeTYGV+^CRR$ $THF3"b 8z*7VuvcJ,_v[Ikۅ]͒X2i믾NbYbV/.. eF}_~'ZAN;6W2D;_ Ɩ_v;^!)W¢̟兕8}('gC7"gF,*My/'?;vx矽+\FLSzMjլѣrtv=o;O ~7m\%%IȉtPD'^\vhfCGI`0KҚ, t*/9);vD<ݕ6fRI5'ncT^$$8I1+ -,J\{# cmW ><8i)t[{zh,0%=R}/ϝՋJnS[p׽O?D9Es 3;Lȶ3xW?:zc~/:_̠Lr +2|<;"/SB;\> .!}f4B͙+ ^ǁόm~`ܝ + )W ūaϷr=HDL5B2`{ /Oll ~D vf̴-,,EYDW21$$r~̃Cѫ-ǔJ_$'&.Sb*@|b 4ltB9p M(^\͑G8nEXÓBIhF<FZN)($]Q&˸.rZ/žewPf>ݡSEx86,,,,,,$NW.j9*a*vj^|=NկEE޽WYkq:cz¢a>(*z ɍڦ3\lJvn(۷nex^-,,,,,,;tM(hE(999͍| $@DW",f?z'da8dm.9!y0eDf;iM-vuLәI}l\:_}83#k:M}t݆ \YC3X~;^ڼ4m C+ݲWG#Zfh S"*s*f~ Bie6\\ntóe嬦*^Ń@N3NPugã+%*RDP7. (~`Ac],(ZƂ E$JT: AJsd&3! ̓ɽgy{ν)NZ $,V[ Lz9&x}ރ}sB{z[d4+O_*< z|C/k8\ˎ3Z;c$oi[bh-}>+ J.5_zs|ֵ@pp(j "5,fbc΀ SU[JW~(%%%ȊJ6`;^'Ƒ.}`.æ߾7HWPe_[l sF\sM_:;W 3YAQd@!22}={֩1՘j)/)A-hujjiVVE0s/7Uj:HS`#A}$n*wSr/:4qw- >~2F> wO)T *#5Y8ph_m;NCtZB zb c$d_ ٟTp V+wKT_Z<ziܩ_?_iʘO_)N/ 2EVh!{EEG1!CbŪ1ڷLAdlu`0Y-`%U`EU1G,W/:(Bj5|]2ҧ FـSdZ-P#UZ3РӀ[ KOP]YI^)DFgnQTT`/ `<<]|'/`tWj+`/ҙЋk=: D1"HWU}^1񓒸'Uw*A/)6Q:6>Sv_wz{Ĥ\Ǘ>l O@pzj 8ؽPmVfkNa 5`hFv9.$$&TWgʔ)%Oo<\X(VpPVVƺuX /@QTulJAVc50m UCoСAMyI) ?~<5ܱeD|qcpB= JV%yŠ+`_a*QQ1p2J>$ڠs"buKe"""X⽑>nz{ݵgЇN9/ڑ*6ej֖=aF.AϞQ<(p+:gQ[AN_fгS kVt3LV^+ⱼ{8}u߸Dz< 9.8}&ՇmU^V߸N-eͷ2E/۶:CO[6Rl'8v~!;Vh ^Ű|3v"YÜ?wU|3v"H~3v" o:aȢۙ|V_ "Ud~p+#_W?LLkw忭btL({[?r/4I 7ps yx2L!cc߯.%{A۞7o'[E6ֿg1)0y8:TW,aгSmsx}o p;7~OfwGgkqh3|cJEc2YSddZ,Uw O2VCUe e$f76]ܩ?̀+98m\a J-^R˭\`o m":7_?$`3Zv?>BڻmWsw*oQˌ_;`ӵGݵOj4"y?<:#X5ng 3?0)Ea[w+J6j ~`R.RCVPrZ־v=VKoٛ-=T|Q>~dKX.9bSC}{f{~EcŪ?+\]'{+hwy;uƽg?d岏6.޽y>όGZ5q<8$ՆlƮ8m ];tW0qؠՂJQViI&""АPK%!kD;DuVb IzϏvGH=.ľŷ}GW?#t{9\=ϯ㣝%H!02޾g`B.*`YzuyWW\Uc}Xyh4'sMqhMө.MGLNcomvW؈``5q>u;^A^;CY]tKv ,F7k̝OYmνHh\" :ؙNnyꢠ\ ܆'n8StØl0zdӣ üWy?|oO=|h,K(Q1߭HMCϘ=٩dBʕeeJ_Q*yexz3n& 9,'-.. yGpΟ{]3yndT(JcG?trnc1~ł39Nq6K7,ϼgr&\5T2DNGT"|TP籣F:snOIKePޒlQAw/Lqh dvO}x6BHIvq^4: -&nbX{ <9C4Ѭ:C޽/wugv#4*o( ~)ڋX.Nm,<ùK[(=|<$=,C#֖wp\▔k|xQ0>~Tqsf{*ʰI!v)JZ"nۅ"*B h 3#u<=ueøK6I^|V}/]S_(x䨆bbtyPU#A; Cj+2 w!$tGJ箝,+M& !!DGH8__ m ۞~oʁ?P(@\[OZ ky| :\?,hݟiac,uxҷR??gl<2ՏzC5w;d dl垤ߚ-*6n Ls؍?BF. >Xk 9IziL^&=j=ޗtLCɓw-DbwcQ.=wqƟ ›`Pc;9RfQQ,|YmFRom^.? xsSy {@Sx V+y_+v~g_䐪/Zy RLN1Q|_m%"Yv 7N{6̏ZwknjB k$Ee&䉱WusS/EwW,&B3H%71dT^J+FPjtWn4jn/!ժ-1#/ҋR1V"IJӷ'|~ܲEaat /n#\#0+*Lܙ'r.ߒ|ez|?vVpfajxl?=_?O594r[}O@2ipg: ʳʲ϶׵{7\ǭt1{GtǑ(O{Wpeݱ;Bx@!k 킮Qm3WpzzDسySd7;\av$Md AX0\/۱D3Fto'rxwנr?X-.W, ! ݴ;Qu2/DקިV>hCëBGӛ?aOe5r] t <C_GF{FNI@O;;_<?l7?d58yeӛ= IRaY*+؁ *up=p>{Bey ~ZI~#5l &·;ncIi\xa26 2+\oks;j` 2>SWMMW ϤnMyk[9Gshp0_q  3rͻ’/x=7Yn{"zb N=Wn?!IT.;7-}?6%fŇ7- jTtj Tb\9#er7528{pqV# ;Tjiݾz%):NnI_ϛ\Ƴ)N%>#>ZKO;JoGLpW~ޞ:|k[{k.}&CteAEUq묶c?߿Y?7O;jV>>jn۾f$ \8 Va*4][Vl*v>Y)7L>'T-Wzo{}LK󟵅y[]>2"vGhaAӛ\_ IDAT}ԥ7$$Hvv\Ρrz-.zjՔY˷wzJY;,3fg6+O_z.&sr-oWFhj8CiEF/mSd`1* =zpQPλz3\1AxOoπpCW7k(wIh[SOI_y~rP}w;]ÕcFۄ>jf,FhxN#ƻY6O"CO/*M5'|&^"PvdB\X žjeo3xz{f>`2֭+:'wېTOd"1:ZR,jjH*$FYHXdnӾǺ WEޔ-yI6bcamC'v͵qi\l,j־nOwyk>t8- ΀^CeBؑcXFӧOwѐQfɌq OXx$ 2s?SL_@ r1|b 7x#֭#+K4:2A_ҽ{#/}{nW{Ѡys[C>d6*) WRzS.W8' | ,f-z $@8\.̣.IY1BBC5XS:VBR^yiGvgpXٮMEM˸q(++?K'O&--]c@pRTT͛y'xRz0xЅrjͷNnGϾm3bHDDQA#44C3b_1$$v=1!=Z}nѶ'I'66ɓ'aÆ 6lɓ%66ӧS^^fܹsYϖ^N ۰yy9'OvS Ɨ"[srΝn7W[c@  ^VBBܟٴq#EEGb͗]T Ӏ3M86|EӻOObYͨ56U((INNfܸqLV=tA`qqԕƵ̀o5`sss;w./N'j]FoK7 vk FTk\44]//fÆ nYQyjλ]ކ… ΉA1b]h4zJj8lX-3j*Wְw>@1U#Tށ\QL VPP@llo]ǛcL"Rj+hv\. 8vv&*< VNC#iQg2]]^ɓ'|r֯_(͊sz|gܸq,[km8w7_yXFT]G_ٮ111M!54#[0HMMeٲe^-))-h4(-/c{^11HȑqÍmg۸OmL =͎dQqbΝ;4'-sch7nWʗq5w\L4mppf _˿5f3V]G_xnY|r.\Alr"g~'OvOޟl͸qVi4 hł]vw>t5s7W>}KݧWTrEqqF6F8d[*-JII!77ŋ~z:t()))'sqClhlذ]~Cmt57ϵXZZZF7oH Ԉ::Ҿ?55ս`zr)))xdjl ڒ9pw}7˗/ D_@ "dJJ׮\rpGѡ|ה+ 5tTqWzJ`kqXl6yѣG3IHH*͛7#Dg 4(/)eÆݻ'R3s߳+`L@ PY)*ԥ3Ȁhg=p{sz"I$΀JAA3V@p1d@"Ш$BC lK.hʚ3jUU5knODRQQJŊVjE @ 8郞ȑ?^̯md%:6)ҹ h;NbYؕSU5( ,!jJ@ h*tJEڕ5z4e5hz43: VcE-ZŊnGFjA@ F2|P""Qi4B *""΀Ӫ4dJyI F쐑T*Y-(cۏ?R|F 2:I'*&ZtA-$6TkFı`S@@ w=JBق&cDFE3dzAeuB61#WNCN;_|"T:5YB@pqE#ޚbEFFp( DZmg bp  IQ9vYi!7xۋ#d@ :v=tG6ؾ];wh&<2GQn 6㫵kܡ3!aطmIFYvv0@.@ mJhX$v*+~ţhх8QqlӀ6ġ8^UШ,f݊$I5jт@ ;$ ,SV^,Wa %6<ǂZAPȱ",F5FFVGR'@h5**+[hj6+&c5*duJRB-+2ΰZY}Çh(8b @ AӭSzATd 1q޻D+)>t /߮mNv($''ӷG &uSk4aQY,h"@ 󏰰:wMС#k\ا/h8xӀ+8P3g쨑Ep@n]1/ZP yljX ZACAC=XjupzҊJ6f tz z8^|+졇رc#U1~m)H##QTTs@>v=kّm 8IBoSkr $A9^\f;-..&""cNJ^&ըw (mTL*I ),SBj*P3*O&;i8jTzNym pWѣG&8^B|L< Aà'E֪ 3haw`Z@I-VFD P { :uڈJ){ӧo߶3fm_oބ,/>Fa$df/b6a5镜dbcTj MfΝmg3;wYA/)>tFnf5#6@p>rŨѓJETdEŔW(-5mg}2j Z0ؘH4-Օt^@ ȏ?ȉ"ņag>ķJ/Ӭ5mcylٲG~GǡHIШTTTT@pޱ=7@BDDՓAٰnv[pU4*+l$݂bD9dVGE0<T(C s$2*vT*:&v"V3vc:6 ,#IjI&B:# 8Ӎ80jL0jn6^EM@ gUMlL]tc떟.3cBVLmgUTRmح6!ĀFnjHR5/$22$a쥥 9Q/@ @.tڅǏ](h:E'3'h|Zw:=Х;]hQjbu ?>ő[&%%ŝ&;;}...Χ70yf͂,?ދjIH̞-ᚎ:c8J5[ߺΧ֟,߳r!:Y/pdzհ\]r-]Z/WFSgi̘u"AHj-NH욌;#.@#-rŃZOb6( H AҪ1۬FAVu[ff&Ft233 @dffIzz:\KEow;7U(-U9SaRN2d%6mr0m 鐟rBfe$ef:9KKӝp0Z*wTgFy'yyN,QήnX^N@ h%%EevwlAȌq ]:'sxQp ]U~f^fGQdTjCetZ fWX |6JT 'nU\\III8Maa!˃՘!|tsϯ_i 8|\DR D&_ YqUM1@ 3w\6nuۼy3k}JYEeՔ!ðe|ru6`̀ *>@E_ױtإJfБCl&Z/~J*hʀ*,, %%M1?)))(uqy\EAftcCxu>%Ejw-s5)xkL.L @pmSkӆ؁fj㕫V!k&N઴: f'o/۟Ouu5ƚtg"p\z}ƗQڸFfNu6iZoe ;a,,[Sr5fhr^ `SQQZ"GSd"::24~֬}.SBg(8P@yE9$Pbj9-++f5k/b]#VxoE Ρ%F3FN|^|QrDEcYYqsƒ+j{R\r%@ .l;eDr5ram&R{YH>P ?VNA Bp5~V){޼yDEE tA֜4K.eҥ$%%1o<[VL<]{y(K(̟\E2fKñ̬,}>*i IOYZgXtt um%jn.\Ʀky@ NagTCMM5>9?H=\ohA"H2*NaRYSSOќE ;wJbFݔWKBl635U5m1hQ0UWawؐ$lp;Dk"yyc;;!9 0a^quJ ;@ Ib l kd]ؗÇak6iZAlv́J x  w>Fr7mΖشIa,ݘ'ƓJ\3.?3IO|+23Fe:ՙQؔ|cXCgRΖXęNyz:fe9;SA\u =mꕙr]z%e꿁\'᫟~\5Cݚ 毼@sʰdBvv cyycq_NED UWb#P) &cp H W 7)?QffRc[Nth׎0{=ztET^ƎUfS7OCcOtxV\\III6+i`UT8o K:>):=,CoqqIIΛg> xrEG;kBJKON:1j3MF^M&hYV u'/\Zjpy#1C(:l1#K-7[~4곾%梣7.Ϸd4۱˓SQἩ5/Bc40k 9m۷FZzӈ>y[uj )~Sl0ْOa9i鰰dcY YY K=ITZ@= d;)uh?m ChLO 0v{*8wINBX""#TGxx(U&cPiY6 !!zFhBB:W4pL:~[VVE Ζ%uz=&s-},_@4+yR<9=QqWQ\]6:TWtt.22$i5c:jt)de9zNU=MիlM[@^[KFWz%s0毼n־ :wJ )+Ga57hIT\çj"D%PDFZ:"1̚5hPi<͛Gvvb.|1ossE[VĬYJ3u|P \,Y3fs'UYN!.NbX}ujwL!{JWoy{թrƌqR9z6Mjt%(MMu feéSWzֿ =Mի%l-[@맧"簬kϹʝ65=_yhָ ?;%CDx*DAOee%!O? j>1aNJFeey?{o$ug̱ ER)RduwoϊGz['wt8a+ܭ)@.;ט~8C[P D.DVeMÏ~]F|>#\[ۻ? _';}|q]lx`~ 4p\3?u菠߇1/f=?u{^[07?y̓ϗݽ}q\(ǟ~'KdOwNu4%9*l]RnVܿI'ͿYW^)0dU>ucr~kooqmcd6dc}w~p㵶qw^E)1ۻݗϖZt֚~i07=mlc>wMbIHpK'#5 ,KqU,˘LlΗ6mhRy8Gt#H%FY~Z|VH&[-Zlllc6ϟw O?ſ ^+?|97|7o}V%ggÀlFdc6}3Dܸq0`wgG78=?L?-;G?&t_y_xcB6mlc?N< pNS/ܺydJ&'nc6}?5(E9>=3OW^Ooi1;7mlcΔƽW@*v8<:bgw^ݝ]賭i&syy=5tӃ6msgB/pWu~;|{?)p,G'u xܻukӃ6msg䧌%]~}c>#N8? z&r3X,FMsx8|'wmlc6'L'OpÃcڭOOK3 $yozpc6},7 LFn,Xk %B`AkPc4RIαzwc6}> )yf0pB¹i/ R8 I#Hhs/ߊ{Cgmq_B@PZ`K+H]˨ A!H 3 0).MZQ~RJAV\#D!~ VX2(G"D;S@<"IRZ[('R܂p_'<}Fƭ߼,aj4Rt(ÖƄx)ѨԠ)C !V`p$V ,VTt5,Ɩ/Xx/ EX[qDQM )kR,̝ c5a큩|e{5W]hѺ)QUJ)Q}U_-'\7lu?0<2Ҝ=6lC֢#("#4!vC%(~q˫nroY:(kURXMd:/ڽʖ 8EsZȱHk,L@k{V,N&إaF U B,R}V_\p "s{Ss[VZ Fm^hEkM q <}E(B9Y2"rڹ(xP[̓f] 07,U ~f<-nnkƈ]BJ[{;9=e0CӦIx2 r=ф@;-i"IDD!t:mlES<vÀ pQR.W9r' &{fk]y].E>>svٸXg@f>Əivcysr5瓞G5V=jb<횃uSYa_a={8KLFZ()N F,}N(m=(.D5!%R#DBZT,Yn8UF[SϮDa"ƺ4pA(5-B*VM'd?gls]. c!8x5\)pǎ\I vē)0MFf,P^8N3!y1Ʉ޾{(ұ%1YcM J@I4Y ^F,RŲnΤ' \a^652X0fµl)r6ob]1͍ʉ,sWd1c0ByaEu<Cf$ z^y 3P*e48r+\xU b-|(хߖkƣ+Nֱo7ilmi8C5?}~˝Wn1F"}1O<9Rjuw⻊!5v:\[R:5sRJ(!%RbX NG;wik1lJ$2"]xXcI~L<ѳg8Vr_\G#%Hb%I)4B!8}|Ƚ7S<~LxG#2..<>{~#}={!;oAt.A(MI ƑEUĔ@ȅU6y% R\]lJd9ʝiN{]}vWYjc*sՋ+OyN*]fyNtw9dYx4,M墳۷dYFEE ӈWX]ؒ5N[kرzv/;X}6aYnk̲kcp]<1ZXciwsT'b4E1Z[aJJZnMYJfXcQ" |jh K=3ථU`dKrv+fE)U<f)x`[rhX7FK>ƪe9$cDĕr/Skіo)uإw}7% q.s17¢GcΛT .%ӮUD6pq$IyHa#z΋pQy\VdnmG56fX Rֺ HPP )$Z˵)W!2bdY VP8iI+A[X(ǥB!x)]o;_{$!/7I8A8׀56JxRx$K04a` x8:nTWق.V,C`Z& ua2MJ☋s{dJ4^P."4G 9)0ϣbGn,F܈E,,],iù>i.G!D=//s-cnJX\hhx . lc|5n+ecq&K*Vr]jˀW]r+ͿiQ2TqH帅,qL\iOF rn8飧\_{߿%u_n\y5sMsS $T8(,iPun 鸸y(W{) CXS4yj B)rP"sBh AE/|cw.۽>-LhLxHf-HE P<~L:=@X>"MS.D2vgi ~1w^y^DžNq^[{l>VpEIh{{8KXm1J:^K!:%gڳ~+pz1xx-.;[;`5i4Zd:AYTvs#\ rƸ*3kJ f3RD>FBڜXv)Uke䓩]Q4%MS,"nJfVVcBL[ YԾymz=$Fd:G 9sF@ؚa5QiMnW 2P(Xtp3cgZuhx^6z~nU9XYQ!QN!7VCnƌG\%N r՚#)AJrSXUv/<V+A(@skFa/WE+W3@DQR0 z:ʺ9,dBӹsc斁(0~Ö33ORJ:hS =ڕ@EY4:IB XkYRQs.e mtuEO$oj+lBC㸳G<uηOpvzt]N=%2<}ƒ^xedSUDgp{ѨXE =sT멄J)B  })*aE9)8mpqh+{lvcyDq6N;-Y˧{tN砍dxx>{.G`x~0۝-LwNI3H"ypxL4Mx0p1޹˭wZ- A&r)gO!E !3Ej, |aQD&m2R_{NHz6Ν͍᷾G(ao11yJM 7jc&/1LN&TX@I|tJ0 T6 \ hc` ,+tYhZt:m0((ykxLdYԭ4 ԫ.$jgL )>"zU!h-Z3׈͢Jv-[D̫ō6C z(ˊr ʍdծ@~hٵ$3`UZ()W<.,7[B(_Z1ZmnS^3n4e0`eoo0 th4__/c nvM5SdW ZX޿ cfhNuhǜ!dy\Rhs]T@ou6TSKJIet7ye0\P~8Y q(n&!{7Th2ܾuWnyn:'Kchr.J WEG?LnDI>ZKR</!^Y!K5Qx)X*'9J <&YO?!^ͯ|}:-&p[$XZF sP82}˸\À~K&KR'S19nwHuWy6V1ѥn#"f9#4^+$v36?p[HeqFD\HHN+7orm hwd8FclUsSG[[lzic J[5XӐ;Ö-Kհ$܈EdgQ{BLpxuAy*}Z$\\\$EPѪtI.+(O[Υʱ8H`@ժ E`07>\y lιeU߸{񜔠XB/)؎bpYLmM#LVō_%Qi/c0+]աu]iB,aնYՌe}hXL.:@ZMY2KVӍY尛*,<-rζxX] +.ZWiW/Z7LB9.sC4@sHD<+o)mC_x2[(m>/T GuYmC7$p: cJ E޷:IĎ$aXR6I+E^ĺ^Gg{PA$M~{,1ݝ=voa'&dEDnIӈ.wJ6@}ȎIc=j.,?}L4i|;(d"a\/ɓ8$R1~P0[=ɣ4ŗ%_S?xIӄ~ϞO|2D M3><׮j籙+}d&-=w磕2 C}|7I&l(c.S>ẓB1~KN?Tk&1' IAK1:9S>i`S&єp Ź<|<8?9;wʫ8>xgac d9,ی/ R;5EMd,/e W;섴Z! jIi"rGiDܿ] $xPڑUŐBU˩cr !DaXs(.Xs$\W3m(J{`Ǭ߀m +uj *33֚`ds*sʪ-O 9/%\*eDZ6%˧MBXB)Fm<; IkZ&ɰJD1886Zqg$? 4f`r $BGl[Hs,1i:Dk IDn4is6{.7Z]^mt㺒V]lg$2:'svrB7lsۧs櫜 јqzSNONp\UFR4!p\AOLIc")b>'' C~//&wbӥ JA=MDTLǧ=p~v{W_} .$?5]L:/}]To O̗|d#V(]t)zO(ID4 o:BGŬ޶tTZgGS& ,KRH%j¸^59A28;2twp}o#[mT$i΃Cfv Ʊ9[BJI zE]PvZ$e ./ؾyֶ.F1tܐ$Ky gg;[Hch{.V@ Od2LQ.t~OG no359g!rXɕ?Dy.>ndӄd( ;=" ǷnwtH'3<(l~b%#>yct:%9?뱿~kܾq~o$qDeu-Պ٩`FTL,@,iֺ̹M" Zk,e4p=sjg-EEYU'CUE5lQwHf+fkXI 6`hj]]n!7(Ð ,yfY5|}Djq&{JosxbHI5krB!IO/cP>:  Kwn-.-giO|̓_F ou ;o}KNrk1A;O.6B)y vwɒi<.6fcHvIJWշ-RF#@2U3DOPs[Q6s3:~k Xj²|i^HE[!OR.G'/={r?qx˓|7wy17X!Θ<:Bګ9,"29*)'ӂ[ܹ}[DZYÃ8=?' qP*!G}Ďeh2fb3FHQ* B*^+Kwq'(CvwIˋ ,2QtZ`3?:f40 p]!9 yz6{->vY5O Zx*:^ݛj, ٜuNNU1cJr~N}^ucnKXEkrY6UI(5.JWs=.49œUAU%-HSsrL%<|NMصBkSc.Sk^78:5碭w]<q<8j):7[$x>Ə`͓%HĪIh gkS54#BOSF R nAd<-98bӦstxY~8g[_}$VKU2m1_g?5+0`*X@fM|EFm]cu9 2od !H`oO?\M/yNBpWV^nRdV%0p.Ϥ$oWEYoբPWtY"Uq2uBcHGt4EOLZm:;*b8AwyxJCFdGO;0H.ဳStn;ݔclO }ݣCnQqgͫ`k5_M7:v},}`bz[;^|"@2&{H;VuzkjݝE,uɒ0g= |D-XE`Ya{zI"^{D);[}nݼE1VȽwF{G\dcnLb\vi)fQ+:2TB]09hIJr[k-JFsUW i Z%)8!O\مKݦߢ3Nb ScHb8B9J#|$吓GERu&Wy$KI越lAhϞ>5:\<;D:L'/t:8[GltN>)KH9裏<:oinuzGwo3脜 rpK2=N?#drNPe\UidV1tJ$D*UM%L4fFGԑ@?d]p L_gc_[voօfW+qhmDS8.NuyqX PQ㿙6V46 X렀z#6W]W9mk:cL^l/c`׹䮾f9 2hQD)UV )3L#A~\-sW^ԆT^pMƵ*96ҫY%IW6Lu`5cjLg&G1&/E@WC7< ;Hg,<۷l|Gtݗ}OI<"3Z]oȵj97_?[dRΎ Z8Zbw/I'l^zW)X#PetAgE/s. h@}Ge ;lmwf9''g\F GcQx8r0"E\b4ᓧ='Dǧn72&1Sr;Z#qF6NPJ}fbiFCZ#\! w{"]"RM0v8a|9bv/*6pa{E7rw=LcBOV|lm1:?ћ98?{8^߹7-%8$rA A.4u2̴^Yd>MHV RU ]Hь*A&euJ,?qbiKYYbc 8M]۬gEY,KlZ= Ce}?7R[_uDm_Tڨe%W!XmB4ǐ)U0fWf,jͲUM1%yK pm+x@\E\D +Y~k, x!Nj,K!漣lT3Bi{q2!M{+>w1dkRzTcׯ/{#5 $E=n<}&|$i`0(OFgk-DViE (3T :uubY(RUϬUV(YB1iDFy &ei3M.鶻tZ݂9 Z|-RӘFgcɤHDdDj@&%\ۄ6!8vu;0IRs$EM;qۄ.XIog~;$)ѐ]}30Glut=^v;Xdӈ'D|qGo Wv wؾq.c9| !'py뭷w]]r:,bpp<9K2bӘ$D#Dq.K7Hl4׊.*=Hz@Qέ.Uc\ՙ̫xɻA57繝^mZr3[UнYU}MW4"ʅg+wp8$" `kkNd2a4 U|!`>:֊ i&jȟa' }l6ϫc\qm_Ix}+J "y!뀟Y@z\\rV]0Ad[3z3/OxZX`\ ǎd?bEI6{mI{ ~12 `챮tuducC,'ϩj*ΒE#F$ݾ{޸JXs*9,A(҇l"-fs=z/ni࠹'Tn >v==ƇT58l (PLS_Q{ C ]ABnquIa%ktI]z S崓eh?'q(qB [$ ZhI"Nq$4$FL @\[w?]bD HPT[UJD i6AD(yQ#TEqdQ p{ 4%d I.̸ )Dɱ]m_&Hf3̦ q_|׸x޾'18>^!q *.$ŏn}VKHJ"NaJP&ŌFIb,B=0UgV@RS3מ = BK?Q^ X}燎T_GNätLR Z!lڬ=bsMxq(Ij5j&QΈxp}{X·Cl=6>\iY$8mon;ڵFH?s}R"d&}z'@ 3Uy(eܶ7R'0Ձ޳shn,!ڮAS;$(.KgǻwxBԧjh珋KOBA%Zm|ªQP-4% XוH ,q\~x .nn-V8szwwmɊ| $`Yȯo-~hDY/^׈(œSdcd%/"!!%jQaopYe&XB @B\j|19˨FDQ4I]jsiZ̈#MS۸Тik ԜGXpֻxepq/iPuQܹOQ]5uUa;fZ=$LTm0;wӾt/H<rgO=y}#^Vz=%P=ԁ,g2U?s){(i].PfD8wݘd*Ç}E_HAӴ®iM~0 a C )(`0 _oq|4fn#PFQW訏K/~K-fh $fPIVMw(oѬHR0lc(30Ԍ $i` 5*0JqqyK()1dHqh[.I.!Zݖ2u#,ˠT1xL%~1 .zT<}fYôl=gF eWLѾKp~8#a Mں+1(JDZ_sPUo:Ic19Pa1Fl rQC h*Tf(UUrb3N;Inn9=Klbdoo2WWW8G6h m!S>K& UZʄךwAjKvޣ9XT> -x3G򶪄LBͦ]=&ցAAd~E"M&Qp ( 9raAӘTH@(D<DkaQ m )F+f%1U|P##zOouפizYy e]Gz}ޗ憯y<n)k;!?U9 ܃4^yq}-016#"ue5S>مӌ_pVejL)$ځ87JlS㡴?"gd0kNl z:%[Nuwm['V9Y5F[60ARd26:9$Qhu rc7(Bs/0LqۦBqqOPV:/ ,--67X(H'o]L4x6q,%j"njFTSh TUTCqhB GD)RKZڍIM 4D*pB$)KIxf(BǠil[\\\য়~61q BM|2aIzGݕ%{@!(mq66zxz65!Fes?34hӇ#TBiigsy{ ?&8|64ٖ!y@Mme z&c Id?GF?Of,|byC !E'JɊ ϢUa:",b'ci0?ʇ}AzR3J zU0'ft73DG&~^#}t@՛Z6nLZ#=x0ܽVMc|Tu4q8 hp^~j;^0Ha꣌PݢB?ućrhhHh )@%L3pP)<%Iwv Djj (%AAD)рLP%) (gP HIbV(%֑c `cpUMPKhƱ. |oEȐat \|x?PMcS"2D lj- >|}ԀV Ray'`mE(]9#P6@%hڰlvQHlbZS߯iܢX7ƏoOi K;%-5n.A@ fӅ遴Nii)J/0Bf,з.BŵtTk~z1bt<{8 &N[W֭=XBṉeA$#< gK]3rM׬l+M(rm 'M0'·aN9WZ k|°Iz{@N$15a=V dbnp; /uGrDZ mL%RBB[n<CPFpukǬV6S ̾GIɦlS3#4)$lq$МCi "+]hniUw HLI.0$`u_`!aUB i1&(>7?<h;+7lW4=b 58)0#JԠKN:6D];L&45ʬԬE 9],#UQ j#RCPR l]oL'sLsk|ŗXms|<G) ޼|1#`Q $Bau*e,bҜܢ(Bbf`,l f,ֵ#M D |t4KAIA-[oo~r'!<9?w߿[?M2|ꫯ+''u]cY҆L2f2RQvq7Z4@*#UОQ">hu_>ո k9BX?stq7ep8l]` +qz{Eziݢ>F?ψS9s_Z4wpI@PvͲqInZB?B`YG29 E4=I^!e֐U*qwwb} e C&CJI =Bv\ݠܫZ2B&tɁn&j=Jwzm~)hRaY{j <.N (2- &4DTEeMcU6FIV!5d!*Hk(T#"$Z)-mZ2{ (;2&Dpb2d=#l(5ʢBi#77(''6UBQpN!h( D8 M=սwk4NZ?DPkE"pk(%qq{??Ưɶl-rY@E_1މwuu)ON?a 0N&56.+"BZoɐE`IQ3agM3R,´ (Q55꺆6xRAQޡr2瘤i@ !%њ<=n#6 _OeZ)-X, PIJ$I 7x޿{"/(_t2A(~-~ߠk5^>dquyw?vbggPZテR />_}B!pvv%..w(+|qrrdl[KnBJ/?d2AM@7caBGC;6 #AN"mݼeYiLY4 $!@`_)t9|>4dl9xaA࠳2xI*ؘmT+a\WeHB+w qXfmضm5^MvWy紤RnXPR紤]M}V}N~`ahɀ>Ҹ?$ 9&:1$|I(*yimmP%ʪz[;!(8z[`>#LVHiBIn@3Y2'EЫ(|}p&2JѲPFZ~x( PQ9dۂhi0͠?g/sbޱ ̌9B0G0 ͬSc;}[9 P{MP(RHlV% X8C#pfN͚:`({ckZ?I @)"u 941dhVSdlɰv1b$J0"0$S~b^-//p{sZ|WJ)\Oo7=GGGs\Di/`>_*u uSWN_~_W_+TU~77(-~;\_aXb6G1xOoA縸+fɝ>WXÙL'Wv[WXV 7pwUaNE=-#,ô@YDMt(&A u 2p#cfcGm9LSfiHbŒ2Oz}?<$I4 h[u)uV!=3N2jk >CywtGֽmUUn4yP%ƴiӶn|qUUh"$C* QtOg[*YnmƚҺkT(ea[`D 0pnjZјUpT;sז{}ld 1=Go?}NqXw U;&l,̘OhU)~GgF ;)qIރnD=2RRm[l~&1n~R#ռiED&\q#Ĝ#J'A(6^BJ='_v;1 tATFT窘ީP;حlAQ4R-l7c4I$1GG8yf֫;W+E&Y,Mm(g)jalд-&)᧷IfIEnhƄsa6 5f1l-jƌĎ$$Zv'SbΡ@ܰ7ڸ I4I s42Yv΂ aWwΪ.1mК~=Qf>uYqƽ<⠔wYxݫc30vFtd8z] s1gcoO>v817[EY˰Uڦ+d=kF\q{8A|rl6l6t:ApB#Ku`6Retzj=Q+Q?} -@BSmt94{Dg(fh=Hqs}}zhnhy91i!A8&3X$:fOɌ9YV4QG k km̘R#59eJQD"@2<g܏"&2=r <݃b* 6H,0gԠo0H$}sԨ8X!M̏Oq|0e[\]]aYcSY#&EXӉy@總1u[%x PP'&=A+ ([H%슗=D<]NS3>7lB141ye.Ԍs!&F+t}8&Szæ`ʴ&=MXn=P}s}0^> ~8 Csޕw|rOk@U7Mh{5.PӆN; ?6ۇoCR;5&Uq, kB]רF\.q ںAo͢fuÎ5Um) 0Quf]4e4ODRklW(J˞5֫-Z)^x|l8䇢A%F+-MhmBvK4MĴ|W89x}]רJ"%Pcm+u>k%n(GӴhیn(mL+>ӭ9¾RA t08{z1?on0QH  DIpMXE%Di<;ß⋻ ꫱04+kuihf&*2K 41)007RڸK!AbDC5-[ "M'HOcP{'BjنvxDI .=j"R"@Ǩ!M&j\bG94OghC6k\_#sl 045a6"GN"ăJPE1f9FXܴ-*+e`\J8,a%syPٌ[!w Cfo;@M ɤc- kx SYJj}lƄ>^Ac=&'v,FG,B6 #Mv=IľOftl숶:7J-s0b!Cn.swO>A뾜hc>7i8CApp0u+-jbygۼ)JVn a1 U;l[eetmn,(^jx&%8Ϡ,eUTfmhD8ĀPxHd 0AV+\%V+d||;y,0dB#Ŧ5[l7[l6[E;7"&8;;|D# 4BB (0iڦ8C^qusFYx9>}֎ e"D @I#I vUUC*r$ 4haMx3#:=9NNN@GJ(-56B3h RTvG=mZ?-{Upxa?Pp-ڶTg1Di\eS50P 2h6Xx-Vl:ل]@>`#T${ (%&K)QI4EspFMh)Tedk0,C9C84֨[@xQ 3$F0T5hk..ڑ~ߔ3'r:T ww mR8a &uYqnFCzggpl;`,N2Ȍ =Uk{<@Ի-#*GB&=c \ B CfujΑL@ tvG=|4I 6ye?qJQOI,p2mJI&FC5uXLbO>Zke(q~q E#u]m#Opt|(b1Nj/lo(mÍ|5M\#LprzqL iAI4Mk< 77hY-4M|s7ozFYHqs$M<:B'L[CU1ME .!Dն% i[)s7f IDAT"Ϩ0Π5 Y6^gSFO; qȣT%ckgH7?jY$)@4D]W8>:BL"|ݏ6#P!8FӴ՛:%+I&dͷ"C s1)Kű31icFot0}q|ڤjkMaRl!Vx&P/J8”'@YM#b3dz\Jz:ՎVa$HU&7C]\'&6nO}7 /;"j`ٍ}Q.XW641 .D%%d#42hRlaZxغ1`'ҥ촑쌴)gޓ}]kXƩeS2(#;R:{ϕ} a)+)H sf*R{clZhS%eM$va0æQR u@V7vxJ ij* ]سm\L_#ՔgLP6Pxh._+Z\]]sߙ!1 Mt_S0!Y"I8=6jЌSV@[MۛI8P[l6LN )#>Af(M]CٜG8;{֯wX CU8?1ָ!0-E[|f;G(5,BBJG Ƭ0IrK ٸJspJ.`t˯: hd=T~m_WFiOke83ДljȢ#XsڶnPQbَy4c~~:Q\ԇٍ86:cсZPĀX8,0ޯ{#Znt #lRu}'އ]i! uW\aL7\k݃:ٍ2wD˜ڄQ4 BmkԭfMcZiXFH# N9,_~/^`>{81@Yy'Ob2M\,S.'Lb$Կ'QyAQ4׾սnPL28.TV cM#eS4M !Yln,5'O~>AzY2u$dBk5 ]͕XBymI|LQ#NbςhW+? \Nc.s d@kǤͺ(3¶ֳ&g vc DOi!ݩp Ln{luC@gPe?Ɯ# ڪEU5:Cr#dwd4j 3 N3qZN靼(RA_# 3p`a|~6T9byFyO^>K,9|>t:_QAΞCmhw,m*pCP[DC 'oxD-δT<LV/Q[P;m +w^T ʑtER4"̪G\v,ME[]3]S^Q5خVH&I8M$):'$%VS?^|owXvCx<_7Θ6Na `a_C^}}c1^}ݨI/ ѱOfهDpN8ZªڌnMZ ic,0FDi>#Tr'p5dKX&#QE:hHix׻Bfͻ]ǥ|@o`|bw/Ur0o}du|x[3z2NZ%QF4-adbjԐBZZz6*Q765jɪʌ[)s|ɉ͵]ʲynU Qf930tr|ww+l7[5qsȭm%a&h gg'+LS fg_9R M!ZTea*ZCYCX؂AmӋRfƑJA.]~D^k>uӧO `G* s<={eY`]A)3G[t"$\:.(ÑMl6!{{S+@61!Rwݦ@oܬf,AV|l 1؎He! 1fdrr^Iu, udځ=qM[jơ`a4Czh8a4ðw2O;vcA x-ss@el8(%V M$c9i+0m݃C`PF@~V2:A9Fo聄PGG"ݜ5= 0CYظ|\x &{v:tsuOf3f7#!Ip#8ڸNy,-8N'. 0qt|l|RjBUs+C+z fZFVhEEEx)(Fn+0J,"MMKKx5=;zMVv6tk4J،C!\?R= J&&^ ;>FA#Sg]O8E$*" \@Hj[$vdۮK]U>=d]$} A&#Cڴ h;R jԄk=h}F#mG-b n")3>бt=0 A8ɎmjN͎wׂq7V6C@KWCC#! 7~3o#H"y:D{wYi$*h*0{IRaXlcGS'ԼYjhƧ!߻ۻ0z:K)鍥R~4o ދnŃ~Нk ]P rG~%~7Rpi6t{= !=!ё4j)|x4 DiL),B#r>4̍h\QjUUb/_ol6CX,( 0a" n89>l2-D[aEM0IS,IHHX.ggnyF6w?[C:Z7sZ'}4twBٍrlj4pRB2 %4&,hy5)w9oO_}c lcL_Ap۹zY OZ 7SbԐ6AIfN&y*ؗQ 2xN riPVL8::F 8(s)UC6nYÒIY <(Cwu#&0 GKڼl[{# ets6!xC71!C֬ϴOk!B(h-{ Rp Qӊс {hA>ps+}}F{Doi%;DPk h8#@qJ*McM:,wykm( HQZ[R/,{lka<o'WO0^Ջ:,%vS)IQ-wry*h,TXz޼9wN1͜fyJj]e:X Rd7BhDNlFL5/ZbY$@{y[T ㍴Zgy{pLw b0ԿF7=O n!JyM}M|O=RD"W:<.r J)aZg6*PU5ptt1hmHΖF9SpgIaP$zeaj9^hE:nHYϯIIp)_A)hO;kd'P(a a "F 5VKLw8mbY-/PT +}EBQ$TIl:g0TԸIk^iFLTo{!Tn€1ٙiP#dE m̏eYP>to.=1 4s_'RȫO}v"DCpGa=oY{*|PR^n@|͇^hμKE,!rds;M+TDƘUxMrRi-iF~3) 42걅 56P;b^*|HInLdċ"h+ Ok%Ilyu>ذ:li8}=;]яF#mCx|ڢBr~udf >;锭/6*(,n*+%M3df I `R"NoqZdQ9B^Pqk}mVFCip)n =J.<q$(k ϕ#iBX\5QF=ۦ7>reQp63XH10Pp@)lo\\SHMݟ o~(g4G}q!H۹!r^#;iKFS^tA3>LlnӅ LO0\*Btt? :+Wݡ &JRLBHQ')P 5BM^_Wߜ~Z"9{6S}Q&V ']F>=ONXiMᔅQ ^sZ 8֦ gӑgSʶ_kӧ ꋺATEY&F (yL^q RAkIwmזY?.wѶ23Mu:^˾9pNk3PX4.:6g_]t\ag4e»Eҫj5ӫgֶtQ%LcP#'[ ɣ&:K=c0s (eLSc /{ts3Us@3szIQ=tLk cԤePxEX顋\E%>. I9FCtp'! /. i Q!Imn,M*;cRi\HRJyS9 9>#}g[!R 7)T_n &"$TB$n^>UOg 14TGu&1iE](43Mi2cGAY|ͳ<f;Q2,Һ֡b'#$jӾH)c jȴڟ:^MK>khLR"hNe (!N8Zݰ$$ ҆~¬k$ewT 2&Y, ]f̧az n&?8d;!\eD75@7nskTh|+Z]2%n_{ [NEew[)_Ԇgvᦈʆ s\{\Sc/:I/JE!, ~[ YГ(tFosezL,c( pMSr9W:Zpe|+,'b 8n˺ xl⥗i  F\>{< jJ(2˅J-jp\<"e,H .9LyUAjwoZpn6nH$wūxƕ43_nM0eofJӉ]|\y$,vȇ-yy[H) x^{l>BzLݏբ!J9vMh7(M;z]@| IDAT7Gms(cxHƯǗP$3 e _]tLCѧtA$tޭoH,ːQVCR+ԩZ cB@ {c۔"mWm(,Vʲto7Mr>~{w߶͏4wCdgysR[b0!IpBx_J œn4x@(x+ flRRXWxso> }ju.BԠ:Fcm\2+0Xvy.]oO>noW_y.]ºi'mӠ @1*x>NOO/@3i4){qPĝ~jBh-<~Y[7w^9@t(Y,<csYA*͊b F@a;;g&}]_yB ai&0>+T vR5\n j:zh w0~NVv!3E٠xDOMSWzANIϼC~u:w_gD2Y@+KR93$C)uӱRpd(n10J!Ʋ~q瘊gϲ/mF& .&̭E|@i0r|ր@IQ#R& !f^%gN ]XGu#4@?kRu :)%%Э<ϝB)SqB$bR|Km"ݾip|zD/A#7;Ff$ޟ#i/gM{y̨.cз%rJT;sAD@-6R6K>RUc( J)sdY$,~3e4[ RYǼ($b_I&<'o po9px@Yx)%wp.i?(lQ*o:;DwҷY7+<=BLD\%Mia0+hޏJqH 1G x#3&v>wGC 7wG wg cNjh-+:>/pI>mBn:nB_`ǸgjL`d {Uh B9Hд%f-uc+y`Dj@X3bs+L>Xzht:C19V|{m4eҶhV+/a\b2i +i Tm9KT"YPuP 6;cd(8zjr Vؙ$?5;tBJ5`~$jsRA;&6To kz-e&ؤ -CŽF˫90 ;EiS2 LYwzekzB-˨2( &ΊǠoFSLWʼr\u]mG5%NWdQP*h8&ú>ljΓ3Z<+1?qy9@c3~im~_ gI4YƉ%1BHAS7h.ױX,ZPUcTlX8$"f?b˦u;[(PBIٛ khM$)IY^ɏOjnX/O1ͱ5il<ePjE $~m\H/\ɦ4Hm,{(bp'<֔v:pŠzN(@d^,wfLfCXLxVşm) Ms`$RJx)lp}݌iϥ`Cʼ+y:!8qJ9[kH г:9+CΧ:+8Uܹ[4/\b^?d"QP]|Fƚ/Q+y$5<]k#IX2."e:)`RG{M:Qi"F %fhF&Fs,@7?o" I*/w=+*_1 PJ)WMSLSlmiDBXG_<u*tPNl9&1H{mml7_gbu n*\~[;O/sO^^UG;ߺuo `q1NxTg\; KB\|PJqG~V Ǜ߷ , dg+mEJP&Ʒ {gkn'Sɐ ܸ20iyPJa2`4a<;Ĵ6-mJ9o"j@gIuF!M&~]WXOO0M0ޕK1Z̀ [әy=4dQK_iɪr=]y9̜k:9f^ ueǽF6Iq1y\7nĿo%~NjTX,N1ˋ+$-E E˔a<b(nK{  Pa?:9Ft7̀d'>xGPl7F]?'+?Б1L) I's# )g&P6<Z/᠕Tk< C7"TBeH7+Ȟ eX7iX3G\F7١2)zPB&io<3)<&bӤiPL' FZ/ǵT%xS_٫?k̦{o֠N7(,Ke"-J4>SiJ]X.y6a:% ҳH<ɠs3>Oy.!OCL$8mGG8>9|>.]h`0sailI䆔@ 2P~ꦔ+"2a>q9H*ԼiF(zhpk?/?._wA 1 "+t' vn8`yB y׆P8(HbP evb.v;s6)\Zk$`aGƑ&.ss=DKsP2 ?FR]@?_*'|7z{VBZB:$+(4qw k L݁Яd6\BށKTe@ n&{tjF1Vwe>@yhrjAUJIM>Y8τ)zYt,R<ќeQe9BY\dd*<99ӧO^x:Z[άH#)y@QG?~؄V]"e$ATJ?-hpzzh,/^3tBk 4O-=n)M!ȅ 1󳟡x!~m#~tZCk_H㢄qt.o)H!6#Ūt4D?RZ7"ѳ 8Znǖ RɭB@J6 gs )%#=EiBT \H?+5>pDGfI)` A-gņ_-1Գy 8,#.IY& }4F~@A)E{?k"zlY53Z7^[lxU2нƀz'P#mԞifYΘRJ2KfY4sVUUI<P/{f1}xS@*͏f 8PR(]̧31/)FH vՙ OFtl!|IEX8$ `Jrȝـo;P~y>O]Y"ӑPyL!E^AHB}2nH(@Vl*>TLMKQ=2lNMma%,Nh>LQmLc$|v7AF!. @n~4& (g«Gc`&Q@r땎PX(sz}p)tޣԾuR PCeRfRI4mд5DU7V8/"[2P ?Kp#o#F{ơ &TZH ]of:`!$UДr{V; B}A H9G Y i@545rCT5*, 7ܷ ^'^i:̙&=Cpke] (S պ2G=g*7m +]bhO}^!@h_@j$AYQzSK3^pjpKn)_0dyffCv};r$ʡ6 I:\LP#>?ak>O1H 5b\?RaT 'j::$YպH;At6qhېIo|j/d*5Q=^TP hmբ &%(CrzcPB7"\4\Y-gw#hf,3ʳ0APqŋr-E ѶZ ud[ PjRA)]˔tE#s]0+غ'"oRb_F-@/~kyi]cQ3=9^Zm&G6w^n2nv:㘿:Ĺ %D;[۸r 8X׺[D Pc6R P0f n aӒY^l n]\Ǩꁏ""Hrf$aoc.:c L/~@._k2ڍ\\wr|Táp9e p ܶ"Dʠ5%T Y)'Q^!S)8vJ3Ơط)Qimޯ4dDց?pb7av0J{]yA~2Cϡ_L:4JC$BpT )~FᴨRѪߘ(%@;18nJLJPTSaL0Odq\yQ=4F)2 X[u ,@4JHTK\P-2,D Ԭ54FzNd8RmC#A5aQm("W+$1 ~v>E+RIU8]+_GH@#k"\)#0+"7,5\.1L1ϵh{UUaX`^*-͊9#rANؐӠj΄p%ix1o1gSJ)PEsxH{ԝdR4oN" Vh&Gx_|p #W.~񋿻;@0hT =d̚ϑܘZ{`ִYg( ilDtObk6 Ca=ol66^~ X-W8:9jX,PUk4M,懫KФ/ݦBȆ~& Oɠxڪk.F&ҩHk Փ9]:=Bg"aYC? d"cExƫ}[k?|nje ^j -Aܔj/JLGh7' (֭ᩦSp~>% -!R:qT IDAT]Pud:#WMc^E~my!,$Ma5zhxPxa(}+z[ <)(!sіׅ$pYV攷5܀ Aՙ<ucNʥ҅vKĨ,3d2EGƘOqX.ѬWgf^~u\v׮]CWkiKb|"&_D+f,3aozJ@$nԤt`R2=HJ՛7Sn(yNJ(G) 'k6U:¢+!ebmmg0,p( c ؚoa{ \zS, % A'=;z/% J3l5_N^K!c<;h#c77iLwٚd{1 R(Ǻ <>> |4o /|Oh[i (:wz=h@ WJRN*R/`]u>ȚA7*u1" L TBqH@5Cg$@$4@OB D?t:ޣv&6"IZbAᬍZ5B ~^VWy[m-$!U V ًԑJ {?CO7`)-2]6O}EF'7lRb*hfh_l‡V@AiǟRm^+׮a5(aU ;t@ KHAA:pR%f"OONq^RjH&c ,tAIec֔"!G篷6g){$OK[Ooۦ5AU NOF79v1ж-ApUU.KW YRk1Jҋx̌k:wDo( ] #Rf̡8+վiTp A9((sdWmooc^C U[c\7g3l]xO/jtڦ[J%IofsZ!S)漁ccGi~\䢌 - %MaJ@'\{_LyZ\Eu~1Pmݻ0%߬W5#%$&8nb_n@a&mA;WlsXRS̡e,2h-2 ?zǏ 0+/~Kx+|/}{ۻPJnjw>kZx7plo`6L0[Yc^ G 9!D8xvNNNQnMU+,J0Wq嗰siM6R) =T!dJzݥ 4`x}sd54#]j<)z"mpxaJlExJ,q}LTvXC)ͺA46S`a2`6a4a4a2`:B)jbբZ#IvQ{n RJS6duV:QwՏ u-zKB&_P#j쇒y\O/9]Ĩth[^xoǫk.aAH^F|-JfRt6"].Y+!q.b,ܸn%G(,]{Ƣdj$s9щPo Yc=wZ8SJ]F1%ʲ6F+\F:*)ͷ Q2W\+{dd8$Hr62Aś;!^yon(`}zX4 Z豎6k*STܽ\Uy f9C|{.xB`kkKo*Wk(\AnqRB4{VpPJ}w7D]U"qRtfh!뉌 "&kzQ졃١&ߖ6Ҽo0n=)ݾЗYx-s  ~V:ͯޣΦF3@p.#-xG'(;.VZsQTK[y¯MM2jӧ%_|QY=awo;; Eg%(!@ ?y|Q9ƥ ?«7_Ýowՙ`{ ƥKē#?GSG0.\Vע,AChG+M~z^H@I''hyW|yFc`E5D%IXi!M,U%98{h*0D[{j@ceAmCU$۰SA3zX7=db(XjuBȀBB5 ?~gNelo kV+&D&͚AYD7&Өf/>7v+uBJ8Mk (ђÓ(d4CQx|Qb+/o%^}u|/}<TDjA? 6cƉV#Fq C} 3Aڤ^ m)f'$ P:8m-<N{*Z1("OB/v$.6B$P껢f [9)'ɩԁ飒Q j(Vcsr!\Tyf9@)E]뜽owҵX.U鱞>>8x2Ob\b%p)X'?Ƈnz^qyckg^z Qrw?O1ÄMjӃCPW5x"/h_ @(CAQFpŨ@gM rܽso}@m;Y(N*=}ڥ̢>‘b),~sOSg۶$|H5Vl_mH6BTe-{h 2&\8"tX,8=zy= Ҡg+CHRj1T֐WӝM-bh~O'5m==S 蝿/(ȍxu Ti;/F1m_ >O.LFX7&n| ;'ɓ'/(#NtӢ:{ŒH5%ƎP]WIgEy.|_pHCj=Cy5]_ŨlqּسwS\'z*Gw8xv7A5mh;(__KӾsZ/kJCU58<"rANfr%\Y> Jp=_%>s(˓S+L,xG9@agg۳^k?3o=l0+z$Lv̧.ŞRPP'Bk4uӓcNNfM#m rX5:o N_)z0>):x8 !yc!fP چP4D'G֭Pc{w gxִt d#g[riUݨ;qA"V4V^ڂٸ4`"AD f(3Ѿ)\̏BtM%Ff*a.\ciIb RN#T(wDj}8o{O߻@qM9!Ԋey,AY{SsN[:B_ R|~P KYPvTPRoYA<˴"Q@jQQEC'xt ޶(I7n>NOO\.#p)@@P5?z]xȲ 'LJKWկ~_o8=]ÇCf9rVJ,Kl899!<a E^W} [[x϶p8<:slͷqWoswuU+U2UNB6s㥨)()Js?G9b,wQf>LkiLȮc! "RAiRY DE֗CRMh@ A(ή/Ѷm0Og0vk_:ˠxϑJեrGr6JoK38 6eWb]bB4MWSz1 ^vڃ*-&n.(Թt_.7ka0QY}< oFN ~ٸ:bv,!@a@IE 8!rHEDFr={vqܯ>$#p8^q Eഢ ;mViV hp h B#IwP+s]&S@ND " 3x'|fgDa`Z)JL%QVH#f=4^NYA輩@ۑv'lߚ)H3}Jdj3OY)r/ͳ!+awG9(-aۮ_'f2RK_׿~i%>x}{ZCZaogE)[[o({"=^=LLkjY݃R.)NN̄Tᅬ_xQ54XVԘNgN' RmjlM|y^1M042!H !@D1o} eQրRycEx x E9@0Pp.!xz {;؞0Q^߭;ȩIfZ{2 |zJ 0agwGL_s.$Vk1PX, 4C0rPBQ9# RVQ%%}]lyE@)`ٓ^tUJj{T6v yk=PM.gS 1/̰nS4A:IɺYX[TWw{;~?~Q7G(nN ɑRx,KYڂM`]O@BM(۸z (ehZrܾٟ ea]Uy-r:E4}9r4թ֧ `l(,!GI rP"S<@!#uCww!jxu,s,˝nBr<nmI3NZ 'uɵߧsg  _2z ob\RQQcIقR>ܦL)LW,?;4807AOb<&2_+_Q̤h4BaujuUC1]Wm .8Zh[0٘sQu]c\#-x/\r 9)FUF6 B+^iJv:!܁O ׭[+.H-iHXCZRz uZe@ ]QynRB)mlK= ZF E2g,͘KԝUy 'q|R&ߴMXZ#_ ߱4t w<dnׯY >)TQ7dQc Cwv âq1J a,3=dj mbw{cYp"Ը[!Bڔ`4sَ"f89>OGxgUF#4ƒ, 4ڴrC Bn@ m^PN IHl@O=%@eDSU9㉾~ zs*j#b Qi/DJ(!{q\Cd^Z_WBĬ&O% Fנ1/&`n=ڕt@~g}X@S =U=O33=4jp{#( E,]f&ujU7X,hFS!hdcgf2$jZkR '9t=S(MSfEe >dW"-j8(&9;GS4.Rq$h1'| N[L1 vLd=mxt& JěΒB5P_S;,&}= |nUІygk"T[ax#brhѹ  )H&Õ19GD3D%BpsX!暀!c9(MOM 0]߻mA2)$L kmz֑ mZ.B1@>a9TcԠT"|#*1T^SPd 9 YQ, lͷpio2-&_KrcG{+dPCŪT5h-@@"כ2)nfe*G∛tw8* } ƊP|H<6fiw|+,!l@7cgЯX['&@nǴ1w=8ʲDe(Rke(Qz @Kh[ZNM1N''4- ]SyA1LA zb%ZZS?ǖ$;Ή.Umݯ3CKl0`' Hecg{׷v%"~%#fVUKW.M{Uu8;߲Zk[40ZF >W]a-V $Sn-:Xۯ`.*R[A#c]=sOqM wׯ G*I !a00 B3>ǐIjPB R(=OwHra=}=*֍睸@`i LDk{ J$橱!1uF+jbJdkVC Fԕ (Аi Jy|Ŝ@ 틏5I(UK(=4^XEZAkKw_|oz]qqy C;ݲB QlZ bJ(ʪ8%-={3+eB Qoszrpw[0\ cڠw` 8E҈ZafnKb@g0+nn;FڂpyqR&*7Fl$s+g\DaH T>]'_ u^qK4z{^y-yՑQYMegzt} }z}1e# EQ  sj΂ob^}8CV'[M\R%ڦšm.rysynGLףd%^(>pVyߣ_ zc~=!qЊ HJ.y {q2͜QbɈ9Xu`_8+@,q hOL6 G;oWYjߣ\W>[L NG 5nd]vS8S0$!LCnC]k]'@`BaO1n'\<aKDLAmI9*J;3>w aXf UVb:.hZNN5օ/yO(( k1Aѭ֓/z8|7xPWp"0Ά" y1j?@1e6vK J^VsZ5ZJȇ l+ugiz/>suc? TG{SjO\ʙkiN? WW?#>Sg0pp`I'>`x{Bp'I{syKEΒM4 =:f=s ItvkD$(777x$r^)>cSF2j.}܇;ڣԧGm_R1sΩU#ZǝZT侘x` #oslj6s('ހo:@Pnw8 oZysUzsCBB!UW5NNN뻾aіMہ5w"h8gQV ʲ5MG`fl[Xk=rʲ6 1^U`+@@^p RiBkqq}ףT;=hwD"1u,g֐GYy2%NO"n@QO(t T# xxzx%j+Haey|s| <{z7SP I!y;9fv\U7|M;?_G ɩN8Fˈ=gɐ_sjhFnqEQ( h*o1gھOymC/*ٛCѲ(F<|_[Ƥd3!ǙfeL4V=_S>Č}ߣz袂RH/ ZUAhc٬IJRqBQxj>S+(R(u.붪*)t0{;M{@5n6qbY k$(rTGæC !kPj?j>(2HC^XpuqW/_ #{pPFNȉiCܩcՁ%3aa`2dHpp4]6fX A,0^1i_Р, A6X&-y@Sڧ VFwz4N̡9JaFJJ *RPEFz>]E۶x5~ǫWp}}3<}O< szOC0ܲ#~ʋ\5!s46n`"lj ."xM&ʾ=DAқ7opuuBi`:x2nϠ /t:8 VkuB*?{g ݡᰇj`ceUV*Ң9aB`AsأH-Ά/fK5{bFP__p+05 8,:jd!NfC@ Y*5V#'+*̌J0djkJfF*6lgzZ^W؉ q] K Ga~"=__~O?U%Zӡ*k`KM}؟EF0Ȗ㈤x}}kj ٩*yE=s:wv7Ak LoRVj\E,0@0Ƣz(ָ%~ .?~ӔbD/%SămBYxaP,7-_ è5D#NƸ<7g_Wج6+=6e0vEt^ǿEk=?b.6G.+:V/`d;],W;CC"=>@_f%yBvpPa 理X; 0R 4T ;%,HV#kǦ EP4F>['Y݈Fc5!5ޔk[rkO&F$C ;*=RCG#'+ "8a(Pq6$?h>E8(~u ]{+&>g&9VCb(FWc9eg !Y\__eH8 Hؽz]5uCպ£G[[*+AU gP(GǷ@w-,?0S%HPV 4'!h2qY!xB9p7~NY}DbDV` g7-!yYtz:g"F8o~Cu &<,.4 @x "٣G flj@WAQ(F1Aq\D댜E+6n;G3a `H hhi댼C'G`#sD.`&7ug1 Ezv"9LBFYjܠiV+PL>49";6(%s?B+^Hі k /3Pe-|15Lߣ"M=ny4M8*FM~@G`ZqոLC1 yN2!:n.4n2jwlyk n^Q!Z9XG% D\@V <ƙtLǜ^ҷQˢ$LJ>Fp51l68==E]Ah 2CBfpd~iL7di肅?Lm{ p"ЅF _cqsC?GP$mV%ȬZJt4)yC]qf 8 b;X) B,NP1`Z+p̐@8I+BJ^%Ӓզb7!s# :6 m:YDp J2"vuQmC~mւ@ h52Ƨf""z3i[֠Tp~m P8%"!#GѢZ3VƱՎPIc7MyHDGԴ-Dz})X:XHU%?<0c/⹹A{YMfM.D$ ^k2Br8;]TSpx{(^WP1wƫ*ku !}.C`Fލq}Us42>imhs𦏑}#'}N1'8j2cTлxd%,z[2Ea;(1ڣ1#n?*s} sfw/ԍnXquF:ggM)N?;-˔wMVݶf3mX &:??O׋ PK+ˣK]>?Ki*ء!ʷ}2^~=v7Ў`\'a W7WxYB@s}S:5؅ vG H3jJR*8hT 7\]\0 pm wd1u`AMMt#,w̟ cN9Rmv]|لYYI}(?Mۢ( 'EXkx(¨7fHX igh-]󅥄F^}R@Z|d,FZRO E<4b|lؔT;;淆KkvxvMӤ=&>Wgs,ɭ)3 UW+\BtK@շ<`Buov0pw 5M<|nHL`^:q, 4с?G>^ )A f\@mdojAo HliHP$3撂94v|E.LƽR4(mC:0G\Mw`ԅn֡PH=)W+|d~],;h]o+.s`᣼Ʈpu}8-*o|HL S٨/~~72CA PPiXW]@`W((AGkpB >&6mR/_{(QV5={my+gsIg=Rsq:c`q *0EqQ1 Q6qC+QՅFw#L0]?A, @)8kpZƣx4ԣ>uslȴТ?ѵ 6 <+囵pss7Q%qdSh1+L8: s4A>2ŝrE~]6PRM<56Ϟş1.//b6DeVG:/@Dtb<%ӈȴxvCF~LxY*PDp}Ʀ&(mB0rxlRb,qQ7:-G9_d1HZ:O7-͚X#(EQ1W7{`Z<Va|;FYee5̉ZXIØ_ (գ:P4?J\{^V~:D{$;7 %f(wP\UYⓏ>'O!]iak`(Dy 1\bn{s 17lOyU@}6k?./d/%~w nѓ^Ci H\>Tc>}l,td(#[7kg&)y&1C=NT\7cX8PkCQ R)sѶmr^,d{#$Ezbm@,VUB(N9٦F-!jJe>)G#tҀшf8O#cEB*RKF{4R{V:o\PZ3{KӐµ]Kȉ7c(]:+w+.E?L;soѵ> ah9I"(jTG 6!*2%id!AF]-x6ƪ :-x] $*≡/M, Wu1cԾt# tDpqq}^lfNrD6ա91=٠x`Ў|F̑8TRyw&ٝ1*MV!zmcNi!vk|g+[\=`{FӵxE- y,:5koqs}n'g3|OQoZ]׿'o7? ~asv S8/q#B(_2$:N/B7v?wLܞ.Fw9#G2D FK\H(~̱ygi5*#LZ{AcJ`(|#,"QIZh^ٶmڃ6$}";Z8}DbBώ#&b!oJIs#9p $xQրS{:ǬӈPt\Hk Xz.v Y{\g,Eu eYqw_Rt6Z@VMR!DF!$ܼ:dTKZ3-@FsT- x P8rr4,pp-H ۃ 8U~`CW[N-vorrAgI5t+' :fu]cKFё;]V 7I>ƃa0-F (JABc(Fj/qgnGn(B\[@0jQҚJg yA0x4x|1k\_\BzP{4!Z9=ųgS\]_9`/ y==}, ~ZoØ>N??^ ,Ț?&Hw_LH2=rS,4^K^5ѽ Y,\]Tex^u^Mƾ݉b3ñIJHs?8жޙ٤r(Z:WUF9,QusOkʞ2i$)Ju'ܨ2=/p[;W; 'kel"M]иŏ6kϦ;:!ƪ顋84-WWWB! fנ9u,@ޛ@_<%!Q,4 "ƂHa;ƜFo£r~3!79] #S=Ekp-\Aa4uɊr2nU2a/pNi3 pP94jǔ;s'rq"B3VY)P).P4h3)+K<)"(YT r;X58tlA~a\57~p`{QJ͌m],tfq]oQ5=&]`Y=}|Xxw*{UQ2߇߿hgOx  zXOLFMS̞r\u~ʍ^a.4-F%qXX\4-zS(UbTo_^UWV=ʺ kq LhLA|'n` "$=(qL)xƋϱB|/P0BV!+~8{`%lrT-#р.t:#3zƱcGEʓ_˳9sǏdZ5)WXUNi:u+жC&Bs>tu‡c ӧOan1/_WB|P7{O(J| ~hĂi,3t{6D8d gggws *9}c)R,ɤh0AjשUȴMIu3P38kTcހ#Xy0IIȫʍFk77-ՒH7#a}.6df89c)6* !L>HK~ԩR9eH'#B@fT1|`V䟗(cP,^V+b >zG.հ:X@ǟI)zau72nhאQJ=u3]%RduaZksox kumh]̘ +sۼn<>49O;(q#_SByΒ(:9"N%~/ִX+l Mt ֛-=yzt]C h0,/_w 'gec=tgggxjkxrb#H8.*Z3!J+Gئ'-_YƝ/*֙*\V|E h-t;ȳN<!^kB)Aк~ ^~*9Zʲ6])o= kN8Zv![i)?__ݤDZ Rʯu8 \AaB1g7nws.~9j0(#ʏ}GTtp.XL$`^]#:^)+\zbV`\Y$B(}/$A9pq pO$ӺP3`)&wn,Mb?0)Q`t */n@ SHW7I]Ә&=AC5*bQ=. X:ӣztma]7;<8zmE&:*y-Fz^1!O\CAi9Cn$y+!p%#}b}ʭa$ 9ZX봑Zy~8{;:Z.=1pIeh6 皱G딂fusIuKHn~ &|VL2ʢkQZ1KoA O{®9PDoz(]$.V+nOJihf;QEo`@Y>(lp -'76 KZ9>F6i~%+"90i$xۀb|NvXf)kM6°+QEaΡ ƫ𺾇117-wX ,hV ZCi~)6:s/)b{r1*΋Czf\`szY‹g?)zx]U{ӵ`Q6JA pyy'gUwl}C[6Uf5= %*.QzQ.b-eDxpM'#ϭ7fbb$/5y:Ib 0֍Zُ̕4m.#?\׷/T`l%-R4ֿ艖]8o=hPPEp~sь̪+H}%/;H%qL ϤߋהO;/HWS~]~nsS9Nu>?8TGX{UJ,&+c ]v_MZ0X~n3M$oXoR' JțN&|_8,5=BX| -"l%s7`.!\:@lAeA TG;_E& eBJ2 XI蒂,Z4mIQ}cGk,lD*%؟@!?-1_8 fX(X\x ?o5 \G74 =NDBc[a5}m}QTmwuЅG]ĎTlKLa!Ӯ4Ah"Im/, .zp}r=e֪I'?_7_(=1:U')}pvN{z)BOkkQ6#cu2Jhڒa3e aZ89MJωs0598_sE`H[Pred|xߣѼ8}`d ^UvmÉE.|Od(Jwp=&./Fnc @＀!B#op2w6z<`2(:q%g֚cQ\>4K3 EQBHXpBͫ;YбZ- roq؄?Gx$/^g? >#_{\xo%kz&7-4BQV*}NPǶ*a #Yrѩe%ЮC ؘ e3^jhFG(yٸ̡1QC (' S'ˆ< Vd%ZC\|#2ћT%pI<] ƶ<tGN]5sEҚIth~&2[-h=捼 ޖS~hac̥ai|A~g <ݥ, OP7mD> 8:k5./.w^BB0`%h,ƿt\4ԁ:C=B  rn$~|̓B hضifta+JAQT E0u> -3hzSF-YKczKAQ8FDKyS̬ NX2z c1xYDA|ɋ(cUQkT>\W+v "N`9 _/:A_0 $@?_J$8vBNgC3|<ћ_ Ɉ9o2q X7`xSXJ<=xr,3jQqۣz]lV6SO6Q3Gy11XJ!3vmӠm:}OW7;8!((uժ*4-j^o p,HyNR"7wyMQ# 6ɺdq\M$\+IkT^__c^'g>lOxrGѐq-i0DEUdGnFޢ~ BC1y1j˫KtÚ+ zT ]P"^9 VH\N}+=:2D84-`Dy1[QH[f4zߡH h}Y̛`S Iqv*qTz.1Riœ{<쳋 A^H^$ 44B6~V.\(]hz8<8zۍ uǦ[,l3r.@;kj^LQ%ۊbYgDҢt[̭c0-ɓ$fgmEC۴x 8Йv'OͫWhCn% &(cdmi$ݹ\Sx ciUꎞr*)VA֭b8xřŅ+=BL$R5FkuYF$ieQ5NN=98Gew^ZCq=7Q3D 0ĞQPG2mH8zMr@XtM=CrёXlH<ŌBkl2'Cg|".ߔ,b ]EOkV%Ek:غ\]^( )렫 "}p?$b]`H|Pj/rvB4GO k;eѫqBtGq!|(zs=7-FcPF3'-'J$ubfd:CCsg).2*8rib$֍#!ف2ZȮ%4L# 7Z%b:NXPKjhb!-#~sf;?2;8nU6*K[zČq<=|<20O9/IۡtQETR}{^<)ຮ1=uDŽ\[%ַq$9#"ǰBd0B ]G1<Ʊ⍠A=G)(rN 6JP`NۂXN\lZ1)BAqV\#G'O;/D& N좇1,q c;d-6?‡Ϟ(kx4Zed6m cZob-ޏ,[Dh>G,S6/'$NCQ5߼6]{η"ӆ#WJJBb(@oF'k<:? V:`Čln V΀o_-tYBmPmkkm|Xv G lhހ:-VC`gz?šuBiLVGSf2d?}ܒU kv<5qLi#|$ *qJ$:In2%[mqTFزzLf.4EN};wO2.p>{ m)8 ʐf,iJ0UՈtp@.qrO/~'(7KMFZ){tXp 06-ѱZvF#L ̃ɩϰT|S~ 7৳w QOC1D6\H%pl5ؐcRp5]gz @k>EYh0J&(;ӡ:(iJ >0i 5X` RPZ*{"8cPWktno$51C>FsSHf' XͫՒAhmS gᠸO6cȘ痁ҷtM {E!o%> /Wp'OamMmǀc /IH)c ,O ]Ǡm*Gu(Xɏ78e BV5H1ڶ2qkӡ=+v6[OLb"!@猏{q#Z)o zG o+0zpٖܐ\ А@\u)W}ak-ʲS@G 70A).7sQ9nU:zɅ 8H$FLƗՈ L=x K0ǯWA4`ƀLzAkQ ugpZ{q .PV, jEV GA 1Vt@M܋1O 0-<WϛGA5^)mz΃T OP3ENYmVzJBev_p>4nyqfX?}I]S861FDf48qwxs[sE]vA FVQ}̛};FfŷL*pP_5}ϟtw?ţ9???.E/6B ͺB 'nlD aHiH) K"v<)/ c'rf"lp9HRtPIG~|@-%顭C+ ]pjz9:^_RV jU0ng#ʰN$}&Nw 'Sy@ZSpAWqh0LV*ѳ E]lLK~wƺTDRjΝ6"[hTU-h?yi[3ƳӍ+9E2E"Cjs1[I9KDA5X,P__+c\~->x<}'7xqXJ+o"GWAA ghfniɫgp0}YL̈́w{\\^zwqzvu(LZ768uHkt$^q2ʠ Ak A%B40~ klf}Rd| ."F5&b-P za{@- dEzzjx/laboTڱ@36&$[D@7St'+!НY^,Dd&4UO{(1=*eZ-Q3H?]F6\dfɦhػ/Ǩg'MG'kԚ)PW5)ěF9fkC$E.>2 ?B2#Ĵ)&*+r䋂Ï86$A?Ck2#:|=&ęɰѦ=[of|i'CfuC|a[\~z]P eYVU@(1d!d9EErsNp b K;R4*͏hZ(t$l#lqغcHл6Gmgx zxZ#N`;o܀np%/L%R5ddX8D9X?K<ݜ?~ AguA@L rM EgPY@9/[\^CB]x{ [2Ԧj s`9,P*Z,+=: " c-ڐ۶oU)c}| p:"ٟN2ȰC4܄&%/ʘ8 cR:2Qu:>.GeW"rA lɰ~GQ4B-2M(cG3#,ˑHyw>|O?d蓣oy.рG ]^8ɑR\n-4ydCx} lWkltgHV 8;:@s7> ɜh x/!|@r 7Lj M—w"y7$;G2Nd4 qCI6ւI(Jcqz9x;ಱh@Bc/ŶԨlJo9?ڪuAIP"]CNA?yI =oDl" vqqk=)E"(DMaZu^Pup.䩆qP2V(:QQj8qfA|a?S;u ?Ȳ–; ^( izh-E#iFG~7?1&1$gg/ŒUYXMNw40F 4#HP6颃E'64ugbCb/\TYd/w7_ܟGd]1QHDfed[>oϮ#pϣ E3+Ev4hܿvؿy n=iթL-)O^ų/<Oytqzn>ѬL$)v\i:%ǃͰ v52 /h3wdÐ= 1qq܀aIu3fC h -gF9,m w+4Cz@C 1sg{<^-FL񼷓=tad0N`#! ^dTfHP^ E 'QI|#cvm'YwދE57r=-<睃L=' ;i6}^4ԕ9b,!S/j蜌Q{e\U?ѷ]hɢi  zB۴X-WA.kBeLUag"PK PT% cRwI $umXkqtt(ma\f"B]م\-,`{.p?N9Ʌ=>i8Ԉa1wEue㖢)W>jàkrb>O=+WKnV83н~.zt\^F9H"%Ǡ~CLN>TE[gŃNX dyAoiI@Pr.8o Fwr,%ԷupaEEW]|X-WpR=)iTPAiL{<}6ʫnly6Yf۲ۈג]@x1 0N\A2RاNNijgCmI\D8;n?dM< 3M/uitulץ8R:^k())xmA>.7fhErc7T좤ݎ~bX*(|p<Ȋyx@JXVXEvx/=}=޻]_[Xyӣ{qCӴpqtticf8q\=pD2A:44O>Q uTHL|k zXgZ^:{1{*(N*;bE`z6$MCݕlK:a#*ʊz]&>{݆R;`/Lm;(R2iF%=DUUCkv #fFn`bBӴhu]PU%z91j(Ds{p޽{8ZlS(KL] qQG(A{?j4iLQ }xwX}F]2NNd複_**_ĥKpE̷n64YB$e8=E @8![DBqb>bd6pTBBvQ :^sz3DT ydB aH]B0 p] ͵+p`a* e#3,VkA(`J%#tk.-ѕOל[a֞MH$m'Z0cOҴSx*t(J!Hѧg~x? :gj%j9 y}mQVҸs>ڶ mެkܻ7l 5waأiDPGqzxCĘ9nFP:cpjq5G&&~PI9U6 hEG)cٵAPwϞGQo)30f6DƔz*aH\ޙ3|ͱ{ IDAT4CcPNoȈI Ψ)Z&LKiA8.t좕3sH 0t,C^d򪟜b '  Q7`Ð:$p 謈;*iAYt{[!vd7XUKDTm-<3(&;ۻhVK̫`h`ktsfϝÉѸT!,PfVKX'Fi@`IL|2LkPWPj]9b1:0@jT8E9p#hn=끂 c0\bt<Ȇ^u8r!vúWagg+u,MrpEk-]p9ڶAUt!Ι "0gƱ5X j(90ݱSfS]Z;yVzuXS}7=ǝU|JU@=k貆.4?s誄 , a pd1̟ɭ##A:1qZ*s< S (VRY"T$41KVہ5E2P=NN#0t#@--f(Ŋ`HB@77d)B˃" g E;j}GaRAaZe=0skTe(Dtl\oÁFrHHb-'5A^)u/CR!t0U5GӶmg z)BY4~-KhkN#?`㏡.+0D* 'gA)$yA340YV\b|(x(B(E9Z p%pq7kT$zj6y$#ab`VU(ay˄r:va$/llRO(E3-hhM/{a4?3cwLaIHv!wC#bHO3y`4C^D;m̝sͩAL לD|(ΦAY#Z2׫5] W 4kq^|Qtw!Ӕ4GG "cLrzAzHA(D9PsilL8a*>.``E:jZ?qǔ>/>)@)u-}؁d:9?kC=`VEr]Vf59E{=`}V@׵pzqƋgI[G9 (x@os۸q' ELS,yT1ք,Pk= '%2@banO#n(mӆ>|Rz=S^F/-slNr~T!Y=xլ-) tڄ('ɪ]Q1AO(ʢg 8S Ci1q>);WMaD/S-c٩̦M7.. UU'Dy4Imwnc6Z+RB@pPQ.eOJT/krtc7I(=X5-pM?oگñD[e/B߾KMI}YF@=I@vX5 sZbbV($LDP0b鷴|cq@S)tMyeQTupa.Ԝg9HL׸^t!n.r2w5HRG9uNM) 4:ڝsppM91Ntq#qXSO6;Kcc40{뺦341ALAnc*uw0ȜQKS-"JKUU*EO3`Eԡ>~?KqE\| W^ŬPؚتJ9E NjgfB?%j#>6PE5LƓY]%Fl(F'2Ip*$"Ih(eX خ;C,'qyHX03{.X.ͣurvz ȃb @ZAR Yke1L`[bT1&j|/e@'P VF(+lo͓89z H2u =P@ImR%kucaFgu+:EьН]rdzptp$RDCJ`emwH1HiuS \ZÏғUb_d0.nޔ/Aշn7񯱧5^}X-cvwQk>aXaE]b BX-N\Gk-زƙ vPŴQ@B[8a MklzAۤsף$4AAV bIooIӛ8?KyN8L~QP Jhe;'F~/WۦA4vn-mnE['Ooaɘt}OB#?|\SlvRYFh3vJ,ps~o_#Ѝ}aSϣo%1罖S Ili7s6ÇkPt"Ν˗/ʕ3ϝ{(MOZ7<{2dvxw'v{gtpA`}?cF VAy@Az]|Zd8ƍwsu,)$Td-g/ NEO*6݆)9<ZU5Ha$kX-u F^0^U*+ᕇW2 SsЬ`b%:hFQ ER k`ҦǩPYD~F߫15O:^ lùu \qQp։~];,nª֔gVmhgr9}џƃ?#ݟמ}g^@Y[<4S;{,.K<8\p@- תO LVX{ɍ,`9 }u( d夌wY~^oR'h憥qoqz㜠~WC*@F{auY1>M1|>9"z"3}QYQ׮qp&]OxZN*>Epe#QnM hl3Ǩ=d]C#߆'\@[73<R# UiPDQsPwl- 3hdLzP,JMݝ\8gwϠ0/J0 ءujL+ősG%$%7j뵄fIZˆGg-:0?z |?.*hG*uY*+10fV23Sp,IWÓg^=>-l\X~ͦnc! 7`}Ƣ#Uk0!blNw~ c-ivqe'م4d+9{l5qZz@r%:{goC޽Cܹ{^9+報Uak Ξ;sGG8Z!BΗmvwwcxϠyB+(B"{,CYaW֭ ѵoҢUa-ߔ`#R ^@4c9ͮDmma6ƆܦiZ$k.S2(4jR m2;їvАc1)λ<eY[0;XׯqLQ \Z^QOH2@$ܶ]HCgb!NJSPPh1h7Yd=ghRv8xHwyj{q.ǰiiA"?DKrP]Xt fEh_uUbgkT;KytEg;֋F҅k{/ʻE^R5Mx ]kܾq ?;ݻ>wG`((m =_pXYQҝ)I!2Vޣ_ʗfxBE]^7XUpS6U@j}}NV^['vJF8h,Qxls r7k*[[A#(:i${ |fث\`?{,H䐡Z£Gw؞حn88<(4U+mpTBOL,*F.<6XIA)߃>-&R?p:6!ԽFYhTA](KhbXou!S-bhQ9NWZ{{cs h#LJ{NFhJX]יXK s ,0h\P ku^:F Nl`^mmka*aT#NV0E=^[HUԖŀjeOGp.ܹsK 1L;Œ1I0`҄lB4*Tñ-<Ͷg9+gq1hܳթ4-bYUzfJ<8\a teb(pCFI>$O]xS'5EghIOE&T9mBIj;xkȩ_dutБMp~ppwljW`STo`$BBǯdߧ JNu= 5A(5$֥b/HÔ [ovp: {`r>mbS#"ἃvvR&:9.93;v^uE[8db 21@ʐޫ0Τʊ)1{`bDfldk* @2tTv^"bf>{+CPV1|Q.>4n]εPJ@uXy+s 1 X<>yW]6ڃ5տC٧&8wMM,І*(?q`sOH;?1ߌƆЇ C kx2`AƠdFA?j{4%;haj0 ey1cߟ7-(LΡ ªi3ÕF]W24,emZƙ@Gq͚ICVUΟ?/n 9vz5im@)06U@Oam`,UU 8\yRJ_DA/18}%s={\,\py"MY^oZ.鹿&@?3`߈5zMțҤS$Ὦҙ#lUQh13(j. qs6T7I⾒XDPJIDZ2E1sQ¦1 %r_U, H8ޛڔ9i%:H{.uAwvJؗ=f88C`/<dy| ot^q XF!CK1lv*cUzt_A>+@)B' gt?ߪC)y h7Ļo~7!ɯs򌟼6?{G?͝0F5mQ֣(*]N.> ,r0Sx/ǟ 2QN3Ea.1: Ff?$jh3nk) k5PEQzVP(K= kb:b/ltLY"+I gw]T9em!- cօioW>"fƘtI^ڶbX?N˓:̣=WsnA:yB6k_@5j3ACL[3٫ܹs8{,r6 @NA+#T9Ug\*QX@|@'Ç,M M z R67lrC1#*g7hzF'8[!\]xTމ.wyˆ>O) jrЖe|i%$Aqu]fv$.nU[H#V<(;bakl \q$:zgstIZx42y˧L@P-eꡈ^<  Ks<07L}X@<^qq LP+(c<h.TʼlL!x7O\֑]ݻ_ÛocX& (H.K&@wƷǞzwG{> ɁA1JBUn­[w 3Xu xaНk`yw;cs'?^|<P@")|bbGuH0bM/9`LT8fO8Ƅi= w= k]ѪAJ JA*6ru|@\;d Tb 3s:qUT/[nLgϦ4)Ddd:  D䨇9#pv`|ӚE"\Mg qlʬplX?9m ~O)M#\~7,aL3;;"'.EV ֥.+pM'/p*VmjR2-P><&Ul6d *sx s >fI)(ܔy!PdJNĸY-дڣóFVhs C (D=̪(9hlU%ڔ(PR ^h *N;tд2 hVb{شIi:XAgǭMuCB{v ׬Ziwy!Tѹ'aYTrCNOD! 4|o{fB,$ QJ#mF1NJ%U^s0!" |:DM߲u ޒ}S 0y0)TNxR-/-1!+Xp盟#+(ƶM IDATJC嫯/u&lA3b`)TZ9muo7~O=$fWvqTބ 9-0P (õ7?!f}LVnݿ];8"b3'\n'7qx땷+җ^|`fXd-.5+Xo ;YS Fd+B>i:'y6ySjH]^h"g.I5d φ?qnq&Ǩd@1E sO.hOm(pkiqqnL@!Ud8er3>GQXgȁe㝽!c  k昞Qvbd^#SPzˆ3AC0v>L4T8%[yZID^=e%%8)0|:&guܹs[n^1X5 Ҷ-FlooiZ0.|D0[e:4jyL*JLWY.Q8=壿uK!]u_aÉ !0( _(ǶpEYE6`vк1"DGlko޹=< x/¥ nA,N$89g.Dt{U #8ǪƁy a.\>~)7X}{JQgp8tm#faW i4s,@k-k\XdYvB~MyIl֤4On:(u}$d {p.ʴc)A7^6^~ep`7]<{Ny/HBz|%tȬ9T1ԑ4=`^4Be REFk-N}U(L"t+Nk;tֶ;.ř [4S?f[[x2ww ]Т8Ҙ9t.Zkn}g`x2 )[vI\(ZB,qTMFkMJ) @,ٔ6Xdʲ{XTrI89nTᑘX&¾K2 ¥pݑ9]cUe4T8"a*jk2*FQ( K i{`-ls6Z7>?>O98H>'ᚊî~?~|+_ūqݷq6TAA}-\b>Tݾs gvNh3sKy(t >'?KʓWc|k"[ }mrF箻iSR\ӛ`Sﳏdž]<7yRTM8Y^O_ BYva4F*szKJ{W5M9V'J#=\wof%5Sf'aoS<ګyOy_Qc&1@To>q{mlmoܹ3xW_w_tcVJxܥD'p NYj$-yE;Zn ohxh# ºҶhӅ$ګIIfq r6%kڬ{ER 9yR)|(zNCW%i \cufh]BSJ qC"VbZ2c|ӊQQ $N8)BSyB%ň? IWHކ(8S(^$QMCA@)zxwkhZV(Q0lxX)9Xrk>č7`w|頭t}s9PfxIhwo⥗^l6C3~d >.mih}kPx "jqtZFKF} ^ke!(ѳT6:r7H*ńŞ=FRq#UTSکxAAkykW\nF&|luǜhΧëLQYz$iYuo'Uf0akyrqvͅPU>5g4$N}?Y"7b-4EYg@׊~c\vzr< <#GY}U>/G !jq0V+P P>N4&6I+$g 0g6 vBvqz6ZE蓌T!\F֝hm*Mfr?Ή<8EIVH*FV*JppK8'W^?]V3ڠ;" 8Gd%u[C$P٤Cg"n&[u8u$Ox(2҆P c<밁F#hH*Tbpl ŀ]xg7ĵ?zB׶!Y~nIrV1m'@/#hA+W_}_W w ?Ew՗'9Vz9ZE(Ds<9ѥ*65 C{G}{?+GK_ij=3;"=?*'Y3 9׿ ;QNm"%f,j *Z@(el O-"/ڲ4*؉,ܧtc ƦK0⒞{Pཟ G%ͯ} wnիO ƐRڶMyὅ'u*nʯՇ_TnEynzP8x~N\l55ZUd%gm6 `[sඁVUȠk)9(qb@+[̪:="hْ^5҆k7PT(ww :'@gԗŏogTPp(IJT®a]ׅh i1`L>ekYU>Z }|w ^s"(4\+PyHE1[/hyɂt{֌3F-:9nF`0wǓaP&+\FJof!҃y0!ڜto>1Li;m\K֖pGVht,cF#EzZ=T Nt hV:.P/2@Ƥ$3f!]*ӇFX?F1%bTŸ9Ee_Ffyq1[ujt56dQsخprr6bAdܭb7!XtC.n!w0L!:lPL",AI.) ]}şK-K}& S5ApAFͧ ᨏBtŇ18UDxǯ}q+pmxW_G緿S8TK#]*]cf,,\Z:+N@kpw?os[gvpsχD<}H]iiMAj  {.㑸<1܏h咜  Hh8R)OApd |. 5m۠(> DcI:جYK?Φy+loG|"!5)Z+v^.ZSxX8~al\56T< &$6EN(p , ss\Sq<6GVZ..PP!cátay FUG!i$G 6EYQ5PaW(gYk჆9*8#쵏:?b1sHg7pVq1 "pc՛R86sd1H0y:MB NC ?4Y+U#ysEpTb^L3 HZ !f[[w6j#LZmY1EČӿxQ6ZJ!CA~ZowE .L2Ѧc1U5e@Oq y1KΙ> gIܮC'lh)lPT ˷m_OtoDŽ,_tzaDpYi #VHxEF1h7R4Hl 7fP= oIo h( UHK1X1%6ܥA\tda8,vF#k2,|Q*HR@iJ;4i4O??׋Kf`qԽG9P F> 1!̯VB{qCOnjBHϴzxYe1 85t*wpQٵCaN_e GҾ͛@+n)C*rY\ 5?2S݇IM)AN8J]Vx5<~I.xc{75} 9;7oO[+V+0f/qqn'Jb]֩D̤{٬D۵X ܼW~׿7`YZWZI!̦`^"6pv=ֆS{Np"PH رp3&G-*V σk'jxv/(UD[Kz0+L'P>-xN7wdAQX-Wh5LA. J uykQXW;'=k">6>E#n\hn]!~wfgQU%gzzN)v-LkTӊ?r+oVH ]  96Ɖ7|JW1fU$x>77[/cVZFnjst+.F]k7Q bZЬz0zs)QXtq9@BgidUS.䁅G蝺yDE>\i"70q,&#J+cibTc]r}\nhW h@]pՍA |r/YLPLz)PlEY, xc39˔+lJF%2Q>gzX;|=xװ3tQbVmùn'8//888um+JzYE#W h.("ɫ#R  Ľwq9V^˭؃TˁlۂFI@#(vuF30D=h5{H Q8='4$szb'XF= ^\mJR>W2m"+&3)q'ގuH7dk#ƃ ZZ>ۭܺ1qED0HCBK4MC Vk ȇlk |&-R$5&9xpw'z?O/|m|[|tܪP:`j4Y} O ij,)F%H2ZwesS 'PYtCuX-mX[UqY &Q$c xYl1( gmzUr؈3rT;DuN""LꃖGIFZe4Fڦ?|2(s6BY ʼnu[c5xs(;O9e{9>P3j8}s/s8njպF[.Z,ЬhWk,Ь}{gs [sg1S|x~KW\o~ }!֍ťt3 Ix߶]9 Ch*=hL`.fPK#&ydLO) =`G4sjãp]EᰳXz [d*b$WFȴdĴBSep>JXH*UU TdMbf^ZP$fz?{{ScӆNZ,_˛漋yݞL5|x8/qT6$O}xn]럓 GFZ 0zRՏ9exwtt"| +BQrMՌ%M$L :dcg6I]UD_ᅬ'y ܁Mk=5mT4fm>he_2wyU]o٘RBC"?~.?7DXbocx /.0^\?_8D~e <,>8~c-ro](_/{pw~70np7[臟l s9NPӹ?&?PJ5+4_>5">;drM WϩnsFZlp~ Uq1 D NI\o RB=q8sb26: $d 6 V5u9ewcح7Xٛ(DSaSNs09a}=>{0pcc\"9w[`{>5 D(쐌YƊH֔8Rdž}@2.Qs x??80/̓I S28!&R7a*JC{댹 PpI5CPҤ@s`uɶd5D$v& ''~Vb5%Emag/kU9 >JcMl`-0.˫|XDRL&Hv1\W0]>?yslU;;=#y{{plZ : }?8'aDئ8P>{c|[tx8>;g]GF/%{x=8`bRI$gHBfD#JUD Og*F? =0:2HY2ʏ@У_pxrx~8X >up\ "v]-"6煩-:1Y l{vbСIS4+֚MՎQJКKZ啴1FG},LYR/!TL MNTPꝤi4K v/bbÒ +/G|%_:f" }_ 7)NCO&CƈuD긨B%t!Z745#m["ThlBE !U,lQY,H%.D(_V_w >?<ƃw u@X/Z@ |ƽbu@0F ƔkK¦eZD"jZ"&8#@>ul'ucH"' 0" cяaEbT׌w&^ *S,UcpݨR{I #.gTfG{k-cб&h5Rg"VJ)^dA "$7`ףp_ Xկ[^{}"FeUAl_T {"MZR šqfb}6o;?>)qOOOp GJpiY+66n=e= Jb. gn|!<;Ycmc_ a1W2~Q᥄Fp٨KTlV(_RF#i6{:b^W7uS14aG=Տ>`3zVx~r'x~c>+1.NӸd'K>y;;5%\fLOR1ՅI8Tq|e#TJf[h):%VM]f휴_|Y8&8{?Q\Reuvf?-<5o0$lmL3=.n=D "<L5?6IuhugvM"yc6Ơil64MsqW8q9Q;y(|svV23AXCS,A֊)$Xw۷q1n5>2Q6J 5gsSify[.YqzzZ,='FհZ# Q ', 8kE90i5G]x;`X3vsʶH)l i;IAڊ<*1'w,bQ=z }o.^~ }.m^HS"4KGrv4EX.)??fI8X]b Au+u:chk etmw#Ą[GGrqqd>ZAz;KB#w38cAAXDaM*)] {"b~U3Bz/͖S:Q6G.FM&A; RMZ$ @pq"DϽ|)5u<)Y^+a64{mP6o$a)[nG lo9a~)JUE TIwj4[”"$6ݓd" \Br]w[q:4UM(LppH#:cjm2 y,N '4Z5A"Ǐв'BgyV1F0m/Q#!Gu!f0-={{GGqqwX,Rja\` K]1Tܥ )Y.=,Bؤ),%ÀrĔ3^*꼊cUqOOHOON(_ݾip.5)ק3kiq2t_Sd:t )1!fǨ|}Ϲl]Wy5pl\iר#i3{юRX_o??'>ϞC} X5WFYc?Y+Q:ÌrrYtԷob(RٲW/s9$}9bxO/bʩX ¿.2u#z c8D[t2b%7*C)3Kd&%L>d'Foqҋ$%?2$*1cJMh\0`RQʝ,,>.D D,KΕWJ4eMz^!Y4sͅTO 8&:ڒ2VľPQY@bXGV5_P4c9XS1rLJl>% ګiIr Qw,!",'Xd#~# rq4â `g/q3OuDl5_\AQ3c񕯼oO6;_.ه FOXƛߖ c`Hܭ{:wئ Lqizd o,8Mji@/W=Q[Ĕ4ݢCuh%"hxqGu UWULVa  ֚|%&0?HkN[-)Y#1طj~ 1fappvF ,;q:d.α:;8;;)={gϞcHܤM[c0#kdRu6+ ^, k,X,=ڶC{ /b◾ k NOᣏ~Sשm f x:8TeVMAz|$SrHq:g !u1qM<œ텱e}BH u0 FUTnXc>x2Xd[hF!*@I11='Ov{xrjDF֌Z31Ij3᭍hz3*USޓiBUX!V 12-Q:ۣM^X +c06!p*@3HʅI|<"Y#Z̠:M 8SEr]I'obZi~mS&nrubjv8GhҚty`~7-|8_ ٹLfXuso>Q•9*bQU?﫦h58q,rPhAֈEN4,6+ ]uءK2m3!stt"(2e-g]13V k>+$Vąi'{>{ }obZcuqgO>9a@?὇6A c6Qbff#d۶@B)Ѳn:koC^grfש Be!{q±mōk\VA[`%8;Qye>fQK[Z*N4@U&=FβM+f>f\nKT%Жut4eQmh}3ʐurfyZq^ǭTn~*10F# Kj"ltSS/RQigtqU)rYڞ4eAKChl.G SRa31JWeAܙƠѾ[F7.fܤL~GdJMX]QqRBM#"yq:ܽ7_Q2bEH#=~7~ۿ4@'4(q ۸yN%R^XFzȞǞz4F(5>Uys5J\rSIL47 MX ru1i஫/3}ˁ43o!`-kD0=( '''8==f)K76Hag!٦qZ1=H.m!X1bijoWo!]ج/X /,_Yk1A+uݩ\3f)i4Mj7gJ9C.X鿙4N1QKOfiM)j֕6&펦gTfR||"1"Դ3zPUGDM;JsF=!`h-hl H7Dux  Z4# xbJy3FC̜`"@ak 0z$254ň!̏aʮ>3{hy BH` QzYXWq˘i8"{pfKq`urWǗ7$j.B8׀Q`״R7Bj}` !2""TܳWfa2}u#;ܾs~ z̨)%Sm]f@Nߛf#@XGڇ}hHS0Ν7-X_ĝnMb!JDp`ȀbYpp]M-$A38EwHvmXJ9>n߹ ;a10ȱ*\@4] vsSWp6'XZ$ʣaIpS1M+!^2yuc U2Hve1U,:t>2%*_i#xqqq3bZc{=ANĶ+ܹš3)G4XhX::§>ģ>Fv8<޲CJ;o[{tHZ\RB7%nםT/>a6S=V1_r9\rEOc" "deoV}UWsJx8eV04MI F909V(3N4ŭ؜NSV^ؙuR5. `щk#F8+qCl6E>3C? 齟7qzvCX{gϟ#ŀr.? ^ U=S,gl~&"Y喥{t.f=s[8MMf%S3cba߈![k,f$cTd/qUKF4>Q@ F.@Jg E"zU5T|aҢXx)aG`T->pZKNYbH*<^$G&crnksfT0/ଅӘ,JԂi0y2cFw$1Bx)I:}\ȑ qbxJ13a (:QtǤʘ03ÇaF [O!OTh\5r/g<CkϷvf@d-' Fxyn* })yO\&y@(kjV/Q k%}<+>ZG|O}/|0"v{| 2@͜';Հœ%Irs_(ڟ`Fii'[)ko񸤒o^U\W_5*w~Së̝i>pz b)6 65|?o 1l臡LS\c~z]1Fl6=_Cóܷ])%l Rhۡ>~)Yk?_bӯ??x&(XN\Tn^7i4+P-jԋP5/m`fDY,ĈAF h]e:B$A%^;v/!IEuYfkV 8Ko|L{%stCo,+CчaXLv8%{ڧӕ&it=7(])u%qPҝ>SuI&vbcFCu}~a]S޿m6C[LeX,2&:YjSt~vxogpl IDAT`[_w)k~[p\FA7ZkLqxvSփF(hT-p'9l)p ]%Ǜ0LzhObgO`0NƧd$hUt,Oi  $iL`آ:TT)gX(aAd*miX.ʖ>Z^XMjLN#\( 3 ̰I@:>$`8L' WAn? @R5bd(^P"`Y-K߸kOXRR5 =3 Z됫Ruqkl٧" 8}S3+b,[w}>fbl2nT_5Y#9x熀=|{?rѡERoE(0O Z W1bfA60Wb:[O{4m! DB@)yD:s_=Me,4 ĽjNN]J}g$װesxy:A泳U_cꛌQ.NO`lZab=bc_4b2 zqka OvcFwND7MTSB9pFobc˿;hklbpbGܸgWrU'lWp˛9韕 K"Oԃʝ>w{]r`;\6!EDb\ m)c?#]y: R~vLiBn`@԰!R!9#Y<5H,7g+fqf=}~>RϡyՌI &$l)DR06= 6p)bBE֓MfL+.E0[N[o,r!?1lx{)&+Aφg@ eL9h}ғҎ%ׇh L1A@>q G?ɳc40xa?*"$zG֯\'ީ$ yl: )qÈ6)a=hkAG?E؊S Ne1 gVXF֚Yiz '''w^Ÿ"/#/`T E@SBm9QwvBJ7`38tsvr@1N8_Wx]LNh '=J>}g!Nclo67$8M6 VQ$  Im\f'Wv,W&nk5ܺs~~_yq̂[E=8oݪJcf$eYte!8N6 eXiCSâ!.1LbpʑeUOшBbp'+z).p n<(F$GuFf8K]c4pE'.Ԯwc8GH[]l(5̲+XrĔ$:a5T,˽+fc Ug¶ .|!5~Sr A 7kM9SCPQ:1%0BSQFP$DV'(*CqVU(]*,>TG?).:8dXv,A6KoiA @"DLKDɚ0!&4C6ܱ) ld&G$o* O ]%Ԫ==R/*] 3ߢ^22%#  [fk'NիLsU̻.ܢ MA@})l4kRi؈[@Dչq&U%BOb!of I@o1qD P,)iNk* t37:-gv9Q\ L xa ^^(C6}brJE;K jn+Bh&ά\1@>ٳg (#D6j g$ q8Lݖ1f(DKR^<G}I׍3+;KS]Xtt*ӒcvƐ6KOp0u&.,fUq|IVeXƩ/z$;YMVʡ3d;pm;o6B@JǟmA~`4,MJҫZƳa⋱U2ְjϪ)&fIͺǟ~Oï*޼&p]|O?_5?"߸ɏo DP葢呂~Xy@@)JHn4Fk@$"ckh4*#N%$ӅX*8恏t`'阸l($!>E;`BQx(#0~`:MSLՉ(1qf 32jȣ`N2 #Mmp Xki Nf9]aC=ʘ㪅7az_Z`& Q%<%X-Řk )%.ߓy4 @uWhp5;ve5o/]E)ɾ whе-hc£ODF$ Hkqu`(ѬޏttVmFƤG)N:*6e fi]Lf?_z}-|8fƢk18@> HMP=,,>$,);R@{P$UM7#FY\R ȕ)Sp6cbL P#>/,ڶmlj [zSKE4]hl`@`JyK4K,(hŖʉ4$ڝ]zfSJFnk:٪@%0`o8:)< G6-֫!HOv2o,)]64M%G=@7\fv+uo,>`GX[dbT qjVlt0Y-"Tf@ yּ]MENZpcTcM$YkO:!S ZT'ti`W}9u |11FD C { ܿ}<6cp^`=l`JiGe~1 :2l{$ GE??G~W]MYG7|1L`׊[ K7 TVBs /]2u1cB@8J%Đ֠$9?H<5@@DZQ\|fܰYt AG[_ҫ9:[rmjQѺr!ml<^&T7UpNP MP,}Ȏ֩'VS״E"FЯ\.Ѵlp|qkqkdCDFpl9CD¥ eXevef'J3y:_c S#el*7q ;O??GCHKRcA@iR**Wc刍]Ub]k˾BЎE1!M]#c~]g@qw psicvk] .B7pzz'8{69,Nk ܛZ$#5DT6dxeǬ/-B4/=˥~/BS fԒ)O-2%ѹiT-*tC =^Dz̜IzD;V$)E`o;g{wqk0xks,[qCgMVc0K되a Lc bHT)&f4hbG "4Rr{h_{} a0Ȃτ1hLgTylSFb(ڪr v(3494"a_#l.5 B g BK9͆&Avu曐TF iXḦ,:7*FM/aΓγQ@hnݺ#k[fQFiV+'0e'$Sz1ws +&76l1/eL5iL ˒H~UF3T|ҾYҠ]bԽuW2BJ`IW sƮLS>$e6̷a$u? ţ!!]H I+=Z=R'K2wU˂]9`Wyt: ,T\ ~by8ӧL$ [Ѫ4hW1ZkPmCmX3|ǽ78JjLOmF Nb=_T:"&e~TC1=~7̕Aj>JY| #oLB7_c ײtnO/@,$/0" {q?»~EazRc-bsL]WҮCQ.hA>W>ݿ+ M/&#׀S+GdzIc#$:#Ӌ$WFWI9;@ ga)a#|=X>bI;8lZY3j|.jz>2vQͶݐQObJ0uN#ZރX,:8M888#ňautʤk~Hzߋ* `Ť55~Y{FbnVEoGq--/qxxojJhޫ:>, 䢔Lj81ؿuqi1C4-geQF0c8l6 i8O)fwT*Vwyا:=!d2C22(cjs28W1jrcw;#;XtcMP!L1m[F.e&rb5%F398+z:9.&Ǥhu˦:ݨ+iad^8^}ץ*# O=. c4~qbM6u$/, 56ʉմT czˁuh}Ȇ2\Uulhĝ`n$g0xXCpŇ}8*I"ZGQXhe<2 NHggַ ?%!z4 8XX,,aLz#6 82ۜG4jHU;cr4O̻e9Y"X2YB:,ubJ($M`GRО\dZ +DwCk_t:[Mֻ)t؀oyۇ{@M[wB% Ib@pɁDa}2"qiD &LA"EJIÚF]Fx/;gWw^k?ůo4BՐIFBp?sb{LfaǠ0uM4X'kuɑ$"1yp@@ hG{q9 c71"Q夭}e企QQ5ص $L56SPHnp;plt7LvYP*]s4֊Xq0Ů"t$f%j7P@TTꌩ6 Pr>F#_M:]~T!Siz.l8'l%H[0MdO>8l:0^ k`HLIO׎3;& .ǿຍ,Ħi}ܙTY"DӧX.J(fjT2p*cҫAHM,6j@ @Ԧ8boKaqB¸IHqP qX  *۲Ab(ŀf>)[8::b@״97ߤE?w ѫhĊb>IH,R4_u[O=9 ['^8 'xǭ_91(MSף9Nzm$abb !_<K]8 @cq\k !sM?5%0J#(%8HCuS),FV+0o<۶wIi°Ǐw~~3К~CywTmsYN^f;N10<{_"޾?nzhh:PSՎHZ+Œ3SXU&L[1.z_ Q\蹼 !tl%=Տ?]]yWTKaG[CE48Jr,Cz7. Xt ޻_G#X~8q /%4541 cqM`h9s¢cl``,Y+#l 1 f@<ᶈa-JhpAl,za}>Cܜ# +(u_2O!y\qmL#"!F1nx.N]vL0I 5wKPdtSbW&uFR@Zr.xIT!*mF:q{T}ł}i!5T5$AG3+e)s19`G]2HÛocٵhXܓߣ5r[aiժ WUL{QTŮj?KoւfCf@gl6iL,Kf%M2!9`itr5Ǩs##$20IU(#R۲* m9ʹzvϳSׄsl5=هC&~o!M,tZc '1A^ljAFF+Z8cu0pl(aq8roj'@H| 0Y @\x4BgF`FY'F%qD(p3s~4wpt6EmѤcngq4yZ􊋇rjeܲ0Q b:瓲)"C,͠]t/vHW1!7HtLZGiZ"L$+Zt'ՖzO>fÂpqq9W#ZϘɌ[1Tz:}H)9WrGba[ܙYjpvvC)R,XwO> }i6 F`,03~DՅmfbJnY.E >Q cg1N1L #:m.cК.}4 p7A{Ϟ?{:t4FSM6Dx0Ƅ5b01 # F!s XqD$]pm&&4N+f̄8L՝!wV %nކ TSJ茳sVOv[)_$̚эNU#[F` ]hRhGS\U\L@{q[`[ϥpDh2#46:s9~!EAB$# e.V+a]zSD gp"N'bqҾpcoH^\Z]`:Fa)P4^)%aFq9{ Dxٙ xjn8G٪ 2Y`t acacy gpry&8~R b6~(کF@:ϩ4 ";ObH @ <1l$ՆƊk~Y>&99§a4`4d`^&uK "AHt9K,8e@F۴5/8i&%aS1BFH"%X,/_`#Ew1,- "B9/_;cL"r.e2K(2$P?励Yvl$$ &:PH!H5&={/L3{y?06qoIvΖRULw @@0 H-پlB7C-bP@H.C,3Y%3_UyÞo<=bp[:Ũc9藞_f*AXᬦr=}L QU2KoB/8(Mt>"Ákahs$M'eGN,Ǵmzk[8zʰ$5x (MeߏC*]^_@L|+o_Wٞ}~)ܿxd}PJZ-Mc U\;bJ54:}amZ 4)BB"cc2[n1 Ya(q9#\ ! ᢁ +ȁ$w xoh#\Yw-o<~̣X; 7:Ҳi5SlY5"qz/EF/ bر<9jBc˛Ѿ kQaRɅS)@9 Bϖ¨= eK9"͗mw֢BzFu%7h˪mI1⚆ubAY]T46a3F#93~_ :dtk'[)q8 -ŚV?ip՗V?k]qYY/B9xgqL1TWt/p)黜JiQBW]\.txϞ>Ev`Q\:iC_Bf>_A\!M]㤦-JeaR95JG{J(1~.XsAq,N0b8\]hHJ0zO8b 0սk$q M^N3:Q,^N5J'цy씰gŚcG$a c`9ivc_8oUIeh,{I^x`8;;yuwǞns!tv݂ecm۰陵Mۊ0,~ȇ}W~Ь|޷9w?N/>}}L(ΝܘA^O@<K]0 iz?2Su ivļ 8D$]D{TNLcPdJHA cu+T?rHI["/Čut6dQ"f.[?نƹﳍ,5dd5ߋ.etbcT{Lc^֊E2(/[ 1u1~ȣsG§ֶXeh 溶JvQ-5NOʧEEKgq>$?uw#s1% HIM8lyĞj"82&4:%RhhO;ٯ:\vn8zm.%TӀRzL1CT\0 R@rIH=l6 Y4777jn(27wcݻuyv`E)|ỿYL\jM.XoeW/il,άoYH2ͲH(\%"@:],M8$<@Q}ĵa8q0RDb4=y=R'Ad֗8}\URgIJt]Wkn郚k$"B.,7鳏Yo78 X:KrZ?yLK+1/OݰsUq/0v89{R\\K$MH##%6{zv7lZzzZcyx_?|__x+xCZ˫ڎaNkOYm#TIYHS{aTfvhrж+c56m282JbMG4njH?$?;d{$Yb$'儤IMi'7sb#<MB%WJc mcc2 jEF~hAI(-9j4-|\Ys]H4e]Vv;9#FMF|)C.kļ'ENq΃O]+YZkf|_a{o;o`mWfGΫGe(9Pwy,sSʎBI.rLN 5noSm$Yk1+RP䠐<׵q| ^8 ,~Uv3.ݳt:(kE(ʔ9E2nU*uXYxϹB[\_[sEvlܶ3tUʽX^kU+׺̈́d5kW>~—q;ps}$nNm8 g<t1vr+%aDEWEy۱A8X(|wM}KQIgQ<ǑEWcgaEN$`=Q\$E' 0&V)k$gWO)N Vig4:]FV3y:݌kwͻ#iV4m[;pá&bͫs,8C]J|2*onU>{<\S w0t] I3avXבȺĔҒTѱfM,ŃBK='!/Mr[]ep];&&I!F)\>@#bGETm5!B:av ͙uL@ocmVQ^bu63-i'u+1&k#SU3 TuTctNȱǑpI)ZYq3 J̜isML]JaՓ>Ge>MH9v4Z"#@$W+X5-֎\mǫmWpmےRK 9u_i=ҵ+[Y~?+ڮͺHmYGSVO,2W(_k-Qn)"h9 8b 8eP6J)Qa?5Ɣ-)JJ6mypuǪqgM+=Y, M`s@C"d` y 2vC_}Zky풘6*g87G Ay&[lyB\&$ gH/8Y~;ѧ~02b;$7Lcj kAPqbѽeRȈ*cYU~yGLK]NUUn\i8>۶l-ݦ=ݎ뛛KBskCk]1"&ZiY ljDVLm (va(ʟOss]]ywac(|X) 3+|'|gZQM@БhKZ2TUݦSpMik2KX1e9,(Q\ \heɡ݆#p|X^lC6lWkҵmiZlҬִMzq-m۰l9{ptMl[\1oTV7@+AY ֕vׁ*T(c}d~kW] 1jT*`TRaG;οb8?ƈm1x"rL@pv~>|_%|ݪEȽUӰ \>݁O?'$'&EdIx|l+^\=x<co/Kxo[Owyy 1YoqBŽ3kW^ra u$]e6 MYC.q͑-+Eyif+-BKJgfIFF3"m۱6w<6ͭ"|N1kIr|FLޗ G:yr|yٖh(c1 > U_^]'lƩ8&CƼ*)wtc(DL8[ݍZ JLFEP9u+ \{# [9)j" ;|fq9ʿH.նZ-ϥ}Q\C4'0BĚi%IWXQM)ъ~$iqm/.P~;k IDATÏږpdTSxEiZ+&qSƑ]f}tjq9,tfw>kz8.CJ4[3 C-%5Ĕ"]{ [.򱊻Q8ek"BOSom}ZYmB6/nXѓ ı !)q,.FɚlJ~RUxvL d+k6Nbܑݬh\mVr)f6%!P(c#JgEu "-3HTҁk>}js+ ⬞$=3Cc5&gIL+Jrp&gΚPaLviכ\=KnlAǟ|Ľo}.W/am7x|>yZO>߇Þ4z^n0q9;8zI#+juo?t]+Ȣ7ɷ?| q5tbȝKWF{s \-#Ia0ֈ{9fzYhOtuT񄤞GGֺnUU;yS Dh4UoicZ4AD=: ^ױQpE`i # L˩ǼuS4ܬ@NºF&j&u#AkRkc`J [EpUV1ĐOVi S &tgE7Dfas4?u<8# .5 DiT-BLYGuVDtT*qRXj!O~G}D:n{}Ja^c]ױlp.t,އ\,u9>3i)ᮢeZr.^#ֶ]vՕrZ}%MRâ`dtXvN]OיR]qzD Ey_'PYmʱ>o7z1?" 2R;V=# LEV4)2K8?*Gf:ŀrmMqZcai=e# *`VJ*knUe')'HA] c:5c[Duk}6C䑫1ڭ$I֍8FN%$͔+1 (a;ajk=.dXzTH$#ll7͕hVF3r Cf$$|fxpx؏w?_)%~z|.5HͯyUGtw%ZX:&UUX4Yg;]̟sY梌Ry{M~`Lr*ۘ(]*ZV9Qsy/q`zN%N1Mb?fsMbZ ^hc Δ>*AhGV~~ϋj3bG^\]Ҷ-_<{v0z?G7ބw~Hyx׾Wx%vՁ52d?⬡m\XWZ8%9QGn/>G v{hkl ~own!-6"CO8j)75JEbƈ^ B M(Q1r !V5)@cjpd9zϚV-*eP1T 4λα:73b9D)J ʣ* A6[;{~FkB&πfɵU5VXxqu~U=+뿘9Kj54+"Fz-o+˷\2]SFE^y91z15nFj]o8fԼ(N7S')f+i̼JvF?Ĉq~4XCh)cZc}S|H-_ɂ9C' `GTN`,f}]sq۸UsG0RNO]]%юJ(=9?FTJőm,ʤz,KRYU IjkLz=.&lϻL)1san"8p<_"oƹ` c?T I]$JSD #6<؜f*8&/0摸66#`$'% i×z-ǹ9>l74MK4?|psӝSb<)8rq~Nv\_PL އ|-i0~iÏϯn7ޥ8|6qc?,n79NF FDOqq|~&Z3vVЩyR,e5&z`ެU>|F֔5a;?q8QDM@eWS$l˶5lW-ae :1?S8 h͢ݔzeCoUN8DO udP*bSbY-hֱRrV:n^9~rodsk1h0jq*,w\gR")h%niRJg ZA ljYRU籏1[g-g1b8i OakdɧEk٤_ Vޏ7 v [N qS,nKN$j(ew3k>NCNȬh&~cjby(nq==\ TOo>WӨe<ִݢJ/O]WG%<3dҨQ.)`ۜ!:eN0V~XFpˮ@}$Sى*L,d)qJt`Zs\lt ,8SE/X0q6kY ѓ'dqUmuMjMYttbYMi#qD6="fT;萻 qcCZs2\ ȭdƈR1Gqu,055DFegT)E-xHa)mWC[vOyrJhv+lV$w5IiR̄enS֕9 ֮hh]ԂwpRL~nYELAn]T#u RB~}RX ݾ*1;C-j"Ib?qА191mw Ԅ`RYݴ莛n5kL\_K_~Ki&)Ï~cQ5fۭxvء8UXmI$zͯj N'"ƙ> _wqqpOߏB֚a_}w9:cS✡k[$$\}Ϳv´)eff5z@(b >[u]'< Xw.[102tV,Y#Jz HbǀwB$6"Wi}JGq1ĠЮ1DI&j)(lNQc%*MW)3 "3:F sEliVdo$2FϘ<@P^q_ƙ.)-s@ OuIX:.*YT~֑M3Z|އ=hъF|Lh#imlNH ͺAx{bPwoo_1=ま bN;1D쀞B8ߗ&r#NtVMfu-%w1g,Hcv%R*ȓ2ZU󲆨q cdm6b"x"`L^_b(1M$\P%㜠5&&Z%JD'|bRF'IG\kMF,1)| `b]xy`1ZRWW>*R)$D=.S˼K623|S-iLƮ\%4JGW8LB>'LObpGi:1It6qH <:[5blSRpcaa.=fuq]N'==9:M-7IQѰnר?f5ֽ; kIJru#+#N[l*Qn mS?uh#8]JM"XU9 5? ǾA;kibOWu*6!u š3Uqrqt*ظJiڶ뺜+V6cIYV]C: 'c!|eUeѯZhU-G1)rbMAoFSvTYC4J"cn&7ZD\tT>o>&uѼH!tg:ҝE󖓨BI-$&_PI8AS})2R`c%XvF=j$vCň5xe^uT'L ΒR:} !j)ګhS3"GϺosWI:8 z<}GE~]Y^BF6-Cwc9Ԣ+czQHkkl'2a>*bԠuB[FF1%E8(j: Y0|f ,3}^;(jD(,b(vRhe$jEC6׵.xYd^Od &kQ\7S`m칻924|!2-(Ғ"m+g2!fa=^{Glͮ@QH1Fnnv솑y뭷wuN۬ØhOnfE_Џ#] gϷS˺T>ds4xvaOciL O)PO2T/o󬸚0mrn_J&98zbHQeN))z*/gFRhOE5_dqmfmW/ x5׊B*e:FF)z?be;Aϩ#kb'!u@wj1ߐ!&bH褫 ZU S Ic^b,dP@k-782dAcR*Q2ÿWέq@K)Ug ~L{0`Ysq OY;"7LmPp"߲Qwvjtۈ6kٳg#k)w3F2Sס'EV6#ΛƑh6o-RoG<)|c;S0"Ym]xٌC.Dle,].\4Ǟ^s˂K9Rؕ]L+ZH?=M:Z i |]0SF;Mr,oB}Y4T-ؒۙbp8bĊ!d[dEV{9X3cN&Sd=ˉ;ZimXoU @+BQ>&;ιV&$O) OGiP =0qK/]qGJqBJ5-Fi8bQ#HhAyM)拋 =yL?h?y>ݝL_zoϐ'xx9H$\t-ݎaH@M9WZx衘RCĿ3 j֚jKu|)7ׄ[os}pօ]MFWN}tJOZsT>͒G#) rʋCrv2d89B⒡Om+bP$t<*t 6H'8aHė ͢|Xjj=GtBa8ed,BF}:2c)$ *$E %>FtJ@8۟C2YU(HH bٿ*<ҔcJr\i~=IE7pfF%HU n׳)0Jiz7Aj暏? NkYmVf]pբ c{vJo?]x~uh˿޿m.]̳_%n]'Q^!@"^ >NL8=~ 0܏r0(zodM!Lř`l58gYoGV:}FNgc3!AMimƚE>>+Rռqyc}%E5g~ DӘHD೺SZ;*zP8+gqЬ[aP'Iuo}mnaݒB:8~?SoͰp+>}F҆1y?9'0aD^i=ë3ZB9/9}~QH r{I>C\/ ̝l,n-7Ba;`D[ VWBiieO!dV>%+^Xn --…S9HWĐ )рAX茦րVQBc;x"(iFJOsմ)GƬNN,#%)MGj} (_L'9UƤc,I1]T5*2:%lQf{~LFwv6R/61-\oc6"c IDATT=L O?wC']RbG p•4lI_ko!l|||{ߓiw1N7ӏrrYC)k%<ߔ T'|';(,4#وkr$EWop8ԯ+|˺gZE LH1TjҲSC-ڨ5Ag=TsH4n1r|ANɎ܁[^3״Yӛ$.ֺ xyؑz `ZnTDq8:]ϊܙ<֖5:SE 2sQ.XSǗjZ֚{Co=y7 s"c KJ)a.Vx>Gx:{TJ )%vOl-ݞx4A*)J| 84,İ^O3#ϟO_yz1 =t2vgMI @r%k8#g圄? Jjm~i`^MxQt)cuB@'SNkVR")z!Gt;9"AL,(؏VF^eG1( %RKaC†?C%ӥ#Q Vwkc B>V A &O5J>R4FUX !K6ﶈ3yM+sqt2e&:$Y3ϟRn,p8N)6 t獕zc5__h>aMCcZ3b{܏=]OkaqV7)'ن?NY663OqlIH)ݷȴ0FZ;E)kH~O3gJ ֢YKZ4l=ջRLmil4W77x<)1#Mlߒ1/^CƊ~>];ZL :[ e'\V"R. .X]sS\3;f;~kZ?uBg}-S\E.` 8'Қ^{ eʮfw~77 γ$>hȃ5(=%\gd8݋_90tMՇӧa5jkӬVai6 VT.^57v3e^c^>xeq0 mkw#WWaݼŋE}i 8?WƤZk2B:;iLL/&;8GF,%dfM4s/3RV֔7&ФDȓ4:Ow<h@7<k[d>iMQHj :;t(0&jCF~9FO<>$FphTk)bBDŽE> 9 8irBNO?\'9uQs}ÍM471E^\];ƃyq5^:C[eLɫ)a[R4m6:.#rP׵DZ(:y ]qHtU6ߨJ\m!x,Vg|_2 Mlop.YW1 5{øDѝ(V j橉"@um)]taޕ)xZve.:2%VP}H?hGӬi\d2\]^%JJ:J6Mfݬ1Eg| xsc?rqYp"GY'ljtP #c!K.ּϨɸ6w% װYG4fI<sdZ-^%Nh=.ⵚx+c4Ok8AO+MΙ9BPygNcZ /d^6qG׶\/:W#F2o1ݺ^y'Ӹaxukx6G U. _pEjcѶL&wQZZ(f:L:~T-8VۮSA0|b#mT1DVNb)j<2^_qƟ_ɵchJ1a]+s\WYU1-Yan$n'FQJ[|z FmHӢoSR`11rqOLAEq a6.E)YlR5Y(]A'MZc &y99kQYoǧ&;ku_#hM; :G-I 3033(5F @Z/N\3ݧ>pɓZǘ 2F!|P%hfqf wU_uH:ÖZ0Qi)NǺw~ciTq4s@a^HGBT˞kPc&hџjK7OAr͌NjR$̛UL p q:B9rtŦjk~pi[]y~5j;VniPŽvs<в)~.][hcW"lcƇ{91M1puyI0 sssJ둘ba'9دJ5ݞfv~~RFژm3F:b(km4jM[ j;tMc+][j=’yjAv_:"'Ԇ HRE"64ZP(Ӑ0=Ʊc9H9e v"RYL-9/:7\K'+8>7t֒zsLa)OޏH)CF?A0:b4<ʢnܤJ*vn;Wr9W]5]l:vB1Ϝe 3YK\ySަif181lRm޼Sn$) ǾNhk )xn H):REg3>|$(@Y\m^_p^b晎\v;ҽ8Y)/3+77u4ҭ<K~ٗ.\8< sݥk1w$fisl1眢1u`tzǏ~.`ơY˺E蔡 !eu}6.,1"`+_'q\]]UK)"J4ĕlͩQ&vSgZ1JᔤqW7N2XNp1wVNsp*,:*&ÂttO#[T0:Dfq%F. - 8R)c2y 1^Taj߈3N5~?x(|gx>f}?^_GSp;Z \(Pƾngp>9F>#!N'x~ypPYxrJ.뤇3ڤA)%nEcd6fZy@X5\MT޼?x7_{̣<Þ?dFšݮ"̓/D9rlaTB /fq٩@*|Q՜a0u;!Szb?QeP.NMiCj`U0 ).Su]Sd@ SgF|lC.mq|7gͯղ 0faq!U$\pM# "ajQ'ZbA2 P".|d$@v@v 䯉A/PYȁ-,K)")Q͞kukʇ>{F>u=gz>aZ A_yιGdQ~Д]>7?Ajgbȸ0.<Э:E!fghgê3}V\_]ۡ#ԁa/O-m|ψ `Ԑ."Ä!bDŽQJ}6c 5^JBSE!d"ٔ-Bc׿5Oc5TTzB,˂IM]׬/tMz;.s Q.zJ apuخ~蜣PL uQ [B΁عz=T@j[fPYUTUM^TEY&:zn~$= _SRn?B:caᡑaP)%F>mH9{Kw9C GDk95)aEjda~5}1e˩euSYI]q77OH,h}I 2p2q՜)G 1q2)gNV%̘Ab-2P LR`f8tl=ژ wgP",>>f z.Mt$nR]~X,/EU>Ua2H6a6b]q8iL_M$Wܧ 7LCmxw}bEs7\b("`L+|@4j"ĆxwOK&|4 ?s>1E!㨙G[[.Wl6a*1u]_a 1wER @T%0!w_Hw# G|@g|*\YR,~{1fF.Ahltm_o 1k>-< 0*Ř2zAB41v!9e|rF:B"eDdB^SR]ox/)?-5(bWĶ-ł $`H{uI#` k yܻuC,x!=xSn>eܰ٦\C]<z!%TkCYTud2IiUE,V, ei2w8AS+t9E$Qq⛢O?9A#Q'om1"$e0 dqtQ/ㅬe MJ1-'>L/AWs6 "AI*&Dj*. P#%FsTǎ#*bkA$SCf )OoU K"/CSWH7%DB8R3r@AͽKµNn+l:@reYF%!d9 1f=e]"ad3c0V> 'ա < 7 KZ4lvO4'rx&*dlVx6/69Q5GGG#z 9QO#}P@F? o<: JIbe7Wg4;3&i:+I IC%m2N0k,Gz}G~㘳k3{2HBЃ ,>ާ4ޔJ?F"RHpmL}fn\rzz6<8Mc:1kϟ>Fv'-&"rOG5^4-]ΛAK4TUU1NNG̏L)u=Je+GQzfuE]U)8[Sd"%-w*,ZktQP5Цdb6(ul9j̃i q֣UEl[1}qGGtԤPGRX!BLwHBuQ>%r.؝E΅L_5FB2ۮLKE""H"lMx%)3U)J0 imA\JĶG>9+JR u* 4jHȈñvW"Fn!>jS`]~d.$ (d)AABdsDЊ%iE!z3s)36$AzQ)1{r; 0F!zɤĔ:'yBY@Rn%d76c'@1i*瑑!ذ3@^k(tTXin(2g#7;ĀP 1YӪNefffg9LsKZJI H\jdh[it f'TXkhG!3 E<8q\+ZŌq(?I;Ƹ@zpl 20Gǔ F4g!HIM$p8=nw{o닽[݆]h"TP"bjz8mX\\0JB`#$ۈA@9%B.ҽҔH0g -f>ƍ3''LONfLgSʢ`SPB+\s>R?CL Ofy.yX^P^>E=}]M9pN`Ǻ]cW+ʶIBwc0As|URh5@q98'V\Jw!¤S;pm1f*cRʩ ԓ R T@ *Sv2B p *(CxAsS=']RDe__tɬIA3{`N&}=D4Dwn;0"&ˬ$"JIwCU*Ft5eoZ׏1ř)%ږ9,eFS?dѦDI ,>/bQGƚ"x.6zK)۷Y.&]wTa(څ+c>$9%0bID*꺦uzz#AjIQ ;lȌ%R=D-P̏:`z#9"E) YҮ,oc=ȑAp'Cw~N;<ʜ:vg^Mّøz 5vo܅.bm{~-'d ='su!E T٬lVOWbDz] ; eȋ,KJM !>ΣD$#|n >V "HɭwT{j|6+$Sͧ?)fG3~}.VJmG". EY&ކqrd6I) >$l]i#Oϟbp(e0-n顊.s08n->g!}8\֚ܥ>{O/.xw)ʒg8֭[pt4g6uVUI]הe9cQ&Mi!צN yUQ8>ɏ$y\/ؾo}K'K+IYo-A].(7 (#e*I m IDAT1a]e;iiYsw¡MM2h( l(.e, ΢eqH)TK%;1ʁ.ޏ$ag50!B$vqp܂Da@fSF9h7f=ﱏCIoi.u >;Z;x{B;wլ? mcb%s,:y!H\.֜ޠIkm*+`&Qw=.,vmwB<0Ιzfz&]Y5 M5ǣcN>Eb/$l>}??LAy-FxTf1,o]uMy8@D%GSЀC2ý=d! a.W'-bc˸{58w@̱ɦxH)St3RzA ky#`_prrP‡k6Fq)INv:HTa|BNtQfJz4Xj'{he#Yo IC4g_ .%":GۍHUprg/I 5YunݸRmtڳ9 iֲ]\<|>oϦܸq[nqM8:SmkLG2i򔃹;EAULW^{[?k?e׿Γ~ET:u2] ̧hkbb'w]&v#).Q[Aܴ~ Y%9v] /h|pG)/| <w^ tmQ ȬH!vuL&(dXʯ4U1ġ#wQ$9xO;7Q\>-E3[ŐFIߘ8z^0YO{_X>yGT%tGS2w&$Pu.xŗ)uAKi@@}i[\6sm$9)"D. e5H"AUm`fn-ìQ(xXԽ'a&"9{W 9!9b%@[<,{???fW &Da%!9P@٧H%)ot֣ @fr7w7?_2QgͰߴG\S]v,Lvx=b6 #SYoC;l61P uԮPGCvp 4*sl6[d ,c@}V0y5-]2ӽ(tqƣʱl|}NjcCԳ^smwCXqwX+4{ĹdXʯk~rw k}1uNwx?qq;$G|Dž.mڶPl`F"E@n7q KJrZrnl]OJfTZ`T6~68 |D' EOJ EhSeuV(Tz'|!OWKQu"erj`B*V*W_9p*?yo&RkZNfW=GU$@}s}N\IDb#%8n*l6, %zNjj<|۷o sqlk Jtp6Z1Nx̧_LY•Nn½Wyk_G"d7$jH=2\ptFu~#.I/A|Ͼ//o.mM`Czx+x)S(BZ-w.;UTīYVEezJʎ,kz(#Pvmw4b= 2OÜ%FM{j΃ Hُ|H 3V"Rqp$I(lcb D'?3{| \[-~7> ~_ ~|t#N[w!7!ٓWFaG%Iob:~Sc}֨8ucفhy&''TE1\),Pro[|cRJe(p$Xr(1&bF4+*n1du\UU3)kF6uK1La~cq/]`t$؎k6c_?n~=t܏&ӱm$1.`wF)?cٰm̮*K %PQ*%D 1/O[.ySܹ˷q<;ƭrJ] %YL^Mcwo-yz[ 1\Gg;B,zʔf'{DHͭ%v `ѣUaTW5f:" v~*>я??ǺݰX___W0~zR'zMPl)ߛ!(E-)+uD=:pqݲlhV+ma,OY7\^.X,yWY2&TF }:(GcД(BIj;/PhV"T "6Dg7)PyPKtݍx3#z{|Uɷ.2ۿ` Bg@y >~!#ɵhȯ+2LyEs'1qq!LZI^Ղ"v 諤K!>D08 !fgmL.ނHNZ/N> ,wCsRN%2-8B q'聫Ø);1;!ȷ]/_]fUxzR\Ր:09Ҹ^MyY㤔f+g}si|du'''L&ٸt5rĘY5-tB埡` h]❣m[gsl*-ur%maZ 5c09ʽx]0+i  fFl\@?djb`5Y^4~v.hYq]?1n@b!y@IJ~':?~|oރ{fƤAxp3‡1``,?DUO5o ͤPW5aQ*ڤLEs5Sz:3:(2KIfE0#'ܽss.i~].1uiU}z6Pvy]Ǵ/]q8Q]YkbGV]WpGcC@Ngj+qk˶+dc0?c ^o؏4!6aDBR)HnNknkD }vjsIY_y/fV6O/:P/H9y}m>OP"Z$ q>OteS<"#!)B@ 2D2!QR/Ls4)ꊧ:*݁/L 8,ZK>|ṽ1#'18?F0z,;# R!F$8@~ZF! Nsy1:SJdeY=G1'78].Y.ל_jw˧+"mf^ |?piM&LKczb9{ wx;XCpNtޣGnk(QQH*g^m®va38iV%+9 O#5m#\DbRꔹ85^xd(a%1NtZ1!{o5}?sR1 870EaYo'w']9/Ĉ8*roW"rB|F*HQ:KA$nY.餛D&LNNXO H`6;_?!Ť7z>S)]pԣX\|8hc뢌:vƅ.5 >f,4Re~ ̛qvf\:0.qg_ z5יst {J;W5paHٸ37dMC@! fSnMjik UKt>- oaӧ,ct3t*.?x1ܤH`m{mnJr4?b6QrI6Ay9ϟ`Znݾ. 'O8Z'͢i4 <||bqvӲZ^"b O0Gw?}ňVBkf`sI!1uE;|l)'7(46 BW(xfqv9!`"7Z䀞YLvRZkL& ֢T*% SrMt-A$Ն(".Nݟ+|_6\; #FL{eu'#9Ϊz7ܝy|Rvc'|윦UKY dWlE2Sh].£Jg'*|8;;5ҁ6ɨp||*u~|G0'&ܶG[Hҩ?Đ UUq||;O?/|(蔰؜?E%|/w6%#pIK5k\8evwG+#Cj30|4 f̮oc(y?@hJEZ۶?z?;1Y$6bGi֪GF#|Oȿ⟰i6)O!>pzrB eQg:eG&{>&qm<6=^Q#kRqd\?<߾0P2^9ݮioEuy^,>x{W },IL~4Ѵ5=:z N+-~-/<=g}Zǯu h-.l@wXi`}nHn[.pn[l~-.866aE 䘛7oSVsMfʀGƴ 4R´`-fVO(./sZ6 z2G21HpYaX+3y Y9j8ww>;,*2Qt&/޽niW [(LcHa!f(N D1blCi*sg%z^iDs0|(eLP3ddH"mѴ Y jp%@UlDڔD.B̩#7X׼'.:QI˗ic,TeMYGH|FFǔI®ys)qj]PJJYl6Ph;DU!6 9t&iHÑ߳rSAXHuM̀vOTcXvyRkB2;Iɶmn޸~mײnӆ e<RFHA{Ѵ+ſ?dZS?Sܿm[V;8YЙJք1a>a7.]{^W}_1XS.]5YkRꊻz z5.306+Bɘ5 F Kn|#fYXBǬ!*$(Cfv%O."$մMS& JkD̈/0 OWrRhY2L;\x LQ`)a?nqqI=z[od2?h/P=[Ԅ5\𹰐YWby1"d˴e: Kqbʖ'N`^(I'تMj&UŴO'</W\.%&E6H*.־͆)./`DMKݚ-.(\@ud*DSXDd3sX_\]8T>iktșAsr<|"ݳS>s%Nfܽsz_7 \H5)dʄlwxݧ*J6D%Ps8,+T$;~Q~6 mDLh "5$Tb7$vD6MCg;\ɵ$UU2@ɈZ)BZ:$ATDk⥛9PRQg7oΛo1Ru-ֻt-|@)ر̰b۷ʟgs_OΈ6nԩ"B?9:&x8wB٨yxֿ!Yl N<7Zqf?;)s]7ZۦƔf IDAT9Ҷ0hZ.$ڶ% +C.ޡcJ+}}|TGsl6kpn,W+6:pDzةM߅MdW xvڽ@8.;lc3{yaR3cCax<>#`=tM 8OU(nqv4lĉrn-uL '#6b.+$UYAn NlR`}fMg->$HU"BĘ PH~SBQ1<"}*CwU##QBN'{C(Lĸ.Jp%Np'MX{Y{tfjyIU|&.H41y#6"E87~Ownw;F6%[칁yz5 PlJULƚO$j[mSN(V`m`4,WKCTs14Gu[N6rCc6m\?=dž|2CIIe* (󺩐 &!G8sԲ$~ԡ<ٕ`ZVs/z^sL84{>BR5ux@Y ]/?H6-LSYu+-2s]{"!Rk=o8R)1P؊`"^I'9- #S N) SO8y֫5ityMfuYTSdEmbdYimê)-:٦R|@x0B$˧nCtsBQ) eC=%H0"g|jmK_2^d>5G;jגE)Rp5J&4R{wز=ki+#$P(> PT/ޮf2/:"ʔs/DN|((AyQٽNXYOC "*ujG5)QRV%*%H 0tF.SZZEמDzS_>L9/޻K%ڧX3Q$]f_(a}nfv]pD.Bw˾ːaKA: !$Ӆ-", 8oZDrb#1(<^w)oQH#Cȴ0REQpd&ֲj[8f3tm}@B Σsk]$Y"ޕJSAm5BHJI$m9@|IYL^u1-]y;o1Ns&uYٺD. hx*s¡QgB0;y5Yg\iثp<7]0^bDu׍aC"qgq<^ \rĘRctBu^ׂ;B!uMW`$!W-!MŅ#R/4t'7<}-2Rӓ#væ8+X,ٜtaE$h(ct%m"vTc$Qi8BB8ߡE .$lRP~ƅk]:Ƙ'+VݸA\6<]kd߶70BCJ3 ~:~)8p|ODEVD11>n\$TOHtX .&ssRc "',LDGU))99}l`a:C_ע,+oѕ(J& E)J=kɯORT\CfWN!Fnŗ^eq}vR+P)ViZzs~[ ;DrWdB럣CM^amns<2lzsٿPCn@6Pt>ɝBv9#eYPV%EQ`D*M]O ZlpC"4-Ͷcv8!E. :!Q`~|ʻOA,[j!P!0#䖶-M &G9kW(0# =""HG(FdPxmp>F x.٧*T\PxBEBpv!N^5f ~uwE*f|Hݤ:^8ҁb/,5ɄRrqqIY=zDY(V~+4!,ta'5cX=Dbz-"XmD.1(Rt=,J" F<=g.H1tMBT6po][USfc:yh6f}Ο.yj~J19R}}?vtȅ!i&҈X;1RL4߲bʔa"#2 _Ɣ޽#@ݜcߑ|ۻ4ZTs1 ;:uLBZ)شHr2ڶЈg˅|OK%dT)efs?טg6-)KN;, cԸK*k\(3-u`dSQҨ4}tX$Z $nd=tx18ipDZۡE6;(FFN4l֡o6(hGGPL3$>X DM8Q106!<1: 44-d6 @9a=40Q7 Z_+zjw"C:rx<>.<wz:N_`T\5m4\G-+ʺNk)J脓82MGt4.biC0tzDĮ#v-v톰قT( -ѵlK s2A(6BDE '!$1Q=(qQaC#t{=nqr#!*{l <)tC,&3Cc\b~<S?<\;"&NC3h3C+um0an9rO׶h~ RoNw}NFS&bs?M3&u!(tـwTEli&,/W,GFO1eI09G,i.y`K. 鼇&D .jZ:HECnpm^JIEQ1[QHXy%'+>ӗbvzKjֆӛwy{-43G 5R @T8vx/WPBs\X".)"qocIvw˵WM6JŲH c X!<Yc#&%f7[Uז{5"'f[Y'(3捈s{nDF "mMXuEڔLHhS ĪA(v"MԐB6 Yz@E^7¥.LsIu:&D&2vbDt:;WAAB+4Q"佻ך|^6ngqa艥i! Bۉk~^y:uEZd}qt$#ш(Rǵ)U5'j2`QWT.PV.=z>!Ȇ,\i# J I4Hj J%ydYl>l<^by&eY3O5pD-{9_|I,FK1][mb=|jTJ@dJ)E((5)ĄĈ;cGQRʖ]\++ J B8N%UĔc)'؊;sxi8Oy䌷KjAl3ֲ،OXWS4jA@?ݍ-bd&>ʠ7"+Klxd{>Jj5 h\t[R"ei6D> cr( C,MǤsA<ˎ[6'Dⲅ6Ü[TO7P|S!J![):$f\P̴ E iw>]ڦ,:h% $D Q*$ROSbM^ڥsES |fU9Y=>,\_73V#Bu#^1 P#$Nb+-Y:FƈC 3˗O78:=DP"**z H#ٴN)+$y!U]c`6_0pk/q6K~#o(0* /VJQ͘%֒"4{#gg"˳yZ\5%sxz!9.z~w+kmp +Y[í8/󧃭|huIvuV;#-VTUMYWI(aF>: .M+Cz DVXp'6f 4sotZKIAe7)AeQl7<)N09:l씭mQl:ER_8( #A[IT>.K;66X>zJH*V=Qrnr0[ 'ǧdy?Y,R?\HqCpL%jcpdA> -,!kV)R%ojصHk(Bk11D,'ra21͙LkCAr*2WdPfV-J]ʤk5^!ȥ6@ ##2gs#&8|CQ4wp68)~fgsM~@O}5\sٽ٬ { A]VۚH610kV̪g1UL,rXI) ^, Mh7%G^N/bhl oڷ6ӔF+Zڈ4:VB' FBcJ&fMQlMfe-yʺsP<9a~z1ML(`bsF-=I )> PۚLxb h!B%GH|Tq|va"Y-7nf}l^,};&Mmܵa9F6gzaL rl>{MT.Sמ\{Yn 7ǿوVxJ2cF@JTBPUղ+? Py$6əhm$ 4q(~Yi 6kS)T*\3$*s !.>(#|mGqwLw>@gvGJͬDȥ?64*grvѿa2_޷qw>ËϿHTIcBC"8Q6#IŚ'̼h"$e|b6ky הrxt>u]Cq Cz~@plx:lMٶ{Ƴ)EGgP$f*ʣcƧJsc 7o>)©RM.WB &QK$4&i(7vC0y=ܹ;oacF'ɇGu]sE)#EHXulF Br _2/G x㭷99G^_߾/җ_O?OTeDZ m!Й!ޥW,ϓ:2cȲlB 6ܸ7o[prtjzJ4eٹEܤ\ؐc: fl:yUU2͈"L/ ea߫\nj@Yn2,t!*S+pu`Xju1(] -ȴ<)A5<*/-#E@=0zrh?UhXت$9A|2_5rB:" ĀT&1#DƔ\M( _gc3;pvzqJ#d@ cFLNp!Sհhk7$J/QQŋ?-|*R&OyJ |ooؿdM߸ ú ڪ =KVS4VO#֋~NKE0c1%౶a՝5!V^X$AhFdDeU#uI":8/3*rrZKM2-nɿѶȦF.H&A ?qy5'Sј?fR#D J!2}DϠUN!w_{w?,3\ IDATpzz+F" (rW<-q[>ŷaGsMg%y*R\rBH6 !Rnd4R'툐Jƀ4sdžD%Bąd Gg[>}7zxl6Ed2(d>UY2_,ZzJFzWs޿!GgS ^+}d1uJ}[JbxFA-nhl4: B L 8'*"mZ e p1p2>'g'9vid;gv'wG"]k:1sAV_~WI=ů}Tu5bK~_V?O_#4<F"u @L\,X4h1 !r>YΫ[pxW Gx7q|XXM aU+Tj8t?z'Pg ky[o|4^yNl D \Tvb!@giYc$Q@)~d6Z맸N19!D 1SFdJ!y.jw\=<'Xzc70VyM BdVXR򺠗 w?; S!5L#[V [-fos`-ͭ>ÞA ;2M1e݂Fzlzy^ث@D#lѶKI#"Ko( ĄK`rElK8sxxȃ}nxo6 !uP\*a1[ezDnvу|^A7{= 0Z?'2i߈R<7"-!%!у1s69C)E!pbOjlѪ|JC*I/Dg9[=lQW[pH'80BOzE V\(Oo)8 `Q(-0E6O|yAkxGU n.o7&62m>%3q>֋}_—=nݻK(99s2CRLFGDoTZ9}9>>/M9}"i Y*Uق;w1/ܻ{>)>k|ͷze>"/<9n: Z{SGKĢd? K,H"'ՂiYv~p?eF#\/B$~G,n-r=t}$6U'ݞvvׯۿ| cSUt:!љAk`\ ^]cNn})JgJIb^\03SU !Y#cDuUiQb9G,V2P1{HAٸ^I|e" gSلX1r,sYrHoP \*JSFI3Fۛ66yN4Yӆ ֲ7c]"c͢z KuZ&Hڠ=~C]Wq{"u6oG;,?pQՂ<3KvH#Tev|QdD!(z9:̝Q?dQ{\ikVo3 oFB$dfn9)kg<ϘQxwk+{\v{SO2"$>Dtn| [W9=>dqrgGTcFFP舵gx/ D@!@ &"+.цl8{_hLeNV3Yư_9~竿&z*98>;x1[̉RL? jd3[%-89w1>S``0_W>^{ODJɇ|ロ *Cy5.lh"e4Q*NsAh!!T ,!,ڢ%LkJo%>bqʢMdMfp uD9;90r4n۝^c?bii b ێɨӲļspE O{U>їQ.JO9%:ŘCVI)#O<%ӊ[y3'^?_`!'0 ˜Sy;gk-]gf`)]&겤*|mNq:0MCrHzh"zL/cR|Zl|$"Ƥ5kYK[ش8s{T1 #ЕY?fwn0EGSu&a]ciK8Ǒ5ZSz'VzNO9݆tkK ,C{ (mH8š !Rm$uI^!q"*T8$Q%slGc`CNĒA-"Y h੤ bxy_4D:vUԚ'/4F>.O.j]IbrvFќJGwLJz8g}Tu{bN v9kvOƢ3NpY>%-6p &LUʮl&hh|Mw )}@Hc4Ey580;ׯ^W~ܫ>:Kil0'pk }\xITyG8;9%$l!ֆ}#4czYsxrDY-Re3z!YiG!YǕx[*S()ɔ~C+-6ʫlml1vq&B0Jw$P˚)(ɡZՈU8NI 7Ž==7/$9\2Ap:/9xm>8:09=nDMH5U*ݾC5'HW\;tgc\}nwn-4YK=#)ӟ?FXƌpG<+l6A!F(ĥ\a7$޿!o {)AN5̵u@ʺdh{RH\/|뜜LR1.v(kƝV=F5e^`*PͭM"O s<'lӱVJ}{VZ'mt]XEP괭x΋TnGS]t0O.U:,-Z}s쾯ep*,{eF]opt)Uc(,4"jɴKs/MLyb {':5khI/sLP4ad2d9|2?xn!Oժ3i&WNia zk{u뎏 Љm6060;}Kc<ԑk6H*W 8S s3_\ F ҾQp+xD>hTHy䊍1ߩ̧cS{p]?()M]gkl+?>Ν;llo{_g1pK4W_|? x!@XXXglsl]O"?xo!xVsr\gdDgrP9A~u{l2"ٖ%2([h07łYMk̥KOǩ+z2!$y@n@}8'.ŏ/ 7o%prQa#}Wy֒>ȥ@Ksv9*O$ׯ%:f3rV y>>Nx)DMXL+޾56:OƎy'8tĐ~ts[JŢ#%`mĠ@2>)uEl5ҝ@*J(S@߼7=sh[9."ЙGSKVcjRw`w䨪ٌj1JQϰ;( \͛F#Rrt|u{{{ yCxǨ+`ٽt- OJNsF)>+QR[&R'k:10EJ#KO$&)>; 5Qb_f)#U6B:D4y \YGUqX&)T)DijA >ǵkW3EUN8:0_TU4S?{e=Bc>"&HRNl%4^SIK7?)> Y[$,+ {#. ^k*|,"Cx)lFrC\ M8\ kX[ٛ:q$7|7\2D9F1!w=`h{N9yF3}$ꈈXz@|W9!_gλ2i6/MZ"~ʫg,# ɀ-x211A(2Bzw|N9\3p/Z yJ QaSeEWHq.֮a(Euba#9[ە{f4{g댶zgHHVROf-;!D EǬ*\ ,!sThQ'e0U,6YԎ1B% a,>H- tv&s\_}|b&A{## \J`D26A.ii .MwekFm@| %?JPc̠Ja\tṙw?$el? ~ojnOiXj;\Mgw=]vO҆>]z;=.y{]~Mtƭh+E(uO 4jiN((#FĺW"l¥Sd&PT$m *ԖhHPuEǵko'$bx8;=c(pttĽ{N@lޏP` _,Y^}5\]B:ÃD)cw9ق(ll|`K/<{6J$$m [lnEY.fe`:R9ySa&(23noW 2y'qN9)q,(gd$[&/p, b!86 f.R{bHԆ,w[U(*y?Cooܜt91S"rMr/xfkB1 Vc /xɧ9.g u,=xyYz_JA-K-9}p·uFVg0]KC}&p'#JiDtHh2-e:hyK 5W`*7^to~W^c>ÖO8ƍ,yr<7xw8bL&bHE*$T$Nl& IcU}ulnmyB<Ԗãcn!(9erv:i0}U>zYo"c/cx)NTU{rg~> Go0Xqwo$|] [Ж-/X=*( ,+ڒ:(;:(D2$+zx[ޖDk &N)\VU Eđt&!ן7ī arƧgeICL()'Lg$n\kB(QJMkrIؘ, К,/!eՉG$ezJ&uf4Ox+RCG#>.%d&'/(8oyERB($Q(lBIX7q.eIn,!(Xԋ`"k6̐BhRk<DhBcb# K^ {"D`8T=幝 W>^rBVit1)Ֆy"WVN#=N IDAT%Ao>G'3T̃)y^P>}p[a>]Bҭ 6Uo(%zZ&ݪ T%DAR$sJ /7&._a>;Up|.ȋ2o=O /eYɭ<lsgLhh~ѣ׌L\&Fz=LZQdRiNk阷~?{ K-M6Nh *1P!ΫʰZjY~]7t~`=A4+fnV;]! y]w]aGe!D7u[~.D'40&oњi,ʒt(U5'ǜMڰ1;ȬxsT f5ZxHLɗX[&HwYN~_nɧ>2? I>ȕFWnou 鮝s"/? ڹ}NpޝK~'c[,%;;=~!sRJ :LuH)6ZJR Xi}LO]rS 1ka:_ސ6'+2H#Q$^2HmG)o.mQ)3%D20H8FDR$Rud}97$,! u}vvEY%WnqlkJ+|WBxgQZ@!RԑR(EXi$$gdA*&g=G0j/E&}GUm)jdhN0q:^uRW"`-Ä^1@e3ѩ|FtCD+ gOi6"R6\ N"]@bR|dF5BR##B(K^x _cO=p3 e1]bi9)Σ&cjCzـ=hƀNGh@ zt]iD q#Dy6 E.r \RƋ/ł.>ǧԋiU2DmܽIEN  ti(QPyɔ^x/},<Ѣ(zzEQ`a8=FaBeBj1(e!Y+X20Ztf !{ܼyMѺ/%E'XI֬m Nؼr'غE*E3]\.cObxya]23)Бeʖ3H~@|D OWovMj+_x%\--@#>`Ӟ8QPŒI\h:M$"ybU I_G $kR E ˦K'UcO n`aQ#G5!LS,4F شX5;t =ƤnQt" %6L'dpBkC7aM#-*!ׯmgs6mC z 4RiPᥤg2bgǕkOGI씳iɥ]vvc)aXdKYk}LJXWBHLi" / e;TZ!Txᨣ E93 :ي gɢɜX2W qaQ^W0_e a5bkhGs<<~C[eJ+Do\뒑.kn2Ru`UVwvY$1z1>"]Axu=[<x(䐶o]> !@x4Kw뭓"94Ǫ BPT c:ҤgQ,ZS5e]S9GikAw-St%pqr|QBmԁ.rf} RIQן{.^X/S@42&, ֧^H.gVΎ 3?q٢d(QZc_:w5'XerpƂ߶ɋ8/ڗ. xn+/f=D$9&w#^u]Q%TA:f1'chRb2<f$iF^i^($"$:?'/0L(/ȵ!%#go4&q6Rn]R@h&`Bb,k P"ʨ=^FX˴IChCRo+DH]خM5C8gSvu,'F F&A&xd2*?e>(rv(2(R˖mWUp։rE#ɖusj5]fq_m(RܝYLEuN@%($6$sC rN?~Kt 7%F͍ܛ5IaǗDDfeUW@c00PLi(#iZƗyQf;҃~dHHjhb8@c@wuUe^wFd#d`eʌ^;@ CpoZ '9w/ˆ͚;4A[om|# !򩿘%7P&jm@|?} 6*O~wիWӀNN{>ØGi>}'Q"*Sˣ ;yRdQV:TΑRUqrh5<,nf7B!y(}N F'.ħ LXY-o Jd~ &h?b8 pkѷ :`iF "(NWM9ͦ%CoˀIr~1~`L7;huZk.˧GbQډn__n=/  Ϛ*]C-^-fYC U5:&Ō+rdj4TP9#- kXGbf˳`ZHpplZ-ڛ4u6D4f`bCJi쀁&gYͥеM"ڕm\8o)5u!3^wXiC)o->(k7*8Ō\grA:Pf͹E2=\)]W nAkJ)ش?BbvP|{y(4@`Z t1'< >g?GZL 'y 0*IM?G0vɳ0y|‡g]჻ ~ZL.@g ^(NyO*iE>J 9$"x'U S|?[j#PE.C)`*nbٸXMӽOT/20 (uk\8U^H߱Jo D7j˗?7 av"XN,P:̶5<c4I{(EǎRA1 vAg?Ȋ.K^T@/A^}(:K#+h)0oW*@{&͙ nqfb&3Y&P&GiʑX{+J.=2^vU+d$GJPrmb\iZ"^q6!2JX&f}!u'Jm9@u˜z,usS>*lnG_o//?G4}-lPs89>%#q4PdC ĹfW79dN>䚭I'w*h8?b4 3X ~>?J7!Tk8kXÑa3g}FL7%Y\\Kss pż[asm663NNLc 'CQj޽;mV kyn1#Gm0!![|̨h74}PwY_{/?F(5jqP0=P^yD>bD\Dfn#LyZpi&^!DMaNic>H!~*jA  "C]õ`A @/Ca< Gl~G|o}O~At-0Jaxc4Vc7a1>EgCZҗʻ69>"QӘ:1eD;Ӧ0 #`bRj?#ݿϼ̰ Bq@ DBZqZx5DرX׬ 9)F6xf/-Px9&7op8UZk.*BjVNj@ӿ%pF[ֲIn6iZ̤ _NP:48dM0d=t ?}qOY:Mi닇CcYx>*}<0]"x|H}'lgX~АvGm]]9|p` Pa5Ϳx6M̠9@eOf {/ᰅәu o`&N^4hïk[-60B ƅIj6J>Җc$C{ܒ&'v|cu9ӷV u2+t25Z)q Zq"wЬa3QHcqi ֶ< C*fup9 qmj[Mq*V,x: ~O`R!)I12sS!d;z aI0!87:;T˥ޡ D4=19~E+t#A[ M _ |VPZ(_'| t}~7^>@M\E=7XAq8tY>ׯ_7ߠ?~[P3b9%z5K!RUY LXc ̨x@ 4 >'`BnKT2ѭF"k"EI`)"Us^&);mʡͣcCIad}149'MGrΙhpH5aYx,c ~(fP]ccqq֫x؈&7s҈3 ʱ# ~9LRlUYP,I@*m Ud]TAy M0&`t#ixDY>E.><0<㈔!hQchg?7^w~?a|я/{mlXna/l0c2?y>x|G/^gNnuS09GK~mAUb^ xx@ ia caW⟓~r (Xvd#NlbФIiV:hbp.G0G0vA1Fi>9'o%w2Bd(mYNBݘ>(LtQ'rXo@ IDATu҆+~}QO prA!G\}# %WǍHg|&v/}nّ-XAk 8a0`: 6Aŵ65TZ2ı<Jz{?۟`+lo-ذ*Mk`w[|~?:|| ^}@p@* ( @5~W/_}fÏzJ'b((4Mub8qm|ǟ9?_@[  G'RPg:$?uZD! FfiDK3$N7EQ׊<[pAϚAdA8nFY>䳃jXa"Fa$i8hM7wziEX4F|2JBYD!2U}KyUfdVkQ}xAJO$E` %9j1T; =V3Pj5> o,K! pB LNjl ;;)3-17*1FxKG4xc>.Vh|~>~~bۮZp<7/ۗ U縉O~wD9ON7p7섎t*$!r8v);kR5q$2M}Ƨ68v`"Ccd$ n6 !DPfB̀.ԲÖΛ+aj$: ygJq)ahXÇvamҚ@`ҳ.X%*KUCl!v8sHڵr\Zr3.oZ˖#w\&$QZOq*IggJaހ>LpANS؟ p4pr8U]y֔ˑz}Yc_O 1Jx8i Kڽ_|~FBUzW~;;@)7Z5yXÆqе-G;=B]dd4V/9栋{>;)5[yĩ:6%F_ꢾfߖƭ*mVY)=/isrlonAF@r{^;gb)H99jsЕ `9*gK 슷j"88̶'g5=NM)OdJ{J#5H]];U;\3NŴr^v01Ub՚K)`FkZRiִQQ0L>6cŽh>h\"^z_-B8FɗէlQ#tV8M F`9o \cʨhSD6y &&WHsi~c&vb ine%EYrv-BN)~uL"Xr\Cy1|XXkap<1#Ơ' [ؾGE60FMc`ռ|K> (:Eh*܍nkvŗ4i%KpA\߮5?'3.'GjVH#8?W Ak<PF"8guqTeX+6ahuBFmd*H^BP*g3 3|sJ𘤖BH]Wwg_:U eZ5]mYj H~M@~z=2B4AQ"J;c4>oQ\Ch=>Զ2@X՟q &dLH1bׯ%|UlYrdz& y@ɛg ] ^Г{廴',u+6Γ@X'~K)<{of(tcGV*:3&+Ĭ->3ƃ8 :m=*x90N0> Z [IKBq1M yM;V)z%qC$><^lm`酓 ̃L"N*k-try纜{Nc'wS aF\S~/ 1|Kl/sz_ZZhZrͥRsOۻ'[Glwoq8Y5P58{do.&8/ӋySŠʮbibi|YYMGt]ܴ0czR((B3Z>#d,3J"5 ØhXeYXSgTvJnTC"{MӨ$ATŨk_lD}9wp{TUC4gqq!DȉxapԔ /d>%( "X B͡фq,G3MZS+]INi3h%YKڻAfa^[u))an&{l&>u0QK3L &gښd†_@,{^WzT5Ǵ>Ev2! ت8:VHQ3+uM^w֟-5;}-ah=34f_űj_'pmy~v0yͺu<0*Nf9&a.T,Y_#`Q gy_/-4FOQɰ~v| y4 H$,ttBHܦm"Y|OQ:5m ceڶ-ڮi"*Q0Fj,KM+BAc [N3.W_3"y,YRlZ/_ҢW".Ft6ӱr'x{e B\SqW >@cfXV `ҔuW5"(֚"% KzKcz4/膮:C!.]K}Q1CJ 88Ny3]߳HBӺ@M VY+6cU eA]~fT\#L i^J?SQ*0 VVmbif3IK0MөX)|Oh܇5@t}Dܒfp$y<PԔI&Ms)O!7ew}snZKW<.TJ"3i&k`)ݨ!kF<Zw~g7uALk#wGF9W Mj6]:+$W06Vw&,<65iRis |p6yn'jP,nᦷ/%5 n=:Ͽ-=YpO+;Dyz6ʇtsyEH(MZk]Ը xNٕwaC 9*]\t샵Mϥ ;8uAu16d.3[1Fc\f?8X;t] [kx2 T~~&ݐhbo|EUӂ`xu)Bzeqi|,竃&=0rr\i~16gE,Z0wln%K`Q VM)4$5lK Rhk:11 k=9rG,yZ:4=$XrpMOjR3$εqF7ir,xY4MӔ1*km2G%~ε$e6L4s VZ!~øK%S7tf=8F{Nl\*omUʱd{ׯHS''Tg x)N=o^fp^g?1 G~߼~_t_/n{~ժzf,.^L< PP`rk@Qaq@U|:9Y82Q8rjݴƓi#b)bZBnr0•lM0Dc-]+H.Ω7랙Gs 8B6P95 .3"2]Vya2bFvV9Wi }orĠ DWrl.|m*%%Jzam'AU뗀,Pgs ȟ_ O w>5w\% 5 'w]z ݔFyԽ$>:N'0No e>AN''Rk46PٶwVi@P0" Nަ 3кG7R [oK:&M hRB ޣsZգ:9>۶\l7Ǯ*-9j'j`.'La< xz((h*]gaV3#(·꬀8-/z .Yrt3.B/ŕBЫsA!"b4K4}PQA(d^ *Lf 4jy1 e+GXKgyB;X7fk|GoћL5Qf1Y%@Q;Bï=”\K~x,9_7" TyuB{* [RGɳkNoדt`O!2)?m@`#D0˪|M^&b}/]3CH`.eRa",>:۬hMۢ:LSRa^?O r;hm1 S{v]bW[@uB?b1&/ M4/ֲ'9k0Ap[Tl0NcQ=Ud7Yj{1~%._( Xk1#1Zr` 4bXMy1N(w)h& Foxa6b}ZlfF2=~`trLmZej] u:G&1r )ZkV+(Ww9*Lc=T0wr6[OW%Ĺg53Ze1?3 PZi@g#3U3$Yy\>;#r|(+'jv#@ s9n9]?J/kK늿Kd֋K-R֥Y2!JB:&I/VicXtztaum&R6> .ͲyW4Nmwb{ɍpt]K\\87$-<׻`/xf $@V9rac 2v: kxoARk0umtҶ-pmJ 2]h6|]ahdZ𹠙3G~qIKhafa~\Rz~eq:n.Q !mssc񊚜GЁGIģI|2JQMUpUgyhUK>P:awq8Mhw'tm10{rMdbƢkkq{CaLqrpeڷ <8.LfuY--YK7_4.5~Q҆"EPHt#TynA3}Wzl&ƼcU,`g&͗JZ38>-1FBZ gbܭrWnKI<&7ẋēG7k@tYvGz}+ LGe6z㆗%XL V \4[NAǣI|Q(v*>{,J0⓪W|+ IDAT3P@ VZ9cP猜y\ͤC))8LSa~x0 1;CӐ˕10P c ڮ=6& Xoh.c٬ѯV}SCXV>Lޡm58Ygf-4AMP}ߣi,kB̭#Ņ ":i( xBP/%6O33(' ޥS0"DF|U5*q8:ÄmZu6Nj(maL c-Hhݠm9p=E]bmt; Wk 5r*r,Es2Q\OE1H< EpN2.my~եec2>K t5tQ1"h\;Rг|fPnؔ{o#n+k8Yh\LjR&ׂ96Hܥ5re Q:%]f-)GPD 5\4[(y)8l0#BR$‚ *R*NJx"z.<-t&611q XG9OtB0p8O|jqt8Nptv?Ưa銬 VzfA <)3] m#AfKTISTul6 ֜>j&lL7MgSPNa-6-)ӸQ4 `ؿ@[O2hn=PِDѱj1EyBD 1Sm[$wvrC<8qqc&Xqi0 #~]k ENZMd( 呆>,KQcK!K IԘr!S+[T%`"NVX'(e֞;9M>fI`&ci8X p%*v\k_㵀 hb @k5T4e 0nET iZv.o1 ] M ^سB394 _l2o/u ^{* $IV2'+0Vty:(ZNӇT"`.Xy@}HowipI7x\ʄPί9IPqBx a0Eqpxv>&}Y.1XOeG[&v'7*((c ࡴBrEf#_a<@^ȬA&8xrba^mLti[]c|Zml>h͍p(m8Zf)kYcҦ0E'|QxCk[)= |Ӧ]cw˵ftH+iBVZ98&?i FЌ*YkжfvQܑi!ytOQ2"ydBw^b]೤ï ؽ&(Nfuhq.3]cpeA{{/A[T/Arh's_fЕ0]:{xKsz͏-p(2!}^1+Kbuhn֚uW xZP&cLjh,Ϛ1*,}ou Dm}-:\Xs^% r/ Ӏ-*zw[W ;|!Yp (G]׳Z3sʬ4tQ۪u^w0b[NGM׎.Ǻpv ȅRQd KL-rkM m"6zmmסmuCuԛfN^sxnjv`CFH]k |2tgm5tf ZҮ+)pRB@ %אR`S{(_Rb>LٙwQ㠮4]ơw_7/sX|u+{!H]́{_lןQ^pa}aV7fr`kqm R̵.~4q`vs[tB  +9kq7]05ujlMu!v"tiZWK1%pi.]щ(ar1~5Հ<;\!d~PC=ǃ_5C3S Ӝ.p?mwDv84pG~Z,wpR4`7EK'5Nj**iQml,Xë#u+4'8^L55Tc4ZϭiZ^l{Zt]imxyd&mf0"(2 `upl8! 2|-tL0'0)/ii7PX+(890AiZzQAQdBA8yLn1v"XFZtm1hx5[?<677tFDh{6]b5x+7r$GRK<-V-\Sj `nA3SgWl "fWtžaΌRz_Eրj -֬ӻƆ_F?XŵKXȮT8D~2&BL4NȻ qO|s(%adC@~aQUgst|BA^z#üK Y iLs[B!&Xk#R>s=UG6]5Č;kt {.q:kXS,̎*^\YOڔ 0dFKHpFut 䈹L]KgZ_t0q o?⚞unx7V\:}}^xLh*a4E 0`?mqE۾fxn{x7,T]~غ* W9",V>ǢS#S*Hy Oyb֊[mcaӈ2p4l}m,gƇЬ6X7XWM]̊k6ikq N#6랳gZ_pQklcKQH+IšBOyN"k?ʋN7"ϲvnf4#43*טʟחY?VI#fHLIMGO9BUPFWPbt*FmX3MIT;ws_cst-ml]$K.2ap9,'fiJBׇ".UK%s)=ׂPDVH&4IMb *8C05K;f-.Kc-.ZkRGl4Oqր2)O65 05BI2!iuY3796}T9- Cime=#| ƀ vFDJ S60u FVp&ن  opm1yh@?>1q*׋9 >œ\peK0m $(,!0^Ma'@4mi=3&TrɁV -f9Q8ϱT҅fPK=]Y$f_S]=>|7,r]$w8岣Sdj`2ȑ&IFffj *:Z/'M L5"b\$T|b4[^X{,H~6ow<#g7b2K ns7Qϖ"Zq" `kߓeDoldry!>% i:cVK!zһ?yZ2;PkF>,XhMaFK1ӚicY'7I9NeIZG!QcODO33Wj.~5f(Xp8q80 "Ms E| L.2a% !fQVVMe*!qHfuO i4xz|7 |?RYL8RqUP:v .۞yEB#~Ϣ܌5|&"ӍAn1B=v΍c> Mf_V5N]]סX7X7 vט-V56 77{-Vl6Vkn`Frni Ҙ"[r *9#+v-n,51^[ll{hʹ #3eSv1@4d3RFktEx CRizXr{(ȗ%9ltH."[s)@"]>cbPyKcքy)bO" \nל;=b)'Yx6M]0Yޱ*U`J}!އה(k(g32 B ґԭ2ڿ5Rv"~y\u&ȮK4T)I:OL'l-S`ϗM"Zk xn8x8CLJPp@pE-Ọ Y+}ˆ_\A{_,x *!*:rSP8st:͛78C/4O0BCUQ%SM!Ń)z~.xxv=޼yzgX7Xoۢ_p{s͆G[AwqA PL[k "yoZz3Zi9&l~s)tT4M.Β[)Sۋ # \V8Jn)2 `> :JL\&FuVgNRhd[JvJ"Exd1=ìI*F F"y OS }q$ ze\&K-L/!8ZNۢV#F6[c/,ѥ@0|@nqЮ{v:{&>LWyhkϺtO.i$kW;WKɰԹleRx.MCKMQt$Ȕm(k\p܀35x p@c\| "t^[2#Y+[SSɧ5)0@c80G q%sId pG"NeVN ߽g)2ۢA1inj3k+;qQ2<.cYkw!%xUƆ"EfȆKAepg";*jC{IZ\3%̬^gcO#gD _+@ @>$oY8Þ̌3p="rvWNũʌȈp7;vYdܝ?>A3mDA IDAT8", QęJ E>,tgcˆZK,}7TW&@cOO.lE8f hB"x0D'@z {0nM<\WL}y -Ʈе]Nlol!{ jTe \9YXhu:LŘ;d4'L41 Y˃eaQ_qht>VЊćee3XަyqOVO]ʏgw~'5ՋJDénhl \Bny6Xo#0bcT}ۉ5O[ۏj5-v5Vvh=-%(d/ )X%i1di-n,ӟD] 6M?F0 yK;8҇ZCK!Fk-Z.6 uPр ̺m*4PvG7S2M%X92Jʒc/r.M8cDGkkpMI8o>{vꞏY2$ sh6rnAQJ_s|Of^ ?#3=ƍ ֢-\OLg(O5XMzT#ۦoڛẉG)mu}0t1|)r˯;_E8҈߶Mcn# w4}gB6P&\KwXx`bIvPóG۵>լ<07ɷ)vm4{+8 >5ry8{uLb4Sϔb u<*a*U mۀGU2&bVh:()U)TOw`6uN-X,*އNAr<5|{9,vGc Agޡu7h=v-.}Dw=l>b:O@x !F,Fd<FzBd{-$nEQؓq+g&\חWJNՊ 1@ܨ( Gd*kg|4V$ &(&v0F<<#Q!뎳S8dlbJ"s|uCX[d=w;^dc&_U=}z f˗/Ѷ]b,^cuΆC4Z07R7_,nPf}# &gl^.+c`(6L?ǩA5s;5 .Οe|ㆹL]jB~RJU$&[j<@KnǨ#h)%Q8@J]:Ɍ LܽjϏj =݊9( [he{ 3`C/<~Osq_3| *ɹMh-NNƛGabyvK3 v]נo;G]Pdf@Y8ZZNN@VڄwRJ{c0 q\AseFQ3mg8v gg)ČQefzi$ӎ:D>c,ɢU"=섔0m`(Ȟ~vgϞP"x7FPa jxOTqBtK`2)T=~◿gx vp^N><@6 D H t_5s0Ѱ# 80.9G1w[-'63|pMn4b(׻F! aI+r3}@('߇kbHBpZbtɏs w pl1AQ!D0Ik'aBA-SL pz1MďȠ V@l}h@ மW_kvIӷΥG;2r͚S[x! *S[~ alƬ1#m,lcb .>b}נ*fz6E)xLbo(t00hZ?Y|g0wp(z6?)`<P&P8$>,CG+JGfXUt9 P_'*h{;2`?#O"UJ(-mL/0X.gV kM͛7X(eSݏ9 4~d$:,TO1ާ2F0Zm믾[ by3“F-\ AV!uK^S\;)Z\of #8ѝvh7؈12”6Z* lnvlv0?s6;?MUߓ䘖-p;H9+8ؙAթ4,~,?EujO xT}Nr gE>| [Sֹs_91c2Ap)cʡSsDN`E ຦[zR,Y\V鋲@;ci np;t_xߑ}/S,S!\,Q>'AP)G١kZggX ]|lWSs]ᙟ LY nCو|/j~H1dEqQPipjܰ`Ѷ-=5v+\;(-8zh( Eq3X&{G5U]~b R +|JxoQ5ʍqs}/^1裏ԉ9l$O yIRDėQ;vo(dsUGv/n0eQbV` >mr@O/t↟WiCEQ`B%cn]pEa` Zs)~\@(!؇sFC|Ԣiἇ6t0nlhAkS',#\w3Yj9ƑS[OM) FNc0gFtQGV9;'fN&,NY'PbV0g*yx'f~ [번 (_!m 9}}vqڣǑF}Pw/?5>=JABAj>SJ /I?1m(RxG)q:*jha(` &h /=Yn O.:?G5_jt]i0_,X,QU\|Kb3~Ԟxz6aF2/I6A 챂{R<̞8Ua0+k46)0nQEZYdSU\4s(l ,m$ .6&>fu*SǦs{|Hm;l6tPʳ"Rk#4ʵ5'yEڵx|oƩrWk:ƹU)X+Z*h`P!# n;ğ0MCƈH]x|6J Og;gop77W>fb,M~lDzo2ȟ Mm[X( :qD5>S'?۷ops}-rlh ,BMz|)V6-^=Kzh|B۶ };|ՕG dqt(޼y7o^c6%`*9|+VkpFfJhF&,h 4V̻,w`RXCiJmYuY'uJ0ʲHCBiXgu̢$Sf87ƌ#@HQ/D) 4~BZ^`rICPeNc3YfJE~T&1kjfb_tSQ99k L*L`g$F6<q4 8/5  \۷BvEYDv =Xdž>{nORe.R";)=g6 Nӱ'! I㧂'`E M2ݺK`4\bp.\"~"K9m`8Ԛ1Kmeb=F %3w|kB*(gR ed74ΣLu4"$Ɣd=ZR/@^JIз 8w6zG*5b>6Xo)Z3q n?QƉMFVvLp'У=L?8q0Sz(aXשO}ŲEU]`[o0_ W%LXtIrr M`V!2d(icRy\~Ir(ܑ)kVz4^2F%i;x8g=,(TXDػBf3A%fEWI(Jysl[x+xKZo_?@6p}x#cSM?$|0f4F+Y*{5c:rSz&a(owMpIWINZ!ix/vknw8ӧsemRzJ(Uk_aZ(!7P[RZCtaonP BU,9TʀM ng 6VT>90Mz)?L0q{iȅ\񔔜9K)r0KI1fGS(1zQpXnpyy٢FY΢jeӬK)+:)[v^c:?Xܜ@m:;:Iжslqv.ճںBєv( Cw$o($9Мgxv0tS+XiF㻨i:l H,bYkAN+⠓u=)sAwt0(^I$V$T+1 cJT!7Ig4zeaI8:0{:=ΣK~@ڔbX+ߜ]MyX Y~iLTHv 5\^kw5c؆~uBf-}6h,J1/u0[E1M{F]c^}:t]''at STк }oaw;'^j B03|f M"kRQa,Aq 1Lso+m{Z@V莏db=Dj9h{57py`.n,zd 6[<{8]FFڣKa,NP ^#Gc~@8") OfL5! W IDATH蒔;5>hQ0G(L#Ԧv{]ymE3dñ.1YP"8 F'kЭ&hqY ܼ4=Ke .UWYkcE^jҨW6*3AUt_ee%ɜ61. ggg:Ztó V 2Zy NXeS(/?σhzvvQQaV>6bR Z4tx}6؎rd8J[KtbCpo/Ng@X%ʲ&|vNc0J) @>h,$vM#XGlL04 s5*Y7YLS(ϟ!sRo0)C18?w2J$>h#abЈ-}DC9l n7ux,S%{,ODr'1@ϴ0JT`Vuʯh8{Xp.<3;iؾf-\ %3ez6fGC=_b1_/PTePUU&z/׮ueQQ]eSfHV̂2Hאe48&0g }L|Wa#P2ʜh8\3: cT RfTvb0Y>(((@x+y u]jtooտUAx;|_\RDiċ>%nC2<;(RXx,u|xL>16 $26?p0&Ȋ{ׇTE]{ÈHr f5ZkQB8gu=*[Ӱ>Ym$Dz1>(tMbaoѴ-Vjbs> (#`Dz82WÆ90 mSX&نƁ)a,2۫H'X7_Za<\6.R^ ((=N':l{&O){?bF䉡`d\d#+M-p\&o{@ !9#(E.dJa̲䡕ZiVB;#8%(ל~G:R{AvnӧDo-n1Ğִ@궨N@jNG3zn9ty7lO3'E|w. ^wd3_o4a3]dd%2E oFL E>" \Tg k5F@7r4m2\Q5֩m[t]^sx}]`5: ZVKa؜Z+Fv}n$ObZ',)`Ӛ1_sð1/jtR"Px42ˊBC3 t_o=D{t8}Ӡ=@@UV`"aX<ƯCFV-ۿqn9Cm:¼O@I"W? =K KCɩέA!xp l{ / EY]g| y A&m1!nBҀ6U5BX\);+pPv&n@G-NA nojbe5)\z>[f)/ BᡌX<_ kxh/5v'XRԣ{E٢i\}Kn~zh!L¨d4{B ]&!>EU3 a?` #o }Nxtࣤy̡!/: gx)@ SH$7'`8f((LW2Rc)|rs?,8?E}Ѷ=޽{o^`f]I {J.<$ckJ dȆ2CZcCk~8WJ("~s|uzN&TU !gsTXScˣضiP4&H;3?Č3Zo!WeY1L *8"<1(SDYJaN0rt$cZ᪮0F ^t4I+[8gOx';R2 V;dct+aɐ]!+zfk̾k۱BoW Mz,Rk"LY'N;bi4NͣTlr_-x`w-B59v}9tQHr/R˜u= (&]҆Q}!jdur(2*C+*ZB޸(GsG ޅ`B,Z4X_]h*9&טN&@@d[`Z:7+T3V >-BT3Vba1[TU SzMs6n@j򗇮k>8p9gubZA E}?zH:WX#p 4J#F820Cm`zFko@#n{ 9z{9j> d.Pӹ EثY&dD&I_lC˲1#uϲU>HtDMTj|$)`yz\w2Ϭ.8䇷lmMӦ5 3O.ϛSQ;=ȅ/{HCCF֗i*m 5 ) hmAYTy0RaJ 7 d쥝Be8GȦM9(cܫ;p34cVpf-7%m}۝6|TL >-Wqć,aĐdάQrI!v~~$+0 :"'භg (7flg?ڢ}Ɍ󂄍s0%ށokEj2s)1$ky :-F!ÉAC5F_?33xl^D]3H0ԃ4I~ԩO c5N4pK8.8I>޻Qu#CC.8 K6}'?CJk(;" a @A3yXαXCoLSRٺqPґOD=fst4^¡`mCgf&e0œDE0G1 (jh܍|ל##RxCvl),y}* `RfnjLƨSʅQpޤ*AkҚ2EQBiI$u[ F,8@`Ǿiwh>[Fӧq<MaQRtE@`kW9pZ4ؚ-7 1޷q_.mO=t<{-9`,Hꠝ1GMS:fNsjz]A)+t-`QVUrm ";G߷&-n7S":KU2f4pqӆmi|)  x`Ok-W Yyz1D0N %btșa HJl)+f m'a=je@i KC@R!40yzK$0#ʢ}dD'VT%},M@%(RPֵ> <"8oYҔžqN0hJaƏj\*x'duPaJ|} fZYx4qb㉼s|  b!K1zA<Җr`kdYϟ>#VKֳ&φ&p/N~ۣI>a#mq7ѝ]X9KgE :ؾ{b?z )t4]aƝ1*<%7z/ox zqLD=HȅϜyU!Ϊnr|׸YtMo]oX,P**aߴ6E '0T <9(@Ye6܋^)GܶF0q 5Xb3G\(m2V[*s(Mq]AsXaJ`t}| KZ$( )el6pt]*A8[ᣏ> ^i4+/Ӭv Cج9nDFF1NOJʾ}K)>ټ:cmBڶ|*60ekGY(JSZ\Ubh5bfa!B+?rQȣb>d6u1 wEu>9hQ K®?oG?ӧr=^BOΞc_a_^D@UYu-f҇>4Q? }d>8H: @ CXI*rZ]'uc 0kqۉ'O.pvvv[e@*g|RuL+ EX8j-m,;RƳ `!yoڔ'&Z6`q sG_5:+F>;vkV^b^J+zC PdP3[o5>}O05 1 zqPCGT:OMAך(}ưz f:wg{e!7^@P$ܩS#gWc>!VE voMmMA|I- Ep#osض ~Aa@qKUU YJw7W`L3${] ' %)BEe9柧@lp3.A<^Oo9=zSwh_x?C*w/6S7o7Ox1U)b{&3I1Fr06M>Rv;oa꧛2|Lg6<.VfaCqԐƯ=,;onam( D!\]д{g%.˜;=Kf)[)qyy޽}ހCQx~!ʲ uQBlI~>bBMWY#Wk۶M5(|r]4h;hv@߀p IDAT){%r0eYaVP/WX,X,Xc>C/_Axj3fU%u(솆Y0>p<>*>xi|)1Ϡw9;6MQ4[!”LQ:oE y7p&: ׷)")G31lO qÃڕ51W {>x'=-KoU Z>;d3a,LhuFIedz}ȚOT,}txo{5y?џ᧟euk((}O?D 7(9dQ/@èФdC@vEQy `?HmK.Nɤ1xQ7ۆ1(&'OxMm$@aۨ[c/`ଡ଼FJaM~jOS'vۂr H=dz[Qh @O8TMʆk qvE da`7U,ʲbH'k[TPມ {VqCaG4"t zS]ONGk\\~#8`pX_~U l|hM,wKiQfOLRh2Yߴ<~&Q=egkL Mc[kœ ( \^^R} ;r*dr }@SP Z!B,n[t/]H1}UCeUK39QΕW| ݤGam*yꢆpm"|>G]u}&FXTubtC(|oblVa6aJNDgZŐkLHX.Ϡ JG.&Vd{(`PbהG{:34tpKr}o.h>zAEG>3V窺wXfuxm`?)WNOSR{L1<,[#a'Y4 æ$}!FӍ 1ŘaԅLp'0Z)J# R7qqv./U ȅ7Fug]RM7rPT£j~IOBM~>5M[ v-t 7O?%[nly~Q?0CYx}R2W=ޅei.1g,+Qw99zݳ @cZu"gzYKj)NKva,~]~M;(UUG8;[|N.C}t C}6zZw0D]5h[wW777ouEv;777CYp-DdE#>N)wM@p|`u$3PP%>c, m} a4.'OO?MkM?b@V=G$w2-copޢ,FYa3d#rnN6Z( i85r#3$Jŏ#y$ǜ~}m>s`b?{# 7`;q>}w=Cl2N ŧvWX]Ӻ3ɩ٧4t7hN9m1T~ٙ^Cqߏi BvˠIX!vq+7' ,x%=(~ xk#sv;(Qj %.DJR4.B:Ƣ$RY4Ky|Asc:3*ꨊAΖWo~Pfw/ͯi˷hgsha`iWS5fe9J;Q c)z v!S%];+^Ӭ9yae!UڈSy{m[ɻhY@]#P7})I;٬|^ 8k:.V4Ch~7`HThSRK>20FK#!3*j- tÛ}s #ac6t 9g(婝b}Hc bOaX`>,7]Ù۔T+C#" \# ֑ӎ .^q}z1_BDװ HQ+;ɱ cnSJ9uBp@\h/vZus罇 m'ϱ![uA~oxD Y dBXG`w*n44H6pTd)h8Ɣ=4Ho]f<5C=-S]ߩh৬ߤ=gQ\dg62ؙN<F ׷`f6kv[e|xe3~TBĕ6'FHlbcڷGHM OwFJ1<>j{I6&XkX 6H!CO؞ҍe%s:ߏ{]`_)׻ֶ 6p1[n22Hd;u?WY YQYyVZ}"1JtgL=wݖŻ58u㷩G vܟ1֋e'КKA\d&4B\.X. -Y29(B&W!a۱$[O[G #HR '[259U<}|R-gXpq#DiQ,VsqVaWs~<ުzԇX;Ms.|ӛOʇT%o_]\| MfS:ަ.P T'KVXA2m9sd1ZuA`}̨r%ݗ^-wۈ"*4lF{ȝ25!^vvߤ[#6GrQb[Н'JrpGKj*d#CniKV8Uac{?{ Y8椅s~s EGGQY2MKq!*ʲ",%odnS>8V%9NtZV<k-YNҴcS#6AA#xc.PȂ$(--Əhc&0nP}}O͗KڅD"|,%2U͙ưV,UޣȨ ķK"$x'"#ẗ_4m< EbɾgSߕ#Mk*RS_+=#] m9E<,R(5 v>0\a0_"bp78V1SdD`~8QML@Yʣ=h/A8Y:<9Ĩd<~eq- -!OcZe 8G!VOiϿ!+T;c LpAV,1`CLǶm%ՊC AƵwEUةhAƴ<+w7&2&$EY2(Fcr2d:CM1A@Qdy7"Bh2'$^߀!6{A+͋Ӌ3gl8aYhVul1nߛq0eY,W %U\[Cj?(S\HsJ~]"N7bMX7nXbsgGw%΂I;(z dH(\CQ;6{^ glAi#hrw)uݧWiv37C1HuKd`D~gjjqܮvKڀAA{WV+m7٥\S:+ Ɍi1TiMk\ʱG჻<24ꜻ|/w$R"'u]CL#S7+QZ.yԠB=q4ȾEA,+i, bt!bQ=6OoB喀?4IzuԮ p)0q=9FaPM]w'/!l3W vi"֎my}b>WKߐIHg:غ]`w$N@h>37_e59MY{t]y޽8s_P7&mn,s Βg!}Uf< 9E &)X8Xv1?O~GycĴ=:?o6֩= 2,Reyt6`oɤ$4*SLSGɡǼihڟ#{uW|}|NլfuCQZR75eES4R7 ''Nְk=`2p5Q$|+PI7(G0ubŒӋ V.LSG/xݨ\]%AyN&8f̯L)$Y tNYyM7:3|*%1wیd^ynaM,ɕFçߦ"Tnzy9.Ǐ1ҺZRU f{SR(Pe(^tCpI+ctB׊Gp"k}_ M 4e럏cZBw:8&{'O⬡^M,MF39OjY,[m, 2>v*ꚦiK;5f%RR\3rUl"<v"K O|(YA[.Wn6ۛ] &pZBƸ%76Wo9L %d2瑍c9U"/rLc:>G!@y Z|}=^y$w88yt.6-. ml?(,5x}ۤrh%98(y|=mfdO͎2д+ZkaX.,|E$G*fhd:e2p>RfӝsJ'C5m|Ͽ3,Itd*ڋd.âZ"`2@8kj] sZqvb24mX>P੗59}ʲA,7Y %2o]xmg,pNBp殷S(`Fn,)f8>wnL -MF$pw' kѭ B"V/^*T}.6WB{o'| 1('2<||'GY%ш54ԫk[1{{cbiW4M5TKliU3o mcp֠f;www"&E@3̈4z4Wg/Nx:XOcV}lL+3Ҷs[lV,fɢ^"u9ES L2P0Ŷ.38.2}8<(M:5gXjUWs|rB]U WHtMk$wTr}z),wrxUU?)NݦXڴcq?`1uc{բoߖٻ3Ċ0Uƫj˗1.Vgm{⪴z)h!SYB\@@Bfo;ah4ڶUwd/Z{d9csŘU'kD9;=c6R1KONǂ~|)G~uqk'{7ХLO6o~udžfxh慛m0'wR ,UѕYںxCmW7⡋,L+sU`=}5EmL؀ӴbC  rԞoX.*ɘ B ZtAuHQ݈9g`>#ȢVт>#|KAŊ/O, ֦%S+"^s#SY\GfZ.M1/_`ot2a:3D7kyAHՇHǧrZLql0)\gHjUaRuuz$\2gΦc<( xm mUqn%Gp(y'B]x_ή ;Qx=~'W1w] Gyspi+SS:bq%5Q2%b[Y7&[]y h3#[3E}uVSMc (ȋ_ 531bI5tNY8XZ@1Ϣ[V%+FH]R&5M9=ӬMwA=q%Dnt[+t)bՙP):-C}Xgau.0{Lz]`coD o 0<'SY6)z8^v26uY5YQy zݠl85Ї[T&X Y@ ^|%tZq L⃧u-V91$7&uxQYzDz-S.loY8k1")oT.-֮M !D]'ʆ2cA2xO>瓏ٳW支P#Ɠ$/U$ke:-xh!!tb}tv!RdF" #8>[ ' d^jZcZI,ViMjmVH#AX%2EVd<~z (>D?qo^[1C_ k'<TCS Ad2OD#YՋIܶm$ڋ[Ƅyds R9"Ð6x"vjDI ^tRnYo?/AUfl4q!dޔO19:nU}q,˫9ɘƽßl,szTĶ=n]!wBY$dYeq,6nD$>hH+^Th _| /_ g>^V f,I >Yҥ'䄴 >״+˳'|srR0(GEIqck]islӦsӚmh[n/ZD@kT!3Б%ohi]tx݆@4E6J@\dۺ-m¬Q 6j.F\(SQhP(V4 Y-Ogl2L0Ƽ9}9kq>>ǧߔnZxo 6BuK6Ϋumd|N+Ε v^- u]1{RdYѧuCŠOc1w v .2*t ]8F4|_=cֶ51Nkc1B( :V !DdDyͲrd{dlSFa!(!hZx΢#UJWu?qnAWi pju>7)&8;1ժIxR6i9EJz\(ƵݜK.16Q:ݙz&32tMI1>{)E6jHO8.,5+z~[gKr AYZGcE1-0ְX/PKoNwfF,Y;[kLݠ[C Șܓg?cdb<>փkZ5єyŪMeQ*R ̇{C0.>~cdp."%jB5Y JJV,#-Y~T<2L(G%Y7ݻl4f2 M g]̲dѤ&-zgu֕c 6㽫nGrYsJ{77_@t+i"f-iZԂŲMPUU1T.{Ośzg:Nɔn+Z:lǤu%GWtZƝ"t629(!A0?{1-!Y8jƕ4&19 Bh!EH!R!+B'hiqvR!~D4Zd&23 c^<*NN8=;iV|scfG1Ӈn2\iC WMv8D_C =MS鈐"&%iUi4#=R*:Cd${V5~9raC!Hޘfzw2E1w`^]pz~b1t̃Gox뭷߿"##@F̰'\ HV]p\,}wxj{$Z 8g(ScxxĢ,jѲ\-X4݀K vBM7f[٢_)Rkf1e)PeDwuyIWo vtmu^J絝wuW@|]51̯A~ul A6}NT r|Pń(:|NfY 2ns 6M::}@푩ֺgzb_%U@bq%`!hy UUԈ8.BDP/>jt&L8:ϣpt1OP:f%xd\̲x1Mi36u9&^꫌$ s~w_|ْ_pzv̨}UGu*E&`P뾗;Ω0dݮwBIVx%sBY0eVuFM8=6q:[cppw,Ccb61l֧fc桐!Fd G{ 6z!P  6FtݞNP@fQ*"h\r_| ԩuG#rD1*)˜PJp.o=~>7 ]Q-vR n>ڮU^:Jo ƆV5@t\=_또T -tkQwHD`eqq$7)3wZ;RQkIB !vuquw!'?,^kN&XYª6dR2M3\tZcO'λcf{w),DeJ%"HI:$IS %(* ,\Rs(2E)4/>^ZZyyuq"'gj'UNj~42< ,pr/m!(]XP-W,K& ZoG}"m[W_''OSɘQVDsMHJg~vy󆶎zxi9x4Os]}+^|o{?{j^cksPr>'|Ϲw,Cg`+ 1$…N'7k/;?ZKcwY2D'IJd(c!G¡@coĝ;wcAjx41'ǹNΧs198m4ֳ\YϗH1N(LbZ>pxlddz:T|/ CPu2.p &j8-6Y]x^[r_Pd_ee4n !eo ?6!nڦf21M.!(?8O'X]o Y!;p6 f;`\7吽{ ׌RWj:Mnbj~nI_6Uf߷Ⱦg|?svzJg]u@~e qF';M0ڶi vTE8lރN/x(g)ceU)> pq~CSsd4=]AS9}-Ǚvp$}>? uBrEI bkE%%J,98?!DƌE܇b1W/WFSVIN۴T*B!Ma6\:LƨdV [N`ϞQU+Drp<{_e.ճgdJpttX^g_W^:#~OpP=W~Shu+* 娙e;h=``! L;b~"IuwX8Onл7ؑ9i\+7)NDxPa£ӸS8"7M8::,Il!|I:"nT܀D-in@khSpvvă4_}wB)MjXԪcl̃G<*%dR 4p!ig!|W/:?OX-,]?$ zOc,'c/Vx1!r}=}eBEQHGmFudcm6<_Txݖ9Bnhk1mkhF^FVr*2_(mp!2YTEQ~tֹQY +B=k]Z,/9홣t7YlvEa:a>,/ӘJȋNqch mS!䙈Vsl?b]f+m>`h$,9=!4^1oLK?`Q1FJEky4{)>C@K1DM\ :+}lݐѼ|L2n.3>Wp<{!g]S9]a ߟ]sѥ¿W,|OiX G,V+H(GSΝ|m?;3^NTN` (Cc2JP r@N Dͺ8HcXq>G5m[VFPw}~w׸:)2o8Ce6Z6pnIw\ qh +꺢yeY$WD=1XĞ,PF JFٙfnϴ9шxDI/_q1_E2Đuk~9{BR%UjB HKH4R "eeѥ?q kU%s8}=G?_ :u7>;k#tUL,}!ׁo{cu]mw|ظ!~W@zsq-g՝#ؘ45(&F#bx5ؕb ʓCչX)zw1)\.Rdbw5=Y KcF0͸4%G&xkǿ/?/;Oc]85M̤]lXcDBA Y!Ait1 ¼u,G3L&{;u_spp??_\~ϣwx⦅e``(HL7ǯ8??g\΍4m/K>S?e4.*FO&嫩U/>}[jeo=Ïxc#MXv1Y>E%dsXs<'p||bl<|CF JӽY)J3 Ɖ4%֯!p!V, $B(4껍Xxnjݶ~{Nkڇ^*\~YERb%6.lN6k҈{lJNZ"d16p()w,bAcGI8clkG V,SyF^d,C+ `:3 Uzy%Ӑ2#A}qC xs:}]`$KQ"RʈMt BXɴySy  E ާ\sF4p׍J_|ChF^^5I~ |{i7Bxl[Ӷ5U`f{e5Y( ԏjL&g&qR~C5V%w7Ni=~)- HJQbO4 hD ZۂG(\Ɲ]ju/O/8{Lh?6lLhoVu=cM,Dѳp'R7>k%2Z!ƥqx 8`v]cVՒ0bw@Y_tUU9S?ѣGIטᒃ+yd6^|'zH<2 m9w%Oy>?#eyߌe}*jhb?[t7T1u~w{}̽ANƐ 29~y&c^oAvBQ?#E(*($+Β[P ú@ KKApST6A%"Q/ ?{o$Iv=g%"rhb!Hh6LW3Cԥ̆ΈH,$@4FWw-F/gw#2r*RR%P]~=.k;#+6綄_Ǭa`Hi0^'qzԔg$,*0C& $?@r\Mt-mCG Y]JϪ32ҵ߶o;syٲ^Ai-jI E*”j^R%QPYi@8<8!D8=ɓSXC5+] *bb9-EiHtZ Iя#*&| !Wxgf3\ayWmPAF/ל$rMk.(xݴn#cx({.#m}G8vʺjzFӬ1Q;RkZIY &(< j$&DN"E'6ʆtB+H\Og?)??дk~?ERB'DK;{b>:FL-x;3/$`y<肌l NQS>ڌywk9/Eɕ^/S>xcdߒ,ʔhUJgM ^2NzT \--U!AFy)oGxjO|vV]6<5X0:10 Sک&mwReu@筮 +]u/H@ iNaH90aUiPi|a EjבӮu] }GHSΫX';Q)tr%XY ƊcޕB5 ]0SbG@6hh̖Gha[1]<%Ft4rlFU5oy[[Z]j׏bAc&z z]FgEsssS:}M&Ê{[E}YG}"kf׵mj 5F[ #"u%,oc1WW|C)){.wVGS;H)Qbn11#UYRrް9S4/ȨnzNo_bNpieJUt /.{ubO f ֤ FG=kbZ8YHnłVZ3 1"JtˋK~?ɏ~fEv *L{6b(qHNٽsQ6fcEJsy';v=η(4$C"݆ԉu2Px>-}ԯ~S=_1*h$WCYԘy[KnDp ӟ[7HlOn@߷ݨR滛CӬgaw-m]v)K*FB>% cR58U"ZR'tI:R5]S2;sGQ9UXka -U}=Y42:Ni-_q7HFih$Ҍ\{'Ե^!.e9W=gνl#JyVnfս};LŦgF./k7 pQ:,//xb,9:G,8+((rKC_ѝZB u6RMߓu=!Fw뜦1>9s^N {li"*ݳVJ%⋧<{ǧ}kV̕%foʑĘ!t(UKK=yL=a8~j'"&PM,Yp Xf{r#҄sX`t w,[BXš)*pj+ $ XBK4;ESpP\r֘0%jb.cNO>>5r }1Hy)%:޶un #0qC®qi>(o{nT6u: IҰq콧]K2tB!V0w&0*Tm UDӣb'gjQAǀMA1M&<ِqϨ \aD7?_e}O73.o/V;+Ϩ)zVghRH(B=hjF:ReҎ0qRʁ{]w[su`,_(0rդ(LKBR->F(lIxs>d'k>1Ѷ&A8rAPsGkq"}sV%wez_÷NL*7'<1l17-4Lb"2#ThLpLA =)z5h=8KB!$MAJFQ+Y%㷼@ "dJ7m teuǟ?!gO~Aۜ!C|{J~|/!ȎEj̋-~![MzJi_Vd!(zоued3[v&<+B3SjPP^6XMz'Xu%ܹR=%?pt F߳&k[LgMff7{NSFs5=5KK^ƾMa"a\N6m1;LS\DޕSFkw9C b tv|ᚱ!IĘѸٲlQR 3Q-;%WT \@ǭMk͛i%-T*H25e=8%^}AmC֡}"Q* ^9&)C{=yFUT9GԳ~ٚoG$ŕT6uzܤ7*{ٯL8nSybDnӬ۞k3MN445 fՒ0ڑy[* lfݼ4M c rE]E!A^[\ތDn]37)h1iJ,sR\.Y7kL]rbj}dlX(gZ^SQn֊F/OѠaZ_2$`Mp'r- w-ĝ1ЩGk:kmJ& )YAwqFzqꂫS&sjF'N ' IDATl6sUuK#1 ]ۓ""WJ MFzֆ"=zt>Z˸kh\ɏɍT[KRzVQCsq_% J"=Mr5], N IHz֚X.;o/7TRլ f qcz&gϞ|=N=[[1Qzb'B?G&:Is)KHŽȘy5 U!&YrVyxߑ8΂uC*Btk4xDQbRO$x)~~[\9),'|s~XM4%4ZRTρ  U]QNK5mَ>UmK_QaвQfNߺ۔n;įI V[ECNQ↔D )E+ߣbf1:isMh0DtDDP!wDZNKm IYg)u*(8IVX4@jJ[R5] j 9WJʢ@zuɃ#޷hp1ĭn> 6osvQqm PIW3Gf|#; 6VuDz dASV'ƱC.]6m{+1,#o mF fh5/gC9kk>}ѽ/'srslAV0n|.K<}GcfKki9t6< ?֔(o_` ,0:%4=4h`d R1ajm bM -DZy-)EyHUb~Ao# B`<+Mnz d @"e˗QMeAM6P7&7cʱ Kny{;Z../(,N(jkE۶m '}ROSFmێUUa;}?8+ODX<  !@_zPʰ\:E]n:\)դ\inR 2ݯ}.9%bZ 6; EߝX=S' .4X.IuRE*'as&'/DabBŀN BnVP4!70S&HڠD)mpfҖ7t>W#rx0gxs>oX,|7kFwX5n y0ӻ&xh/{ln]fzS 'nRE]@n7Sg7Y f씺60lCaplb5۔kpl,{/Jm'lԘ9ƭMcpj!#˫+y96+.ZRSU;4,o}8nM\k3fcX i<\_o}o|Rq 7=s징slq`e¡5A,jO:hV (@rjBBLQ dJg_L9KN'G/Q,0*58vA+11L%7MצJ},|~hnd|F%6^c /筌-or;75nh~0?2Y M@ UgQvD4*ʃ%Z1{SgS: 1BFo f:+QDY+!I2(1ݨ`5nŇ pތjHrzvpt|1D]q~hvD$ȽyGo ݷ(}n4}^ k؆f6}mcb~k;EbRKEƱIVӷ-rpEQ!pj2Z'sYV3 ̍1舐b.U.z޵:Qe4x/V,۞'e:}rD)zFk`lVu}u#x +chڎz6PJsuyɏ~w׿/ߴe s]=:QX94@Wk<˛fv#ovpw[sK|ÔRۮY2C&9bJu1ppD!8vLZ-Zː+Yo, V> w-E+ ge0 gmʰ6o?gQFqԵECzjXCtEQCiUV(cZY7 ''\]]Ͽ'O>m!놛! [N.w5R3l% _oȰ~3-Aj$X g'sJr;dsBDɃ}c Ew*%(e>UhY(olB?D;NMz9'0iwu{LX0H-p ⬕+Δ8Ue!etĨȩa1J#:G)sq8f%gV3 \Y!iM@KIx:QRh;)N\ozȃ'c68:8%c_Q mK8:˼ݹ[C:}궬}7ou^_uP^=nc} y E>L7?N}W{{LG1]Fo߱{ܰ1J,"_-VKcBt]Ta7*}OӶ(t[(P6Bgw7L_0͇ۤ$3̨,Y,9==i:fӳ ~-y,Xsq\>/#Ӯ𽈁=}_~o~[LꊓO>_'@f y bqv pֳ[6`y*ǐ N wQ5gÔCmByAΣFki8bĿ~Ѥmw%:pvKCZL,:;H/SIק۸jsLoLy5I5ըC ׹s쬽:ӤUFSN$fm:TcIxڄs:_A8I4)zR *آX̘-7g3]1!DY]+#zH9m1*7Lj$*sʪÃ/>LLjV>7KjqJ{9 ⽱}F`_tDy?ޗml2jn?%ԶD]OHm>}cNd 1j쌃=!\.S5eYnNR8躖kI{%`onQ8WhmTn$9;1@Mnۀ\|5вBz,//y o=|>RJqyqÇoS~?nŒ 6yH|e<MWWțUJImOI8?;0ϩzdkdK|HǤeYҶCYVm[F9-#Պ(u)y^kVcrt,:d$Ft$zVKR+Rs,+Њ!;Uk=E-!Fc,Z>o`0+?iH!?ѬV4͊![oE$m&И{1voz#Rq3ưX1FoV׀q\9*?u$Z)Tn{a DGF+R%#"vmPl2բc^FplRZz).A)#[,q_1`\9~ϴFJk= yscM P{7;^j7w)bM7M0CH,W ggW_kʙEzsy$2+) J^<.,l ,ռZ̙1;3%=qw6 "9t_ZKjxQ`ox#1`X,f39Z둍j&1#UYQhڞ3kg zt0vZ E91#Mit^"jfJ*o$jLiAq~[? ԽeSzY5-眞^Z 17uג Pb*R=)$ZRʣuhg RLTG]Ub[hkJ1k+blH@tԡb{^n=''G|}ϣ>ju8١U}u6;/Qg|&֧{S5M/z=C{Z׵8)#: mcć̀a{44t΁mGnFgݼpcfKܠׯ:P4-k[ڙa j%ggV݇3Ӥ(!xJ:k($B9,~UhJ'!;+e"PWe+9Q"Ą1o#)/q9&1S3HCE)(:!)ݫe?lNUf3x >xįaGv7 h(vl}9^u[Jsһ7D{à}oqY{O׋{ 5Mz9'w:N=}߳Ze1S#MR2b5ƌ- 0]>aq yGNm6,2‧3k|Y.-5hJKQ1 f198XPef9GUEIUiIY_E;-V T#Ea,*HRL}pvKV ͨf1B?#)q 5󲐬emҸ5P`3^ϊO&F9$PgCH~v`vkʨWcr 0ƌn\7Ą0ah(HP173,ۏ+en^<{ٓKU*Ҥԥfq0:֊<)ټfMhQI(kiue" L頙,RM6#\fFdgl1pFk./8?}JPo.|G^IRlFÎ_ { _iir_ ܫt+^'1rna%ww¨> s',Vr|߳Z8=;f9}/S裪(^8&r0Q+gYi[aJܬ!xؒ%d~$VDpLc%}>+P9ZCYX>5ei *ҎQZ!@jo|/f'|{ېt!N+ǴCc\n@)%B{ QAMvhgR]]הuC?& X6kg]\bDUU%uUQU2j<:Ci[bLJ~@quۈ˸Ro)׌pk5TL #-];f<672gzоM/s#zBm[.//Sʺf6=ȺYP0^ziɍ CV˱K\1 1JRcWa'MX6j^n:&By*b18=%6!lbn7*-oyRm'irb$8?_BplIڅXhzlfsS &4ZI,C_-m9uZ*-ݢ,X\./hږՊi%|xдݨݼ|ߋC{|kRU1Z|0DYs',cteХ-1aі3.2S+! VfJa&NRgM"8P5IST1I[&k\/cڻ-Hl0J%5NQ8Ķ Q61DLsg( bd=:sO_qs~t!Ѵgg<~IAYXgi<)EߓEP lF>ӆ h3ebSJxC<)$HsdJ[ًJ2=ћ)7y|[&WU?8W|q [T(o>}^ڥ4^F*?z}j^.ߺygF`wfy+Gw8r︺<9QUs 4m32>We xUUm;ne)ݪMж-1 ct :17 nKMcp#c(⌑X托wtQ 4|aWK~Tez,+L)_>~_|1UQ7-w*2rslvjQ$F#B[# $Vah ;OHfCElК!o-]o. ͂6iX6`")rVxH6aúY,,J+}z%_<Ӌ3Љlo 'D>h ZTɢ<`FoZK7eYGmjƚ̠ 曔 /Hg+JerR4FifGܯѬלw?C~YUG'?pZЙ V 'ɮ+3M<wwoX/j:ھ|_tkٺg}7I]ntpmm#X4ܤHWus;DT5 }quuEYAE7YGjUk5t iZuNH!Glbi! |XR#0VQ*' DC}cFHU:?9YGo@RXAe Ǐ{#Fa+w(TQB WWYvEH9N)H^庫TEqC} EI>qXcIuT4VJ"b(48"F{P o;b_I2R"n!N:ĨX-x莘>O}."yx]m*_kjbB ( ckn+N11Iw.^b;IP{n_ XE;Dlt%nRsc_j۶(2shvZq$a48zǘmMh.k6Zmk!w9NB'lE}Ή^nIČc%}3?cPd{䔪x睇TpG)ju~|駤3OH_֭ |)j xtvٳ ;>@خY͢{ 5ej[skom+Ys^zG7ξT%/t=< )3~ ^T5$qmjղ\XE9\awX™UUY-;.9`qbqၰ|NYN6։AE=in{S7n&0}ݧ\$J3aT`8 sibl R3}$FE))JG]Ybh\7bF lNq0/JU%\"nZ-cfB Hӯi\q6 W82-EpE+đ]hCl âs1oD6d7B(reąg kշ*^7kvћz}n@613$u񕃸޷}.Ovz[x})Q0|)Žӊ~o8,T)]w*5܅:u{.&@jut7#O61JgݬXr| $nP^nlshJJ\W 0P*G9EԠ[49ƑMj̛!9_kMRZA,{9i-%?ь*0JCO⊵%_#ejTld6?Yp]~[k 87&wm+R׺T?UyK*d8],.yRU*g,YiK#[kM}zn$3a9 p$qrU(8gOk$f}uS礡md<OHdM]yF E{mG,-y+)ʮO <]p"C|_ '|dn@'R{љ!$w|C12MR{;>Hc>]00"\Qgr ǏLMO1sd &&&ȝIȑu'G;>  F2yMV^<\7rDCp#.%<o{=eS5usw+El`OX`0{f=By${dV@0kJ U%wNvbˌ\2\ ӷ DkL)2E('.]0q!`ruC&\ z~4YbTnd0 xgpYNz'Ycff;Cd.ti. O(stˈxGOg"y%w% g +I|;]~icHa ߰#İЭͻu+Nty;'޾IAGD V=,P7sM1a-FGȥ>~f \yiw:9<2m6%D3[:\G1LJ%(DBX'S! Yu7( X1ml`LBV$ƦN|UZ&&lN$\!OI2"QӪ7? hQEh-iB$+2aJ`%3, $"T>vOq·z{Bmآ<5։XY[ݿ _H0<> }^n0 yRX0RUlYns+[qiCٝݖW^x7I7)8A6DVkÀ~_Cj q@GKB?dj8:C?J$RpsmtM'R"KEؼip./nz[]&54eFcڥH-ӹQ8LYS2Ѩ7x,SMΞ=ӧ>}VWbjATpoڡ0}/ct?Dq݈ܳ % ؗ0vDвO<8Gw4=lF%4X_^oEutTw9R7BD tO)IQ#SXOZ;]:dă.~4G7NZDͥqZkWW|nh5;ϼFӏ>ӇG8ڢHMY83D]zMƥI׾4:M2;Ya ӏm̑8&]6aNsbb$6Ҝ'eP-D{ny$<-]eJ |ZD{%1,<&hY3bťY7킔u1~.#кf(V t:ZWtFvp1ܶ*Gl륭9 B)I':/PJpM"A¯1O/fN&i2 imAg'<^`m(°ݦE Ry'Os2r hCI.'gxs-$ـN<@4s?a4$iԠ d"#$koɇ+L5~KFJF064bL2"IT RiM7Io 䖒7@h*|VruY~Q0sgW\}waҜhl4]e<jLNL8R=ahGfDg@kC?`muFL)J@3l $tq?&34qMN) ^Np?W %~!pg7B:[1%H5D#Vt^BJE"Z<_zo?L]&N2N|o}?wXO~N;x^6Iد~j<9V]d0On\u-.p77͛fP!GKX+Qҍ`07bZu+aZȲnKV+툊Q4SF\sDF[RWk "W|\:sX$:RlMsQ EmuiG$J$IґDpvJR½7k>G[M=ddDA:`g\ޠtAuh*m72//$lX+vs2*6صn(6Mhۡhb $$XHS$FfZIJUIqQX/Si)]ӥ>DŠF4/D`ipn,G\abWWB[k_Zru- f5 7 wU ҍfhKYǏ>#z̽yO D&mnU  ZB?$6ZH,qMTH2C-HR}a}xciN9Y`-} }uA#H?[T ;f*_~VS<~vM&:|T'KڇG<+,٧Er݆;E[I~۹-GrOaqquxַ0TjHa!ؒQR#j{k܆ݬ,HDY3pmuYb(E46[h \4HsM+e,qCگ#MWaĠT)!=xG,o86 5ՅHd`&*lX+?Vs\͚(J/[ -,lֺnѪ*<FbZz^<1-^^K6 <Əvʓ';mW'i A 0U212'o]LUN5\ȊJ6O9Ak!%AlikOlx#G$rIXɯYJ ˰}÷L]#nPV"h3 b;NsbFsNͱƱh"˒ҼZPp6__N+vNt=@=FJEJ mޝkVW?c͟-w.uyhwDIj)H=|:u91|\̙ghkIyc#(B9ƩN𥯼΃'o% `13⭷X^8MI)q;)҇EJz>]B)jfew'ܹ/ѬI. N^KX (0 RWӕ):"FӘT ="BgTҜl8kXZQX$ 2gD i4JBIr׀g5&x'TQojt&8^8Q],k |KVXVx*ؒ {ԢϽ @1kkk}<' Q2&q/$RYK?6A%Mף'%y9\䵦rŅe/43O c>|H oDP3OXE("ZT 6V>gceAfs?0 %zUI,Xkɴ,MdnmlqÕ )8sE5Vz|sr5dĉ(&NfzL)p'OyJQ CtZKD( DX^j kxCEX:V :3,C)TA4Q'sV4u6wKn*E  Lϒֳ.elndu mabjZ$bd(!7%d9v Vr)6)@I~:J_V34P \1yȡ xyֹ͛$ [ȉ,3|.vy!^fI 4|4>X =jsG]}ÂEBC%m;2 ١R!"Kzs7g{۫Nh{9)C%m+FUw);=mrty1r7-3Q(qq zK- ò;ջ][Gyu坯Y`)Eu^Wgq9j V'܉FsiFRS۝KN u)=13o̒$}.kOxwC~_>?oМDJuFHTW6tѶO&FзD1i6`B9'0WX=,MdԣzlN0sǎ`95^uA6ֻ4c4)m،hYP kLOM5Z_]40 )\<\hWH'!FZE`%:&Nv:z=+7Hbؽ(z=ҸI2c Y$)IJjcs@6Y0"YqϰAF$a-D+,:%S"xiLFh'K@MLJRmеz-tJ 64FCT_^!(vo֥1 E]ve3{1 V'Ry֜:~ t9rlf=d' {侏N 1?^)=ED)K}:ul 5hnCR[n'AAJmI2'{ 4e{w5hR7$u=`S4#q)W ,ShSm r7,  rF*ߓ$&I,ZҧE4b0uRI5Tw۰<W8ouw~ܣԉ6G GTk"jxϣG't>|(cN <&R|cfz)"zᢇsW* &L>iB<r,015> 4u& N8B?A)HFN0)]jTPB *jBihN:<|1ǎX_8~MޮPґ8!]*՘ܽ j *TPU:::X[['{"Nrx0y J!(7++TPB ^-t;AFzFEt{}tZ2H^3X!Ψ:˜࣠TPB *Txv:ڽ>EWqB5YN j-q*TPB ^5:L!c&uN;>f0rtux.cR7MnL]B *TYyz5||yPJvƸJ9C)%^SUB *T*ۿg&C4ap)Z\ޤՊZ8! x *TP«n˙NjAcFSQhMĮNYF3W` *TPC/h'c'lR7/hNO1q8 ICkC!Od!UZBuξ*ku=ܹsk׮\O0~=jaVcrəGqm| Xk>ql|HW52TPBƍܼyD ƷBHj f ;Tk⇇ 0Nך9)Awc&7Z \~VBB@W\[\\۫@~mCӧs5[!d BH`}IY'"0lҨ5ﳺʵkתAPB vp'XG>Z'";uGcrj !$3)Jxw !q.\@ X\\`uuK.!`ff7n<+WA+WX]]-{5Ɗ///ŋ:gnnn{ny Xr͑w!cxϋ˾47|\w>ݖsNsssܻwΣqǼuN82v(}fff~4˽`Ay{~xm mg{zmmxsss,//s֭Yw{{ݎwyxSefff}/0DC62o.߿w>X52DӓS4uLLN2weee2t Z+++ܿ\񽥥%VrC*&p N:n}_^^1ܻwg`-Ƥe7npR2b2ϣc,`eϻ>x;<{V9GNGlD˞8}ž<}Ҟ8}Ҟ8sžx=u 駟?/Wmvv֮_ZZWVVl*1~Pc/Ǵ}]XXZ{u;;;ko޼ivaa޾}{Ocb/_\k/c߱7%;;;}5n~n˲7o޴KKKv}<8Xnkvn=syw޲1w<{w^{׌^,τvl̸;qۘ/ KKKj=sU0Ooo N7m6:{x7\ C  mg95`ha ~&D^;=3w~u~oٹfgOړǎܘCv~~~uf dΟ?o޽kϟ?_][5???nc?nY1nc)rʁ:[cǍ|Y|l7_{qUY^aw[ҥK,//o;v9@o7'rݎu?njq۽`?Ay?n~x6γkmymyr v;ݶQ]z*`{$N#o]~?^~j޾M3ie_ms?M~[o۟ګWϗaVe^緦2Q,W^Vvvv'͛7lן97n߿?RP%߿@ߊ۸m>ҽv<|\ۥø}m-ulkǍvqsnzqXw;FG2mr/syw޲1wK;[<~ܱ#Gb9 I@oݠmZK" Aˆ*${,/mu+>-ytnr&C)++X*^+=XYfa!ҥ6nݺ˗Skm|>PB iR!JӏӏyaljA7pBZ^^֭[.\b};w,3ٰ*.]*#&׮] nݷm;G[ҥK033S*]r#N!Z-v͉#p_~b:1>myywﲲBI [l+\~k-oIl7nܠjvu?˴ZݻYYYի#*.\j ML^>??_F._7΂2{Yfwmt/sr^ \j _mk}1;+GRn\G[17n "n[]cS˦ݽ׮˚VKpf$333,--1;;Tܿ޽{Z-Zֶ+wݺu7nTĭB ~6^#N:EQKۥ׏}œFΜ~ZQ BN8g9r#ǎrY?w) .D1}561>>; jVWW˿ݹs)Ց62avV\qrXHa޻>Eƶо%\n7[6MIvZ߸4h7oֹw/1\Yfee˗CIΖo/8d>]D [^^-*TPCt6?w珞' 5;[巾BF^++$)ibB |sW\)#>ێѳ z"3L B*5^^ ΟX\%)Kcz{"THtnm[\uoU;1LGn߸1E~@Unݺ5BvJ󹼼\Fw:NJ,,,p%Ο??Bċ+TP(PQ@knl(!O{*Mfϝ^B KFe>Jkˮ]/Rvk WΖE>nˌk`$p{RnN}xޠ&zTei:QaPB *r{QZ PR66$%c}8ѣGt;j+TPB 0 8)%diF8<|ϧ뱶N#N>: +TPB Μ9K`m}S#|+_Eg)?яX][!^X֚ja ^O>x0!=)|"p*TPBWR C$1Zg1H 51E"ש7,JZ51%I#pnNCq~BC" *TP+k #}jQ8Q)tPLysIX,> *TP+x>AR" 3Әƒ A Gf26hcà *TP+k5B`4g|ZNĩf|Ç3 )%BK 7 VPB ^9ΞH uT/Aթ(/ !NbܹBew?߭Pe|n~EhS'3Z3xǛo3{ A)bmnnNG}ͥjk\ǿj^GJ6G0ԕ3vrs먗+(汲 p\PU q*Ο 8x Z6mf!Jiqd2x<IĿS2:AKJz! rID">:.gz}.OOzcӱj}Њ55YR#yو}rImG|ls@ )vYZZ"'O$w9~mOf/~|}|xw|o*/g<7Xdb}*~7B>ϒO''?N>?%=0y鏐37l@GIvJ:9I& xz&v#]ˆqxdff 8řxH =nbSYJ7r?&;-HWT^c6&z[ZA}rI?yBB)hG~CO>EO>EاW)rϓO?pP9{+2\e=ȷZ`=~CߠJ^i}KgFdi$9vԷJ۵.Ԟ~{<i> ! v^9nz={9:VbMMVH~k\b6z'I,,, a~~'c`򫯡\TӸ|ʥ -f8P)UZ~ۯ+@` ͆:!l;F!H$IVVt333-n5~l6~{@ĭԞ~7 v#Th~7χ`0(X57߿"WiݴۯԊ^f?Əݍk{l0Ru3ٝN,_+diR#Te>JD6JDO++  Yχ@ Fsvd2VZV\"jf k<ōϴ-`?ԓ .Vc9jZZ|qIA>$Ixޮ/;m~gSzBP0;rţR. K._r,L+Y0<2݊Z"x%PPPPvY~r\]04dCv-e\|_:6DBBtcG2Mx|ϔ#P6* jBj(Jk}m_ǏX,`yuoǜ0s&8eYmA8ێyG<@ܰJqc&,ˀ5q BrˁLor ׂ_,_Z&&'w>0AXDPJ&_%T BP(7,rP²Ee33Y9ž]BUAu+eTjKU0&Xf-Vv<߱b[MNļC#eP am}Zz`YlvX IDAT,KhՋp aAyłF]mN=HC3BsĮӉiíw:aQ*A!ı`9np\a0ۭX[s96 7ݝNخ:S(4GNhڞ6J8NL5#c `q&p8>0-מuRZ`=z~,C1P( B}8NMfkUUds[0oVVW} _Ի8>zOF\}1eY遉,,n'=  AsQpBJt\\\l$/Nlz6-4܆w;zcHDovvdRW?gͺDm{mbtJԊnl7~vbZM=w^WzzH:V^t:[c[҉]Q57Q->FjAgJ7^TsHӘZm EksZm E/jVfpdV3ȭd߃ۏDZ-yTo77?埑?;G_}_uWɗ~ɓdiiI \.d2T\M7Jdd2yBEU6$J2ti'Gs[J2W꯸]=iв|#\l'v,S_=0Fnn'<F<ˎE9]zi[5'F7Izc55Tc[8ZJn?4JsDN8 'a5X1 ?|~|so"D~%EߧRi9dFI hOG͹xx<W޶: آܤ62uN2ur{ɑǞ ~'OZw;8>zr*66JVkXZv6\{<@|>D"d2 ߯ڞG:nG^:nGKڽ'ry^NڒCj݉nj qթ o/}XF. umƊQC#>RFr83NbMmF͸RϨ˶zcZ^"2à^RbeeR-bf 0CՁ,yiT6ݔNj߯rh+FEQi%%S%ko,VSI[j6맚|#mB!il#׏t:-jZlujNWsl6UG7br4O4x:kVk1GjyܤgFj4x:߈:WZաzVsVWQ2צ8!+MMw;O}B-B?_!4A!+Wcii Lb_ }*e`td2 +l z T$e' ._7`Z8CCcdz/? y~/XͬcV J%ZQ<|:|iѶ}HP(bB ?es85ھjΩ,Xb{a%Za[}' BP([bF\!zuFBmP?S ZrJB2k9miԭ(hHP(P(8 4n(, Np**2ƆP7@?TjUp,ͺ--( BٽX&8Ff@-5YM׫:O4o^9 ʵ*9Zb@=HP( e9p,āaE4Ѐ@(6ھj}tNup*.\\jfJ=E$J t:ғE.,,`vvVz@⢪=LVnZ"`Pz@m0m BPrٵ,\ +kBC u%lCvmZc܋)8Nbddt FD"yLDBh p8 Bb B ǯIRb N BP(; Á=fd3`fY@me@TA@>\< p}on7xpkn[/M\`0 (<Ϧzb!N6OP( e &?~VjZ À3`Z-nM 3p/$x"zkWj  fSi εx<.]✝me+C 6v˧北P( s)nl EQlfn19 9l}m_79F\˸pr`.N݌A<G&A(B04,CDjvCP( eBqp 9`X`10p4n8V;.]JfS{q[gb/HeYv~Ka\y<^49"y2-P( )ѨVQP*P*Q@r`M zz39 C`Lf ;1Z,C6V~*]xdkȨ zxP,p;pm{t:vp$BP(Mz"4]$eY]oU+a!~J\6rbiFP( 0Z`X, BBŌ=\wq?x6EdVV@Uk  vxGP( @`X,ǂ3Ծ}(.TСQ*kd2( BP(]ǡ`&T-0ܵFFF, |^!KX0͆ PzBP(ʮb50 j*j*,V+8b:X\z2LLQ/&plZRB  G) B*.]Z(&NfʕU9Xpo&Cj(T60okk98 BP(]DZ #Àl8ep܉g`,j66 XZZ\<VzBP(ʮ>1>>Q~ lkBFcp( y;R7j>D=HP( eQ(bϞ= 8'z\ ,(C6,& .^ҥK٭2t*BP(]XK8{,.]rQ" `6p8C0|*zM3s( ԃ BPvv3fV Vz 0ԛopĉ{zɗb~FzBP(ʮVf3j`1قbZ@瞻~.j=h W.X*3N( BP#kh _]GZրf q䎣 8{ ^{އD\F^P( p8"˰٬0LpR…+_~<^K0s,\o¡C4dD"F ~\(y`kH?Lc~e e1<21n2 N'A0D6>ow~~`Pj}at:Ҏf9icN'"7#aTm\^wM4NgZPVUGJh>]N =Rl<(}-߫&y ݆nt󪾓K}].^=  IیNIiJcK-DNtNsOCm-5h]O;εƅujʥWpU76Ш jj*<.\0{'k(mP*]YEvuo^~%ꏶeNǑdx077';P0!bXXXP'Jd$hR7ߣA4SfH$Z>C&A&ڎD"yLT drSh|L"ctp?]'_ >! wllj!<ϓUۑi& $w#!$ xdG+ۨ^}Ł6EJ6ӯn땯:Mн'Z1M'>x<pjԎx7{Fch>Oq?ѩ}ncWmGg~< O3~̯ɓ?4GZ^B}' ypf y7sSx<Ҵl(B0T?JId bG:H''VvL?R8r^l/c2Q_mn >H&A,Ӽu+w=Ft=52V$6~٧i^p !_cmm |r U 9` 1"9[QEHՕ+ ,w|[QK~>HD'LKAb1|>ayzk:̌wk#IN^juR\_j%v+~4*݈ O&-z6~Wm7uw=jw#G+'D"E={'՞ZLI[<VR6o٧?w2f̕[;o\=0V;X]YEv5K.}oQ*#pvc#X^^Zv{0<<?. ٿ?([Qߨ( eӄV[1"##8x!]n\]w;Î=_WPPPPvY~כ?T> Վ:n:\6sϣQAP(RRdG2Ȍ BBimfl7k/axdfŁiLk\,+WP)WP*o`dd%T9A&ExP(7"j!+CC #Bp%Τھjg^r5WAЀeȂQ eA&(4)ʍNTDz pjŌzE ŌI'}C`Ft .-QP6ZF^T4mHD#x lj Lkf%t;ֶ_ۍ 0bf\.&n$cvvdRUW5ݚt:굍.mM&pݲ5oti]irPӧOkB#%wmն %_ѥ6^:Z9MnJ1NSrvu#Gwj 6 8?kVeTkeX-&0e 4E"O-GLO z7*[J۵쥔{@>rqF5sѼJv7lm$j?i8UO7inN}]X]͠hd2XX6cOCL?~۽g!`4/ZL˖rĒRkZPO\Fr,|7/zx7Ƨ-#q1H뜥%rIOG~ ?Eukgǿ|W|<1'0 z&3FWO>A>~{ȣJ~wG׭nrۑȉ'ȝw9bgoF8cDI^lxk]fNݴ+7]. BiZQkΨ ;N@вY{)X,&nb"ǰYNfY\7Jq زW9E+w)pjmwhJEo^4:>ۿu[uz 8=3lzbp{#$}!G!~1'#<kz\._(*h4vVU\Y^ukJH{<~i&H$I~~?t˽zif}dvQr7*W ^P(tVذS3j}Uӭ)92DYn>`PQm^Gq ڲ>t}iNcGJv҂%FbԞ9ёQ SSal 8^i i{&wq;;lj&z+9|09x ٷo@f.5 NɆB!<@\.tҷh4J\.צ6d}1r:oJ5Wۚo]ZoZJ~ B4;V m]{_td2 X"hFMh-j63޵.#Q \z}ew#Kzr\z^Oղxћ%rVOfgs9R+. ܽK3y||g3$'ɧ}c 8㏕"U& FC7KB l0 }I&z*2 BnҥK-?12:={p>Ӈ0daԱ_J r֮?+\߯|@zb~ umm b|j2a`Y؇0[ԫG2٥$Jo -hAPxٍ(KXxNl1o>ȍtP("ˡZ0@]"B1 0yxc`eǃp8LBBN\ƥ!+p~N*}jwc(X^^F\dBAPPUhԩ)ar(|azPx(>|3 1La LLLb0JZ={FFrV b&AQAբP( g}0FGM jzC4,jbz;-T( BB Vv J {N<4:F7 XkJa;{r0=}#cށj/2)r/^i/&P( E/UkX+q{/ھ ܭn7**,Cv YE9 A 4^/B(,$UJP(Nel؁ёQu. ^!}4s(lŋ}m_u3x"]?#ƛoe7 104{<  [,ns:ScH`H&6w^wStZZ‚Ԇ4{kmپO6>S}Bv$A8<<bL~t:DZt7jBP(uB,bl͇n=xI<\P(P-UP-Q-af9XV W`F6E"hY0 $JIP_oҼ-HjljW<G&iy(" y~S 0!bXXX~"rD"fpd2=NnUigۛz|HӘE*ՁaDQx<\d}/R)]ˡ%SrǻnB:5{mP(CM /".\<5b2cĺaFFF0<<=I|j6d25`X ,l`Z`6fL"HD)^k\Ne:g+ǥKw-3LOv[\BK2M<[mBP(pYLNMĽwmG:r p,˂aYU4Hf l~ρKRKjq"?77EB!}ˇPHz-K?*'9\f4"Să|AP(JH>FCO!_(_?WuԪUpP, 8Cڬʘ>HDeI&IY'*Ͷ_vfY8 R梤Yf?GVj+ছce}peu pjU*Ff3L&3 FrzBP(ʮÇ`,ce*~˰X aQh@\FX^zBP(ʮ#J s٫80> >eXp08:aPUa2s8A BP(WVPp{tb~X5èU+L76@VBA^d|JzBP(ʮcnDZ[]!ޏQlJ8u#ھj7NڬZj Ōz^BP( t U#S0u`<kL|_W-a@acRJzJjs( BPvc<*J N.RS8v(Ha&epT*q CgP( 6>L6 .l6㍷ıOP! j |fˡT.QR( Bu,& %ZqTk\>{BsVX__z> Vk/V" BP(!xW(5q(p};o\wqE\FVC4 F.bP( 9s4nu݌ۏA~dXQG~=w +*z p&ˁQ( Bٕ ;Xa8ڋ; ,̹38fpdl?@QGZ?ЗBP( e9q!(7@H f3z Vj5l0`0,BP( r*F¨sgϟ}xCYbT*( (K(W*+TTk; z.,TvZݮRAq;w:ŝg;m<8ߚvwST ~ŝgrcݚiqK}s#u;farb=88v1X8Gp͇aZW;0ێ[>4Jj hHPH-#?egN ]"!-ml|#>h۝C:-`~h35{ɵĜ_bW6or1$C.Vv;*Ŧ^^gb/OO#_;SH?0C#(N@KKp4\JaX'ktZ@<>Lfss?ſS1d2RpѼo"14H< G*5dIj(%H)Zb >Eq\NMۙ]gF&3Lf.TЁ UòHbqޱ~Z+FM`pBxbXXWNXD֘3ү^ߨw~wYDbT'Z}k#1oUH><ʑz؇Tj 鴠}>tbNFrm71W =\#j2:EzeK`y 1{x?D^fMw߾euscXg{}1rםǎ[L?@&Ls#'O$KKKvZ# 糲UۖJ5˵[jjU$@!$.kDeB!>_bͶY JǩjKN(@'?3Z|I*hW+On1m:Q! 8tF⾢tknXi+R'[+#j2bE bs뜥%rIGf'>q_i_Y?&u'w? aYm[ sp`^kuiz{q?c! l6+}Ao+IiUUnMr6E:-E_%x Btuq$ x&ZS<͒-N?Z:%6S0;u>/9#e?]+Z}PjbrqR>6+''GVګ_cWc6˙zm\$[vcC ~tO;ؘjƐ1KMGqp4Fðlv`dԁa;n$Rj)ԒNN - 1IłmqSt,V*8k\lN?Z:tRd  o44^w#y>( YK M/M^\71 wc##IԫJLM#<Ο9/՗_ERnŁÇWe39$;Zn +W_CGAq^fYM&7x}=%--j:h 4DŽtgA0XcmO33&o߮%7+-߆#2"B!ajv3_';-o$>NuSsZR~ndCA njt!33M=zѱ,b[wc#XyFInA[n(JȮ}-xćihp"h ԯ-oI?7ul667ul>l\\pAmm}ym\ڒ|Kd4]<96cK>hkncX|9+V445X.xy;/R"^>r)*^twwS #Q$q-ATȨQ 7!IְCe4& ~㠡 IDATs0Aք_KR256>l2]|:?|'ߧ.ώ;MeXS+:;!W>ϪkD@(;o Е[¤ }v$A8q J+(F3\&MƏ~O 㭆-װ={ [W^ARW_ѣC:ЮR8Jue;;)hmZf&|Vȼ58zjPD 5W_&) k1q oivRDOO/=IHv7!1񪡆japmmX7F$Jb8s<>|i,ZD&|ob BS@66HH)7oz=?6 -7-[~Cfߵ k3s-3T}͈VƎϔ;2iTFty;/̞)5]kt豣}]4iÛdxjQjFl#d1@i9bheFo۷E6T*S+ׇJD)nk\կfW 5PC 5EW^(dDk+A%#Y6AiƌCXbժ.$AJ0BC 5PC 5P[?6!d%N'ѐov0 knΚݬZb_ c HRu~?gk#ZC 5PC 5)1}^m#<֤}6@HhJXҶ H\'pp<0$I0Ff 5PC 5𖃟 0$\J".۶[r  u( I1!}w?i8|{g3[[űN'o)I{bq\yʡ|| }]`^gf8`]ogk>l;9C w5KfU%G ZK`^xK?,^HAknh-C o?O<;Ȍ=nux rçƸ`]}.8+9 LUVISkQjyű97.:dzw܊;![ f~A7'~n37.?sWR,9>̀{f>'%ϑ۞|M͝הּ/5qq+yG&[íG>Ul(XQZlK Nt!{tRz>w6GnsEpng_|mqS粬rD9&OBٸ\lcKۄl09/җnwLV|L/ N [/EsR}(.xM]<]8J<%|֬"e zXkJQLHDrGˏf;8R2I˳Ͱe٫g~M{X߾ >N}'yΡ쇟e߿7L4yFsp݃ '|6?x ~SYn^v#o+H#DA?RN~S~a>:}c2n=)n;Z=d+ߪ].~U΁됷=B.. .;䭂N𞗸߮~) ~nQͣy{lE ;ӧOgڸ,^5Ȟӧ3}6m4wj6OaL2w~F7ܕ$ol:atcjO.z{ L&ȶ6ÐPwec$)cLkΣ9hoqkGkgV#uLGN|P:Vo{7 -p;'-/usa7P;U_5eyUy"oߏߜ2W wq-σV^YN]7i=4f=%~g)ҷvo8:h72f݆W q,y;x$ѣ6@Fy M&BNXϻV! 7\ 1+")W\D1\||fo"A^ý?sXe2b4'n4? e{xǏ#Wwq+ zt!cgѳօ Y9~qrsߝ˥Ȼ{N*&N_ֵjO(:hR`yߐuh4Q]`gj2PgkՎޛ{8BKsYaVY" fr`v͚5kØ0Rb̛c'ގ9;ϱv~˟:7~8C3_)1awp5O΄ew?\T$z. g hF߻D'>>;a]ss/mhnݵyS2p# ce`kD7FݾY)8jp+]ٰhA7'C3ϥxE?뺄`xdyF#8uCu/u^}'fd.zV(z/ 3> _9[zGsѧȿ43)u6QQcϿ^=S?qv>8&ܴ=P WHo ;0c`%TNv-5=6S^Z*Z^3bJz=^eֳvvc&O/̥LZ6')whVƚDn|>t)ӱ瞛Ŭee)1$J:)aDk+mGa! L[6[t9FT(ֶ86.QqƇحcSOr֯~ϧfN,9cED۔C8}8^uڔz|>1?`e,wnKiGęo87mSV/nR/bu@NF L?KM ZSoFp0f2~1_=D|LQ~G>(=C,=.ǎ`k(>p ?Z߷2˙.k:cY =8ns%OYak'?|q!=Gz_\T_z: ;c? ~wG㎛F0̻Aǝ`b1aɼ<y ?0w̛`䘶4A2įt5&׳Sޠ Лcݯًh1.jr9~K`hԘQ7 3J$yR e\nA߁a?8~u7mJ_# w¸(.O~?K>f ܁9Z'S.}FLxi \a;ջg1kՐmMy=*(ߟ lpl˫kVn'R":y,.D)s.z =?SRr$*86w>wѻ8#ZNpBSە8bネ˨ۄ_q?h~n.F'Ģ޿s^pxw[>{7uMwNf/Sa}4:DӴ ٛ_+޿K_TyO>=OUpҴ|vփ;{΢ p8~~ T6qu&2p?ӏcWXҗ"hBN_бUV;"wQQ3 >0d ɶhbygr>3?' ]H^Nڹ??(͝\}v:'ałFn6PUiuoaRF7,,O~G9k/7\#gt 4,།S?ʍe8o~* xd]y'O+}Ёܻc~}ߢnO{_BS?s.6KJ%&dS|ץ|fͭ!6%}[,x]l-rS/+` ~AӁn8yn:hBr -> rVD}nH-ȵ~lz#˨Gz]\w#x(Cpg?Z%t_vʙOڇ>{ ~ ω`V AL圧康^^|?Ji탔-|%y;:;5c\ͅcw\>tВkDfG{,}g H'G'~pO8%|cq~ХP{+`~a XXua< nn w0(Vv-[o \+X0ane&rc3cu̙3ߢ| ӧɬ&ja룣}9cƍ q.  J-G5գTLqLNRYr5ӷ6kjhH!%=2,|7Cn׈A# ֈ\T.Fjjxˡ {!CzCH9 +vLsR_Q m"\C 5PC 5yfq\#ˑz.‘dYF4#twoýPo`'M"?vŦo*쩧swo]Wd%|N[Ԇ9Fo Ff]se\{6덍6߷1Pbr鵼 Oלo'kбr%sJv$;бϐ@kPQYt9|hz]q#z{!Mo6gX9o[j8ٱs]Lj}>/;?}>w ?>Ⱦʫ'^ 43>x3>{ žO>t}덍6x+xE)YiW^#ն2c w2;yxZp)q^Cכ3Ftws@2ۘ2?zj%=c5WТ|ScFfTH̊5\v.7j4R8E73qRWBͺw}bo87q{ogHrF[ZMi׸`J/|~t>nv27\u('{߼t*2}o*=˸ Gj^tMC_G׷__r ^L¼Yʟ^UF|vnط.9wǫ?q՜ѓo_ c;_r/= -g\ͽdH7t@xM;o7X~o? LĊݨ<w·~ɸ{Y:mؖlcXحsw^ƙ'}{ozo K3\ے]\W&WnhY^<NqHzkڐ͙lcvp(e`}]*ps_>8.jrt{ # \5khٶI :I-X 8&ɵ IDATmM?7FIJU^ٶgX{_fWkxY|̿sɷK+-?^\;ގ#:\ms RC\f/_VBiR0ںo;jLx9vFn~tN+=zlq|ky x{#cqOږg3N͏Xmˋ>:27ΝG~ԇ}u}:F$3adQ6鼁f#ɻ9Ws߆0< $RӞϞHykmwp᝗;۲$˽<~b7=nRv09nY!A[zݹ\vEžx/J׉\5c"= ?{8 ϕ|9_K^ : E|6ږ#."N^=#@kkoS'}Lc+k/[8.z0.n{sА[n: ɬ ؗ O? >\v-y@adbiو! $ρaC3(yY ⾐rZy R~~-,W︃FǦ:vީܺ̾$OOs:KԾt/A@_Ob_FQqeqA$;1 6(,y7_mҖwO0swpޔ{+cs>[[fovlA +ַ׿MzI!Іƺ<'@ۘ6(Λe5@WoBrz^ h@U im8%ʥZ[[f}obѾqO"=e9Kj<jc[CMf5&tqEL2#G`1@'֚r1dadY0QNo]"&j^CM'joFOORo  kjdZøێ9C)Y5k!I ?^jj-vߝzZ[1ąV6>/@ Q}^R"탔 R"DaۿJdYǵ7ڶCcnz=)%a"a82BJBk#%>BJ\>R$J!fJY0M9DaJ[knzA JFZ3GZh{L{~U戴Tme$4^W~Q']VL*+ \M.s~N~Rwo#B:8Vjm6V+1FSBX T^H}u=6HDZ}5 I|$\֚$IR <1WXt R#*(nEmoRhpu:i'U;i;Ϟa(@y?9h Jktґ~߰*I !0JA:2ڱzZ6VUʑ0 щBJF\Ekkʥq!DjG2TAzU=U\i:c(jU$qapS1JkTVֲgV_,ղs6`SMJV"Ž?AA%*`&s,K'k9;6(eP SQ*%4Z5Z*qp]:)<%a!!'}K>C pQFYǀrX&IbTM iSy hTwZ##7FS,QZ# Rm'{Khj,yd:Bmtj4x1q@}!oel!k TIۍc{u()PDrO NeF\Xi;!{i?Ae+;ξ qP*#JYNzN sʸUi$8%P*$G`jV/L: l6`32o{JN@.hcpu,ɑ23) \G:.㠔" C4VVI:^Bdd?I$@BhSWZRW <>h\Ϛ7mL_]WR ~yL:fQRg.qv40AX )}:m׵?o*ר!KIlb8pG.GVH(R(mYyU%YK;`MX,e8 !,i \TJ8$2DN^v\$I,9ѩv |ᢒIKS=t]sJ[9[]IbtmY(Wᤄ8N*q@VJ0%8cަsʤIW5^<РľBlR[9~v +Om( $%q$d2+h_%lc#%ݮ%&f$DcF8㸖ULPE'w-"$\8cI6ք\H;k$vnf?+q!o* uud2$P\ IHRBʥ2^/AIɞxdYۉ՝Ϫl;;`͚hhjvKRjpAb BwBL6GCC-͔!}"Ib5KH4( I$%6j*Ɛf!MCVdb#Hpce2]݄Q L]>O&$ƶXu$"U}y6O3t-Dx$Jq?UO mte⭚u6*bSq1ຶ29~\.f),ZS*߳',Id('d<H!dxK&!0n8^}u1qẒl&Aaa'gMuQ"cpp\&m@l63CҎy8f5RZZ2JjH I#esyI0 H+Y4G:WYC1m_qf}d5RAN$],i$AkǒJO+lmts#RJQI"+pQM:jμʡ6uf˗TEEVi4 2!1Hu{;~"vMIPJ6>u`iЖ2hܔdDE66k,S.\LM  ra3pdj-auIC]HJI #<ϣ!0*KIYHD+L7pJ%B,h 6xPu*2eJp\t<:}%TJzL R&ZG#֬^x%w]$!n.O9,S.QZlJ]izYiEWwW5>!etѮdJfM:,ٷ%RD%.4=|/@HA\D I(K( td5-Du]0d2\%^jW4oWk2׵cl&qKtqtP(jem~_ r<( Z*lFJ|u,YRڒC |'%Vo+%Hz8j'aq钼] Bpl0:x0 R(46뻖*钿PDQcJ)iB>R"X'&PC98]aÆUkW6{ѵj5p3.#G ::VUXC9> 6 mzqK=k\Qh5qM:%&AJ\IKmu-]k- ,$*MQ+YT[gBf:OL5kǡFʵ]82]F2HBdLufD[B6x9B eqp}/%!Zh'vqqs ?iٹ8.\|e&MX` o%zģIkVzj}eဆ(0Y/ YT| PFc uBY%hI6yiMyqBP3}2BJt.LiG]=%y2A{SRJ:BH%,Yy ~M_.^~mkkm! cK^B߶ olD Ioo/NgmNtvv{FkC)EoOr_%IDwO}!(6g3 5BqlYye]+{F0ed751Q*=g6~pKDv}TbPY U"-ˎޙFgfnN2Սh0Ku?RA':頳4se2d-gd1+kZPid2HA_;\| 0LʲI+l(9"12 YO{qI \J=?d$-0_,͛$}Kk1)9G&P pV7aJ VY %m4tAl/WW/߰gPFa嫯bH_b>i机=(f͜ʲfwZ4ص.k#BU)H'{ Q@HA{I1g3)%;9ee&k;&6,X)'%DNM%QwBΣ:31S' m78fRFW=!tʓ vHQ~5UYM k8j9K`!SIDAT'JkQdTUEU(#%_|Yж-!:2=u'b;nn^b#1),YlŜjVQTp:l.7x( Y BJ:^e\z9q4]ByOw, ,I!5s $YK-o_f\rqyI25etLz]Ga N eb1(Jf|>LN6-2dTU)ȤrQ4hNC vt]+`Flo܆0ãnR e],{Pzrd9)4uw{6=B<Q2h5P%(x 9e{tZ}хJkX4B%kAà%ְZ{6s[]W4UXi=jDP%U%w> [eCR UcQ iPvo -9=Ml5_~3nokK"aLwSkMb- 9#Fl17ئO{NmhxjM4̪_/9/V v;l^!)Go9QQ7WlnCzC{`V7tmGSYm 35[uc+rpK |P:O};n Jak cYVs~/X_m8 o)a8m j@]he%\wrOI#&",C2ݔLWըO_Vv0{Gp1WZ7泙,n{P9)˒H:ÍmK%Ӛ)?Bf$5c0jZU%֖hٌj%lSn9Gu8caw|>g\N(Dd mCTbZ2BQJg [NẁXwOQqfbrofڏҲ- {! ic:Ҷ-ESSڒJ_ZVShɢf6sxp1еNA|)(uN緦ihf[z ;\xQvlObĔ+% GikfMiV==~jЎMTQZ%E)arŒu.x"Ȣa@J$Z{8:<75}P~,dQO9ҪJZkk\ƒ_A<(4h}jXK*r"CT+d?.r RTO(~{#j̃k_k A.xsbwwwSְiʦS_]-NӁj-dlJJ^q˂9(,S\EϠE!YG+ۇ{bpkHR뗬V@wۧ >b*h*+ c,Ŭ~x<ċKBL옲O}K]WBfsLc8w^?飨Y]gjt. w:齣:!r=m׵ܾ*)p8e{8pquAS7xljdS;( #u}9$6"}6w1 )±4uoXZ^,tHGXnI2HUU AI|6nP5mc_c-.tGpwNmYN$hjbRV>66cD#CPo̻gJSXOY维(,5YeMrG-EQ1#?, tRռ8ffqSWA.4:|JڨB4ώ![3i@F=h1;/TEk D sq-[հ^mxHasƚ9p¹.-`/': j%y?eT064Fclf !ip( )4k%nGU(Y*-łnh=3E٥`h˩q1P5*Ϩ˚[e.4MZꦚ.q*Ș(,V+֫ 1v%ND;,bL!#Sl6@zRpW**( C2QZr!St)v-UirEe)b)#_b ƈtUD{cT<@|W|!D{x): VcS EaZ, 6kUH˼D>b oUa)˂rI,X4d9wf.`PUDR=&Εf\R TJ[Ҍ:Q֫4=qHKEe#9Y`VVS$GGB\n6rR-ew[O7ꜰ նxq&pg{.y4m>{L2222222~č3N4wP|}J-` yab6u|w|H.0ddddddd|ND9;w=Ljܧ2p ޑ=p1G28sF{ wo<|x>y[FFFFFFBι\wzzg!q\u{Ny.######s$pݎܷO?P<_Z-######s#qSIS978!me򖑑I܇@3dK~({皥7#######s"pOQX\GOe򖑑Iw>I~(R?c#^;#######s"q'$yCIVV22222222ߑO SI&n,ҕI[FFFFFFFedddddddddddddddddd2UK??IENDB`mu-1.6.10/www/mug-full.png000066400000000000000000021474561414367003600153310ustar00rootroot00000000000000PNG  IHDR5؂ZozTXtRaw profile type exifxڥkr于k! 2bv0˟>'cv*DfB~}j9=MPM|F\RN=ϿP̑|/|> *G&"R~լi7QRXkgU"{1~j%hOm9w{F?q7T8k9_T, _*nGNJ3{{l #g!N\\^_*/[vZ6/$=||_vބ))Tx+buTZEK~;ܹgN- zhL-vF=~rETy7FE;i}X?U <շXqo!夹JVڡ˙Dv 4sꥶ@)NժԦGSχk,)pZѨͨb&ZCS4Q12dji۴j 6l[G a1䞓>'GVYu%KƚeӋWo.n}<ӎIɹ~-yJ^n)RhZ8#"/E@Wc :LsCk|Ey@diٺp>=[PJEsV>=K>UGvg!$E뉶8:襼 ® WU6GZ7Y]>sR3͕MK7aqr9Xm%Ot&Am(~\h:R$e>:C|-u@ZZQPZ}zjs*Ec):p)ao:Vm4ӴHk>zwPo(ػK )gBDR ͞*#;p 94&ni.O4P%J`ۡ*Ұfp* *>aS:JC yKUۥ9NJy_نd5f6Ag )}ݐUP-n^-e[h$Oiz!/. 4LԨg9:yiDs xJIS/(~-4iWqgrel{cf zc[+ S/icq=\p"Q3 fi) Żw&W^FM 8>k9BO^nOxc]dkI=`7} ޣa+SCpǞ TQ*ܑ@T zP@v/׷ePpr98OjJ7X11^5q܃%urJ%:f#OT lltZJԔNUm5HѤo tG[\ $%;W^v-0pbl`!wk'da(Am^ZD;p Zq#;YY(r}`1,NMY`! 1 4ZZ5qc$o[6-"F-IOV)0=G8az H~q`iAgv7 {C (GVmQT h*\5bkYEFOAe+RlO"|p!ɏ!!"XU3U4 |DEw{{: ު/Vsi3zP0Zƽd֛2= BXژCQٰЌgM{U[&.AhۇQ~ XL`O\3`*Ŗ(c$i@3?]AwPBf1mÝϰʔ ZAőOD * #fZj1A̷QNB ؙ= Ero{{=mR+KkݙWlO@zJ@su-`&1J0 cDb{p:Lg9cbg"Qa{3?PM,Vkwߘkfj #QaҴǐ7:dF#jQ1@z3ZB`cǓAu$wnJy]ڝai`.Uň25Pzh1Xc !r.͌by:0Üvzd{Gb,;+4~{B`fH+]FP-fr## xh2D#] N ,JO87}.3YǾXY3FU1}Fo$,@76ZKT| ςobkz(FF!nu;Jz|%Ik!ZKcY<4_&[TaiNF"ʣIS FI!reuI>|BY!Wss bO0g0JA HYO ePPB )).,#ϡBA jd!kMq81G1 2*JF)\"Sq+=c#C7NƒT Jw˄vW  Ҋ`ި!O,Tz9ĺrpՕ3)ю/'p'Ă t""%H@F!lcW6a7H9Znbކ# ]v4pPDJ,ꊾBIi`eո's٠W1|\.&|BQ18ubDo"G5X L>b aٰ~+>H~t G!"ldF A?'BUĄ?4:`EvH== ,eep.rG=}4}{Fmpމz 8,3;^Dq#!@TCM_t=eS"?¨B'4&+9f@.{BiBI3 1$Wބ\l HF0H1~`Dq2s9C9}-iHá.v,bH~Blډbj/bvu ĀpRsBITO IDATx}yTձ9v̰3 ,APDܷ(OM@%yQ&nAY_ܒ5扊Q هa5=B|Os,ujV:"M3;=Uh YZXX:֑(4`vuUM]]={zgûm-mmmX,D"Lē^[UZ59Qr(G9\&qfaADk$Uv}F@X Zk0@ ,k-h`*_Bmlvw ЏhmGs(G9Qro@O6//u Z`XkEc-Zhc"(DK;h5`'otDk|_| r(G9:ԥKQ=Gwy?05-kD򢈈R4&HR0G9 F>QmlnDVY4"Z0h!H[kl` fGk}d2'셈 P_P]So}?:](G9QrtWϞ߿;zB'˯{iSrX\ܥK=YXXx-޻-knniii=鿢y*ֺOުxoAk03yq>yݺuY6l"nܸP]5GһkR^-{-荚3b>slٲI&puQk׮0;vڴipOΘ1FaÆ7AiK~|ÍS44tXk`2Y{ }w+u_h9}r..\yͷmo1(G>?Iuwڵv'xbѢENW7߬J&#Gܵk}_QQѫW޽{Ieљg9suW_}Ν;:p֭#Fd/=z|1nj3&//ٳgg1d7tߣGq/⁹]XXjժ!CҥKO?tSiiO~?o߾{]x;vtr9#g6U}<촤gF8&OЭ4[ 5+kKo޺ v ʉ8熾Gp̰CʺF]%G{{)U?~w?:suxfEC4__pӨF|]o<G9t?ٹ{/CIJރ K9(T*mٺs{^*/ޣa Pztي?=+jjnV=ߌ} G֖;cEjces.].2hI >D"'|AF+@L?i蓎g``mҋ'Rx2OX3{xގ_?%@3 O>P9s׋/[o?اu]O=޽S[oK zg u,hmKv-)>Z@cP)0* Z!fp)j[O?㬒]R^9h֬Yo%K aN>Ϙ3+{6oO ~{lڵÇ={˯[o>H$_zW]uRj޼y數?1q}{y2lذ|0??˖-W]uՊ+"G>0ƍ}/fG?ёG뮯}kw}w~㎋.(Ώ?G9|TQQ'_\{> ?u]v쬘\ښHvkEKK'w}Ǐ7_ݕ|~>+U4y?mb壝 RSs⇭=80Gy2Ɣ-ӻwֶ17zKJwaVAfg^KK[k߮@s=< "gsQ_|1"sZk 4e5Gݺuk,khhX|ԩSx㍇~{ɤygq̚5kׯ]wE4i7oR.]ȇ;&=0f̘9sܹ3UTT<Ça>ڽ{w"hjjZvO?]^^99 d[ ] TjgUm7ƀ5k 1cc|c~`}c}c|ci7 q٥ߨݵK/CD'J$ ,hmkK$ݻw//^J<+--oo]uժU{wz`'9:t(444y+VX|ٳ/o_ז-[{x<ֶe˖GydXPH?2:&L_ZQQǫ͛7qDzSg֮.W_|ݰaUVʕ+O=TDL&>`y ۷o5k_}>~e]tx[XXHi;n̘1\J)̛u;gΜoQk}X r:sϵȇ;&q+,,4K.WzI'YFkhѢA)*++R#G9r]wE**kkzR8x#m5t|`B4cZ0qOя~x”)h}F?"nٔU1"x饗>#رc͛g*%߁*++njӭ[E=s3'N<ڪu6xAtIcǎmii9u:=䓗^z̙3uesϝ;wn$vss93fx?xvnϞ䟵>o߾ӦM2d">UUUH@9Syڴi?pii֭[z'|@D*V뮛0a… ΝFө۷ZnݠA\;v쫯Ɵ文/]mΔ+{N:iРJk'6-{}QS[?=ҷjjk>w3pϳ>ꫯ666׾5:ڥ~衇Rկz]RRr]Nꪫ뮻./gf*>}: /z|ld,,,: 0~֮]u]JA 0@)u74%\R]]y|VnܲWX )?؀2 ` ~`ese|] …/To ,rW/k׮fA_D"'x~e˖Ι3GGuT݇ڳgϻ ryAS [o*!;F13f(**>}hD"xv\{J&޻;w-O0`"tM#G۷ɓgϞ}-P 8nb]wgݛ|gSS):9k%jnn裵osN~[3ܼ}~M0aܑn<R)7W|'pQ%ŅM^kWl+]qúo{'<1ֵd5X,֯_X,vԘ#9G'|1TO%T`ua$mXk5oj7bx* /R)KD6w?9YK/W^Xݻw9{7`ܸq]tQg&ug̘AWUo~]vW:o޼cǒ)_r/~ `…`M |'Ξ={eee'x%K-[dt1cߟ-r]|Ot]vHG:/2{g͚}}}ѿ%"w/-^P V~'0-wIy( %R1.֌_罳sښkmҳݺH*@<@KS[<޽{cc3AX^@CHƴbm Q!6)/HTO$DW 2"FէOXbŜ9sxꩧ™3gٳgԨQc}nã*++#C\-..5ko+\UU5mڴ>uZ~3`@y#~<|(8RyyV-~c2_huEE<ׯL2֣Gbή]Ƙݻw}"8YkW^}e)Fqꩧz]v2dȔ)S^z%D|g;n@AAW^y7NzK+k鯟xN}k1VhWz}mCC6?pCCCCCkFGqҥK15IwYgcǎ˗qlዊƌ{Zrqq1oD=Qt}[[+7mٚJzJkJcʕ|fݢ5v/&)/@J{=5)3kl" K/\['?3`Kڶ:@|/iP2mjM60&R~2)Cٳ'dǎ vqﳯN;wҗ|n:pĉWZHߜyݺu'D{]`E]t477+ x}ߒۙ6yTmظEӧ\//}Su@%RA~k&b`P`s>s㍳qΜǻ_`9{P@,X(9ѿ 2d۶m_vuN7ng3vy9̛7On[dɹ+x]]}JJV3O;ҥKEEŴiV^gƌiӦ7n8D㏷l9 VJnYkr{H|}HG 裏~?uuueee *,,lnn |_zgrO-`G%mw>=+,(8qN߿_Yqյuo·}֊ᄋjoc+׺ՂK5]ܪ8XV|?ZѪ%xP Xl[6mݻw h;&H%lYOҢx!06}{hwfT1"tm}W3ӱ1#^N(ȿFӧ?#w8pĉo{7HwqAk|aȑ@}3f^s5J^ziw!uիWWVVڴiSN}W={>}!C~h ,[_ٳ^إK?t~iƌ0_YG 8:0@Jw,"yF(71xW 2ݔ@8N;4:t8h ~fڵ׿E]zۜQ%顇*--?￿w#Fwyشitޝ4>{?[,z7_y38CYc}=餓Op3<ODĪ*7oF7t] ---UW]w_V'8{l7L :Rk׮t?}STTԣGk-9s}Z.hٲezy566~0nܸ˗kgϞ|ɹov]wq^xᅆN-Cr~}$)x۶mOSZ%Ktf.?nj?~^^ҙgI3gApWb1D[*++.\8j(xH쮹暶6҉,Zd ]^I)ٳg|3mzw5$ Vt<|w߽ym۶m۶[hQg8󹩡qweMN ̓{v-zc&]&}Ϸ)ߦMԂ5P[g;~ѧWpϟO|G?KhyAkOٳwǠ߁RƍhѢwy /fXr޽{/VXqgY[,~YFZ맞zgD"{K70}tztΜ9o˗/_~3yyy  IDATW߿g?yUUUĐs.XZ{WnٲO>YfQE;/^,}РA-]u]^{Ν;uMM5G9:e+J)kKX9Qر⎻.+ϕU5Ǝvȧ79ڳaE064?Ew.ZѪ4 |cKVܻ靃oUUGk:WUUId{I;8m߬]mv6nmmoIqU"u^^E ,Ӗ " VZf͚7xYW3 0`ݺu6m{?ç~3s%\BOR)fZQQ1`I& 2_>nS%O>?[~߽xҶ5hϞ=?pg'|?niʔ)z}ɓ^ZZL&7ocqwuwo޼gRSSEgR{G^2Xjתyˆ}EL9?;[23"kp]믟6mՔ p5̙3s9qE]wu|meKW_h$8iX[ߚL~lп)'=d`?䳾L` 7._Vz=zEHmmmkmHjbpֵ%%7񤩓܈yˋުփQ;S/.=Wzmn{>G݁=Tֵj^/6Ⱦ`Q=(4l0Dԗ14B5 +&ﯿ]Xw${7vXcLn:].}T"߰9$≢BHyݻw7[s3{գomniU]'bIw߹sL}]}sK+Xn]}SA(+O$D"](-}؝[x[nZ떖n!{(??ҤIk׮C9C޽f]噧Ux})/ýlڼNy}s#Ӈ~v}c>~O8DȦ:^-Xcu -=tӣGx޽~ Q^umoᆛo Nf/6l܊-\G'8k%M~+q.#9gQQ={QCW\I"VΞ梢ւkc@QUUU-\p„  0l߾}ɒ%w}Mrn>G9Q<9Q:pL7s??+b.u⥰l׭~q].5XG0Hc5ZvZckmKK _i(i͇}ޚwG =U$Z?>}ӧyѢE_?"WQA~ގoG"n[[,廥%QGg,ƘXƂ(Gx"QSg;vU7x1뺮Rx<uS)5<}.Qr(G OJkȻxL}wN9aK}w GJ Lzo_a{Z~2i+o|ncScsSSsUUhW;mk]Jwr(G9gҁ>Ԉ{Em{6I/֭<|T.ڔTHR};wU]YY\T_a@ԎӳgD.QrI_.ӰwOCrIkmzԮ]kll.,,I&Sx~+ycxssWݻo߲hD)TJ)ݫguMm(G9Qr/6o*JKV4544o][fm}{& cU}z}ݺ Qr(G9ѿ3a=u\HNнKƘ Q)8y_nCOj '"c(60u]?jFn*r]{O7b,}86h@} 8c~ Zkqk-NJ&7^@&z4qDLRuTM1z1KZְua$K\/`z&+5clg#%/7XuٴsgD2FaQ'Q?KȪD,r$6$^l^b; lBvS&#+D؅gI8[* )y cXIa)1Xka͂ݑ liziT$'nYrZ$[&ńP< 9fH8Ai3$PFZƠ (Omr{ $4eXHM!D<#17؍CL7e! B\6y$J] D!Yv?C92Z'9KDn2SRX@"b 7rEx,9,R9<)il(NYȥ0rDۮlD0EʘY;`˜&I4"0!"*)! H1mH8/q{&.\&5B`? Cς.yVk]{qda%bbs +J_:Z̄bl4fx$@ve0EMDbj%^,0B.v1" nZ ܡ sT<3_ @sKH?ᥲ[LpŚ-Nox8.}Obs\cJkeLc2ZTZЂ5, "Qh * ǡ*D``dDG)' 4 ڣb-̊4 tdaƸ!@gYI,'F.DC)x@#yYOXY`gvJ@Zkʬp@b,U ;T+iI8\$=GJW#2[HB&SPQ^dɤJVZ+ X&d B"2ۆuEQZa,0af"aF] ^#^Vj R/6"=,~+K{5܅ S,]RQKVqSZl)d}ϐш-8Mle#<_.x) q@śbTsp ;=+H>E,rrHb3 Zgj5ϼlH%a!Ńgȥg!SI;څbQQRn-yU`5q y8$a%iZ CG "auX2fI0"-oI!y?hVKYF-LjYPJY6U,Q:1h J}A**#i 9ވh:"hGeJ!@{LœbV31C09yFԓʖ;vC!!LX'=4BZkD\cM*@qT0`hO L wP82~`Ei`,~Bc_hp4$;2ذ('HH߳OilAf-,*kZd@`N JɃfď$fgEe{<,$eQaQ!`:>:쇤xҗKsdCK@D[] 2DQJVdٞDF-len/_-F7q< Bko3 $1#H!T1V+FJwvnJ"DL{@?H!B:9-O XQ"]vIue`RQ"䑤-D06 kO:bj RR`, +'̚%M/7Į+]e*bP,]f+2V b\FsbW#v3X\q҆Hk"la-4 -><0iHfs)a4 x @7!NIG Tz,Vqap"RQvg$yE{î҃h@54XZ)u@!tZ'Xu%A."VJC:mj)Q$ .E`IXƌERXX!:3bUCo"dT1x!f3>XY6lQwOg&lfHlxKB2bK]YZ@mR@`gbi4D#EziMGTLaE҈ȫ1^8VOUҀp&ͣ쮸k e) m&掔@3, "$ %7J2RaeG Axs.A@99w"dB`E5RA@L$/(ms} < 6;zf>ߘJDrҢe^ Gg 5Ch!؎a‖V9fH8d3~YgU'`3r+V,4 V2O0)o)Pa)頍b0e6k-[H!Ϫ*Ր#b/ ;YșV$mpS|8\.?KGaHiW}}ysĈFvTޣ"51iiV&y؞1<*$HͰ4uIRZZ¶Ŏm4,yEΐYiq(EC(g%TJe 5*0u;.^6RrKA H&kт8Vk dU9J555Uָs]YIĒi"∪J {%Y^ 3A4ERRT"=3DJx.KGXSRRbC( ,R K;bӘ֦%A`|LK =cVh}vHxFa$7Ҧ)E 3SY:,̤6hg1˴FQ2OY(Q&>.0$$aPaL.= E?q2jB|iADJJ$,8GMrI}g1ɨ@Ҧ3?q:הq+1'%͂7@ \psVCr!^yY_v .0ev"J)KeʆKBqU`|=3dŌך'0J IDAT6f(@lN=20( aR.->1jiX%󲼸L(_2Ae~p*~[МW B,8ڥ1jv47jr Z@Tg(kAĶĝ4 'VZ^(pBF㫓XAX؈12@>,rׂ,,i,bǫz툣(* 0^3 <~>[T/rv K,F`4YK5s;٢>#PM&71.@&f:V"v6m~R8XkM` Zk1ȼZ: *+A`28l|GQQWͳIRB213Y &-` "<,f!]2U$v䙴}DP<d#Daxfڲ% ^+,,8^O w伨+PQ K>1/5£i@H[iO*HpȃbMMM%]K=/hmk۷܍DV9'd 263sԗ4UJё_B(`AU:XtKmZb&^`k1"Ǔl٥13; ‹".B|)#vl;`GZSZz.1[(.o6aT,N6nKHbCq7a vv?s"BLs \bˈMg^X)3Yɓp'&LH#|y}:sES)8#ላU;0MW2hiu? A _'-sۄw0dAQlEKL0Й"^3@e ioG &(K{n<_~ڤt#jNء7VSSt3D}kQ|}?G7 ǡZgϲ)~d% „^2o&H̦L{G"%9y$cXpRerYEDi%S!)py$7g{Fi*o/ %c9~gjĝxpϬ2- ch kxlwqs \^<٠yU],$ $`ψ$Ew»uVd蘙lRmc҃4G{DžY$tQQ{ D|3Qr˾\8 pw4}afg`3C=~,Ek (UyQJP)X qV)(jGmmmA`}?hhhL$Rhݹk{%ZE4A^Q@kl{Ź%iŤ"=Y8f`c 萗Ru؋56v*'0L=;v쨩&LѼ9r7f ,NF2_J)1/t jV UdI^\kL2ȞWGA֠ 'ϔ(7gcRZlض萷Gy*=b@̧ؗX؞`׈aJ4 pY Pَۖ)1-8ff=!Hw(Ϧ 5~4wbfꄿg1X75y.SY'@5j6>V@G`JOaZ#(c@)RckyXWaU=1D\q]G+n_ I;gk8Z uXRiYāM{(nfNذs q# X90#nE&JQ7q5M"+ZP̈Bf f#;^4bJ/ҼR 3)R RUl7E) *k81`IWgk(KDG̭𺰄 [0H a}fHh5e*2l)ڰ퐡#NyJC;[$ݝ\,G*v=t]u;HB aҍ$H  g[:fqD"A`HR ٢rJi陬ZX`Y$6JkMA#Ekz@^^/b;TaaQކƂH$#kiiiiyU{J#ŲMОAD|?Esf7"DAk%Ȼy",](Va@YHd6$`'`s(\4; 2N āz)ctW* ]nL?Ow!EKAv|"&uPvĊe0⑨\Ul..c `nA bQ&2h; 48lCdkA /0$-$U!X*,DD tf8K 0j~~Ƥtx 9Õ"mmgZK@,}1䊷ay1CRnAl0d);}?ftC\XwO YEJ>9ZpS )Ŷ%({1|ʊ>MmtFdeq+bfv)/iT! UQyQ$yy51Yg,,'6w'%Jy$Tc \"gdz$͸֡`k<a\ j<*>x]p&Yn*+%bA:cC@% L\u>Zoo ]UJuzA@nsGh;J|O)H:Zk|29R0Z@D[֤oBvM-H$sE !X*@{Ju!( @XSLֺ߰ni\9@*kAfjUNX ,.8|,f&{G>=yYp8K+ӕ<h~oss3g2^"0MǷp'2(b^91hT xNTV?! qw&T`*թ|gyb.3wzPBoh}-Lb j X[ @-IlfMeZvi+s[z{ښ(?3˰ RHPz4\,ZC%SوmVhYKiͺڨ o߾}i̲<~ӧ5-%2oIQ Lx;vbVSNg-jM]z'On~/yst|w/45fs3px,Rj"z',]$vE$k u Z%wYH#4  7id=F3YDwu.D@7V; $$9zSLl[9S?/W( AF48zTd!m:rD#؟AN%RxŬ0KaXG,ȶ - +;ɞ V* Y #RvvAL! jXWL*M(\DZ݉ܝɜ91jN-TEͪDHFCJ )޽k?GKYXcfuL<;;ى 2bX#J]^_^_x2SPfٳ.>x{/og 1nNo;q[J\|TGl$*Y~b13MqH۴ZWH-D6)jc1dLbԃeT"oT{ i2B~,@l`dpYZbd<萐Nй9 !F{Tk޸ח7ʢ`]0 v d] )ԕR+0Y0NpDީ?j]ЦC6ۿOBv4HcKؗw*jϪ>p2m6h!܏m 7Ҿ2䊟N\WWC7"flNاm~knwo;AA:pPB<`aMSm̜D U0%Ybm7W9[bjZ5C"(S}{VF(%ӗkU~.&``hN\(ډa(hXzmD9gw+唑b'sз٘ 61'Hļ YF<*gT޶:yẬ*Y@ HR(P;C` 0AطsQro\iV >xX 9Ȇ& F抈$yS9f6VuqM6q#hH;H'/ssY,Rf>*Tء|c[,7">wxx<~s]]#"#U>MgϞ> k6U u7j4 e晆X8X\,&ᨪp>0B2P'VE$STZy$XXż3ãY3XXmM%j N5 \A -lTYqӹL;ŋ c`[)Iy1s3H!bqP!zz ɰb`]mi}Ն'A*8vT ` gwbﲷb ˦^W OWot }eEP,hi:Qz@ʠ{S McLuR`BtEdI꒜;qRf){-2ٯ.ڦ=#"+7EcdÀD.!hۆq7f5)8u5Km4":2wo]{`V;r¥|r Ʃe1xm~:Q2%нLmv*Vfj"!*"ɓBS->r^/?ϳ<5\aSvm m!iե8'MiSvO 1cxZ?xHU˫wÇ]\)[oX8(gV@ܼ+6ϡ/`'A'w@3pV7o>꥽`ԛ+S%p ETY7':*1fjG kNB:+A6*@U?狍*UHV|Lh{X(!Y1ZYk8HP"%3 )&AqyO`D:AoLJ0pPFhpŀ ^2A!]J ! C;)n$v !1WMr<o6̾= MnF8j+hfb^]PiutǟX6!ѶŬFخa^Z"(۬ԶȣZdA[ߞ  qN幸hVb,q9d$D9ؙC ȣh_*b3z \;CӫjЊIl{ ɻco|,UւFVFf%3\)VT#%^1dBAI=L dH|J0ZgX 3<%K)Њ9)b:#H^Eϙy R(DͰxlplUh W#ɑ5\닉4 `xd)ApJށZ]5uV}eI=KV=[Y&-`̨okyPc*9Ngggyѩ֐Emc!af3UG(Fqiˣ o@#3YUd8E8MyyI;&aܖQl*AHz;'O,BW0/@sY`Xr_?t/( yD,TF;Y=P?乧~ C))Ƙja. 4^{rA(:*skc[MT/TcnD1w`.P08t@[e;Ě\XV%L}P xKȀY 𦓏dek+n&5y!al;D8.ժWkD-WlS_x_|W_~Ջ/noo#UÝO<...H-nLnb% Bp̦FC¨𓍇fJ,7{^ y-"XVi8Ӵ,4Tۨda,1}_  l3yN16 4JP!_iI:й`r&0֔5V畅^b-*/[)YeycR쌞Jn VQQǠrZg+9BE/̱l0Y$H%[%z?eS1K+Kw5˟}10pT1vI8K'"]^W@Kbtu#TW9Q7 (]$TbfM%*y(,rzy،ި=.UW$T`nBnpvpl0XIQ%QUvIjlz:{%l 6 Q&/1 Jq3 o6\a26T BBec.۲1!{ꥑC ^B ʟ*~pu ,ζmj7 9MhBi'lƔ̃ 0} qoV-vjшDj(<;r.=P[5UUdl٨`AJy଀֛ݗ_뛛y< MKBD4"ޟ={v~qzg]թ%Dݱy&yζ\qFt Bo^zͫ_\<|tñH:q}ݿyr'~c,D~V\97sEdl̉EGqfnw&Ys^ ګp>0j*%r0j-XZ@XCN`Fg21$Ёgg6U> 1V<_<6 HWh Y𐠪ULV0̴F2T>|]92+ RAQ-1;![%lmȖDzmru?3#.jֳAKD3sMI# k(SnTi-* &YI<:{`a0E$#CcX`@mI6nVQaUd_c 8.p ( w7[:"D]tB*lmwG.ٶ8Gita⯍?'}hal6Ai#.x.֗ ^E[CJ!dcd̚F1̰=%|կUS":M?'ONePFQ,Ŝʋ:[cXZ5*2xp9޻N뛛?/ѣfvuwisZQ y>Sr⮨nQ͔Kj7F!ܲEʊ1`QPL &#ʴU‚ ˘!^o*aAc^e+ȁ,NkvO9`T)18)91^jsatA+$Ndv[omA]‹NN` NH% 1ب-b;僸ieٔ!`:עF//RZ] MA`f$ZP_6"23twQHW#DsC""^,D0TH:gfsÚYB i{rRND%^оQ ~,|`KS)5%$JRk93FLT54 HDB_#Pmq9Z:,AXega3hnTgի ۣGq\JQ#P>FWC9U/ӣ bGXaWLEx:{o \pX9 :@EHrFS/ce ǀdsɖV*lwH x<cI{{ia}1[6mȺUQ]4hFkJw,d)کUlKw!jkfm-FY_Nk7WgOif=zW//޾o^#TDB|Uׯ_ӧϤLik6c رr"LZD/(, !l ;́FM$ڗ_~oo~ko~7)+isI+d7O>:gCv.FG`flI?jQd5,\90Zv`XPk*:0JRlf0nԻCsliϚP)'!B5ȌܢւTpTVB~Si%>bcKww]i>i3 hm9 v,s50XЫX-3f 3gc'UQ:fQ9GU{k{GB(F!-ӧQRmdQUy A|1+Sp+TjUҝXVw%LkF)3\n8KT#EdSɀgF]B Е|ªs moDNȗf5 xg)ٹ vslӓ'%fiv`t UtM[?z_;NBpٹ{GB, mBJs U5m~e:%Iul-ȴV7 EetpRMXpd-]O3[w`)3}q:ǫRm4q9yBR,kE.iߪPaC;8eWT;G0GШ(+hE`bz6'Qu |=[.d):_Y@p; Yhb9!;`䲵!ZmZui"cI *fX _m+iϟ?'d)+Rfy&™{5IHAJl3(t %Zh3 wM~5B)q]S<ZWզ0>'0K}_ӛ7w977wK,zaя߼i}z锰ƶhh ԫ%W2e8 ,3c^q6iS3;N_77;_xqugD_Zѣ?<ϭuU3]uk&1^mQ`*c̦Fz\*.ARfV4*R0uNq0֟;ˀۡGZ=j6E5{V1aJG~.()EEc/yG#[( =PI+!D$dI6l]bf֖dSabQi|滬iUƎ1I6=hԴWj)xBC!3u@ KfcxE0OT,2sLU {qNj֛(uvZ4ՈJ,/aV[D$bw] UZbU2VB 4ʱJЬCSDDMDK%Om Yڍ]QYN2^#<>7 @$e0*lBNM;EOer,_Nv>\F ,dS7|>:dLjqb< | +Y o7U&XBC"WC'K*(3vJ1[!ufFmqcG05~5JV&( s tn:s@|$hW`16un\YIH z2+(TXeuexrwA,8+9 h8e\uս{m}1y^k&Pfvk 9ɛ-F7ՂQmӷ@ s-Q@%ȑMͧ߼ysww6Bytr/1۫CdapvEu-L#hU4,"٧qv:޾}ů/|Wo^xNjO?J۬EH}H y(9< ChBj0@ucA[-:mxQDz_zB0k!$dXDCS㋰my9E[[ R)A w8w$%2"NgM}ӂgͨ?$52*(0]`,%z2Iه>AcQp J֬ 2NNyVUR'@@P换j"kc' D߶. r 67J00 :; R QEe⁠?nfc'v "8Em ]~e̞h)"%V]d9#.ad,۝mLO=#jU5k3"ިB ٩2+ǜN:`ǻjKM)8q6Yc׫(j֬kf2qqvvaThoH  !vΫƪFR$8jh]!H8P13Byk`"|O1vQP!HGd#( 2թ9{3@^zTH' DYV!0a`NH;Z2 4nK &\ȑgϞb9@tk'CFrwSDSMUD=Te Zx "#|lP5fSk3o 1Fg;[2f_z}yyp<8Ƭ*xO؊|ic^%]"gض֔˕,O>3G^ǏDΦiZvj `*Ap.}\6,Q"~)>p2W:A:=A;vM1@h%> *)jmA{9Ӷ2 =9g` VE?ttmdV mb-,if>P7" cnRNuii0Ȥ*\ .W#"/Z˽ 7hH%"/ oU+Y7<7g0\nvlo(Hn2*= LjNqyht G՗P0.X='Xf^̷QXUƌWpV+TO B۞. MuȰAL`96=AAv*Y;F^k'xJ|cTA7v˶cewWk + D5t2mx2 bUb%k2ژTD`P;`?XGρSD?-w˼pEluPUɯTKP%\hf*:tB$JG!rf,j!يgsT6GD!i4$oחo޽yp8ٳ?|ٳ>BBcr<ϗW4Ϸw_񛫷>'ynTvR{o+!y :w)GZ+:]"޿yW_W_ջUG^m IDAT\\^___]=Nvz4+zP5(`cۦڊXPeBQ&UPo3zZ ea WH&F(hSWxi~bƬdv$aAygڡFP, DJ vۋl,"[CKZ2Zȶ[4WܺH,YP&Y޾4{;f3LU"tvQq2ٹo"P!=aA# y.R|}H#ml23rBy=`>`W2 7c*DMB*@T5/oTes`~QpJU 밚^qIg!U4u@եFb} hJ#:`H+;^/=$yi UsXOA`YQSH ӳ2*{"Ja־݁]6Nٕarte,:&K};xxS$nRd*`#zDBQ5fΠ@(G܅:;@#T?ԁ4ˡ,ll)2'  .afȠo*c-Xnu]gPݕrwwcUyɓ'1<___3yӧOw=z{W/WQ0x""3jGԾ?q>xD֐}Q:j9d`3#NOeK;<O~sssvvvvv*|7<"r}}ԑɗ?zUko'}|Cie*R-: ԨC2Lę^f5j n[ `{h^IQF%ƈZ|qlЊ{aUcz{%繧iTMmϟQʥuع";uo9B 2ljfR`E5 l{ f-fU03 6XrUP*xQ繷3U&>狈 B>$(5oHM/1[3k]*[쒃a$ j(!#@;K:ms=1)") Jƽ؞fļx<6-aAwxP%"TŧT %Nof+̬rNT`n %٩U`&\zC]s yt=tI,?90AyTH{ix<" ߍ3ȯ@¸N6ZX2,fq P0!hŜ) ?M v+Bc@^MdVweE*+^ǼAw1m[cػ:(DczNǬbYy)3)V({JҫhvPh/\kz?Y-'y(5 ϣ 1f[GQ 0SZgS}o.߼|w}3_| gӇϟ臟|gO=<֚o޼|+&>zhqTD%Dno?ڧ?ї_})D޽w8on~_|aOۻ4ϯ^ݗ__xi<7W_~tv&jST/.~}7^iݗo]?Oruyuws+1^.h6ra:o:E津r,? IB"tD "`ô4yssp=0p2B(@)b6b Z8OFx[ :Ks6ƫ 0揿`9U c5ȵJ310![SxAa$XU[-&TؿoAS6df2FnRU-_GC)vQ*.&)2MCC[ 딶A X,m[Y#QHMPpea[kgKN{xZBmwbNLx<h?xpӏ?O̦ #^^^b.t/~___&Lp8C˗"*M{3kww7s8?Nݗ_{zxŋ,ե/me'"tߐ6L؀i{ W>|dA 7I.M'lj)jdͨ͋hS|Hn 4llܯwX{1 xb [1U:`|EFY㸒!v0 ce pbYv]T=[6xj.B$ cqBBN?|=`ʔ BU3 HǤ_Сni)cꩱL3@Ոvꚏ@ oiTJ3g`j g̱C"E "w?QgRZxЧQ( PU/~ͥ/1ggoϟOr{{Igu z=X`G0U?}C_t_Ӳ5$eg??~do/K$Mns6Beţ^@p''ְyOF{q)dy̨ :`Q,w!R -gLUihX򫵖^ C0vKB4V?Jd\q|j C͹CN^Y򳈈ug} &bc.5{Wԗ /2ZI8}gO<%ZU|Cջ__J__10i}{_0X,9X@EXSfE_0Z/-@G,s:άX9MnPEu6_ܹeʼA!XY6R - rC0$XF5{9ędVJz!dmQCrpid[ *mf gK㱰0B`) ^Ajc:-[{[KFx '"2dohV0:kHes\S6f@]M"Z_;BDZ֦͂7ft{ktUz{駟|'?ӧ=jjݚԧҗ3 ɋz˿B{yP_TRooi77<ã~5ⷫj}?”GFڦfA "+l W^ZXD:NmTYVo*:M-QYu肶fiRqY2$liu)BXb-`-UucH0mq&Z!!*bciÉQKmmKEÇ6c'm9G:m¬E w^$uIpN^DT-D"[d-ˤ/wc[ ÿ(eNai JhYS{`@O1c%ob "\j9{l*Qo:^A*Є'06P Vn-c b*iym> & !r_Z7Ə!a?N ""yt0F(vdL5K63r箪&%"8sc2G[9H^J 3Q 9nu N9d'>ۍ59;:7*-xxmgxx&P0S"g: )ʁ8wS$jh3zt:y`,s9jD$˃\a-GJA@Tm y "?[bQ+*m/`!S:68[؅27}7263PlT8ξPS|,G{>"d1q5c+*t ֒I"SCfv:!2hWԂ;# O̬%;O|S(La`&IКjv_+gFѫq@|?dQ`/޶+=Ԟ9bc%}[M`EAD9DBg9l@1;&W H"؝pJa 5؀9^+X dBxRg2,ŚöQ%aݗ(<hwx˶ z/KZ{Qp&-U%ī*ZbSyWiۦj.2|޾z۷wwp81'?gYon5ί>(O*"27/_&_?~JFL_\gg\<;T'L-\rj<\U84 /2r5.> jٶj*hP!lck A J$A:wfX ;u72(r\8͎'hyCUpXkFI `NR_i*UED4In[᦭_]ICU[U|֥.p$B"2uUͶ$*a[Q;@n3A,Q\iM5 ;*\[ǃ=[$v#Pؾ Le( d`|5WSc0YX{F}cȅEg?{0Ekv:, YXrE,R?puj9ǥP=ً"R{Ә&`b$za oP+GR)6x A rN,/J.p=YW_,h;$?Ix9ec&g52JΙR-FU`RV[+fP向VGQ Z!nTa϶` Écsd9eaӏan?±.(V6 #-"֛JkJؾs<ܨ@US~d\,S8/(45 Z>˺[FyofQ;v}}7߽<N7w7w%uËgΦ?'ZkgLU[Ѯoo˿n?@Iirc?_'.^Uyٔ5eجU"BH,D{{5m:S3C뿙FPeS%B`Ӥ@h Tm'È4s@t&mEt_Nݫ Y81 dT*/'4(:3vQs8%)b&JuVwo1'XT$1t&պB,YG# XdVX&M#hu1^lyVlm`fA I -ə(T浰ńMy)E\Xn<{= &ph.`'Ȩ,'Uc V`GFJ1q4_19]`lm+ͰL;?/"ȃ}jko%f:%B̡l<@](mAW/\^^nϟ=?ٳ{FNdj> f fkMM$]z7U[Q8 |qqyiv'L_LEij17.|磏Φy ixFM)M֧)$ܽfȞVJ⾦nzhza#= )@12Bj%mrJ" 齹D~~ѓǏ{vyyyu^12٧'!!xed6GLWfZe3gT']Ji`PQgT ݺ vW#"Z#$v-Ya|f^cXӳ3VՖ# bY:/Yc{T- O%-e C_c3 ry6  =RDj(>`?VY>mPp7UZu 7p`Bj[^o<0r3<6rߑkBj@^?51f.jS-j0Q(Re6Dd.ofWhD*tԠe^$.aJ}R't pW5Uܬ܆D](<)_0g>`&`"Xl/UjҴLZ|QH@ Ai o6;c`*Ħ 1ޅ+fg*i}< |D!iӴVuG XJGc"ۍ1hc##"gHSkjjv~~*Yt<߾~x;gO>я~|كT<Ƌ߼}ËgϞM>YHL){_=!nH ԈܻF{hQ yZtz%ìӘdli)ӬJUf}q}ǐ ؋'PV1p1diCDu5Dz)kMLW]f'& J՜3@zysؠW~CB6ZE,$j`̇,o/f\<>>yVcRw&3 QYI]{Yi;M^rϭhi(qͧ0d*/gyȵRP{lb>B;WigwMi Q-)¯'Aw*U OrbĻ94v >"{2TU%M ZkMrlFm c j2otyeYU&x/ƌ "'HLDFA~j٭Z*TYNЭs@i%R"U%MX($vdBfV~I2ޭ{a}ZKP4N_{?TRsaC\Br/Zwٻg[a`ߤ0C8&/<BC}V}|SQQ` ؗQa39an}gb'@D}߯={vmmm2Ngtxkv} i@5ݻvՂ KcցU mGTUE$bp' ilB&q4emŅ-7ߴ{G۷'ɴ#FZsk#ˆ(%7+jw6Ԩ_xml{sI\p`F.sHUrѐ3dqX/*Czefc,vU5oN=3NDFXPUm&9qhAu|[ubם|K<[*#!`xlΆ?_ߤF=L >/,c۟T(%զiEO K|@m#U4C)$^`!*K 'x)DbA 3h)%%,ѩRJ_4bPk)d6Sk D ^z , HOFD4Dd,\dHE ![[J%D9dAP3f?eW>+S~w}`!dEJ +8 ;"m&DRd'%}Bq|Ъ%aGQۦSTw|R\anXq'b,<X0`JnCWrXK1CppTR9.9e&9 oMD@]ߡv"[)LIM{Q{qB$"Qa`gsʱU$ fU\*p]m -&[OtߘųbηKrn?h~xޔcGbr뻳{w49% >8h8ML؛M/T㐑v =HXU]kUs ?H#ԮVKP1jTU= JjK ߂?r}{WB"Mln) {*M ,HYiBIruŋϞ==uY=loOnK?vcZ"^J n2,//ݻEGb)%Q1fm428qrkk> c"J)Aԣ cBmG. slBߣzO-Ti-Œ٬<)\G\ź պ` ! WyhߕJ"si1q\C,MUڶ!+Ð-If6TIԸ\+K,tnwwA [ UD/^⼱Ijs`f6,4n)b;B?7)>e\(hCr K%@767ϝ;wܹK̼=>!P6+G:|/,,,FpWKr;˱cIlKtǑbBV9yVx>c}/C}=FG޲9r;+g][[۷ouuee<G M *$w**eG7~ fJK6+=i.izYRR:'"B_3! @L405Œ7)0y{DԂř|#"!FhqDĉ6Yҗ͏hxhVEU k!"#UiVb. Keڽ#.ؚ={σd8܀ݶC8=" 'dπ+ܔbT'`L 40=C[F gQ !fXU"Qeqp:1rpdO*s(o*3mB"^Q.y%1DU˔J؛T?K .^<+&2hV aʅ8r&% Ye"RΒ-g!SBVD9>R7 aQ\uU_O9RSEBF34K&XBYKI f gbN9'KkVi i=68 Nf=ѢDrfKw@`F+ %P(Ys9P̩ We1W ۜr5I*uP>UFH)I>u}B%t]Wn5Hn4nn|#vޥСCkkΟL/ؿoiqmt:Z}<bҮL0NAL҈!bƮB!(,*8Y fFF9NNI >fP 77v&{FP`+, GL%u0+Yu*IO 6H NHΈgd 9 AUAqz0fiJ,9儇"P, aȐ0+l he6rhyp(!Ɯ%6MNw=3S=Pb~Yd`5pYlqpn쌗@ı;cĔY5Uga/Eڣi(k+*@8҂CpYb=)BR- f.X aNIqыezpT-B5XٶEя5TRA괪)v*@qe cf;0rbbqxX+ULXSIayq~3o CD 0 "o8|?D18^`ZkN;!Ȧ9}ש` ^zqmL)BU꯰u|Q"A Y|X)fL47d7QS!l!'mD\e^Cm&$YBSDPQSU JLC+ەP4hf6U[rIMRJ}Nĩ/UV)ěZ?`dBbfSW<;~ D^i w4֚ GUC *IH) \HS4ıʺe6 FUXy\"-wcȈ83,?;p!}T՘@? p70c,]S̱ d"ĪX_Pc]9e3ؠdxRNC xHvv,\GlTX!bD"8i$ۍ=H9ṗkgCm YK/O3Ku4:Wbb>dJ<8G],XB>1 H%UK&m^]2LLcd>yzӔn! P /xzR»VnQD &j]J\yG(ק7U|x/j!GzRniqԏF e^{8F欬HHJϲza,$XEAYUdap]ׇ>! >TG%SȲ$UE, ]TId1י YesM~ZT3Y e2s >n!f E-P_P~Ÿ ;I9rƎST G{:T_'[j6J3TIe #ا(>4>oaZ(ogȺcuw+ؼ/\ȇ7E?H:WSC)ʓ$~ c+DKClܩOT*K@ )>c4iYT7+"U՜ʰN9#gŖc)>%|pu. (L7'J-y l`!tԿKG-/fy<uyQVҔ)1\b)cbb q(ݑI3u)DCq{ m)seDn+H5bZ%/+o*lD;90^{ЪZWiVv7/}=/v؂e4\G+zjC/{WRebie H%>B) oBQe1Dճ0Lt˖bVuؗjԢZ^ѷqrhOXj8, 1Q8&4 E69cHܶ#f]VAEYrM8M`5$^RxPJHyƮ.pǍ'd"Y( LJvrefT4KQvI2xHZ6QpZbGKG%_G!0WMB{=H혪n߫*Zҙ`g: QP4cXI߭{O#x I <-Uhˠ=E%/ex%vV_+\[ߜLgN>d2t}u31!"ݳ{ׁ{_}U,-,4)^p4MӏQΉR22t:]ZZ\^^F!D)'^cؙyڞLʸ񭝫>?ς7߾믻U䬠T|gw}vǛѨi"b]JR!P(hbJ%Ĉ3`) -m-rHc(#NDJhTk 3'(U" ( r6ȱO}s wT%p>a*ukzEX(}aǤBR&B_=LQsL /V6䄨dq-,MO %e1͘Q99û-sbfeaaJ!zSmLT,,gu3 q:r)~ ,E/F\o)B8,# cߧPz\1=GSJ>$"v-̇m%ʛh Rڊ^>^%3*_\h)4cr#U)%<0JGRi, B`ïl|CD| `UBƳdC%qJT麮R¯z!D~- ;EUI)c2%V"L*숺tQ(i4-V ˜UdGxUh"Чi-(Vʈ`Jٸa@ }J!p*8h.ْ *'ՔIX\^&V>%:NH quE!S.%aـ06BT"u1#P-}-`g" ? Ds8KuNµ9 bAJ4 ef/o9snccJO縎x;KKڇ:tpuuuia«U="7m}:olnnoOr?iG qvst}tvHݟa^XX'F>9{>UHSѣW?nmm}{{yUWAh뺶i<Op3*Uֳl=Ɫ4E>.NTRʶتF -41\}By=8[xU#{hy".^c&"aOC0"J+UE$_2swȩ aMx@)A\eAZ<  H7%EKIK {ͮhlkw/м0t).kXSJpQnGs.f$px7U,&vy]N}_IE}B,5nn^&ҾOta˷ j"UVU 6~grNˌh!r)IUYױpm@L0`gc;K-UED5Љ R`W(*$}F gAB ?`}z46ixUM],S*4r‚t >tIv 5Ei"w ͕SzĔRKZ?4)r!YyXi.T/{Dpd;,J߼Vbsv113d5v ( ^V ),Ĩ2b ,U|! 1٬/cW@pV[7uz8"wڔy,t,L#dCDD~ >sK N*@Ąo-GǏ?rȮ]Kmp|/eơ98 H۶RJ @*66Qp.S .}z|ݓ 0IrgQï1,>%˻v'N̺t]v-/y\-w]X)I3Z2 Q̫ZQ7}J;4pDTYRlC SN\d mR:Lmp74L읗HHSrU'y[zg.h;:tTЂCTLudTJ}!6G` @ 6fvIL;' Uepo8feOiAh˼4 萃uR^62`LPU.[Hj5HZMQ儘-QræØsN}2|Pq:UΡ!V \yj]GjJj'xJ VA^+:WְsIqX(IZS 50(;:O[_?e:X jO*XR,2UaM(.|-d>!"&ao"_, l݄v\sޗs,׌]qXQ"RO9J؆R6Y=pjw6^k9g3>^B %|1! 3⇾1%FVO]`.,^;jݬ3hP\*Y}-u]:STO{&۷Q?584gH2 (g+X-DH˾Y 7T(%R$\m2M+KQ̺~2loO^^uX.(߷w+ dvfU7 [U8Q`mP١σ^pVNp=Ě%Q.~+!_pK<481Ѷ.}?/gЪVK~rA`.w++{BȖɅT@[,K9?zKXɌv/3+1šeCɃ%"8L/ Q]X+ԌQSk.0Q3}C֕=dY8y3ATu]o eOisUdTfz2̒@ ѻi#sCDHw G=vض78^**óBOX620/f)fmUȲuCl!DBjE#iq!K'̹il͆|VJR"+(q#lhy۹?\'jEFSX3bZ)SG8J8U[yh~d-~U%ա+Eq]DsVLWk4̾fZ,ʕ*fJXu|6&2Cï֖Y䔘)Ę+_ɺz\Ƹ?49;PU`B߬ψ %-R250b,6)) 6BB\$҇q;6Tzj8T%[>t01{`zCĤXlf ZZq:)޾Two :76deK͠ bm!ScPE{>lSJL+'%Ę$QEcQUsh-/V}^6wJ3Fx=?/x'?meeo K”ֿCLˏOP%L"&7 4_=@J=c>c>>'FAiSSx/bi-%K/qD4wj{6C|G~WSYof k|J2kaG;;íՀ2lG?rOJ <)]ux{ J)ip*|͐Lkoo5́CQ.KQX_)UꜞR@INͺ%f{{BD[Ο?}F%U4>C$ϕB|`XAbl(t@B [v攕T%0jEa/u 8D,f\:n8 'hT5;0 G5~R5 *b8Tݹl!OxiiiuuOӶㅒ.QR+-S#ʢLJDm~gpI2*!HѺx0"w"3(9_P!֮Of "M)\\ޞL&vs ] ;[)j~||||N~s^ O^sпAb|֗߼SOR?Z#=8D4TB F B BCXUmZ>)t^\ּ@`+muO?!~it:'􇪫@(&tkq:;n>Cm5Mt<uRxcԸh'6W\SK\dO/R7Ma7/֐J]Bl| 1s곈ѻO<-C}6Hc MN|B۰|<,01|ǿzg}ѧ%>W2!Dyݰ)%Y?|ŃHD4>|_>}OS.;gLhɔ8 MRN1FZʏ{h:9v  YdzXu^=3/ Tjw:~vT%v*g<׃ᒜP&fik׮ݻv-,ѱ̬KFbPdcY&b>wtKKew2R,$)I!WO ó0D4B5snΝu5$WfoJL#9rȑY=~lTW|>CZr"2 6jҺ,W*>3sL+z^}^=\L7v^1>43n~uJ97{_޳޻/~o;9Q]{㓾ǾYG:hr%1>˿??YK "<cO}#o'%Skd@͡Th1rh`zSldg+x2?ك ;% (><q1j\u=:DgYXXTQ0mx)Ol:4Mx!|&ɋJxBM0R*.TZy)?BJ.)^dm_LVY'(V"2&i?|ǩgq֥cǎ9rWӧDSQ{| _3Y򃭿OPD҉?{]|\JDa߱Gv0g}=/^4#&Ň1yKտI}ܰ<;7?׷Ӄtͽ6^z+뮭+dss<yc{k/^~J$ChUBT61D5Fz -Pٚa:ƪI/ḣy 8uV* Q ώE2(=<5!Am@q \'*8Ý-U37F둆b%Fe=bsIe4itڌGu&+yo8Qir Q=<>jm %u)Ϝ9!)JU@| 7\{+++MCBjNUUy 6DDJ&f^(`fn\!61! Ӆ?>_ꯦ%3oo|?/齿m/_ܴt]_z;Nȁ?٫o??~1;?sS"wwس_pk~^qL<{_/=œ+olyz[~%Ȧ6ow}͍b5?s_pkwk?7~xm}Zv_;_w⫞/~':[2i?9+ dϞ=kko˿/ x___۳g ZTg7_d+UgYa48OFcrD!+e_Ն_+;]|`Z?Na0Ou €= `Hd1t?; T@DC4%(Lw]%/HU1ئmۊe@=+4LAltG D=$e}D덌LӾp™3.\u5ԗ]Y|—3.y+>W|_pqǑOzng~Ɂ>|_-?s9_ﻸpl6&"ooyN|߸_̅.238ZW6 ?(qoOb?iO'ͧN¥f-D!0TXBZ (@go;uA9p9 FB=H5r c \B%l"\[y׬lNξPh~AUIﬨ(Lb"Eɧ`GLgɓ'[_P;C1ݻ?^}diiHbl|Q7|_o?6!F& }-;~LUf.Cለ86ϟgcGSWWwO8>LO^׾^4zoK>6n뿼}Kd^=?yk}#_yC_<#<~7_vfb>kM<{~헾t>gn}˿O?K}/z W1:s^_Ɨ&3H,@(޻N=NoB*Po2P_;!K~y08CDCٯY7cY닦R͍C2OHQb1Ɣ{fF4|U*dM&>bJ*Bսh6>rDDFW;CUuuuc}ˡCPxP+{X%0ȩr&@$k$*LD$MR󮏞={]z>Ys |g }}]7od0{[󙔮~5wt}p,2~bV%,JӍ\j }g|o~[ϼ>'to}?l3E/]%O:g>tR^>yG*Mlǟ^)Q?bk<(2݃c ez.#+~\A,z)~P"o߾m 3vŃ4Mԧ~q9yɓ'P<񵀿@s ͎<%KhJ"% P%Ӊ0|M9qTM'Tdb]e=Pzuo ܃5!P933fүhpR@.KL JmjcNB1p2U}ܱ{4ͬ+NW2F$!Mzm1%OK~Y!Qq^Y/~I7]1LJ;;t8PGDߖo~ v̟>4;eUo_-Hoɗz8n> :#}ӮEHhmO*Ĭs8XiK~$4QR3!Y9OK<Ȥ- G~ݏ'.NFCz^r%U=O~i߇y?ï^5O >{w ϸv<=sk~w^tJJa }ۅ=~֣V}޿p}1M.K˗ 7\']kpoǹADe/M"ڳgϾ}y.\=aYrG>cUA鬊pӨQ9sq;T(a1`㟵N:*hhp{$ƈ&MN?A\*.A1j$ "2pPQlh$t  4><7R"!DK ua;ISmوVB'$514)%bm㈈":w¹s666TUDz-,,3x{6?c$=~15y&OŽ@|Y妛[ۮɁ*Զm־!C;K])0!+dܫdݽ{<mD$9Kb>c>>*M76K3w~dӅUit?Ewo}_>igky{o}[?Wp9K8-y瓞%wWp_g.ͻn{"]w)h҇ W2p bEw?Ü0~\gdd'čJ퓃CC tM8rdfr9xpb=J.!o7ُt, Z ԉyR"(*3ya(^V= dc`RM7Ӆ)(B+b+~iƕ Cj2W9OH݂]VSx/K_'nWp ǎZӿ[~蝿TOHzā#°u\?rf;o^|v+K{:(aq칋;mcZ IDAT,u;W/=vz#rK,",fn>@FJERJ1Ɲiu… .}SMY4]ؿɓ1 D1Q_JJٜ4?)s!3uڀ>9}QVtq☒XxJȜ,Be"hk<1mczo"~ˉ'pyRWq/?|R)'ܧI|g$,ܹ]^{ɬqg$#pN zb|IRm30H)_^`6?F`| 꽷?+ 6jZr-l%#̬MR WoJ@b"gy\~lvKԑ@*+RDDBt[u1m&(1UEri!ŴO777O>Sʫ++;|= h4OG)>hC"=Uٙ\,TӶ3| UE{zSYm"9}ME9bamel@ K7_[_n 9\&i€/`L&;ػwڙS샙G{v$e,@۴1E@1vm-J(s~h&Bpi}wſ߾U5}vyiAi~<$e*Qo\ڨ*+KutTP9(;z%,Tf!ȬC@ID8BGD#m zcueg<oll^|y<ǘ2@hʍ7ܰZiÔzGڽ قAƸw^쬯3C$̼y+/^޸z5. K:۷rѽಧ4 4m3LYBUv "FMnkz$@r9WJ۶XQM0{F$CH@ 0/#&;3;}~bZB aa@&tQi`fr)F@!Jb^Jۮ@Ռz -m_sf_SֿjBu1Jvɪrݶ5W%F*%߶(&[gCVGi::1'#s"t I;_;qtҥWNb3+ kǏۻg6K$Zg>V$Dt.~&i4VsDޢy*NcmH۔9ZDӶtI`qˍf#%yV 0`5gRuCD$D ~̄5jMӨ>b^i l,`4*tXXekYfU^ejyMb]6(ӶʒGQd?!훺5IfJCb w1Gavcd{3!d_re{|MshrDQMmTJDm4f_m '""+)X5kƩEτH vYh9oƇYbB)G 0`<0k ua9%DDγuNsm:tC뫫Mv!<;ٓ!QX@J&)F!QETTbfBg+Vgr;4MM@Ow C1FDB8Hbf~@)%` 1l9!?` x#qSbLfo/Ej,LEd-OefXmZR؄:&y'D3r-o#xkjmUOEUgn xh=Kn6@C)کoCZF#'r!z.ucSTYv]0N苽P:m񥚑_8AB!&s @t]~~\0` xP$: E8SaJɳ^5VzUkJ+5Hi-d E]%1 %nX֒+)X-%̕ǮEQB t:,1f(zeQ8 ,ގ1y]@Ķ7?:VXM*5vkd󕉄y Dg) YJ 0`H| DH  E.$cmgPdޚnR㟖kP+ä2ΒR9Jiv:UClQt\iJ8G1+6!"NZBDrNٿl79r^j(, tP\9`  AUC#*L|㛶*WJYضNJ8M,dZ vu]Wʬ[oe:sr$yjs*kÀSObl-/ݺL2L搵(hǸ^lޕAz^jy:nlm^xecks2foQ?45Npi7Nn2pbh4Ҵm"gipg-b QU6: 7Mow䄙rvmw>AʻDLvi罰8rLl 0`Gje4idh0@D4C@DWrPjrkbn xV}EYX38r0ź< Q߼rT=V[pMAfvLqj-sJ0silWFx=yҥD0NQ5Bǎݳ4tSfƫjyYgTkwM։`_鄲#Γ !@@@"c{'ܜ%@!TKls wBR".N?1@TŔEf: 0`k̆Ú,T١yRѶ1oVyEйH0Aq|ZG9 $`I,x0]\|E@6Z).z -&h `}rU3N5&9bv_Z``$Ky5 zqpҺ3SzYavRLxggz;YTE=u]#ą///3sT+tVSy&R呤1b`НeW/5^s]7:罟o@.S]l5RJu!0iwLDDx_s%NWS_/~:Ipܣ7vKOkI|;] /)G~]i>k{׹ 033s)sPy9{S2VY2L<0kF U;ڄ+͕,A],kJi1X}K>T&h),fVwٴ@P \ADE|)Xڛ!Ee7J6[[[W\oOXh׌=<mmm2> >3rGOA_F zlRtbkl_ nI~fL(IGbE' HTc1YHRVlJs9N%T["x:$ ?{gw>7s Wmz/E|%NzMK"8=px7WV=s+_ZiC4ƓĢ*"&N;ɇ{?3 0`L3,clSӈd?&Yg0QΩ̆v! }N,52 $]j/ Uhi]5(}5 /Zふ4I?Ԕ?5Rm+6VwMB>piիW7nbnjŅn}}}}}uޮ !rƶL>eez$Eh(CԛRB v@Vb ,ֲkm7?/,,pLEcc D"̂h2z諜[~s"e5䗞= qJmCGG%Fc"sH)$ ,W BDc5<X#04q&̤RD ~Z_NL|­xTFJ#n(̠ *`TM׃ T >ݸis.pR;&&L\DVWX߷웜wG$爙~ڶY\DjՍN)YG}hv_==w`D{4x\4a"{䘿:Y%(o<_kfq@I')9%\A ^D̬9z*.@<}?wh_~,7YFsfqs|67YZ{'߾~KnYXz?xS54jmH(#.ǫ̒!m^EiS0`>SBq_ jgaZA4dWMo dNX2׏K@cȯ\9ZsS*(aj[~w\vi\@"d2x<1IՁaKYX8p`޽ RfR4n/w[Z@bHQbqt4g}[0l@o}ݟVo|==c6nu]K/CPR O3`< 0͍D BΑsPP"RB-cUI`,ng3薃Gjds]+nb(F6\7-0Zbni"P56i!}zu+W}HIqD`5Goۻoe8ci*2)Ld׍eVgѳiDϣ M-DĈ9xh&ɴR`$v ,""snR ѹ $W%U ]J5 9DG;Dč,92=u}zj#KG ^<7s$2}mی`zRh^}_;p?EY=:rky{S"7/ Б۳G{s[%C0EHઆ70(?SL_X6^S vsƷ>nQRu~v2o^fBՇ IDATdgZ}dZߌDN,9.E3VOӰ}xXtE9\\sս{lEulmVaiZ]D@m[bmdXFD:XruZ۴}L-&1cfgRrmYr )J.u4HbLOLkYq/yj?6-xf'K-#?w~s{|n? 1 ^ҳfO|hܻ>+~+p酛nw?<ѾwK'ۉ%XRkZ>3$Y?vw~ DB BEӒY:GRH0M}ޝvQz-Šӄ*Fǔ,\ 멦iQ"Iz|VP^^(S'\,DN?M)mnnml\1Q3 Biiië+ mR6f$ycI3MsDd)4 ڬn e@m6$SJ"!sxNB!$Qks'*sN(J68q۴M*JJ?˵[6+/.O7?^%!SxϱY.]{box !0(,U.o/==7]yyC7}gp}>ƛ'7OǶ>ڎM~# pﬥ/;z q?YڷMh.oFXCxNS 0`3D\V xĔ {gSDX&nKI*s(ҍϨ|@i{U"_oqͭ)n쾤'%Iօ 93bn M,ٞ#\.8}昅VV#KK{,>xdiim[Lo[V3ZЏb*2Qg'dF%"Itl[4fcgќ#Ķ<Z`P=IQ͓> +XGoU*Ew5?{avD \0-R<9;}xvn8s?Eܑ$Fay?gzʼnnr\X^xd>71 ϖG--_FM8DC -.-(rч~eoDi{ݯ6[mHG|=kwk{EB C`@my8ODh)vĎ}b4:nkkue.rk?sOc;ĿM'#Mct!ݯf<Μk___^~W-w ҉T4ĬJPXiu‚Dm:NnVC,Y{b" if-KcuSb@m.RmzW+#HS?ؼt'o +{m>r;?  1B >xngGzrF @?|䝓g0nL"  lC܂0q9=xS ~ dv|1 ]>~9c8ԇ?yۀ_'i: G&Dr2Xq&ER8+T*g_ Tkmm[!ŚX~##eϮsSi-Jj^Ws^ (ij*(tεmMٳpȑE/{ѝǏݳg:$YvRSLQ+앞P]TJtk4 mk~h`:jT2ff}][T4wzVwyZŅpCH@y91ךC-t$뿯wGOчxw%10 Nְv9WWp_5M;%Ů3Q؇A"*DL2 qTgﱿ# e!za#Afiyo<Ż>˾Y? {D ]tkW'8ܧ _~Onyq2pI 1h"޿x/f@QmA&@=+,3Ĝ/\ZܓsLi:40`2x]ɦH,B躎mON _<H]d(KO%gv0̴33i~ FtDd>=* W:0ok8+WPXSgՃa#^yGXZXlTLaS-2ouPF&` %t^.MՄg,~bCm}<14)rY@RJ1&N1&!$GU4,WoU d!9#BĐ$!!?>~oڻp[oB˜0$LO}]rKV>~ <' wkNOs#oB [''&n<{o|?w?Ƹ3!=tX8 qv1Nc?| >!rHi BhYXAf)UL{cʺhrw6hzrsN+ď.XBYץT޾vFw-Eb5VaƁkZ' h S;,;P8^,ۖr4Xt XFzQb9!)+%ޏvkkcRne[5@+,s5z)b E4MADPn-ƈ'e^!)},[jKx޻heD$@G\fz3T1 {Wߔ^\<~Ǎ_rCoCP+ z}uqdon Owz߼9/{aN#?xk߉  q<Ӏ[}ow}_?pROa$k_>d- 0F79'f"eV($yO4s2o]ն@J)1bH ̳^VCP+P2&J[|ȈC{XO1|sCjkd,yתdF5t?ztaCD]Mu`ۚ[ܒ`A'1|"Ґ0/WRyxtw⠹sB8pn'njFVsӵYQ8%Dl|3!'"rv{7!pQ=UHb!b3OwÝ'/7{'7}kC.5Goۻ ol6nsj1B؆8i'WcbZ}4g{+_8MqR.8VJJqy\ 0` Xz2CCP !?FC!YB˻[^ئ1@ЁA>F/|Z2*m Z^&ҁGQSk7J}0eiZo*nOm8GHд>5HBX=U663ypi@8Oڇs a6JiUɖ8\.]DؔXv ]o"I.;k,Gm FW)~E`:j`LWٺG-_EGM:H]xhEo}|f/yK^, S0Bd{s詸>8Q'ρwdp~%/~:O>x;NԺ 0`Id;zHBq$Qfb6cMDNYsy$$~|CMTC @ Y6+4oAN]X`=Yjଃy-}Mb9iʩ;I)(ޮgnYs|KCVJ6 lUj eM rEJ+ދyz)s|s`Wo46 ` 8()  1?XKF E"z m8y<&VGwkNiC9xH}wtp Az`CHX"yGmiqxp׬}Wg͙ŜL??y{8 4* 0 !ܹDә4%"s:sgE&jRH1P8JonJI@;K+ @?hJR= BrFLkŠ* u\@cBΓm yVY*]*RJB(4BYeDlae-`˹\0̕~Df5`] $9>-ߴ״09{_ p'AyR 0`s*9K&ٷ˄TRJk ص-V1{@"% " ;3f@Y+c'h_ɪѡ[/!HGp}CjRcdǺv ĸgj<>LM GuogajHi c+{JSe"עif ~[4c53;QvPD2ڽ:KBQ}Ϊ ,sȵrlsSJ1YyG*vBI So_8s?=|M/ ,M{gYukpA arucQV nhzW_qX`O ӫ} 55cNP>x~F,X"sdA  s 0` ,ܐWCk(eV;WNI?L1 Q`aɴ!Hs*wYQ3Gx+T5 o&:O46bZ0,Hu|foK)ɬe/9ʱClũ\VAAo^=@KJT|NR0eAt΅#:D"qbüek,Bh0{fYk$✿~bDD0KPN@׉œXA񞙧14HwE Ir$@4ZމshDO|O>=?5 tKşw➟t sXGFvg]B ;xW+x_ ?2_2%n-x!Eb D/D&kg ""(ݨjs$-MX%x;g9T Ya)|x:$ neV(\QY f+!inĻ*rNToeAĻg]џ +)™"2̬1MٻH5Z߃L1r(r_KShb /Ri3-ґIḎiYU,RzD4Pv^| R; \ @!EZ}fK/#B)fүYڗW9҂c'k_޲щ{|[Ǐ Sn/}7ߏWG|Y++˟+7-D־_;{ޮkz?{CF! 6䏿30`>0Kl?k}mZcq1&ߨ*ݬNA&*QgT JϨZ$` LE%Umڴ3PfinczbV-a"⑀%KQA(joʙk#P]NOu=[4f 3GU ȨD$zR5ڈHK`BVR/#Νj^l601 c(s"Rp]cH"þ]Ogק:wޅCOޯ ׽gu]4~3"GN9PŽHs{v`ssx݅W.a韊ih7_=^^^ 0`YM"'9flA"$uwF|Eh>27ne(*K3QR.Engv^;Ǧ1*JE9Ҟ=͵ 0``(wia>OWcJȨrxiξk6c,L3Rr\ ݖFbE9є5ā"&KE$!YGo.M.⮎ݙ:ou)cRJJ߀b74e {#.J[*US*\@Ȫ mۺbOdaՠ4iK< DTJoJ 4c9Q6@ ѣ(G%-խ r/<7B6u0`<0!RRMSRQ5}cRͭr)reeL:_ CSX5/`P]jl ,HÓXt IDATRVl旦j eD`=5"h # .Q"VX 2Y@FmPzdb],~֚sLcU7i6֤ 2=Y 2T&U߉HҶ,!=Ե3=4MC0aSQ$k :{Fs}Kef[,1pNQ; ݄) 0`>P\5ED3#Cb *Yf_k"LϢtvx$f$jZ^Ww("XZKY+>`˫ǏF,lA]P(STP&;^$f ГkW-4AV"l:<$9%e -wNCd5GtX{[hV;.3:GuH8Ҡڮs8\1՚ؓsΙr0` xޢ(/rgcFyDQS̓=jI)ϱ@HvŘ-e5f0eX #Cpm3:TI8I )@56j1'{UөhTA2քEdԹ.}!(֭ڶq>["h"q)*X] el2"3iW j{giyF)%|G EV5GHFDP-PyV!a+$UΐMZW 0`V VBܽNS1k$)%jGi׹cO\wΚt+w ,CT7YGs֌sJ)X[DH3EU0/ckf)w*hc7[ob +[(QYۀ=9H) $aطm;4 u PSIryZ{]>cZi|4D DcֲMD"XYY'@NG%r꫙-q[O)#9IY=[bQ qbRf#!zff֦?A 0`+t,4evmI6M)+7{drozպ[QP<@͵jyLb)oP20+,>:-  IC;Tc('i|tpeiuuL`c$SC5@//"]U>Ȗ;ѬH`_~hkJ`gOF"Ft nZ;D8%v¿*WC2k)0}ɦjD1Ĭ kĥgH rkv[0`g0>F39Q%ttONW$w0%`J-jr:fП뤹)\`?Q-2@X^ߤ V$>U /v#imO(ͺpqǜ毫n/譺bDD6oXD",t:!ك.dj(R"#M}oyKK\iYc# HUSesm$fIszO~xQ2B I:}0Kz}rpf6,f]C0` x>N9 @D,-ҜDfݽvy̔<}7LR9JMBc=ƇM"B ~aRJ]שB)v @ϡJmN& $ o Qoloa'acAF ;kQݛ Y#A Vul ֳY!FPVyB^GT[s.%mZNN)s̕y 0"c""I8;9yݐ !J:!鈀"(HU`AbTHGi"UUZ  aSw}sǜ3fCȒgS|'Ɖx1&5Ve^HX $16S*/!& Wl` њҰL=eC$^Ap`sĂ^]%(!2  auؗcFD"H'`Yޗ_"!@,MV{5uez$:{0Q! T灻BӎjeK#e ]Sbْ<3 [)y(Kk uĐ8ˆ'+)G@OrB&r**l `DܢD.@оƿ+IGe5حEa =j`]bl0űU\$QJ11N8Cb@ygF{kO9TS=!^1F>'ȧJuj-E[CTxo2y(I3c,'yAi!NAt<heKӺ}CA /CK6dYh4t $عR7k͊ƚҕ;qDb,['i!\N"cZߞVYeUVYe-sF( D%7pK-E^eVpPMb)O#PV@|7G VGн@2W9Lc4K'V+P@Zu)0uZl*Ji1$ڥzZH.V̭̌mɺHe0"E#$VɘESYZ-!d,-).c"E3s=,^ M*%~c,$}7'5kgDx}H00,e9 { 9YÂʚ{"<1`'{g'v5}e\gVDN+*yJ;~\51V fI$s (a 7*V@ R"<li8Tμ[$ZcPXDՠ4.U ")FVqՠzl6(u@{:7 G14A-DgH3>+JrYftⱉzZ2Ќ1FBCSլmRk\3ߘ5mAhc̀+>|a+q}ӧ7%8[DDyˎs2l6c1ֺe.K!9!<%0eԣ(O>9v̀>传f͙s-=p6;Z-RYeUVYe}zP1  DR a!"$ޢE@`.2ˬ Af6Y&SUG#ʁK(T} f U%@`; XM׻Kx]nr4^R,H9 0)7zgggZ ;!X}eplA@e,y΍=2̪)Ǟ93{,YMo5(eD chuN(*ZD+?|&fΜyͷ̝;oϮJ{WW׌3rzޯ_}ԩ&8oHd+}fAk.GQYeUVYe}A__^99 <~YF9sdc'YB`c 8g؅DMV)9G&"RLj~fJIPoRG"&ں |NĥQxNjVsdkRBڪC&$)TUfIBR}(š)+527HK.Z >x'1]U{j9ykubCyfF42W^)f( y9/E-gCteY$f"fY9=< ̍΁/WQYeUVYe}pC:k0t|'H;0$ 9;眳Yy 8)D>ii+kC FuF ݅(@RFLk&A.m$WmHA IC?V]TcL]FŻ>6;)9gL2!0 .1GmE DibMhrNxWRML&@@= / iAX hQN3O<߿a_8( &:HJ qj5ՏҙOQoM7 ]GF GH +F]U{)@=eY9p\$}˛ J#m`e!:C-YYieLhdEQuhaIP3OE]4lذunM:{~yo,TЯ"9yH{ޣKi9Yj)/vCEU**lQD('Z A B` s+hR0jBD{G{q,'t|}"#ޅ\Jh2 ^i8 %/anMGhS,H$Z2 W0G$R2="1h-ʒxe(KURzѹI_d$ؒ5B=ŕ%@CVjDSpn N0mmm{,5'8=@ˇq'Mr>ONj48dȐ&<O>W^{_gtzY4kzg3lnXSMc !&s$BNu[wksoo9 M5Oy|" =:'^|^Z"kOmU?kf)>9bu>Z0/]~G])ϲϛ܌WoGvЖRx?|+jۜnκo#Wh—껱t>̼l?{kTc+7k_؁yӤC0IB#.%kMQ?B}@j"G#uHO Ibc"%e4-\5{P2+uMIfFC{`@VsE#u'ҝ "Hd8e$"zY =K67@K2 rZ(.˒S<ϭ5X;@ϟ?.{W`͛<̙3dfvE +Kf\Yv5_}t嗧<ϥsNS=d8G.^kw4X[tΉ:ooY!?reqvcz@=~t3v<[?x>hף`_$H9K-+cqW>][0m]h<şw?ب߂@-~ҽ{[(ޕ*{_qnCιLB"!7̨9jZcyGD9K$Жɍw G!I)ț=݆4> Q,HPD IҊ4+I &1 cqEiB# :N^(S3ܣ`ȡhu"k5"bAUHBV}uUb( .ΖkPʀaxazϥlϓ~bR 2yo\spe_w~{xl, ;31fmz⬅oͩnZkmsP~?f#>Qv(^^a2rFk :|jCro潧kqFTVYe˓k9C @JRDBHDb,"]D=cqa"jGUc^+HVo6IUA.h/cP,X9@oɀd(˲b[eۄA"JQ -2c {`t#+cñǁ4ӑ J"-Ge}eYƱP#Bڊu bSJ@YzCh%ɺ1ȗs1{/Skͧl}-=8W~' y꫟Mn>_/a͑3?qq'O96hi1w~# @o{]}x)] <7~ɉ>wOw__*x.?:yUw=o6~qN|oEGx wY׼bl9-6?fؕkS&#{>5>R}#'|F͡wf CӰ~yw_n񌝇g.xh>{Y|뮧|/C;tzKO. IDATqǵnQC?=ܳ>3ϹIKE`ULa ? 웯?v I[i e6b-|9>L|򩧟; 6Q OeUV&"Ck^mB|]$NKxGLAD0%cҥJyJI.vURI71K}Z^KzcD_uHRvui."Z?J3BRb=&PRFS5)z$D1(x@`f9#"bK:P*êtg"CvZ˖+&͜% ^1&˺}ɍwO"{0LJb.-5NEj}= άƃgF#hЖ1_fV5ɛm}W-t}j~okO`n?zvERZoxy{/3+p'׼xċN:-_{nz9K#s"$c7'\~ߤ]е&C۰~}喣.܅_<~O!tV#Rwlk^?wqěggcX{X*$DƤ[.ziN}x[fm}ּC<&+z׮[tN wئs`WZ  拻ꗙ|W3 ˯w0А~Y{ WqOo0(lzۉ}smj{KUVYeK)@P*9#f"_ fX B$@=2!N}A+Q8 9%Z( C0wԎc*o6vWڼF:;'?s"[2(5YR[y:7!@EQjfv.\9'JIWG~ͫ% !Ɖn_4 5Ben=n+,K"oӂX./ J(=!c *XƆ Cz)v>-azIǸh P3ScGy]\~ּm/ݮ0ח>ۗբB 5auDcŇA?{Z#ȇ܌&[bs35G_u;.z}luɡXnziP-v|^5u]z;@fFch|_^bmFs쾵N{s뭷\z??۾a˕UVYe}Z9gŨDDD7դA]_ޒx"CjhX1 JIqhMir.ؽ,ᕖ Po4 )uJ@ҁ3ӢE%FT! #1d*eY k2d:<0Z#&ξ/$Yn* uKtV)š96(0Qu'Uۈ(*D!C9@]la"K WU!??4 .-=CҳEɀ<<3t>^l23rʍ=9_Ưd~c:f3_||< &~]Ӛ K;jw~#8j?GZ>?,dvd}1?3yʔ)O^K{ۘ%nC3Jvo{K77d73ؒaTs%x1bw1{HMi,8Sι~EBGW+ FLBQ W/Z) 0=urª@R uiN%(΋Ȃ"[k#c:Hos;ǑoHh\~K#;"juY_R,xK 2}!hoG8~Mħ[+v-6#cw'WN:?y{z.w~䛧Kq @ 棏1{3? `?Z}v|Wɮ+{qG6ct#{<3z[veb(B>WhcWf\=_?3{.tpQ9skh1c7/voQ]axKu ݷsCoܘ1;vdzV:;W*l92V\QrBG"k3jSd&d슒S΋<I*E!Cr$$j8cmz_uhi)rLW@A}YNk^`PN3,cq sgꫯ>bp@dx7NJW]u׀r|)zcQlkk̈V):Ubmf ,dg KdҀ2^Jd:Pe6n49(3Sk0b! @*\(_e@ "yIj)(KA:H+Vn\#_ SHm0H9-]NWeYTR2;-n49aDeYQ+]2Mvh!1h4TS+Աk6r5VRB({F1Rh4^^ZCDE̷5i}%sd~"ze.qT`E,8Yo:q;;td v4qm)o=A.tŭseUVهB_U-j4c3 (J"B֍'2{1q@1VJWd(زlfY&gW frqA-"}L H>!Wd~@ bjV4 XGe!\/eLzTGDebD$n\Cʲѐ^]w 렬Ta(B+[ |=**^YeUI=3Cy!2ƨY-V,c&5D] &^b{`z].rn ㆉPXl+I뀅 81k+6iC$E:II7~Xxmh4R:PmZ|)ˈDz1՗7?𿲦G({佧iuł-{ j>tk*ޢ fHD(Z-(͸ҙ23fY'+dHJ{4+ZkK0!+"Zr!"0 Fk"(l)UįztV"N!aBN^C̀H@;'T3H$SMrBGĢ(Q(oaUfd9DK`"G=N\ES{AB~Q4f |l1i4_i VIOQHLRĜNFHQ10Z$h]B(t&. tZI:P#N&tdl"2(1HSw9'qt"qyM_͘lk>|sgoDeUVYeU6oN=]3rtAUJ!YFHP E4l1`j5Rt{E)"ԏ !] e=6Jҋ@QiqƩ[)ApP X ++۠sޗƐ1YYR܋H8<֚,}Y@1wx8 a,쑖*k[4۔Vʹ&Z˰x̊ u2*6Sd}s䟸oCY1Z>c֠C?衼`^G޶fFmpdlUVYeUV٢,G5v-+]e H '""CƐqgƂ$<{Z+ *=ђw'h!){SlP+|_4"ޣUl6M9KQeH+}tZ@TrQ2Bu]]C"$x6jG骬*1xW y' @ ޳Bl%B Yea,<0SOq VL k@Z I[=+,E&PET_?c7|}A{@gYf˲4HhaόH1[Bctq&@hhxYFhp Y|윬R␬ Cc: S4#Յm֎ ,*~r5a! ݷ]ltA;V[e5dfo1~ٵUeUVYeU܊Zkw8Wf6#$̪3#_D@s΋p>>S%=gyl65zl-‹TX RLH5=X&$KfZޠ~Ǖe)U 1C֣( $ԡsa\yf휅R>z]kdyDн'J}1+fW'5**,5-@Yw[k!A{YD EQ0!>\ñWZ{`콕h@*Ѳ :b̢:ӃpkUTYK}dbvY"1xj%f \4c س@X,ffѱCScT! ːk +_1@ɠ%kYIzc NHYy^7&C,3c8pZ֡Ժlzheb*JΙqa 8?S7gv<=kE*>lP?K/`#hjuD(u4"et7D eh88 fv /•9@v༵v!"UH ie uƫ?V5!΀*^ ͇Uov]PJAi.|& HʲRݘI  QTrdyK @)CB95KDe|#L2g)Bke0ȀG{eΕ/NzC?5oy4x~˵=**{C!V3YE4CxLO#нl-?$\/T $`U4peC]R gY( E 1R4.wOʩhlb@/j@(4V 4rp1*+-cq.r'I(]" r /(0EPG'dnLadv( 0,.TK,_[cmC]]] ntgfX>ىس_x**ly ܄S7]+D?G~mmḏV)v``Qerc<>3oHG`fd|iD+vkd- .Boe(wY kT]61!͜T!k^/hP 593[niv(;B񁵶V jmm.\7/O EH,.xE c4"I9,k y>Hu< eiLB^ɉ!DjqX̡o7m#UkC\_>I4r| &(""dfZ֦Ó{eq(J}@1QA3y{}9hVYeUVYe˨=胟>s ?#%" ԾU @?\9&( xF#;%jd43H{CqZ KWyAt.D+Yc/=XT%Vu#2x!(rL1Ru,AĆd+ߺWO7e# IDAT/"" l(݌\:gYxpRUcB䗔5ȞP#Vp @xfF1TPW`2+"*çͿLaD4}_ YL!u$8N*AP7t )Hgj\烡;纺VbݴBm˦(>Ir:mR$ U2~-Nэ$`b.@@tx * ik2šiRPZvٿ^4#3 ²Ecea[Vײ`.,7<㵗:@e>{ i*D*93E@ -H GJ^yIBZ-4R~!i+Awu@}}SD,K dkABu=v_ߔ=!Y)33\|cQK1i6|)˒(hw"˨|l MpT<ĠUR2ZUl4q=gIwtQܫ{C= Vv-kYj>³OLuys!&:b!hYJy=01[2` U؄tؕ}G(JeR ˱V>Ԧ C5X BĐ70'F4GeR #"B"B*: bS,|!=JZ@w<01Dl6%ѱ:ッڲ,LkdιJWԮ 9^\=LZ֠&YKƘp*.\4]bhe>ED, `-%;ۗ[YkƓ}r5n+/Nzɵna^ !f& V\]bRz=^ {*!/qS֌u /i*i }rI%&uDLGOE-2)@ ZƋ2e./#/ 1eqe,K#iL@LE!=̄0.RyS(F@݀n; ,9C"j5""=\esnp-wnY*+9ʻw]9;ݎ;o[!p1ݠ:zSu_'ly.QUo-~guGa{W 7ʶ!5^w=p^X/GˏU#@Z{id'X¦rwV-|V{|cS5_^N4zB{csulNin[tr޻՗?èU,qB~-ܓ o>EsG<&=ncc_]T{Dc8UPEB1vUD@yt񮮮DX@"@!O5-YHAibJՄdb+[%w#UQ,&+C^}D Du;]Pv$Q}Ť=pO!&IUB0=j5h]ܲ<*INLy'N ^DpWefi I& IڡY-"~[ӎS{O^=a وuv7@09npf7<7 II4-ZY??~}ӫ?qNٝ]}˯C_a߄W'VZus{Kig˪1҅9o ]Vpa^Ww;y~jۯ:sF%\ >d_<#<gvu~tÝ'NϷ:CgK`/^p׷l_m=+ Xki<47ٵ/7PO~a}w |W}u4"o~5?OfE=ywi{`?yOy˚uék]ڿ_1oYZ;!+ȵcn4:m{0#$@4dz Q  |Mcꐨd*lP,HR'p K^#= Y9: }l7x#WSƊ4o+M9K"0tY%dN8\z:QJB` BױDed͢jeeHWԭ\lˬz\'O➵8^T C=*ik1ޢDT[XtׁjIQp}ݯx۱۝u7OW t$C066C"mH Pr<~kC7ў't!k|MWsy; 2~%>KoyX6sﷻ =F/=:_.9v1ve}vd8;OxoWݾ Ξ74CD_7.ff; X:awo}#>п/À776tү?a|K;_r+/s_^TػNoލ؅6~nmbͷafT?B|IULN_S=L9}!#rclf,~/+7 ~ޟ۶jD״ kjzkayi26!\ܑh&{_1$XZ- [:+!x/ovFf͏7?ޠ<_a͇?y6*VzU(G @Xo;*/li[]N=~L/]w7GMKH4pٖ~Kvҝӊ_n}G9e#> d+ ~/{_}!|BD  C&#Ż !bK -KN 9S*P3!a(V'DOXx眨뼥{ψiBLWNKt0aȍ?C+Q|WWJK~Y^gO@&af}" 1ƱG紃kZYJct1e kYrey_a#0&>xgb[ !oƘJEwn~9蛷ƻ~|;|׎~Fn=O.d5=g㖧﷗w|W @4r#Y3~v#ο`qn}"!;W /*Ic|2k3a3CdINAF sKVH깥E4`zoZm-,rfmV !Q7X̥3`4BuB&?bdžo6?aBdY.\"HzÙaOZ_ξKcO2䜯k`XV7' Tr"'$j\00S46p_FC@5NIpB—[@Q@hnڢWcdMQsN|(Wr/@")z0mUg1lT4! (=轗f[  !BBeI u@HUX ޞzLIRV ℃B@E=̾J}hsRɁy-I"""DE?ʳ 4-"xfRYi3n5>ڈOA7W:#c _;20l܍=΋\*!"Bc~Wv՞':ʡ;T[~v#[|b[?wY+7~q[Ko#'fzǎn#ꔤ oU~ycsw~psm"6-n[֐zߗ"Mu{~^^ڂm=v򝷾Nf\ D\Isf˩wrsϼ+Y絼^eYVղ,d q$3އW:D!zheIb4jG3"QFuf9zSո,:]o;Ί"Wu]s00 P1gg3#gB̨0 Hΰ}oft~aw Jؒ{={UVպ+Vp۲e H$x"'$K5FpZR٠.nՔe|Yaּwn&icxUCaXN,F| d,sR'bMiPFlC]u- C4ޛH{x{)qA]8DJGVJPkR:PB#> ÀrZi-4[3ְ>ߑ;z^oǶE5P-BN zoVB tQ- (B*%0! Cy@84n /abVeH=+$Iim ^TJsm3K` ۻ W!K qExl2 HYerA&?OS#!a1ܒ%7@#*a2]ץ!e%Tڤ@ EnqC!8?(l2)vXyց!m۴p srb~ Z+JЧz]7BR:dэ Ad}PLo[BcۢwųmR$D @k@@GJA)Dw$_TpW)8v[ש9=Y&ٕ}R´U呉Rl#;}y<-!4֨A ׁ8D-["5#-&Ie8pMI,e ЇaapqG_Iv\<0`Yhhp]rP۱U+&5"'`5-ZDfV<]w$c@~d@dy!_3;<}n(.ޥ 6oڔ2:Hsh[aXC>*J֯)\t;¿•KK֯Uxv>^_y{eZVΫknyM? &H #Yj'V* Bq@[%(ZO&o0NCz0 Z{7c[B" އMvt eZUs5^rYM,asP/MH4D̫BիW/ZTkݥS͛K-Yj՚wҥc9SY&3)amѴ v]7$e0T o.\d.]vY)%BFban(.7w;S& 0 ]O*s;**.E"tvԱE@GZ2 R*B?RJ]:N6mxD"}K׮g:}/;(77K^{ _HXlCoW؎_i[L?W\nfkXok~m=t Bt-\Ϣ•i3Lyեrʕb^,5󫿎cK* $FuM)5FfVJ9TJyzBa%~eUb6K)m,S:`!mܞs1"R"̘JQd0-gцL, EYi. K@'{BT֦0|?0(\JZ(&y^"Bpz"8Zx 4 '):VDYA诐 0 L!TAJSA2hc%M"~B):R:JB[#BHGr¦U, 9aB~LXG 6pȇ^&{CݗyCnt'#cUvl[T_[vO3!sˊj EMQh P$|Tɴ$GI50ze0/,~!5 ZesoB)8=&Ҕ잕)E3& p=B$x-gжs)/yBh, T1SK ,.,2D$\G)Ei7#X}.QJN07cM%b.MC\Bx׹%/t)[a%{GZTX9h8B(V39BJʊZ{c@AABH >*++ +˴;HiFI?y0gz S*ߎmkk]i튅Y!@vad CyICj Ḯ@u&= ? 0DNhDd0r٨͸weErvnzKeqږ7Zm(-414`dQV' ҬvbЅDz/v o-;amcYO<P)b9q?A)pĈmeajB`"@+Rdwrh 1/'4u\P:28V3ĺ0ҝ P! f* qdB*H(  AZgK Qdxp9>|w9cRbyyJt:Swi U3^xYEMw;h+o& Ctsul V.'byzs*ã~;XvG\v<ח'Og>unNi3նo5mDa4:oo?kC8d:&DX8{⚟[F50r9j:+iNîknwn5M]|E7{We R1 m9ABǑQ!!B8JZJ-T DFBd&J$nT"],.'!HtlkhաHpQr/L40~(ePB 0Y NVJ~)▅~ӷa+ўQ 5ij$Ѡ6Vͼet]SEҦAA?aoOZ8{~I:Q:2rѴuEhY?97?{qkv_~KTʯ_aC/)vSO&cjq·_q ξa>kmV5M~ǝbJMz: !jb0I ^T={'_(3Wu=l6KFuw//S"À=4P-QUk, "`[nhM@U v8AYT D:>#ګ^(T"m>gǟj]PID~+pZ 9E/>xW*E:{w`g0_=8eڈn!{s ٳG[ЀT?tDϯ *>6&ܹXƛj~Y\HVFYZ]+u<2*Sm39=_}Ր>lfI{K7l1B,S Sk`#![1VlvdpdnP`qm1y`GMƜ1}> ï/&Ң󱗟7qQ{9X1çhAy]pm/-3ns著J6Ÿ6:u3 ,^4Ő?HgG[,Gu]Lf~#֤U//^,?ܽ?Yf.?p 4k47甦-E+0?WnoE/y-ovȸ}dG8*(ꆃοvزN;oF;[sw{'o~68Mv9ģLWsk5߹@ Ō&Rg7}]kkVV7reFcm?^sbbIUVw {ITK7/OtmNK΄M?üjey 3o/{o~Ƶ7.ɍ5j~+QP1kܤUMFǦN,3~,g@V =:ҡ `=M Ion2WG#jT""wg؆ד@ 8w%Ì'! h'lVRM4cg Gp;rah+W[ `I SA@r$qKUJiKиZqp~J90w\\`A)%tG,݀]*duϙn[É 9?Z 4b^I)]#KF 9:{B&?NB!"`n}Z0EqMť*YU,O`EpDG*n5-k9𢋇w+}~̴d44VVz왧8iub~=k߾9yG3 !P0Z4F}lo{%ʛ9O;p=4}m4hY| .?B; t1Ͽ[`๗}ƿ?Y_T m^ǽw g{]t=so}mfIƫp]bKX&6Ѹ:h*,)X?Dļ&So.uyP =-$Dt򚨙=kyNS/=jw|RȆ[D:y͝#}~'uշc^}nlH9 YҎlGeNMqť{iy^~HO* u:}S\ݹrZ?&y͌ղQ?u=8: flM5{d^}5Cbvb;w":-zyn4Y%x+bp:K/>X,i]@DipKwP|/s)s&"dz uX-[$g38u׾묑}f<ƪQ޹Mg4~3 nsTs?v\xQsoQ L5k 6vncyFC"G)@JPD@M#uѳrSc!!CK7Y "YcrXҠa2kA-ݙ2 $lL.UjVPhGً g 9P`0y7zm 3ȍ@QMVXGp _q H(&4ٌJ2VP+ RJe  jNdܨ3uk2*FD)$/dU2I~B`[ѩ_%6O+즫Zۢ\^j#?Οc8.6*WĽXY{%]cN~;)Jj)\Ð$`xfȀ=7ڛT3,1:rYp>D"APџBX,&@q:B @ym8:7BFR&UZ`h8I{6#)Ux~(4t*^O! bXPAg,yajWYz?tݧ\6IwI!*W/mSO7&94 +3&cݬ=B(˯HkfyȤvZB"j9Gga~Mq WEZ{cJeY ڧPAgOC6dOs@-nQn2.*O`ʕ JnbU?9ɻš6`kҡߕ7 27_ysȿ ^~ăH&s% Ӽms<)5"0O^b ,DUu/pM{0&SD@kJ;bdTV4y,0'e0sCddIY" ?Bd|*g)֊NQ'NC؉hv,6!$^K$0HQ:|6`1/ Oa!2f؀T^=W^I<›&Lܫ(N 3 |5;AG?-ajO:n\O?R45?t'\}*?8xKT}}NJ[ZNǃN?oV }f&CM՟=tui7uPz/ ld&{ # yyYR3q#U >0sϿ?~7ZZrꢄ_tJۯ0QLTC}w:3nǾo.W?O#Q™iy.U5,}Uq^pQYQe"QRV><{?'N!+.(jo? _d6;z|mrcNO0eAt*NwM2>}>8o [*s@4yXU>ާ@]O6w4B.ʪ>O;y=?WO5gM|.;l^'_2"+k8f/Ӎ^bk`}D?J)Ra`PZH5 rZk8\Fy6YqڪTseɂThPH]Y9˧y,s'J2ņ[$Lq6eDT SKʷ04!8$L%DYKJJz޶m[WYbŊңsR& acJčYbTի͟߸q^=zb1DK:|5{ E;wҩKI)Kq-<783zv־}[@@`Z򿄧 0Bϛ7]vx"ѵ[qcGK'}yg^ߑ_)ÛȲ=sj?NÇ=nc-O7}v]Snw)-)~O<"e+ '?`g &KMIEQ]Tl`8V:ّCv<ѫu\G!2SkQKt MQ+s@HkNPȼF/})W)2Mf鷭Z&c>J%ix̥ݣBv/:+?kErM.(~e9³153nUA1bVTYL,嶲·12r~QBӸˮ;|vϔuJ: yD}&Nlʭ/'3`K7>;$^@cg`ƌQ߹ Eu=λri7MSSVX1/{kovs]\r? Wژ4蝫Kի=\777uݜzj߸0#{dy:%&DH54z h]?!{JU4e320?}O {ģ$FLcn15^X?'ƕFI킲Wg) KsU5lM~*k ;G %aSOvUL9`ñ1'l8Xbɜ̫ !MʾKol%ՀD"ᚬGhR+ P Zu ɟ١M"znFpgm&b񞰩 BrB_I))=,`i,&)f%heu*;8RQrq[|: QOƫmt9~K J,m;l\=ggOKkmJi|7`<'[v$Gx>e#I|o[XDB0i“Zk"T#!A hM`N:KR(mHR!":5]lCX#bѽ|mJVIט{v'Q?la`?d[Ѡl݈ƶӗҟVzg8D2s2j+Ab!XXy9r(<ϳG=D&L6Y əBfQ:B6RNH)P)Z|"L !5J W(_4yoxopÎss5qz2PTx<;¿bYϫm};@+Mo ן5J/]S؅hčgD ?io(OL6aΛ/׈BJB J* YJkĨ+ЩP6 FgIwR';7?˓,J+'VM0rݹ֚޶˂Deg%p۬ϲ"Z0HGL4bb4E.7@3#>rݻ]D"P پ.%~@!D"A"{h"ִX&&̪S gE'Qh%Faa%.r l =2# $ԓJi))+.@$ץ-Ƽh!%4wQJAH;=eYF!0J3'<8SOcۢ~ h$CT\Y ր~J)r 2LkRhM? ɘZk ZI0h|7 Yn&0tm^ڽqZpa@4.aU*=q@k>ЅQ'f{&OIV_mBAPqM_[!H24ypXi1EhЄ2q2@pm¨~[m4s!1:PZ|VVEAL`@&{pPȔRDoq)裺P[;yuO8uWY5dxO8go;x߯~Wl]Q'Ed  $+mjb#ք&{4ɇdb6yh V.L@)2Xs 9M|%MY`coѠX'b:"`wC(# !+/&'M5ڐ&.<"撨V@0j_}6]v/]jժtڕ~B#$a`wCxv,,,}X,S^)S08_~e˖ݻvKӂAxͷ̚kX{.0)ܜ Nխ{0@DD".AuTϖRYn;kv,YbūyաCbkXo붲_lwxBknڤE!XU@XP(ȕh5&Hky=fGF"c4!Tm6D!HAf7jX(?F-Z)mMsgTvJ6"acPaY3uVk: V}vܹs瞃O{F!ץ?<|7,{Chղ~%!.[֠ێحg׮]:w ,-<묪j x̭XQQFij3 6j+{Iդ ubյ?PZwѻO> ~mo.o6_}vҏOҺՐ;K߻uݮrl G6\#t؈:hwM|K/|?,^>P{Ͳy %;׼<-?IRJIE[aeLr<`͒X,Fj JQ\"WA"HCDJ$Kղ,r˻p\] JD"e"DA<.:4 hA4-tH[b=pެO-*[yX]z~ YCϺug OxY:8ֿ9劁<:hӨ+'֣K^^x=-Uw;8*'Tk8e1+?~;1=t j6"(t&u`ں%.30Iy\);TF&^}.E_>EÎ;olhS#IjOe_?:5AoS']?± ڵg}:d.9j_@YRw3hKf5rkgɍ OfLthn?ᩕywoUAh%vl>7}mFp1a(P<)ļ;t@d-s/+ إ |$"jZ%M"ץȹ!\Do95`yv\$0Ri~؈P\j]+S&m0J95f(.@O%ڪ4)`AT*U+qrrr>'R UM i#@EȭڄFiraZ(FI)PiaeƹK!{eٶuZAբf9|?:l]kzlmǍmvgVШ@Ǯſ*Z&Y\Em@A9YgFhk?}t]bX|7|5u{Î_|vm ?t1gF䇇>=&o>f/:.)򚓟9sc?i X5^;6_~p wunNq:=4᜴ _ug~]h)SRJG ðw_ξ㏟6?'q~ןOPɉ=m!T96اŧ } e,g{`(WxͿw|N/OhϬ:f_έ]Q<kZ9ɗ nu]VDM{vw>A޽?uˏLz;v&w/WCZqvESW 룇E7ocƷydDf[fwp5[`M7kbm) } 0Uԏ5l`=u cޜzUu[,i܀YOpחm nW}zyfvr Qugo9zJϙ~co=o8 7xOx# fP h0dA\42B]BHH`A܆POeI߲5&*JECFz!ԠXf8#b6VuJmm FD d46<BI%!5y "jx͌ab " RP{^ Cy9J)A$F(U5'U+^x*T IDAT&{H)B"Zse(&(m0fl*)\kiI](B*TZk.RP !P))l`}"JxʎWJO=zȥ5_3wCAע{KJB(0b*Z ۽cUG= UYaéAA,pE{}*f\;›}x@Baڏo:ߏzSv.ӛP#zutJlX&~~ʰz#y3<ׯH߀(p/N{6_9[^ɝ@0}/?0)Ď|Ӫ)'ZtM߿[?聡}ڷ퐹=Żj#;(BpeZ[/4 c[P$?>񮲳od-Zp|K֋Y08Y(-[6J>;}YךiT|̀/wyǜ=snUh:[^?ۭyco۷%|Kt]ÝhYN9 Ϟ0`ԩ~ƽr#q o{|Ygگlۋn@H޼%= VE'VfEcJnVZxNMѓC-aK5K~:-}jIWdd,ep//֗G),Ͷ n6^R]Lh^S&!eSf2' ܽ~{괽ؼi3s 3f|q!=D|2G!)VkV/^ǑA%ߒbl?B8J\6ɥ`TʵHH( IP:jmːuT O VFǑJ[#݆U 6 u+ݔeeB5`VRqά`i+)5b1)emNZksi+lCF+ (!4n&^qfiΎ: vAzKG:~k R0 !tB+]^^>ۥ{BT3r v_nSƖ9Uk:1SgU݇USL}#Yq;2o1:|r:_\3W. $BX6ko+O]ۭX7$Ӽ9.x tuw+ 4OcFW?h| +7 W9aVwZ׼͠ ^eYZ^g<=pjVMCd=wSn=3]bbMi=S+ >NdQzea'v}^Zr\cnn·QJۯXw)ˇ-ssxo*c{!g^}-ѕud.6>,|֝]9qPеGT+AޙgЖtGwP#B BJQAͶvdye~UlEkV5fk*~׬-xꋓvkEӲ:=[Fi[O׾YqU,?+kŲp;vؑ>KCBfrf맺;VɱCc:n^}yo8q}>v1/I B#0d-' rè0DRR#'Xi7itYtA!j#2ó=$A=QJs4dʓ;d1Zk^0*Eb~8u,:ڂVbw H!kfAz @*ԈM~%v+ G؞V. 4|n(0TC/`0BJiuH8fyOT cǟ۰Qޓc&׮i3l>&*8');F׼[{nnE#kfw#dkc]=UvBJv7O& +YejYoN9(85cn; @BW7^r^{X~2uA'F߂US|棞fɨg?ks:]w/6x+ITOiʺX^dK{Q/zQMEԀ)V]Q_81n:gDPkznNlW3ckg` R~}サ!C9wKm=yٟXs#Orta-t#L:Q&COۍ*ݥ~Zl&6:;z\yA=o{FEW_UݳL|ְ ۭwr^[gvcN3:Q֡!vm2~OlWƟoLnhm?_&Q.d맺=Қi?7nˮ#~{~ Ms\a?@,4'3ۦt4Qvt+Xw9 Z[]#(!iSM]!H)tDqpZee/M`t;S-k|ZFnٿ.6o˻SX֏6_j_p/}fص>wڨS` |5Y.nl{j՞<7ĦF?yy1[ϨnTU[֯?j֖{9>lv#@U[>}clۣwZjj#v}w}mchSu~{oJ͵k\(63ŻԪ ءϋ];{ӍŁ#˯}ɿo^oٯDN~nWdNS[g?la0u0~6N~Wg ̪utZ`\㳗7;4z)$_=sSUj}j1qύ9םFֺ47|^n5?  OuޯW)/v_} صK/p9c >9^EQt٧o2jf]s- S ;@;>"kR"LEJ`QTeEe+M   = 22- Z6qG$bNfWWҔFris.-2FwDqG Q\6^cd>@)!3766kb!kȉ9YJY vב KZ!h":IkI9L۴t֣e>3wsn͈4q)Mpv6Cٽo8裷~->W;B9MfN˽[s?#֪'Bs:U:K?kqά^!pT^5р*go*,M!>q >u.jʼn?}W٣֗0q-t~mo<~^jBG+tЕg~z̏6u;a'>pA\<Y*D?>" tdU.Syk<&`mX #5boݰ bd2h]ԥ̌Za?߳o޽biX<5}=tk ,fLM[ Gן&[/O=ovuh]rOowz?!nn[s&9LZb.\?x;<'W#n~~sЬsϘ4+\[N<~EOiܶ'Yq͏j'\瓷ӑǃ'g/^)ןpҹG>bp]0vfX?S6+yī/~|եy棿=򮟾pgX\C #'0̞}`~Q[TW< ǿ_?/zmXiUEyg&מ5? 3Vo3qdHe)}ȱ=+z&͏9j֯_gko׼o&~[3^Yd lfkֲ: "`=Mݭr*rKRh" 1P\U^|k7-'AZ.3S4Ṷ!}RYڬWV&{K5~yC:|=N9aQ~¿l#0^?c.1l],E;2.zxIjE߆m9XN V6uXM 񩱮{{DMK޷Sx>m?bMZS޼p[s{PcV;twLꝆrAog^dgYsf~Dы?P=H7^syf̙łPWWW, bG"XW,2QB g2>/ *yB졨$Fu0@\. Y3r3Ase.QԍE H+2‹t d aD\Os9REQ$j?R=m"DbfN$MR%ucYDDD@>,hFnaq$ 'i-9c?&f)3gdq|{/ZhPڦ6YiNwg[Lv"S35CHƴݷ֞ ڡ0ڡUp7Cadgh k+P![ASf|A$`N70)^!Qjիyvv/j5bH&j4՜Z?DMY)SêG\@Be'QÇO:Ulnn>ȉ7tȑ#(ҫ5ײg砝:[3Ԉ+q?"H ! R@; (ѳB\"%hKwtYGQ""?⋧zs!VϒظԦ"咬[k"A?!0.!Hд\MzVedˢpn˵-B$飧i_?~ƍ;CO;4c̖[naVO͊qoM~gޝtmosߙu} x XHItO"Qf-)I_DPë*X>FX.L)@w9Cz @hfHN*)TҗNՄ("L'[ɉ x@ 9jraA&%)e]IzZ8rd D8=m.QFߧLMYOٰ}j d7pǰMdrZNF:I:@0J $FL-bV Plw]yq- R[Qg @nUg}3gμ_}ѷ~;عsgZ qEgr[m^xo--M(^Vaേo[u@,Y. caUP%gLdeHQ|+[!k\0] +ds_FJc XґƘHrBQ}}5zH2t(1S"01űaAgY45O*!=)i:$} r.Ml-BC\̼8(Z2CKss"Ҭs5R*R0#!YgSQ@Q㲵eBrhQ0oX;EHx껆[9i]y[ s/NJezqߨ ?lnn>=c= .xz%Ҟrar[EѦtXoT { Zc2 1^^j*j\zXˁxXVֆ׎J+0 hk Upϕt6MSHzOw\$I!rg-@;@dHJ~>F^+LWHRR"X*Y`N@Yb2R#SP-D&Aۑ.~Bft(L9ۧpFqd Cqf2 $b6[a=75|a[|s wGh[󥔡klGl!׾=-h @T.ڶ*_˖jtrÆw饗ve>BKK˂_mYܖ!=t >N xsE+A:*2OP*BA|J=@EH:B xfqoO>tLVʅAs8Vz!sNVЦ$\)Y$ItNslB:"]xq-.z9-fľ IDATkA+$tŢJ M"T* č1ό3^{-7},6O3J%kmTBCD&"!Jf3JD:d$4q$1f\y!\m^IgfϛELL>mLO9'>ڎ\Ճ'_7` 9{x#Ǐ;鎱0^{}WkzmjY^/[n܈ğ߭[78أa=ɓwqe=r[&}΢Gbt~\(@aHNVi]b` |_EH՝ RqܧW˄JR*JD^WDRD`i{UyIE^ *yWPYȴq2DJ WEw&A1r~.<RQ$%GwީSN)+dSiDZfa!wI(h}ÝG#u=fMḍz|Mo:xkn2#A(6{é9_N~2~ĝuMvYCM &Kɚ[n嶜s.JIDF~ۓ4[L-6^޾K4Y{͚752f𑛌fq8du΂! IqIS?Ʊ\x@e͑T dil: Y_`QiŴ@98nS=G Ҫ+2?RP(ht:O%$$ku§PFDq~'7e%'>)Tr}xZ욗vGqe^"b"ys~uٍu3\Mn1s[@rN"ƅ֊!3gWPzϞ+sf=uW*d?((:+ zF焽R˩V^H+x[vUrV,|AVcp%*eCÂcJ#zaTp %4TY,1R`% ?h- Ew-DP@oE*o;fkp1)`M^N]E=wg}&l 3gdVk- -Ϝs=٭vꔣz?+#F@d7kr}̖C[ þ?ƳW=9~m{ַH kvG=nuԻWv0R @KKsidp rCr-rm6"-$/:wċL̡/ !ab~y""[{WxW-|زj)uP#~^ 4=(&J!i 6H7N\U H|\ B;HGr mqvąBACU޳&tpE{ J񨌏]B nꐀOD I [k[[[lʌkBQ]:u2dFm4OxHXW9gӴX,JH(>6:l̕GABDt1p[bN~r3Gh6}(q/.區Ν~ !F'~*8k2eL:<%zv f'\yn{# -ܖ=Ŀw_|ŲZn}`gd;yߟt#n uk "8Zqf,Qt&D6ƔJ%UmV$ cq'Z13 $dfǎ, ^At_7JU0~ռ]Xȓ oX,j.B2lcaH,h^9 w)!I0J+n5١>~=ʀT666&֭  GoE2~dpNxc5CAGXB3CS&43uYy̨-]0kXYoD/oXȧVy{oΝӸ٘Z憬!3L?zm_[nK:QOJ={r_v}_tyg DŽ^9C"B$189* ^^,V;}Io BUC4ibg= %K!)7e].|,3I>Tp̮\Ƙ(q;[7֭ Q)I"S3DX= VhYѷ%VQbQ!Vb  Z0(SPB(b"LA[8hvvBv9|ɢ.+:RZʦbvޥC2}bݜYzfMu-Fү "$3xaS{P,T22}[n-M8I}g7s_;>M,a@XGc Wl ,_JWy{O)vu^AHiٳa1jU} ' kaA=zw"ᢗ BffR4!lo` {-KT%p EPV.MlE(Sy-ˠÙB @ٲcB$ I#6ӵ{@)0mV0ئխ)-V! 44xQ\iC.[nĶisfQzh]wg;;S͏PMYlD1 #߇}8y]P5;$K9Pٮ* W _|Au#. RNQ%(jE* d>iC+k3]uuRd;#jA Ք%@?W(i6 9 <@ʑáU AMhy¥JMmEmm=:tdِ XMIa;;YڲL ;FDp ZCG~'ڲX bbj>R(qFn=#s6"̋sm"r[Wmg9( 9G>cG"LꡰGv,l, Уo *#9P]Uizӆ4x[sz BؽJْ#hmm*WuuZK|{qDeL=((B46Q)M3_M,Xrs\WW'TQh `<(%ZYm}}:vH yT)2Xį-9G`e ٽq24I %Ic"b24̌TTÿΰ)1o2fiZjiO Zs|hafnjqcn[ne1}W ^{} Qw;gsh"; F(*z }ū[؛GU9Žj7H ̴B7CfRc4 ."+H5 ȷ%KP(ą~ CPgYH$ >u8ޏl7D&@AI֦{ iqEefqtBvjn Z<(qL*4aifOrJ$ Ev.LStH+E$c4!.MZzCDcBm~؈Mz텧qoP}.;;1_[n֎!k/=S#u/\w9D$#J(Y$"CR#p1a*@ +WIvB탕!96uA!FC dce`E 6aPTla9Y+IO$gЄW]s q 謋XdX֌閳Ůh p?",A'T ?)P5\2#JA<O b}ʫO~*o$ 1Zgg="!yE*I H0wڽe^[Zu{F??e=rm6zqž-ܖ+seh: ]PhB rWoA UU XBe[1г hnAPFաcc߱X`c}-Y0~-#FUF{hXQc"_O,n\9þT7QʾX]}RBNY@IŖR$eE3D޽M7x#>ofmgmٳGkkK:'#0`ٱQUScDEHٞGrO_oH9'V{释fzK2b@eKXo!rUȻX,Ο,MD(8hSA^DIcEI4ƠO' vHZ+#0I~Է|TJ>˯jҤIBaݻw_guvu~9sX6$)ڴRV5hbDR%t_l 'rmSK:1"vZ^YE1ਰVPk|NM1Z >OA * h`jA:4q`C}Rv MP IDATjVP0[v/6w4> -r-6!y+7F}bT -C?o{Be,i-4 "]!*ߌL :+65EKY3"f @qWS)̘@PL6@kjg$f!LYɀ)2Xhb0d+[( 2(j Eu5#% h"?ʈ-;&i٧~MM_{u]:G3J|I}}C}}}kkk}C1uQsY2dkuIټw˟ǻ *.ot}ǭװTG;M(t8r-rmэZk"QY 0j4 A-1{ @+_9 )JQHBPE= W1$8<-L!0;ʃ c|Бf_:-peڝ:a݀P}=8[RwD`Aǃh'_k ʅ3C՛]2cPܬUz7|_ZBX<5>SO=ē?80o?v1coN r"HO?4I={4442BƘŸS&& CDG%_7^vהV^YxI]An&0r>ާ$O[nrh\[AȠ-xMX 2 n_IjBS=A/YPY=YzȵcЂ@j`Pg,h6Q*&MUeHE>RSk<鸕WiYuAVRM~b((eMn!̖ht}7xzV"bf ,zzxӣGb(#ŹTZ"0hZ OKI c7}欔݌3Ν /ǟtҹJ=\kU/LSqޞ*M|T*Qd%䎕.BU"M5h~@EK@qt?[JRHG#1ccޟRWW?qxcȐ{I:YY[k[[KM]ZKzuXR_s矷fƞŏ~WO.7vѵG?wGfûg=r oBqW?evX\gUկmcM^zóm{Z vIs蔹lzxm75%Ν3-r-e`U?ľ$auk9'\ R(PFCʏ/RP,mERh++TB6Uü,SȾ@*zΏrFŽ _s,e!;YtU1PLAXaWD^9bMR[W&2@&X&Cu 4M[kKRTR, [ȥKiYGi$l= |&psÓ^~9szz;gBpkJT]c@B9ӟJZBEHcWlf2nW|;Σ'<Mp|kESLе^~#__r KR= W_8pmW)F_þG\56"KXó3$10!O@3bQk)*l]"*ą cIb>#BIJxU,3Ŀp0f Fj2 ǧBQZP! bM}5xd7w}!"R3~;l5;iRZ{4kuy m ٣ÙWm8R_-r-ܼ!L\: o\C!Oѐt=QP ڐ5^&$m;+^1*|"LǠf@} j4'B I9ԓrh4pqzSkf@&Θ8g8"L折,t,-Z^M'| ^] e"2h~ ,O?衅TX8`@X,2 Q֋$>.)`~S{cWy{?)oO: džc}kseT@tf%+=[1>p;]6ns;g·mW/k1b%=eFIkM(-ʈM6r-rm٘@!Tc$Ql1dȘ8 ,a?W.(BCP*3$4 X'I`=da Ej|,娇$оDx|ծF٩ _Ίua}'c K*%B)aA尜B/MZΗL =FIp蜫k(KI^t M)[ aIwyγz3c?߳{\|>CkvG=nuԻWe9rV/Zo "4w.y${"h1cdytMVq_M/e;v5]qI lyvoWXD*@׌+;U` eOGXVT:V*%,vFOujHPՓOl:%RWW/e"wd·B%Q.z|G1?=F*xM_3F'~*lk}ZB%@WEg#^P[JQ'R>|݌6] TH-5-89 ĸ5®o6P(G6z0˅#qq "dt׻U%w U܋r._ 'Ɣ#ŏXi*e h}>nNg2/-5"@Y1bnN,.YӼb]};ɓiwfM]u>=d>z۾[|xk)nawd'3/״?xvmy?{[opO}w3·]K5v᠑{T J_ŀ5\1E&iޓ?^{Uf~~N#zl׺ { ^e56xaknzs"X^uϴҷǃ6dNaB_gcύzlvp96. ,[n-QC;f폘ù@DdѠsc$hZ@"$Ɍ22@S^ |V+a7}`Wmpt(3jЂ`uB 7PQȧ@_}]zGTB](*Az`]} $6ƴކ%H?hr , pACkpEs }V6Zg?U AK-#/vck}1gZkTfLqБA j\vE|Բ z_'7{SϽ|;!0w9VI 6@뻷ݲfϩ%";}E϶ ׼ +}ʿ}w:oˮK*Ҧn?5/|^~Lux0Q-/1[[؝6ݵ'_Lg8-r[YSdfBf2dcȦ^&X MP1OkfƵ@gu2] @-=$3vSOϬN:ps"*I!XG|N0$t . \i}03%+T,K744_^9G; 4oB\(sԬP,5tL~K-+ [[XC*Xd;[KN31r?~4Gw=eƚ޾oxp[mۓ. ٯpo 2y/κ剏s&t/?=|͏N˦=5;}܏SrsZܩf1"EhyK&o|c╃NZe3v>;程_lozs̺M5ơۛG}x_^9yË7uеq?L*q5ǟ~cG\s6= a;:?]6< Eh|ӿ.ŜONݠAۦqVs{;W$cLTm~y;.ޣ7|gw3/?9+G<ߧx1 f;~%(htօ}1-c:ḿbo2sX3qKKbt?}ʉ>4?<|أWo3q>>SFtsv8Uwdb[9^!u6rw}1:Vn嶴,DX,MQ  ԑ`0 :z:3;Qd G9߂W#z>!!LEe'9,ZhZb$Wa` AOfH!t::& pY83ܚtW3 &.pO*hKd洞`=7НB@S.!DegYJ.!3lFl¥/͘٦i]hecǬ|/zXiw#ܠp6iF _1'}7]c c`14z>bG>?9wnq۾Gi*9myƔtN=#/ xz3|y;=q5ssUw &au8_'+fa[p)O1i~Oj}֑zi滟mzg4wp7:px?/!' {]{|t]Z鱓wWH?~ۭ}ݎrVWn~󸛇lF27`xwOymeoG!GyzM{>s7icz^my#_{3L#obL7>q67>492{}Įӵݴ+Q}_=Z[s! 辙t~ܵ?nqۯqRJҙOqi+Y %r.OՒZus-e`"1r#"08;b(<\6&bOrQ+C)s>!aC]2&h+ibI0܇0=nkQ|mȫBQ,BBN%F`BWЬN/U8W8r!mͫ$t2]88s>CLءVrK2}Ve$l/n MHBvF_ A(:Y+4-Q,/ݷ:߷mS/? _XLɇn`Mg'Ҕk;p˯47G]p1C1xfJ;yՆt [bD#Qn#h֛~>WL{ƬV7^ǃnB~F=8uL~g娊ꜞKroB I DUPdQQ|Q\@\υ;($M63ݧG9s3ht$~UW۾e 3>G^sK%۽śfoW0&U|p&Z[ zet2VC{oy( IDAT.ב!=[u;񶯉ޭ?xoج#-?睿8舏na~&A]mѾw'_Pc],uw}І'jyaOwB*C%umH*Ec- 7_=oo>}ɾMY^q/߬-3Y}ۚ_X]WPz95$ E0A<F#"2dP 3ap("u(=0Su D M#J)$x sR/%>7͒isLcz$ѻDĻG48-U2(ff$'Qa_+FSwqNʘ;HǤ=:'&@^t<۾&&KƗ`Fr^UuwOj}%~/y?'xH^qw?3>v? N~﹗~Osa}c^+~I'տyd->=-~q'OǞsR8_mߺBx-W\}\p{Ͼ,EFo;˶Y=4>[W@k~`Kpp,/<*L,롱CXvWn;onǷ7,QQW[O]Fǃ~kC~~ܰ?{%Yko3m7|םriύZ/ZFW3n:PaWe@/c7.\]6O|#N*}~`Yݳ:4ZS|1nlڳPB t Bd(,RgB(ms\;\AAIQOv-#F,LCN#$-Z@=ʫS8.A|1n[cszw(,^3$D7L]ܠzSL䒑cJе%C1ȟN| f%Ieιf2_qc QK a>퓀+_ n_R׼7͐fç|o8ԥdAaez&ouϗϸ|}>p;w'Y&eoSq.?qu [~䡃Sw~7΃>pp =[~xu}˞;˵;''mv_|nOj uA/s qk?y{N~Q3fM9#}񧖝|ꁻ634k‡7EG3'nGlK]y҉ _&x;1~,9C>wM:զ7s/k96)ǝ{'uǙw8Egms|EZvJBm.Zm#[`ά"~C@z?= z̫.ȀhYup^}773]L/[ͺu *WFS?LC"_Ăef3(Qmn =QPI !J=!:#\ipt4Q~jj%`K.RbI1&xl)){n'Lgx衇-XӣaC~6gΜ=wiӦqlb%s%1='C-9sfxGydƌ ,rϽw,yxS_8z돳:L}_r?1k%EXwXZRbY D"w^{G^?7~/xG od:69x t!D|eG~]P0S'jnNP&n?}5kp`+ rTYix%L套׿GYp˖ɷ&!C`@.ۻ\@MN97H+{?y՟QP[uN+6;vk_~.qXy-J(Jzzz{_+R^'"w9r9G*q*j)>23#HC˩H%1cTqpg;"RhncYu؅șSlqUݧiG"_MWea`^=vciJ֝sU,K:uٳg̘a{+5(.tFFUIQf q 9Lں&3cp_gd+p#"$S5m2|_q/ɗ])? W<|'x4ܗ~nJ;$+ە`='tMٻODz;2ۻ^9+N1T:>_Zף:n/ ΃䊿^|>{y;ɻwbu<+TPFIb.[D%!L#CDBy"$Ԩ "%e6Rfʕ"k☈'&brK[:tNDBo5w1f%ϱ/ SRZ9ˡ+`2wqpO:vj PKG8I=8$t$qa3+PeDT46Hye^Dʙƶ%b7$vT_Rn}ж@9g/Z Ou[6)E >e]WYcxRB 'DC٢mVZƥϧ2a aMV/e:+P!iUjq/O6`UbM ~ UNhwRyR2Z,'ɲL'88CBe|tݐ8ipJպ(WҟK`DA [ F-V{hL]Nf;{-K,NL4^Ok[B *TPEh'9,?xS 8@-V%\d#!x"!nw(R< 0^!SB5(HٸsJSsVY=aEIᇈkzR#,j詬:\^-WbQCz( %n0ɽGS\mkǞi&2$z40Yc}6+sӅn>km>G? =|{sƹ?oޚ c?;lm7_Ӟǝ>y;<9s+vfYsV}mtۡqYז[nYnw_Nw|*^㞳-[.tlӾ֒\j'hnp[lp,2zޱsӮuy]zT׍_9-z޻[u#墓^N[o쭶zOrx^*TPatb?ٲŷH4Jj^c'}$*TTN#ͻfcgSw0&lTLBw,Qn* eu-H5,%wW1] S|uY![jCl5Vm]¦2%J&u챦Sгqi-5}yDĺ}6q}-_]Qo EQsF+ovU4޹_/٬t ]|I{,N8s_}l)?yo;9!MS.9~}x۞uݡऍ|}YCd5N]|0ﭿ7-9zͥv]'~B/eڣ6+n9'cZ[!\? truo9w+}l=vǟw\+TP-J "8*Y C:~ e'z$f&f!! '"&ܐuJ%,rwp([t%λ쀄E-1iЪNI>u!QC"lQ)"ćfm 3 JzlA8 ߶P#V@P},n:bT\BV˜WfYMHgڷ*6NWeJ1z:U8=}.w޸9s^}évi}ժ"[/ˇnbf|֢#?z![D'sg˙=>}=%м?w8Qv`JԻ kہ73㽚Ϛ^m.r; Bv8yY@ 6Ͳ^so1.c^oOX<>w {wo;ּ *T5ꅌ"&ZXU@ݹ>Lu IE_c!U[$qDMoE!``5yˤTy.TdTYn(jus/ڍ1.ڪz^ۊuX7ZD.y,BKw(FHb:JJ 42To+M֩5PADA, ,uz9~̺pi;!򟯻4_N?v?9ea|m39g#ro]CpEh=W -(R_^;L%̗3vP}oYMWs䤯}y=%/8`_nY}MwEZ=k opm.r*mP>:gy,=JVd0aIT8{w_K?56>4W_~%_xo2l>n}C霛Oƹ>?q^}O z:릑%~â-}8e;sNFF=ag=/<{C/mϲUvշw[m݂yM{}+(=WݖucXa3/ޢ-]A?zg8+C+n{|Ӟ|-[ڎ~w~h]m-[qzK~WóvY]ş *T`@fLy̚0]94 xDbD.jRO+qmIM4D,+BHI,"f5v:js -n7vG{Rw'7'p/̄bN\DԹ|XF:X3e[oJmӧl, PQejqB U5)ymDuYHR\ﴌgTXyדJ *TP?'P,CLfk2̙|Ь1ct;DQ:!>:`b@5(WQjj2ij!#);GꛮV;bgs;e?Ěo[ -zOX1kgH~z1E Q = I^8uIWp1D,}6.XRў.>Jvf \6أx Ҭ&g-S%*TPB 8D@b)DDafn*.rh?G}K2흪UG&wBm#/ q!+@!Uqѷ|WWl8lD1d",owvBqK66o% Kާ-~˧Q9#5|X78pXB,y4fo)}NGK*a+:hB *TP L#""GNU@":GXtʯ7&rTӍHLurA(X`,W94ά$<էMpcI(qо<톕k9~Pw4XO(8ߖ` k0#bV˲L$C2ne8qI,Ĭ=&묵[#8J?X4'+"D[=+lx>J}XicDFB*`D'#ƈ"v$C`6pVnz/ƌE+o4Ɨ Z=SBy(B O7m]x^{Ne_1WӴ)mOzܬ{Ho9jsW⅗ڋk^?LL$+TPBxl;*>G6L *&J'qQ%D`fv-t|LÒ*NDկ~cϬ[ m+xful= O~ӡO>YEߝyK7=iwϞ8.T*TPB@Bѱli9NC,LRXބCɕ@fYfDA}oS9=|.D Z@`NHHFYx.[v(/9K{[nZ#v: 0zuSlU= %&EED8)ׇD)dCTbQVpH1 pCD(!HuDX͖0#?`?l6fSCQ03Ek[.@Uy>\zGu:H9{[:/̏?;cEv ӿ}%G>]_kx>j)c7_{+<ϼ_;ϐ{w?ό6`B *Tx恐PdHN";m;3{?CxY$eBystԀ &GE &vP'#A uf%y^*$CUeAÈD#`|ޝ!=D4IFQ#OUUE9r vZVEZMq^'xAlVb{8sÂkLS ssSwW6ӣ_Ef#7}oS_u36fO!U{Mjqp jvꏯoUWPBW "1U(DĢ΁ "D4OZRB-Yl)OvQぺ{/ݢ}l'D*PMǑ^"qO83&9t'\;$ިDH3!QA 0`w֪JF!#k0h4@u* 20i㰏/$-2AC"rH>Q+j${{vq--dqڟ{ߗݔz*TP3H2z*E>ţsW̸TvtZvHNX4s)*\yoRcY,ܜ^xR13#AXADۈC{MkB *ihF Rڥ`9. Di)gVOdQjs _Gh/B#H&##-B2Қ$cˬ AIӴ 3MP-zEQһlE BL: _׮~63fy"HRb! U$1;7mS.ճܸ݉\~2g;̓ya]W֛EW~}=[Wuo%PB P6Br@UPzQN(Lm$%:+K - ڈXI!/+vLzbř]l(o'mKP\|(h'YPDŽ~ iJuREM#-BdjK{Ӱ̖R.W@M~ayw 8]؛P&:$w)cu2V(ڪV~6׍6c:uJ"U4}~!"ljU;߁믿Su_K~yzo;pΆt}Q!`b] ,f,tÒ8wAeuȱ.{PB P,IQi ,^?v@e,O3…  m]d *cCd ,=ԣ8@ϩ6$J;P,mK"Qlv$q`r乯1f̉4feRlk{pj,?, !1pD dĦg1YZݳeͼ=(i"zOڊCf?kwX<Ʃ “u]wGO{3qG^OylCϹ℃feSN>]g}ÛLV>{^_QG^5Y *#}OJ_ JqVk*# Esċ;= LR]*CBDDX)~jB 9aq,D.#B$<oͱk9:+hS׭v^0lIpۖRHDqI}ݪ0*Kc#4T$%/`w ~T+ ,d¤b5 ӦM>mHC.(ڕ"%0773:j?:Zs`t ̓F{B *TwDIfR;j"윟܌J(PSR2*X#_5k`'֚fF$ױSY*8]"5I9m-egCDmYH;k){YDٯvVPm#`lPFIRP@{xp _e.c%Jwt&|$f. 1h+{Oh?Wk_vt( $/ B  Eju1!XdpG^x׍WPB .D$n8+Jͫg罽)Ok'--;l%a&YXU'%,\nQk W+0} ^zitAhڒ 3%dDs^i+RSNlYa:" {rj s1,}dAg.ҟ5i.wa= Os@m5#]:A#HXNP ;5j===PB  /RS2ZhwoKʼ"?`1. NH3*jyX>6LRo!W|H$D=Ms&d;0W3WS 0 3`O*t,܋ОVXAe9"`396ٿ), @E:-DeKCY`tf'/FeoZRN`!b(/ vDd h=_{{+TPB6`؀HD!QIb_ -QH#2IGjZy>M1V@s^9У_}ࢮl޿nD͠^IQk*ΓDΗS%e'^{Q= *TPW&!TreF:8)If*e[aix칢&MCx84fۓKB8Ͱ+7#ǓIwY<drш 8sXVZG :y^FY AX< *PR":M<XPjE[$6%#D,S1$!=O<5{qt%Iv0t˧+ओN 9ȵ\/O "RR+*_B *Ӣ^j# -jºcש%%`ڐȡI']aJ&Mm֚md* 9J-mImr0<.AH8 eA9304W\x #"z!"sێSC| ['ۻ뮻N>=fZ;divi4&2, cDDB3UXY ˲f Оgj[Yz͒B^2bI+v#1\mVêhm *TP /R$,3U}7>v @RGMGU.js23K}D͌h[b6I+R3z K0s$e&ika:xlnEޓ#y!G RHYl}LLLD@W^xIbiӦT]qt<drش%ޞG-`E<-ZOޓ+=Y'iAD@0I/v9FASW3O^X ĺ>qOSO<@0%tNy$PB *Tx&,ae.gYM:E ˉI0KQmӽ *9"s)Ӗ%]9 I9]4^NF@ 粂 P,A*igj![oGe\׷v3gJ B'?44y+EnAKY34)C!^+lYEFGGujAoo_EAi^VNbY$X# *ؗbI9GOeR=)jAh5S O(PP%e Lcdd-*TPBG(>&G*De*p1'%QR=|W# Ǘn HKdi]k,`@P9P\ vk"!|KȲ#/{3{2 skFcppV9{xhhhll,+?q|||xxXY}ʔ+W.{l9sVr3o72jaD$hLfŠ+FGG/_^Ν;}tG}k>oa ( Zt^ <3g>Ԅ;]T۫lO:U 4h\rwu'3Z=00700P+VxΝ;e`򡡡+VL>sH}N˯1D1^Y~UD!oph)*_B *Txd_h,J0#DDdqT,\h㡌gVRh ^08>+%VC,$S}IQbBD_&#s9'0v3y``e3\5=;s|Sz^w9O,{l AC@ׁh \pUH!&7R5(! w@THAM%I[Oqh!4q˾d ᐈ۶޸/zttll,˲z^k?̙3O>11ח %˨jei>#{衇tk:-qU,d@?z|uppPtfY׵xU=41ZB *Tt̮yICbv^5d<X)E@Rݎ4BtYfͫ|ˌQS E,1> 8PDB<mߵEgFV9FQEQ 82Y{\Q xͱDUU[5,q]8c "| 9o4!zBZU̹ 7p֬Y}}=S7xctQ˖-S)FSׇyժUccc˖-˲g̙̼t<a||⮻jV{򾆈Z'&&`dlbxxxttlllh5F_ߔ(*nf$V,zY.}LDz{ fn4Y|V/|hŊ1qE z;л~YB *TtB<`pAI;[圳qh ''D)~ZT;l Ԟ^D$r8CNRkSI1o֦c^=},HNKDJOjX~brJ2-V+M2e1'|oooxbl\5:`{@EXo,o+qVzU`h^*o2ϋ<ifn0C>Vϼ@̙3l6C?Hea7˗3s5ɥ'( 9odTQjZP4V516FDj6j)< ּ1uꔞL1[y'V8hVObx`ffy*P8|&WPB @FuzJFfޅH^ -Q[:ɴTrt#Gh)v-K4Õs BI[ d[!CYHCDĹ)}'S[.@QTTЁ@k^8M:5Vea΋:B1:: > jj|Ppooob|!\026@F٤̋`Ԁ) z}D\t;',Z,sZ]u+j"iL4ZO <ZFy^hj[*2p, iwip8׳5MajS! *TP3ED>y;wDcGD, 3̾$T)JLLAMTd {*yKORtC@Ih$j,͟s3;T: 4%œsF>Qh+i6"->Dj|Y{l6M\jUa___ԾWƳfyajfקBV.]'yh49l37$Mτb9y3+^-H #0  ƀ00X_H X#1fh$fXc3lK{m2z8-ˏ7"TVvg5xJʾ9q8]xAaeyG$d難2sӴm۟1ƾ뺾V?crQUNժo&ؑlT!s]M+>^^j }8CbU8pCDs裈ydyƌ3f̘1ÃSNrbaRa)trJDx|p𜚆zF43}~cFTv9TzvW (WPB%Y 1g *"J,+6fJlm"b~&"̼ntȪTSeYu̇Sc{}{XTt:6E᫪:Njwkr!ž5婠m-Ų6!}<}3r\.lAseUEag]v]5MCDhJ}߅ʲ_.iٔe齿$NNRoк{ǀ)%Dv\ 0:!F@ETc13f̘1cƌ9v?d!!*hc$}("8 Q05йH\kk1s  =_8ȟF6r^"YbDC$H/ IYr YɖH@D9lILl q¦6ގ13{  !0sYٻr8b͋Hm۞N'K-JoB^93圫zһ>t 1eyyyYf)c0FQDB}ιjMӈHۇݾi\syno_?/h,)B4U@cLA*F,_g̘1cƌ3m!koFx˟0dcjgӈydj嵥33h$:ԜSɍ`N*89P$&[J;`3q[F Ps@F.۴t:YޗeY>nЧx8By^t]c0+%RuE˺m~Ze1F+].r )g͍l/l{J-JfvMGFpq(X.RŢ(뚶1GG.//}݋˭޶ֺ.xjmB(0 8,O`>@UfZגiyZ)j׷ADtX@PpŒ3f̘1{LLD4yA1O9~1_D(VěqVE@J ŌF~L)AD;;03u6 }MI[$"*Kg`8t:۲pcJ9X(( RpH 13ᰎtsҹ"ƨ1I+" dkkƺ O o'}="Jֻ[...wDEcttIB!6bYQ4]ɫT`Oau]j1Iz@PYEBh1V?Egw~Sf̘1cƌo3j_CD7&m/-۟#4 "LB۔թ Ga2"Hjsb~@_x`8SO?gUfEXDmMDyvQsfgAE `EDthABRTB$larz\e9EB* eQV+`cfqTU##숖eYbbf%,tiSߙ鐪Ɣڶu]Bڊ02RUUBW\.VUVGms"/y>ŜsVPb^|i҈I|u%"Tu (V믿[1@uxxx#Ęb)F@@S|u]U,HRʒм>l׌3f̘1ヘFKk# o~gW7#)ss"r) S|&DRvU h"U~PrMr`,w&4;?ǔ;I`ϘfN::YAJiZ&KTMZ=-&Q{E1%df5ð"0f!;.7^MQvy}Եa9g9CUMC."ZVKfD(%:r4 TUen$~I*!c{RնmUQ=qU֧;Im9ʲ$䪬[=z8soo<9_ ]nqy^ݿ.{znOR_ŋǛ^TC-֡,KCFB )}9*$A$Dîg̘1cƌ3C?/o=ndix6ۋ/ϩb! " TF{ǎSL#b123)"Rl#`{76ѹ,s&IXCʀsrYKǁN Y33;{t6G#5 #, A1\a; UeМs n=LDM\mT&iłҾ=[F5#"0(k] 1SQ{R !t] _ED9ź?~3u &! BTuӵDĜvy&OPVEUU!˾b9W/{Ut:u];NXձ=$hk#x IDAT%:fѭ뜣yN~?vkH(L&Ҍ3f̘1c_ywKWպ9(c@Q^GbQScKJ YsR }`s+^{oaYVٷ)qQV'Qw] h![-ܤi,m`\-q"G@e>;疫Ǐ=z_.miZ낎1܇L@IIuXVad C$hbE))\Ϙ1cƌ3|3'Kg>}?*{1L眊( PUQ "⢮s!F&"f/Rm 4ՕqѦX iȳb9rY/%d.9&PGUQZ@mGaD,꺾xEIJm`/"zK؜ƬPQ`W9?l3dL䶗Ho|_>XWWOn?ſmr-3i9w{{DSJJX-.u]K3 6Ms!xjM%B0_///9?z*4iVhupjC+zeftl ̬+hxHLQDr z1&;[B甮<}c>7sIX}k%|z\ E:쏿6Vr`Rr +'O wpwwb (:4Ptnf̘1cƌ^jٿGZ!n `dqι} ݋j shES`)WmvxNTbxxx઱;!0mgfzW7#2 ^U0{k@MX@DuY,ʴ1ǯޣGt:,daY!bC[@ o|֬1(+$gU?6ۉ)~˲>]Gt.af}%2Tn FjTR7GA@ľtdD1 )u{um ShoI05*]׵(Bz.6O x<Ϟ_w;kaIEg̘1cƌ`"")+v5;co۶b"DĜ%ƀ0O5XVUs>GDZ!#Yc?1V/}OYUFڱ[Ao{]gJX,V~Eg x#Rp8؈'JDh3q_ݺA/LT1fI0cu6Mc&/ LJ]r9窪{U[dvCYm!=2xm[:>Jn9g;,Y8:f20bU{uQ13#D[UTYV[o^[9rd uݱi1Xr,2四뛛b rQ}fU3f̘1cƌbĔ@B<詁rlB 1Hf6ϟ?Y' ;gcTȪcVN1ݪj?'ql۶Vwll`,J]VB(`EUØ>%;9evB# -oHUU1bQ׵;$FUĐY{Yvs,="A2ȩO_!uqH}}1F0i/c[ĔWUo֢ǺӤSA G"rr)sSX<뢬j}O_ bbܬמs%˄WKզfE]DmE];dm%9zQ1,I%TWeY]߫Wu3f̘1cƌW4!" 1F#ib df "E _}2r2E+fyg9""e90J%eiF2C V01u$-0{HmeY}oO9}F\su+:,'#DMGf\)r؞LW1/T-KUAI2dʓZ+0wU}bC777ffd7c M<1 'G;LU caHܢx`r΋En@NurJ R UዪTp:Tr{:ղ 8u'{k۶("g eVArJTE:$R11o??¿GsO?/RVʏ3f̘1{5G:(seYEcTJp;9f.FxQ=13PN&VmH &VhR׵U6?DX,bUUMS:RfrӜz>t]gn:@ULH͝ ;+tapG;g5i90'RlTDAtڭIZ E)1b`*oS A$"k\->tUȃ}L0xHB9^{`?u]sᚶnwY P9窪.<{+_srkN'Ss;N%|=x|UL{.$( K~:ueYzvd2q }}Q|ރJ/>N՜*/ҧ\߭לw5}1cƌ3fxhwX8-V(U1|ᙘԠB`& aX_V!ۮ.//'<^U뺶J|X,\V{4MYVLdRrv!Zǚ#*,i,H$]|EIlB)BD*ei[A:VKx&8 B3`I1sLISVGlE(F'êz뭷>Ӷm\sԧ.w}_ v<BŢ2bmRSlYE],x5[ns;{+tI PӧO:(J HtbLbH)ByoYW~QsMWҿ3qvG??3f̘1c T?n(K[)_L9Db1x<朐, &ʉMb6bygqgPDUBPU-`8hbsTE˘RjBN9gezx*OCU UcQT66F!xjNݮ:k&bX,&/S riwd4% "y*rJpc Ɍ!~cTDmc0}Z$l^0f狺&)t:}񋿳Z-6UQRYWWWEqCc{BĻ,3(3WBnDdleYD֫Md,[kWWCB84'KzUrΧ UI?~۶mE%!vD Ef$~M?S*L~{t~[s0cƌ3fZ|a0ݦȎzD\CvEP.Ff?cD\UsN5/aJFYC|'9uՒ`Xlۜ#"YDjX@5Mԇ\A4I&IT03 B:GǏ2h"& n,mYH(Cm#EIzxr9Gfv8(y1!B*" ZڲcjiȒoG}#tRZgoSͣG>}^Uz8kN'#}eYNOϞu!tm[VeD:&"* ѣ?w2}]WBAлB/T&3 cvz?ɟ^~M`Ϙ1cƌ t!QQbG4M.-6/}___w{$v(bZvQbu{:}"er&v} XYzj۹| WǏ@r~?y$TϔU]{!DEUBvȿۋ7{[?i>!43f̘1cDnkK$"҆wƝPW*1$U`?f;Mljӂu+N;^+>$j.c8m}@$$,M.)W{6?'?ߌ3f̘1[2#z:ȄLΩ*3)"2113jJw|uj:hʒ#y gj SJd>,du~݇<%!`fDEDYT9IQT) Et<̎4 b&PU&J;luzs뵝~VB0UFK.9z<0#R{EK_3랮_l쭙zHE$ч0 ("C`lUnWWW9BQLɎ9s,˷~~cym@Ub캮[瘘r,]Lo}( t*cHXAo딜UgAlu_"7+LıPՔӤʲ>}pEFDu]oכx3ٝ79ؾ^~?cƌ3fA9H)٪b{%r}裪2 "Vv[BQ|<]9b"`UUH/7[CeIJ,?ɷ )ƈ@)};vZ/CEusljܨL1cƌ3fnۏH&9 h=- T]"DtnWSgNr󘱫ᙱ0L5|-`ϵ$mnNg~n?BtMߺy_t!}le0z.蜳zvn\]9GEG V+D4+zzi]c~px|MSf!"E@Ĕg"a\.98f" 1JeY3j 2"x_tGۏ~jQc,2@PDR"']׽=zDJ׾~ %KxG?E~{].:D,Ssu-!w;cƌ3f̘] $s~/E-KDY heiX=i`"Ӌ ) S9n#N1&| a#T!&M0g%2{8/ۜ%B K0Sg:~ XeJ)K0~YSJDEؽx<cd@b1([t8ڶ9á?_TЩ" 9ie91 ɹ#1}h6tE ]Ӟ*,˺^'>ѝcS>|cc"E)x|bYVlzO;cƌ3f̘J˿>8{^UAWeJDx+@D2V$9{`EVHЇ1jb TXM]apH$%xӻr^4ګOˇbc/l\"3~!b)#dIؑĔlCc4yxc4!AYD 5WuMXu)Mìph>Xzi8@OD@("PvNz;ɓd>ڡm"o n%ef)fpu'=t]w_ɓ' ! hJ)s*|t f̘1cƌ3'~z{&@D@T bD"E&d<"2;rP=(|b .)%3 jn5yF QDKl'@4:adbaA}4L1\ӳhtENNU'[yLڅH lᔒZ- sEq:E(QǷՆGfO+䜳(J\QC1! [b3ѓ'OhYt:9B]ޛ[RUgksM`7@3+2AFDC~|8ĠI$qCDCTPAETFzpN{}{C7M}ΩSj𮵘rn9"Ns$ss"We988Xu,WAsB]w6mef޻߶J 4hРcD앯=$v}cDCkfWiSh1I6~{RsjW3JQŔRVmc=]w1`d iF!9^ڪ4$(×a+$& u=J ̧:Yԣ, EU%Nι9F'k{'Y4cй2[!`vX4HUU13 K%#vm3 P_/:Y7@>3CRLWF^ !Ʊ*pR& ߇;` UA4 Dmq6hРA l]L$ 4hРA;L${Pc(Zc`PA[1T$7Mү&䉡^hZLIk~LxD9d?#Ҡ$D]QOu1BrU92x1Y!˲sZbRX 5wsR Șv3gY\vE\1] k-Ydf leY!!(~ze_nׅ!-:nb {_{dmm]rѠ* "@qȹE" Z[*B،m= 4hРA-T(@*~\>2Ș%@c zfA A*2s1,I$3s¯jGpX # nY4ĞܵV)OiT6Ѿ߭ cfl,gY0vcX-KWEUv"MDQ!V^|MvnZӧOHy&ȣ,5}_3#kyV\=nMCMbɳ:6zmdB#bS7t/zӬeU"]p }0&s4؋q]d] s"֒\ Eyd.8:"g̒8HNH;!c*:@ JBW5@r$tD#P |ȍu(XQ\F (آ(s9aF{_WB ջD=@ueJv:ꪷ/A5 R +˲/CDK9Gp٩,S 3Ƭ]v̙;ʕ+k1:˽w D2y] *fbx`fDZzuߴ~<,2Sb97<<<8884hРA plB Wy- 3 DQB@.H%~Q ,BFxJ `UEMDX!Q>$5MTf,ek4VXIi@.;240Wj(d$2$D{ɐ]k F+V∐Ja 0@e㽷666GxmHn,8Oʲsɶ\{t"6yϚ5k̙DdjРA <_1E y3vbIueԥ oPJQ|6E0SΏHzQ۔1+q_ J~1ruT,A&P(RǦ~ m!`e 0Pg#9XTv 쬹sD:HX#Z.t.(Ґ 0icY (![Wy1,3c1?J y^V>n2``! Lf:2yZ|f2#ԇcc0Ǟ8 ) HA7hРA #%0CLLP#ZKZVRWovd6kѴ)4Ac\5(.~0fujbNjׇ|X:l%g&_+$>Tq=xų"u2]r=PeH,˲LdzubA4cRC8VBbV;_ Cf3UIʹaB{BDksrI!2Ir*^2NcLa@___fǻBmذaӦM,'Ty:/U^̌P4hРAȐ0YY@@@W @YM;S?E_ *Cdc4fBT{{4cYb95 T;Cz+H!7KwAWZ*{b5TBzxDCB`{ٌ,,#2D*;_e*WJs_EQ bp{#"a #yW: ·U;㹪{E1x0BUUv,vw.t: !)ɶmNpaΜYzvsx̺ɐN)NmΜ"rً+>HbF-5n<CI6yxW>͇w{Í럛SEo-<__|!wг;~W]nsVf yn]g3l5x>c?sM_h=guLn|o59L~b1$vR@z:BL)0Я'Q@ ˇISj@ HPgQ5JEDY12Ҽ`teuHd䃈Sj Qgf,]< ,ë{()dֲ 2Nwic=`[uDd(*!FvWy_y罯s@e~_0K hk$fk3$;9)^IizS7*ӧZY!Q "}HSU6TV[|*Kʀ< s1uAӑEѶ_}wIxDI'Y>Q5$.+3w:c^$Z- Bd Wzژʪ+}pC+cM<"f],;8@tWG ^}剻qzˏ;h/ySpч= N9W_r[9;o$wVO^p/^r>uz㴓Ox.oXe/=?aU;OTCۻZsxڻ};{?p~GԿm}^~5y^|Pg1t[o/~ ܆A쉠(2k%UňΕ YH(1spOԞHFsI*Y|zTW e&P#? rS;7E^QE[ =C}N']zݏ:_?zħz#w~l\U\Z~_<{Crѓ\sCy;zbP{ʼW]wk.[3ɿ\9ۭay4{lўkCrx{G_<+ng?{y;8Ͼy~uչ/o3Hןi݃0p.^]sƎA[okW>s巯s]-8_>ٷ?߯z;2^t}x~{ P=||׃t5a1G_o]XXDo'Ox%^i߲4\}A?ALu=x>y{^?3ӎ9Uo#^k"/Z7xaߟ뗴=Z֍?CfMˏ7ы&XvuOS_З#7LrC˿޿ֽѺ|=~^psr`wx+ 1+3QdzEפC\u&=-ڟݎo:cYy}իdzsy=y܂_t{=^yA3F}/Lll|? IDAT|qfٷ3zܖN!e4h- P|`VD&)')*RC4sT,>jWkҝ9a6*A̢rRRb4Ŝ4)kTHɃV$B1ba݀)Oh*JOz;Fbib%HL@DD=𱒳D)=dl[e鬵A cD 3r.dY!3m}V|Y5Bf1ߚzLfQd =`.}@Z`ŃE@XҜ\-ɭ`Ѷr1=#wwްƸC+j"d\y']p\[/I;MO|ֵN99W]k7ݷg&)υcGyKn5̷;s=/޼5_mCts5q} uj[ѧ{`ϻrW~o/ } Rf7 O/=W 5xO0)6>u=ZfҜc}ύzo~!uS| 0 u|5)РAg s& 0,g𞍡(X<$shjȭ( 0=56"3dXZKQ}`61vS S:?/u["E*밎D q49ߨA1Wf  M__vڴirQLѥO.]#tV%N M#)dlNGh%&CeRV $0Pq.ݽFT1=ܪ&}aQ\z|d [w j~([^sK+nd >vՏw7oˌĉ#ZI B` ՛Z;tuN03Vt`fשE`/Cf+™D7 Bf1D,L!M-Cky~Za![r7>;?KX/oz`v}_k<3{V5sΕZcLSjq,O&A"ynE+{/*|I{[KXz ;΃<288ӂ.| 䧷޺kYGDd25Tqo?ӧqW;R*yxx8?bŊU̘1c֬YeUUuG9z ̝˄w!dYFIAA{BQ7t+_}Ŀםk>j8xXciL_錺Ot0R4ODf]8w|Y;\*Mn>_~$~;|'W&ݗ^+lCУ{Lg0xp0bAΠZ^0fz'0+zS B fS7b?#Gm4h0kW>)>ɏ]K6yk E`OWҕݩZEQUVUYͲ2jv}'1.$2nG3>ۯ]Uْ.kls>fn׽&.w 1^neu0 μKiOO#.z̕et[a#wpwR4>xp0PS%>c:_I6gW]#EL0±oو'A[QCHLD䮪 }YD9{UvQU2cLNz?􅄱<0&?F}l* @Tڻܯr H# j E(I,}V: @hy >0sQGM3[KZat:0$z DTݤg ?kO= ?Рt[pgx^z𐐠 #Q" "n7axW>͇-|u릚^|bsFc?lѦN|q!W_ynoq_mڛ~~ezcsۙtWg35;~?Bk:A~8y5(WUsSڣ#VDP?&ѷD=楚ZE!H` HI'PZM:Գ?j7-& DDv½V03;/ᄑ٧rnx֬YUUh,†2<o(@[Sa\/ߜwKZ6{E'-mvN|q!펃w^R|k-Nfjp~?8[la8Ny3mn  mC?!x);U+ q1U J*7C (.b#uJ+R=ڲ6.?e)-d 5FEtm$6MQV@C/=$繵fJjAGD.x\ jPYtb*!?Z-k6`SWD BLT=4H>VToRLn3KHꔾr%Qe.ƠղΙ5VBD7^Ҥ#5k}_+C=nӦW#?۟7uQ}蛷jC(MVEYk3fΘ1}ڴ¶s0#v[h.KOzߵO<㊉}}/Yt8Un g4cd| W>?QK=o kw9w~'^t֌tH=qqp`ǝqf=Rn٭ߜ~ҝ_xnZ3t1f^0v=f1p'j4;+̨xȢ9'v 1xV5{/P*S<Рs!`#3ދʻ PUjW&Sݿ*sSdhk5A8bnΌ.WGjZjGu Ub!ZS?oW" X2F#32=@Ε ( k(ʴiΑWbOS=71 @Ÿ}#-o67STJ:T: YwqӦMd0mNJI/탿e6lشq&Q[O8DXxWnƌdfEnw6nV !trppiϝ;hUMadP6ʵ+uW_y};欋92o8؛/cX{_o^w]W8;?ׇʓg[3p$u[ົNm_Kwk?7W\|;ggsyЯIhhv ?lO5ay4~+:{E{VߵZ~hEw\L^y윍7}E>Ʒv*npn:ХnjNY9})FvGk k#/y?|.OMc7Ha`̘׏i{4[i'8m̌^} }g}{ZCzM9 bK={S4PF܎r  4OD *m%= վsJ I&ThO 6GFUFI:eg Tfֿ*#n˵Se<-"!8L-KCr(0_Ӡ%RK4x%V*˒E5>c6R\ daFiD{"y.1tDdʖEĝviʕD$^"r.{;-xDzwxoԿ~,g3fjei|͙v)0<0SO0oYfV<)e{ӦMC34<䜯y9h!9|̚|ϗov}ȫ>/uFqOߺpz}"v6! $䵣oUGzi~P{U%CsyGv5+^g,<7;ܱ\^Tf݄]y܂_t{=^yA3h"v?  ݳ?][FCcpQ`f9kX]:E\k:טY^.E=SwL~?ꊧM8{66ęZicV~}~̉ϓU}6 y AtWk[Ѫ;q=c{oD},х4hmQgі$+#c#()b$Lj5T$D|2LU@AG&vG$8Xܯ3? !ITA 3 PCyKbH ZYc IQ,$ {O` 1Ŕ|c ZtD=84zb'X~\EQԻmɒݞ|*BYV sJ1<#0pʹsM!_{W>Vcw^<''o+6|`z=B+OHyj{Υi®{ݧz7|Cuu1mӗ>[:\uwt߂E8pk50y="d?Ǚ8x߼7.@ʥ?cd$6z+?^Lt`#zKX} &ĘxO{6 8^דLde .8To&OT'  @5F16 >)-y:x"B|!qdBTO:Xފ1O#u?+dݜDwsG,98熇t:PH)R,%9nyhhns6A`P9hk#W`,F{BG)]ɨ_#"Z#pt1r*_wt$aƍwqSO=%Ptm"0Z ?g_ܲ[n_;\om7߲[?e7d٭~lm~_λٌ&xò}.*VWܺǯ 83/ \_f0}000su=̥o`2 1imwG`/]xy_3gDs=2D%OVucb3v3bWl=ynaj/$]oD{V5 ;qK}$wH_۹g?=Θsz3XtqI+c~}¢b5썴ӁirxzĻ0'~و?Z8zc/Mxw|4m9L#0 sn'y2Aכ1qРs2ԕ)cBJU8L=!)*Pn)_xO1O7I=ھj)*)q&u MU af<أ-cIb g` sS%*Wt@sj@B4Zyƍ?X^hu~vwqt): (4ZR!20eyQWE.nX2ҦD&Hek>38ޒ%K +Gy"qeD&Yȇ/>s7`]&W\;r, "H@"+ƿ"@r%!@D21u0Qcg0,NQixҹߵ̀.H[K(i46|o G tyba'EYOiwďXc1^'j R 9`LqS;%?]?v5_Lԏ%A+?{{@frwrكOmnEj(ZE_/VYgYVKhBYFHy^KfY#Ѝ(]W zBb>}AEmSGF +1}MPO@imӬaq$lY:$TEQ %O_ '}H$Td9g\ExUUI6C,Itp@Z8YՊ!PŠ(TRQBE@ZٵkQ]<=6HƘ,^ %v$EID 6^>>W&LGR&]bṗώ֣Ӟ#q&>,6bzrMkwzÔNȔ7q3=w:fcZ`L>̝WK0MR3Mi{ D|i &|i@t1c疰K@hW\nc"!{JLt%j__\P%<ϳ( k2=(46;Ƈmo³j. X{]p8T[ <"?BRs͘9p0h(ɽc*:Yѝ4a(D!YiJ>@~>jhȁJ=$>X,rLG] SyA/BJDd%7Q\:8pYLHDX=vZKERntIb/RbUº|hZVKQ!xQ-:RH31N+˜!䲂YY}%/8`,;e{oMBdǍ&<<Ҡ`d^mu jDL":CHF.b)U+rR,˲9 \7C ``"oc(ODjsI s 7 N0词5QV }U""1eD$6:I#R^E1HALtl֥ s9@VWx ]Is6 qUUIolr3U$5$*J R1uTjРA 4hJ󠴟n^B !TeeZ1=u*3Ն|WI>~LˆUUPf;Q;^ē(a5C, /HEԫ _#K I)?-h vhh:q>nCedʲ,U!"Ow 0@"[`ɨfSڳ5R?pL\ZEEeDyUNS; [3f̐B o$#p *3 DY:t4hРA LT'$@D ΊO!J#QԦ,i=5Д#^BuQ&T,4 u/Xԝs@{"{=f&th!"1Ra":ۨ΍Qter9x%0|Rw)1ae$TKLdei@bJyk3v^$PXJ6"뜓ؔJ1- ` Ƞ;EʯrZXr.UTkYM_gVE<%ػ|@qtұ ,\'.1@\U3zϹ͡UՑT4B@Cd,  ;ؠA 4h9D C9! Эz.pc "F ޣYŹV W#f4$,KqK]W:"b` 1䀙seX3'ZP<]KY_cXEQĄtjJb|:j$] HOS J.,KBn,rxxx``@qF z֙LUU*3I͠}NWY40iN eg0hРA 4h@X>,b."DOHƠB!ĞhITD$fՋ(.RY *c/-J15рaϞ}@-늠Ĥq#!6nDŒWmIuBb``DL [Z RT]>c1 kT@w_#Qv[ƣ_p( PNsj24t@ӧOԠA 4hk\D+1Pjg6˴s^j5pT'rHJ~X2Lӏ&ѺuB' wSkCpDTAnGሬWldb} 5ư1.bpHD*`̈FYcӃvڥN&4.[B%1_EQkh:Ɩn|TY$S 4hРA00fYgnZkBF@:8"!Y@)^Jeg!r1%ha@B5& <35KhhêQtm1XD1TA2UJ3 y;h˩nAf !Y!~{JT B vө:c,}gA`@T6q@Ae5Ũ|h FŠ/ $#i Ĉ; l3s3 |N/ԩsL-Z{zzҚPp2&qsNy JZr9)%cPE֞2mhρ9OBk 0\h_2Le*SMY{6QU Uk#|hZ֚Үt"c@f^ V046R2D>xɥ#HG'ݝ39\pBM =f2JǴRJ ^k<' $6Zl6˨)yr!貮}k{Hr.TnDRԽoy`vٯK_v|Ǭӻ5g{:hO7Nyܗx@ _|ߵݹ##`nd=9e*Sʔ c j[0 m֊ @I4uz@[ 'ŝI:=/C0R9$G @Ks4>#$64jƀUDE1gjT(Xc₧s%De\CGR!RcFc 2f"0y+**RHCW 4TZ m5hF߂(J)]6Zk]e<9 SFr m;0m2Z$Sܢ_^]7ɷzeǢME߽/I(d~ wh[٥TK|M(I{g=e_9k=-Ur\D{gj_iq U}ڛ[0yyT2}~_ثȰʍRX_9n~Es "·Q+e!KQJi@Z""2)@H5q)!Қ:kqA~}Ƙ0d:}P6dؒGe| ιays1u]g!!վ$atBH` 2?~dZ'gɗ4202Pu D~#|cccCCC\]vڵcκ;w666D'͆aY?[АmD@Y?g . lO1(,T?4eڱ~pJ)Smֽ8{}Pp_to2f}U}Ҡ#&uJ>6n1b\4vQy~\pjG`٬N?wwu ߚI!n{ymSO8;o/0kvޫW~gݷ,7'\OMgGu~}ս XKcuE|`]4nz̑cn}ejW1fMrŏL9j_XJn٫A.YcwohɵP)ywĔmRG v=׍8˞z7eO+>/uRv*Mbj>z7zGYc )8SPڀz{3g };JMW2db} rȭ5bdFG8{JҶ#P #BrA0`h j.jeT`!}K._ϫ'g |)_OFֱ1s u; xOnY_q]^tȀkym>\,WnĽK>Z_^+vJN -"JĉR6GuPh˧]yWז-y+{ t/S&X1;/yފw B􍢐KrCG]NB%8+pǫE&MO:/e*SR*|̇<׌!R07,dc5@#kR^(jHinn IDAT)k&5Zָ'& lzDIucnS\{i!Ncle֮J) !U5HQ5[jB>Ai%Bf)@=QPCa̺A&n\ץa3 d(\$-IG+c+RQէ ɐ|149 20@{ F86k3W^_|ӿW{ϯ;{ckMieblh?A5m{huzQR] x!gH9 xFJGTpY=Xo~QbzWc!VHb'_d l6׸~L +6˺Y3i'ݐsב3d1aޚܱ],m^0;e9` ak}ɗO\oE{$ֱO;M:n.}PӨؤȡ[.E|KNW2e Lr`H31Q .s!'O!mcˆF ĨÌ1T&$gh_ALa0ko n%a1@$kXe@RJe.PeLj;@ R <8Cuu#h1Q2KR۴is)(@;31FC5I9) N)%YQpH)="2h2'(q'2㲺ӿ5/wO\:8]I$u mhi-M)mfC#͟ަs{lq,>Z -,͟$5i$>oE{wcTz^ӡEGAH+:6=KCS处2iwŞ~LD`֊I)҂ )$H)*)dD!lRwƅؕl j\rD;^H/"9p&L%ȀA)%š7;~c-fCRg a@2ҠQ!0rca1EsBTUU .Q:2%Uh VF+M !-?I6ɐ8UUTTxd2:~9" 硔}?f P`6YX\xjo2}(XsokD8Z'aNCɍS҃vNs v^\ kԸڏB@kJJkѽO,m/g-aǾ=ںR3cIS߲ 17ϯl{otiQtiU۪~cwn:5-"ObEӹOs 6'>͕=g'f%H]7"OS_gmSs;h&1].٥+vhܪym:KQU ~ѱI;ߝoTR~h@@4@iU93H:1`r@`  < ?oFi4 u3"2}qh42\('rI:!q0 %ԌA`,]aZQp Fы}U % s΁1+-o$sc ,huQr>sd1cdlа jX`Zh"!UVe`$cAzAN>4!)zRr=יnu0T!~ݜ |?S qm/[ NkN١kʪUO>kyw=5guC>cOy#}[>^W=~+9nʊ~? yx闟yVe:գ_?>·>}R_O9ǫuMAḶmiIt4ł_fFW 4vl׋MpVݱZPȼxyfkFLUkK3CtmZ|NR=0R{$['>Q'?޽SNkf)>I9 ;i&T~ڔT% aNX[:ݭ BHpD%c^YKMB}h.V?Z ǡՖe@5f0D0&&FX ժ1b$ !#91G{8)t1 C1pa 9A;1C>a.|æ۶mۧG+bXY__ߡ]8˸dK$a@d28ZkD.c;RPk֬m@CE+^uvZO?APGxAo֫.PmN1(EA>Z.Rhd W"5m R.zZ`qÉ16eȯR*?<ٛuZRBPk}MoaQ׭Ad)KLO2Q}(FrD G9Fi."B"H+&.ԧLiHr 0*2tLa)0`"1ׁUAp.H؂y Uo+;\a(0/kChI(:Ab " 5c̑NL+p]u]ו̢p1q}T2L{H2H%--^T/ j KܠA4Zkb%1ɡ$Unj+1Җ crd2'Ek DK(E0fb'8)21*P:DPAB0 I6>Bz(/ qƌRJ B [  U $v [veqJ۷bxgv4\!N$쁨(BOM̘f`I+[5&juZ'=ͤcd\sm@u]R:a1Y&*IXuthav'FА58:MN0 L 4RʠQ* XGRpJmew!4Z`TG\!hTaZIwG31j+٥[~믜~yHl"MѪj`5g<|MV?wN~SXaӦolpuG;W5CjKuT 4DH7.7*cRJ+A~7h4sЅ,ڥ<䢱N9?ξs>_^pͥ'}"l\+>[nԞrAJ]Nᑣ3{Bi+/ZȄ&eۆ'E%b_&]2A-D G&6Bh4TPI-6HCUyֵ "v  h3FlH!6uL!NDĵ ň2o!-&NH5!GP~@W g?=9LPiOs#=!=ͭBǖJ}}Uʓ5%Fv ?544r\6g~.~X6 00jLm./c1 `Hg c8UUU6.0JG:v/ꌗ_M'/`n>\u_?Bno>K._ϫ L{7>~:rW=oQ7.hٲ~yB&شxq\ɼo-?wo~yɪ~=2免 Mm;3аYWO3߄䖺k|twѫ.Rf?m_xnKgSOQ]NȄӮ{.]޳iѶyor*oyӯaſ}8_~œ?+=4+}vCTr>eR[-3(>sl{^[PdJ6+fDٴ{GҾjӼG_f踎2HQ @|ڕw5K'*eT2}5CD!f0 RSvjmH$="s@Y͞r!1-JL$'6s!¥"J$joaHf} '\'Q99 Y䒧azGٷ[T' !|_ K2)E0ƀEYT 0 t]#􊔒8!7+lfCq٬-[qc[8!"a0n5>Hױ!cL)P׽t„2}o?_2+}5L8G#ܡ'w-!KO/ɤڰbZ 9{hGG &8ƅW9[/xQg6M5G_|ZJWtlsۯU>=+ 9})>Oݮ7?x_11os%ܲ">cP#Eo]%.NuL].cX^iV_X?pD1>_S˗b,~)՜ۖ~N36n;w >{W [0H,~T2}EHP]\Ϣz\&ʩq*0cQqj|Q.}wصsI MtW>IZ}HA(9ͭ%&Mqb#cLJIa D fzadTBZ?< A܄0Ph1Fs@DJ)DybPJiH֒[dEeNKRd2dQ+13E[S__OoY3;B0 m G ܑ3p 4"*311USR'DdM+x3qLF7]>c/=xd;X}qoq2.ܹ/?q_⼛W ⟟WJ)3&ABJΣuA:*p㌗.1ԀFW@iI2)͈uw3M<͈Dik6U|ۆ XL! 2έ)uR''>t%EVcG碬i3o>zDɉiz~ROls|n]‹qt+Z{,fϼsE5P$L_Ь5>⮅xraTZ_-,g6fL IDAToqf.k7hd7!w*g܃{Uk֬YvM:b|Y}i9$$֊)mvOs 6'>M|e۞]KWU^Y k"ԋyQ R2E9& S60PObUA:$V$ժ[{)ݵ%s#^;ɾXkQҴb,bP,N&ױfyx'[-} {b)'\pCh8̠ EV A̧KJY k_w5еMeUN-U_?|bm_9~gX,g'_v!=yѱ9rkBnmK}wK3CtEλO7mVahc=I=Go֭M7͘vlMͯ=2=k꾡g['>Q'?޽SNkfŒ. R2_H\3H(c(9<& !1|ͤGɚ1X2ޢ)to+'UIbm "@cUM|0Xm"!"0R<9ѯ/ !P[S ^+ DԀ(P$; QU+cP,nkiWe*6TCz۵gTx8 u]@LZqhsZƍޭ"Waa ٶnʕ۷^l/Q`3TUU @)V MNUOHE7Nm c P+ F|.0t?$ NBjeH^$0Rɓ<{ӂNn˩)gP]D0^SWs RJHp}-.1QʦYPk,?9ߖ* ٩UF(VOxIƅu:X0Y+y$ib &6Ԁx'_SS*NSjIt/ZZZ^N5h%K|+M:} &W7'k0QlVڶ&o%6f3Lm}I3,|v)tVmX.UVLe*Arxc19ɋﺮt1FP3+ENb3ql/ZMȭnCtF9RZ|?!$=9Nmrdk XPPf wK s. ζ7Ov֚;]J)8c̍oa@xr !<BڌuC暵,ʊn)6 { !RJrf/ z+8wWzj4 ntUU֡nl$mĎv8o-"qsLj]_չӾ`K_Glod|[LXAw׍*?~R:KPXeU2}m)vPBHJsB @JD ^*,,҃IOf:c6[bMRO(19פ1 (B|$~{%pKO$lɚ(B[ǿS@&J#q+4 CabP=3uq00.֒[KGHctaRVTT6Ep>vvb; y6oZd!6* W}S?,nh;f9?wĽ5NKuNm:W55q@+ڃqv_=ʿl/}>}/yB3qґ[%%L:RJ<s jE\ϤXcnreR9{Nwd q^ e^[lG ܂yF!,.[E&ih\.G ݡ]Tw? \.}}X+I?]m۶m6m++Pdu(ǷdegM4z#6&3) (À\.qcu0qEXm+!@'!c_vxz^SSC"Q0 C]FD4Ym2f^׍=`*۟]Oϭ!G2V hwQ@UOxJz+'oظiok7lڴ f{֭XKOH;-jOA8-Zy ڛ_9k=~ n]o` %},A.X=ܚ"g Xի2lc YCHqҳs*c  :EQrZ4`4ǁF[p̆\.* "R\.W__cǎm~hF?fs\$>ngQ;^R.+F[ns_L$#Q7[RDδ1@-l@"Y='Ɛ1\PeYBY8FZ~A /L ?jK?3vNխl}ymSO8i9i;qccs+ vzӫƘ^gV1[Oq MΡpF-w{گtvi0u6_^p \pGLۖto2{;|r o8􎏲~~`ÎݸځS76s1/Y7~xm={ <4n1b\4vQߙI!6.Gԭ{jOq?]p_, =8W|tQcw )Bp.Rt}ڭ{^u߲1)6,vtX*9r$ymGd[^x>S@ BvODܼ8xS{n1IלrXN7Ϳ9n]kƭoK{9,_lͩ7ͦYh#FLǾLe*S3dݵQ5{sA5ƸԿC*M466cw.HbfakNj EcMsD! *XRZ*PdO"6ЈlJ)Qg\zdzеYJ)J۵vnm&kc(`,,k /ݲlk2xANil%:Ƙl6K 6_v0c ){x}挒K"1Ιz\PSgw>}H!>\u_?TS~tTSye_L叏O`@%ֱ1s u; qYmfgn:>?;]9:~ɜl~xr=1jFX>v=,]⭟W?uWnĽK>Z_`ɫMn}k#:ڪt(e \>ʻl{^[ՠh)\%];烥˖n^5uq `zqg̾Lg X~œ?+=4+}vCxgU,I 2t_6Ф3l {k>X|o?4Vpv{u4xgMEd~ݫvfm#;igj!i*śhzss_~Ȕ6ѩg)8y;ދp/ϙ:Jg:e*S#uMbRs[ BTd*@)[U\-WzO^IaF6wY>'u}k9@G.p]B9M[0gN;|̞\̿tCㅣ~\щ*xb{ϯ;{ckMieu?ƷB,I Ѧ!hm7V2\i'_d pS g[0Y鈪.=JmJ3e[fgm?`Ck5eQ-THѶ[o 8yb|~OQ銃aαcuGugV]nOۑC9`%%z.Bpy-SQy9 @ͰF1m#0 (q"SWBp P#:0ɦRZ aForߝ7˱zi:yH/UMy (9mHd ۋQDmT"8B[4q1Rc\8`4A 42['aM(2Zm&1Y6$/*9oll$&YL&c1܂"XTzVCJ!Uem۶k׶-BBDd0x\RV& UvSMe2R)A!Ҿd4:r@A`WSw/ٞ9?fZu"3z>҃GwCnpsĥC~qދn2݇m6ۻgbOՉQo`AE+"3n]c'6s2n⻅U} SgIJPeSwxSNtEd]8GD^w34R #TM+ S9J}j!i*=p'9P%I \g)E!L ?~.0KMsE>M)$xPS.6ѦY]_TTB}=apgp[ ?k7d~oyݱtoHÝFI=e/z6+Lwm ;֕j͜K "`nmU}1;Mt?"m9mxC%t [5Fv~w{Yry Wށ\hXxM[-z6vOCɍSTSYYR캅;cV&Ua~^}biFn{ڧp. :ˇ-a+_xqu-lm"ilk3bS%*- Z-m%iqZ| uG.r͞f8/q[ܘLe BD sJR6FH1XyqNi0 >1ZL*xaC\KCz^^LxFbL#Rh bAzx U>-@Rk(fZN,jŵM!6 y(n}hIK{R7N>P=3^;>?^u܇keGݹjSpЄ8`zK7NxCnC#zcs Y?,*4| |ɤ>k;Rn#w{vveEz3 IDATNkN١kʪG)m{w_^͟ϔ3Z-EAi-9'7uG.EȥzV dRAZ>!Jh@Ƙ1LL,l4L-V6ߗ1cg%&欰X&EUY4ͭŸKŭ!2`1cAeݥAg$ Cvp?hksZm>Aڨ)D1ZkǕ698|1ХK}kSG?6lܲeKUCdf6k{U)n%E,1TUdnݺb*!GѶm0 -r4|UVѥK>^}3UUס 'x t10T*D\Zn]6ر#UÎ:'Cj:P#yR fE p#4Cm1|ȳ7-x䶜fB.̹s4% =Fk"0 p)0D`WNiv֚͘g|In ׺SzS-m)ogpaw7>uݳ<;k;wg,fgĮ׺f30{Sm돳%8 /Y7sg3LhsE$lA Zj)PpNԵ B8g)%6ź14Mc'$0%j"eئ{R{2 f"Mcb#F| ۷97u! k^?dg=\ݑRj!F[ BcOeX!2m"cmBE}Bqx;,ĉSLTcSre1ŞuLmIR=LC][NyEB 7+J!"!HsNkʏI!'h;%lJ7p_/}rl6ZU62"kfu'^g&Bee|P(YmDشpU.ޛ& @DUJy(`L=yG#Ç{Aa WCG>P6RqX)% xjdiP&=8lԲW8$v|*..[Q lstMVU$`?[ksk*4fxȠahۊAKqαbo;;DAl[洇]1z"jFgY *4lVZ(UM)%;1Y({[HKtR`ґ"Iלx|^\.eg ˜F;ڲx < { R$IPIT`$Wb\#GLA kEi*RJ! Ŗк {E)6OW%_!SS|sB P Yhuimhv 1!vgKw>G{3~V˛Nz!s8s̩g,}:,X)uV)):DP_|d% ʶ)&dYժDԠ(a溮Km32juYM_V4ǚXh]E;md/vhI (MS^Ok=2i-\:H;gbjīwnXֆ[4s8([9@[~HQ;G=3!M=$I$M4M7lPEYiuT`-SbkcxD@Fy"WUY$nAP}=e(Y$wibvv׹f>`Qw|]Ɯp='?fv?q>?vt:)ޛ::u?խ.T)#;_{wY?#3/6VFWyS^_O:q[gU.ΟO{ݖmo;?iB4C/Qv!3~Fp)orxZo)]Gnzo)@K;M7M{f$tGn|Q'\|]hygzh3>%qg/O;;|:kE<X{ Los?/w<3.}3p~˿]_xu}5n6:;tp佒rR1PyQ'gCmHx2&&3{?[N -e;>]opԘ',gD'H厧oi/ds[ۮoO߰Tgm^zECp}~Ͷ}8)q?e;.l.m?v _I7'|Q_gv HD!"4h cIYVu6}YMm[ N2f8bC9:S@AK%)Pp "W30MUcHWJѸBo]CשM䭛x2Z./Ǔ\S-Ph4ZYYFPr0?iL9/Rak=#LUڔI5)PIYg, {1=oybLjJ)-R毳/y:4:aoP%`@ĺfc7}+6u͓e6JXzᘿ8}D{%PZ"")%;F>ՆyP R?sIuVg}xЍW{%>в{7"ԗwDe^ͫ܏oz ß{KNܐ(a a=ѽi2Lnv0 76j%>aö/ͪgo{{k{p[牿s澖^WV^ic [5}Ʒ<6GV]xGRYW+7^} ;1S_#Q̾1x/ԋ>29osAt.}' vsL>ٯ֮[WI;eV}ys_Ys҄%t>7klo~ާ!6>g^$O$-N>uw`qkjw܌~%֡CoX&h-D5'BI"!E!X$IUWJ4MsRZd,!hkmg:R,K 6fYVd2Tqhe'yjRy΃@ш=4S-EF!3[.j7ZBJ $`sN)1[$Ryoء hZ7Z;D nX\YSue~엿;E0}:k :K:Þ_5>}~ww6?-ipƇ'NMRJ )u0k믳C8*c(@QZ ZI)ȋ\0JB;JpۗʲTeU"SUKih7{|]W_y˘B ?~fyɡ/7[{4]Gݬ“;o{ӞzO> >HxŽ>N嬍:{ 12b34jQEyv5B[(LTDQM,ˊWo+`]YD/Bтkgy;hPR+O>ʋ{'q`懏}W}> |S-樋iR=Y;mzA)"#_w_z;}^>";/J^?#a&ݬ<_\zg/3!^ǽ%<  _aߦ>6sdy//_?gCrImzo>~{.ӽ^{KrexIϕ͵N7?/pljcwoK5/ [?jyoWY[a޿y>ݽ 3ӭo'C6:;t#y"DV* )1@4@Q E+*^]W! rY;kbY׍A=WrPxnnN)ŜuYW\\-..6~a,ݰa▭d%g$9?kk*h2EQ5[lpEܜ QƿRW^;9@"`%` 7 IDATJk {cbsĖJ)Z1ss4??iF,ܲmqii Q>Im&49 !Іc^;RynY" NNwcnᏜs7R[ FnS"wy 6ysm׾vm{35ϳ쳟u!;tY6 !bf<9½޷}iiӦ}sا~+^vQL}tظ!>'-!'' Dc$@)B!@A J8& )0xG@5֞UWO(7%bh'z;swsM?6cd~^<f@zbd&7o~ wwqLu"w=v_M "}w6 7jO1|xԌY 95СC[|'9{*~;Z 4UZjlڴ%֊*+r!RDX׵s^{"y3uu+Q9r?77' K{CY֓LDF|PUUu]sxx"9u9MZ<+dr#BL7)Rz'ι~QԚ lVe6 /eZ'Aء f"R~2|D䌉> *+5ʬTQvv9J)V뤪* )e'wN?aL]ׁ .W~L[]1pxwyZQXv:T2@{. ߛ|TG8o\|&ծ1׾3GkVHm#lru:IpeY, ! E!뚹Fz&stJ)Nz^7=MZ)M,ϒ(,K#ph:<o*!&BJA)}f;I@m @B<$nBQ㙰GQm$7=8=6cx(i(|nMa@a4I)'ˋt΅RJcMf!8[Bn#-| \6ЕbsrGkTSX8DrJcda[$IBi 欳N.Qtjr?2{(G ܸ;B t]ȬC:t@@,s./rs^d2@@cBB۶m7F$Ko "Jva[e8?G6[lUfYmX g),X $I1'lyd)S96-{ clBbPs0qa:}9_Uep%8ڸPH?ʡRJT4u6RHcg`RWG%ҍ6Um*jUAyv^$ WEe): ET/&Zcu%^"jǰ ṃ^xj;tСCH몮Lk-dB0p4%:Y42r0MIuֶDq=2=PE^[k`QK 8c>*f le$PB ssjƱ1fͼu[F ;1m3.kBa^{UNz!B$My"αI $gmŬ!j,.]Z/^hFۚe*|Bk"`Dd;]g!PY!D灨붜ZK@P`nÃyv7O{8, ry^r4U\+K㧇Zg:t?u5:M,k&뭮:<޻4IAiZElUkDD!1GQ9E9`fۉHh(d-Fq_`0`7L+C[6P%sr&cD Yk4vɯTS=wTLcu8J*@],A̮-xM6m7lذiӦ^8C6m4 42$I,ZiU(˒MD"TUA2yV;\([EcѲ -*y8y!JB@4EÝ|)-]XFamiZI^s9{cEΦ\h>iK B]5jw3ֺcpB@!)$ "OZO,{(ZkLy^^#_|_ל?'@_ z& JoS?]}Y Jl.'_OI:woӾy{-}i/3ާ}NQgL %G}o(?xo}2K|glXKN_#աCB*,C!9˚?*I.IbeIRJNwLI=p{uy-^,:d5l$l:Ƹ;b J"&y! 8t>1! ~t&۽M39Xϡ, -8&XJפYu]}x<&,~5w[)|b@B RJCAOZ24W,L&}NӴ( `5E@ɕR $SA&IB>hKe1?!|Lۊ)\bܩ#5hF$Q(̠2ظm^G!y`+7^u wU 76j%>aö/Z8<;tСãP@Z+r(2I,͜p;gi&)XkB7UURP":"r<罢6꠵ ly!3 [!2-,,eb>`1ъFD<$IRd% 4Mm[I4dYG1iWVَeuÕU3TUMĒ$iivb{3mhǗ#A 40CT-8u]/..."⨜pȟkRJ&ZJyָ,nԇ4Cxll',ˈ;y˛6ۼZ9j?Eyr/;wh~hT;gub Ey"kmBI‰djG:}뮹뮹KI_Qg3(CPku$ޮW"BFkE2i&Q:tСãD^sQZ,+l̐1ր@!B1)DkNAkr<)˲?7k2 +ʰ8RL.)NZ; _yL XRk 9Ϧ975lc3gV(`cZ.O$pRGָeU"Hi$2[Wk$@zc4jZBDXSrh瀞UQɇǁL d:8?KۗTK+{[YkZ4gg8C noEQ%w0 ?|Z+kw.RD&6QPJI!{+{.t΁W(@N3}{ ^w\ػQ@Ffswu>zU}/#,o}Tw|wg N>ٻ7}osw翵+СC*p52Md($iPEhdZs\kL]K+ˎ땕buyeqVSդ |K(r2K-X/'I+`0Qu&iZUOPJE٘LO$U=_dD $M+y7#lvqo $~n>r6=| :tСC(u÷Ji^/ [ QkZJYצ$I&I:M9G#UCy>"Zk9$ ]^Ra=0)yhDD~4hD.+BDI)u fDٺ;?lÆ $r BhΥ<Ϗy ׿~ܷW5 b婧>d#D4ֲ>(7[dŭ` y g]y;d_PJ#$D=q}9'$@ PYBYHׁ)@H{СC}˽×1ǝo^}~{eY@:IH$M,I IH OI1Һ6*M$M;f)  /8dNMW)!DhRQw.xSe+biiyi:٬m;e lėef*[l !@ xsRh6IOl mWf1Ƙ&?LZ-{?l|#j=ʉI yWCS:̴3gʄ-!]ͳчh-'vuIBSsBR sBeU'i|ڝ b׿P*nCE!UYW~,B!ԘkjСC@/\*ŝe] :CY)d8IvD\Q\rpāfdY6Ui?rhCD*7b^۷sssш-O8 R$IzG|Mz1gzkR  >o`h|Hۨj2yueY/GR_",mj5qofCV,,gU>:P#@ƚM-${ Ncq!5'Jqȑ4KmrB08zljJ2 d !hIդ_ؑ"zGqA:tСCd Wi!v]e~~~wRʢ(lܴ l r,[Y]'EVIg3ցy>"A0,//QJȍ7զO/rG[77n[^b>=uM6ed"OsK7FgB-~~p.6pxi.Zk4h?es:B)%[-ZXqn%-[ARhԞ4MWVV \2%#g9|>3]# >jNE9Kq|cI~|u7M,k2Jz@H"FW6rpq6'/ #ijmB*ahLRY1 [2D\=e4Mc1x⮑v/4kV'YGD(DVBD|S ҡC:t6&Ip8dxReeYd2.B+(i*Z{oZ"BE\:.K6G1XhWJy"[n$Mkf!uS [ⷠL&z2iƘHjָF`Skă%:RyȹR)  8P*0*MS pvvgD%"2iIJi0e)Mu%'D'i}ksQƱ-4h4η+h{j9RT%(P,M' ,,QIB.СC:tx$Tl))mQEQr4Mʲ7Y:\Z[ }3`[Bhh휅LQL&YcHZYlF6FVJfYZM,bei B$Jhd2IӔ}h8Gዢ`}Opeyt9 2-obq shno`]ern*PC9dqqq2yC0b`!nr.!D:2Լ\>,˰Iq/D%|K7o=JKodOBh;iZ(Zkk4DC:tHf@c@TZi{"CŠ?L Mh}Q4Rtxr(G$ !xO =UUiqW3u {H8eKbҨ;đ~seY!Ht(gVX}յs6VHtee輯ꚜŶw>pСC:! iJÅRJ[0mfcZx$BLXpJH)0^ֺT*XJ)jp\FKe %50RV9T]@ FJIAp]Rj, ǩT*jk1Vsх RJ5`sq"^S d @26Y\meElb?ow{?=K}hje$<u\K`U*3% Ќ"OR&)bRJ(4M(u];.[ KPFJ+]p1XNXKm,X~ƶGT=b d @2 BIIi!3F(Esh6-qTV+Iq)+`< C yAհR\qK)ccchn1iq(esJ4A_J;rPt:JTǡbrCJ4M!A@{\f$,CމGZ!.cJv3g EPn缟Zk KPSXJ.84Z+JU\c8od֒٥LJm@SJ}Zkqk{r1I)XuAno/5e=@+#4RLYk;VѻPJ5&@t<N|DJiBR> CI&RqtQe AH@2 d = F gs:XB?J%@vJ̲fUV:NDEZELTf@B$Zku2blfR<o88]s]y@$jRz ~r]/Za00nqmELj aH,`b}u;ԎXBrޞ XtPVιGsF?: Xe7ͩ{,e;F VsJe RN)bNLJ7`Qe E\.6ak3n2q颕r([1Br^8bIJ*ط̀Q$i뺙ZRd2\qQ׶2je@2 d A0v<2tjt:u+tc:#YG2s}ĝZJ9N*X@FQdA+h84K8cAr!5ajIB%S2˲ܔ'4Z9geeRysc,0 Acq]@ k-Z4ys; ω8'\eO2Y2,SRjm4y25HV\dJ,8;q$β,ITIC@nry \K.WcKIxKQZ7h{Ig>{sp=wttT*A e9^DN ةo|Ȓᡒ©/;]?Iv~>}'7uo~76ٛxG^'v)︫{8>_uq1Zhv) =>|{{lV{oɖ= d OUhI&RJp!;o}LYcr*c q4͢8n4'[&h !,wtXQZYvq0 ss\.t axhhlMO!B)aEfjnT`,8NL3!bi@fial6Ӎ,I0DR<79"Vbr\*0D8 VeiVSRn/@?H>]vEQ?Z8$Rj-ȻMpH) =k7Zx1>!YT9h$X$0222==&2.16==ݭHSq`s(*"о=cg):"yY G[k21-& > +ifۏXQB{BRYf]R(Z-Ơ1u$~_qx =nڿn՟~ז '}0Z/\fnG_՟ K,?o,Z;{ǡoIݼ2?vPB2z .Z-MkfSJ9<4l\gU+Jjm,c)4?1 `~1F)/St#rL@zHt$Z1ԢRO)H0N6TpN#  wBp<}^/-a_.FY&mILxŀc PF9X5Ga)rJٹs'V{E2q]uwrr*K3cMRer2Zk%}Jss~_5:՟e[[Ӹ]Кk9:g՘ֽy__{J揝ttH_/?G:`> kzcřnzYG=䐃 ^{g[ƐW=Z0w5H~/{UtNv{S_Gy#GCʖC}-_{=b,.㵿 8VCăX}k[efmSxo~k{ŧl8Еv՝S_u;ag3bÍz?`''7v:I+i|ҳq%7)c'n8}/O;c?L*[fg&n8%/x3ν{j{{wQ-׬Ypw4po3P較odlν:l+~#?m~kF@7ef ѧ҇bc@ .V2{Rf2Ve{pIqEaQeZV-\.hhvҥKў,hIERu?~7c fhX0 ;J8(!q !FVX>F~]lM>11e!dtttdd$˲,ZƸWXQV}G?[$غq ksړ^*A۽jkƐn'/4Mf x3beg\6g2]eY*H9aw78#4L НjhJiZ QJ% gߗ B5F'I,c3AJ6Qec1chMAP [bn.Y߾9}?zq3{j쎿ߜdLQXOC/W??8+\\#=nřEG>kN#ɦW紐DE]s,T`@ ~Oo3ly#?+>_Oou3ʢO}v}ouc} ?_c~/2ܿK7}g{mo']7-xu:^t&]!zUz7#z59̏~xW16vn8eu'\~ zLMm%|=8O f5<5';}?~7~qUN7HZ:nF╟{ݫ&ߣodFe| {>ٯ?y`+58~k@0Σ(1]s]hNU_ժĸZ+,zi\Ns]6&Ikyj+T7sD=5ݫR2)^j?ϽǞG_~T?PF(en??omY0rZz-m_w恁~Kn}yk{’nT?x;.N:I Tm{cG]ƒJ'Mlhm1Fg^%:Tb IDAT j54 iKQ<Fgκs.cs,xf.8z($3j W~HMj̒\Q?ÝcE`dj-dJ@QgE4M6!W-c4"Ϗ*SSSZm||<'KO$ZcoCvq4"E`"[&BZ-)%2ʹmDn#LDX;AY!11@c@хLN!tnv[Y/" =dE?:?,EI,V"t9i-\BE'$I](FH)ȅ9RJZk%ǂZΑ`<>.D,=gYV*J_TVMeJiB 'F[ىKBيRFQ{ W8q7q;ݺu+Rqb\~dd< 眢wK@қ,0S$ "rX3 c@)]r LDQuȋ&WyBeQh4G(MQ.Jjv{+ $I"VDI:>9E跕Jj uQ27nsv}|@J),ch ;VZd:Jik(i(t:lrkWoX 6T{w._{{\|#Z?pS$- cXcoz$1 \̅ a>r=MKэ; y}6f*omAU t2 @O(ZY_'~+0#B=k>s}_ʮ#^ZK '[n'%cZq7m oQ|p=?nw͛Sk-vI]pѱ[ˏ]`G[Yc:}(:fEV7[ko΢c9 P/ҧ(fĹ*MWہ d P&)CMAR*ͼ8ժ՘۹kcv]l)j1&\/}VJU* cDm6ǘMFƵHOoA"Vב' vO8nZ 8dqnc:Di6ZcD^9znqDȋItGa&oi_K`C1B9(ZhZ肴l^h看M׌JC%P??󚳟K-K?}/8qݕKVv̺e⋹/gw s>t5 ]0cfZ>)Nï>ֻٝo5/엯m}N_K\}\yq}=i﵆O^wrw9y?__t/Tcdb1Z"|ˎv!4o|_>}g@53k>{_{#/mDn7O Ȣ\pݡ"}}suv 7<f7"<'h}Oz䡔r9}h~C)k4ZKG\MSNRVo PJ2ф S%K芡+7MC`ɒ%=}ΝR0h Km۶}!d^)ؼy-[{$ !@+[~Gr)88f+Xh;v₏ Q/ R:&V盬V0FI^3Z 2J|$?r0!aMe>?~yC]|d!`޼?Ƃ5 Xx[=UY tGڐ62Ff=7 ,c,[tߖl617.hY`"-X ZW3oт{Ssk 礝kIo⳧TY|W=3g‹_7yYOc>odD{:l+~# >SWM-4xOPL=׭?J8.c{w=\*18xJ@jUkE)sFSB=ߋD)9dԂ͐=8Jc$A)_Jdtaq)X!4 Joa4n* J)SI)ϯ@gƒb ֢lAfN@?w!RHϓ;NYI;al(@)2HCJj/&&H 41&eڟXdIecJB Ƙv3˲Z͌PoPtN 'dY½XkYw/u]us RL&0 9X#AyaRJ>nYs<ϷJ `T@PU !FG,n0Kо5+O>㓭\`P6.34IGpO3Qp yκ:Ƃg.V4e3)gH,R6wrߟ/\pѤ/Lf]ΙgFf<>{,,k{E(^sQ%9Zԫ?o9zd{y؊WF?R"Y~yW u; dp %zf)v|FRfi MRyh !ʕvZk6[1q 5I$I>Rզ.91jEu]$̲RB:N8J1Z 1*y c|*h1:@N ثE$ 6tsc4A[ ca<6W|#*s2Tt<|-BIi"/gc{}Ҽ^|봶_荿Mf}6~_?r^ Loȋ?w2Wsnz?nJMzSC'}x [!huh#C)URYK8PJR yry79#n7jjQ8Ir[F Ƙ0 1)CCCh ]ׇ22W*dRQZ^Zlhl#Odp]VU*BQTTFjѼ-L ܐ^C2D砠bF;cBkTZƺ~1}uZ":sQ*KS1Q.W$>ym'p(si4R 8:m~֎W`G/=Y۝Λ:"Xކ Zue ~R~퐕JZ57.`V-X2,j5ۄJ7xݣ/OsDե@htuˆ=GxЋ轫Fi{'W;3{-Fz{[@2' Rk$\*1!@fQ!QJ(dY@l\{fqdAn}|G(5wEgeY&\lc¤InkcJMPȲuRh40^:" JFtBfXЯj5`unN}ZX9&}x|,cMz'[~X!L,&\0S;jA1:ZRZFkS0vp+,@m"*OYdYnJI9S7P1ʬcyJ j՚z!p !8wA;1Yzڂ'|_w޲mvΝW?jM|޵?.{ɧ?σky^]c^_gS %yz_IeS-৿gy=cgW173/xygq[mxBt1vo?wzIzɗ<&'nӾ;Ͼ?U8_c[zm t;ΛwU=_aW lۉN_+#,YOpܡ+?;-|w6W=}~}rc|wuC9hѯ=Ŵd-׬Y(6E/hg{ s޸st׽a1R~y_w{> N⾠=uǾ}yo/e8s^هmۣߚB%?i@dK)`q?3)41(eZ$M,5Z+$R8GyhiAwc6 Gw4xbR {\H@"BLTb udhj4Mz\8xJ)t<AShbZ4z;&4]LNN"{ȞpGGF<9(E=A u1gI@hfqpS!?E?!ח$ ]"̘rP+_Fxe Ͱ Kj-22R,4,!0 Rz&n(`=B2RkV%B*$M̔@ ѽ d>G; n>#ٯ?+\x_ʷ󳯽|Ls(4>w~ gyJ=L~'!˿rַ/:С3gߺᔡmp^<vǍOQš^uӿ>I<~.3]Х~c7־x_G?+ wJvӇϚ@wMל/W??8+\?β&۸K7}g{mo']7~/#?s[FrG>s{3}%לzUO%[W_}7]%twJgv4^0*Ͽ|:9)V;j"n AoNl}RN;x|vv˧0wwG)O:2>J,M+ղ1sB.BIiM)c&ZkYt[ ֝Wq<vg7o},9W-=  e|4w>у׽oE.%h3cc_x̰vQCp +zozk֏{3GgDEB0nv:s'"-럽?~*_YyA?(h_w恁~Kn}yk{’n屣.A`_qWgv,j\@ v.8z(7% i9F IDAT+^v¨YwsمwlNbF'.rSb.Ҏ~sAY4<e.N~f#(9^{΁ d O }ZcqcYj,SR)HJ*Yf~תFc VT*պcD <$OOOc/֭[sR]v@EZ\.7 źi]lYجW)566B4~EQ^o޼y׮]mұ1F޷T*Zk[fX&Z+V0IN399<$GuCl.Jc#c6MdEZCT"RVJl6Fc|||׮]v*Rj5@ZIDq߂a_B}eJ+6q!)%%3eYSSXȂ~!nhɮGJv[{nw1ԏ=k>s}_ʮ#^K:P󇝹M'a6z;S-Z?pS$-V8&]N -Rc{ԏ9cҚO8^zټyם^=ܬL~Y+Mc!%++Aiوk.gw s>t5Uo{s\/~d/;#nMKl xd,;ktR=(pSP??`"߼h=<* 9Q>P=U9rEY@2']0'yBE>,kỌIvm)ee&G(;bXrwңNǦgJ)?<<94&XcLAtE $IA+VB#6 C`!dӦMNʕ+wر}vlҥKrɒ%YMNNbbthTU"Z%b Uhnn`'BJAJM)%*'R.J%k5f9UA\p g9OP#xszZlwBK V(R)]kÝ;wrNWX0}ͺ`#J)W,]l]c=P&]em_[bO=M,")ȲLʬ\*Q&VXcۦ۶m}oŊaVZ_ )uexm c,/=k6@黧kѰS!K P> {23>X PF/0FIP#06ֆ]ә;56i(c'sZP?V)|w3@X5kc,bZ5~Ij@ }z6; ϭnS_2W{ߔ-8Yݟ|=^b9;)_J8s58J~9 P?qˍ%=ӻ޸q\ky!@)}%PB=s]q(%Fkι&|Y[T} ,d+up^jXVZJ%`>\rP="'&&1r9Ka~>+FEʕ+نf4h"9 nD)>"ugjjh ])1sңlϋ}Ѱy?*sL B*%Z`%RJvkRW}=CRjDi0'oYZŤijFSJTa53IucL ['0`)mJ) 5MST>F墔8Ѓ$ٵkWfR…h6/FK,֌(I3?jB3([Ѓ"Q>EOϼfﻱY ZxFf.iE?" MQ6\3k9{#)|Gm==x)@cY[ӽ>z~Asv4̅WRvM8N<@RTcf` |)[b ]!юItKr qDcZb#0kmތ A7hcfZZ v[zj+D Zsn_^}\`6yhjǟ&Q-p|.t6BZbE"o+feYƀcg8mTS:8;q1,YIqRtzѾJ 0Rq{> b}YR0 ͖ g:FQY[VJ?@,dy BBF_yoϟ*ȓ(%d O-t:3c6IkƨֆskRI%=CR =l8Hҏ0 &<6].EG1 Jwp]8^ϗQ$I6慸 =R)ckD>ab-.5A ZB)!19(Z8-:N5$iJLF)%`Š(}rEq$cq<%c*M-XB:ê\*66AKhC۾#IPJ%|!QuŊS֭[Pg!cm&q97֦i*mJFkl4\-S]Kqd O <<CJIGnYq1mXcLbZkð 26:hXgY?crLg!mHzrA=sCq%V }K\.Ŝ<< <55- LDX+,@RM a@\.۷[4 J:197=Bisp?;cfr&Ms38WLxΩ뺁18jSUwL&ɾIXB ÎGDDExD\ЧSA@)k6Ce^oݫ~;=3= `SuNU|R ܫKDYę3A }쓛f@tF8\V>G_ oAtMO&&ۖ$.Qp5|P(RPMiJSҔ4)py@(26kj^CP$&hyz$AD)HxHmc.mHV!EMb,F$ ZXGJ*ڜA{qqݲe ֱ@ma7"eXFʦ>cPK|`tqB-DZ&88J9 jac9CT˅T[m\H+z{*eYT4#N'Z CWI,H$|s\m (ŷ \G+/*l'h4˄9dE N5]bD)Udؖ뺞y#~GBs;eGO?{E>9~'>g4aUZZq~]~~S)Ce%NcU)M5^|vM|8 />fl{}q+3O;}\>gw|-!h#]wagVעEnj~WW:&0󐳖>@iiMKt:~Ѝ*ç5FysҔhullrRF@8$HԈvH%SFt=a]4bt:myM$T C#^CC;Zh}TP~D=RXTB:{۶#H4UU5N'IߣH$t]G~Rw@<ZibhBrH$6fdge2>=B eWHa„ }}}$,:8S-$!)\܃[Bj?010W оG`5[ExX`~mGgQ1]ϯ_*eJ|Dž+J-!kSd/~߾r\CFFs-ڻoEӴQ t.~TQ =3#F -i~!A@u@ξjǟ_2K2V^Ԟҽ"PxSbB1pmY}7u+vϼi8|~z禩ǫ:=sO[sK=/g+*8[s>Xy{~.z>u]b6h߻aZv}^sҔx'@H)(G(!VQ2J@<cU*x<.˲pl[ӴX,V%( Ym6aެ딁eY<qzzz0,;A >Nr9c+М] 0'"L) MjYV}0VDB X,*2vbXT(VJbydάPM ^A\ \|_n2Vq"f%BC SU\FD)@$0-:^wEQ&LIqlyimPJ,+DW!@E {arlYVK͛7ASF'@јx븂D<*$LHzA(BH=\\}Ǟ_~>i 3mn5<=cOc='mȾu̞۴S7K}fΜ1} K-'9z{2/ wʨf2MfΘ1uھgܺ2F3i4LJd}, u='M6mO-.u/,5/8]v.S9oo`V-f|Eo8oIG^,Fv?IA,M<ԣ k_xPjE7ӢJ;󊳧9[ "$ƤyLP/·6\-Ŗ.ZS \ ec-Z~/W$٘rWon=ᝒ.v}ݸ#wudHg[d$7Yeث{5%aU29½.]8$nF:MiJSvBTUUd5~?@ 8N6e|bb6-G󲫫P(@Tڴir"eva7oF8ZFpOjdhR"u]leFKK 1@<B}PlKO 8rwbP}ITr.v{L&_LjժYGs iSX$afQ@",@mƂ˜¹pY{{;Ry3G߂+4-BGԶ "c,c#ϟm۶[-j 0F#nۖe[PιeYq˶;} 8'm?жsįXzk.¼8mnn;nK~Cjc#VO,]qǯ?\4|[w)zi퉭 Ek/Z{7]߾pQ=n\z#_aҐ4|A>ɿ#Ɖ5tuu 湛NxxXKhf[z[&d-:.~jm ߫z'|[&b%4 0"GtG+ח"c;i9 2(!RiXIeR(d^{t_>efTfdg֢49uR2%Ae/oWwo_4٘ud_'{͋Q|c_[9k3䞚g j IDATi߾r ?$Abu"z :~{_{{]q)E" wh^XXje%f[`]EѰazM|qS"2S8bwe7[n?tC#۽RM厇۴ͨm!|ׄ_?㷟m_ӳMzdz_w|e;$q,1 ;WxC=h$#ՙV:?p-@vpAmTgyNl/_q>g0~IlaavaSݔ4e4MIb#"˒Dk2|?ey$I~p!(j+i(2WQ\.ad2PvxnH$J$y\.( u]ORhU}hc)4R }}}3$ i\ƌXH^TEARuޠ*lB= tC[1Qp(j>PK@+>VAƛjhh4ʄ@um˲4-"jdxhnYV.8躎yuj!l+EΈ Rf@t:b_GHBXC/2fd!9~#I6Y>!JF/~eSyLJWXu +V< ǟlM^w6c*kpϜZtf^~sͲ'!F(c$1-״5P oB^׿”C[h|"i 1G4zz* lxzs{G .9ѾW/R=r;ܥhc[soW_)t\0Q#a2J:ZlCHi(v/o:Sxeϳ~-vaiJS#|_QT"~,UH1*˲8,+D)=R`G Ţh 5fKKR`b6-ha#9ϸqyp]BR8d\.s΋b.?~뺥RI49D"ب jP41R0hr#?|09uUU  ΀`4QBYFr% B+ \ű;?j',bX[[i۶U~!ї"Q*DNH#)J7FtM:!JTjkii=0MgX JR-SNap?+V ]U"UܿxRT*Q(A@]S]# UQ HRx<F"M纁> @x r{NHjǯ͎U=Y0#`ZwP$ek\vƬN7/m ,L:Í f߳3E3vm+ 2M7з@rrjB`o|ΐԂS߬5lvpui㩑В8߿|nϑ6Wf uUy3 /e޻'ziKEBEZ&'% }/:{ŗّHP_X[ꋃ_YCϏvp SQ/O4Nzo.  8OA~MiJSvBYFQ1f[8r9ۗ޾}{_6kD"[lٺeKoooww֭[o߶~-[r>D T* XrY-s}[lٺuMlӃfx,ˊ [f__888Bcm\.r9$\.W,"RaczR.qL!˕e=u3"BF⫵`އvp8_7`5mEyv.fdG" L7;i$4341O'~$Q]W=s qGU~(Yfi?찈u5gp]`i>vX,W*A KC]eB좰`ApN t=8c ^.W\w]ںR}>(3cQp6ԟaW?RY:;/j;vZ]D<0]rIJTr.\xwkOf0:]rߝ]t,}y225h7\ygO8דƶ͋O{KH+ͥF5bj?^w>lxt'0h3On@).VʼqߔS?g\2moc_YK )G}C^)p}cE?I2g{NKo9K`_7w 46~WN鐉Ywޯtiozr~vpv8xm;!w=t~zϼձ$ dГ\Sԇ݅4);|`Mƈi)bD"HIar!D_OQ &%T"e䱴ms!R=W ڨ!=m]$Mae,b{4MC0<&Bc! P(yx<;!dڴi۶mCtPýTU5M==W*Boq0Ш&n7nDӮ.VD!$ !\.AkmEͬ8]Tz!=Qx.aBX |^.e( ll=OQ &qoF>_D$p#jH焜?Oir1溾B0A؎G;vl$ TӴTP(DT\.߰bYXHڵk{q D @jm+Tb8 B@mqRS?(g2\4DAdHuMzݐ ;@{2p؁ګj4g͇GyX*|I(Se+\Ti1эlߩA P?ORtAt6FFZ;?;:r;:L9pT7)zيzYW_|w[镕+0&1˜(!hTuYeIW1UF3YR `4cbC{h4R`0,B a-:e;B @;,;Xi,!ں@G -X,^4  u(9xT*%l6xh8wZnavpo펎܎0Ny׿G8#BSݔ4e$5 #C<5=J)jP#(ȷMs+ z\Q(BԘb+ǑU@Cs^TtX|^ahdE>GH B Lʲ\(ГAWuγR$l6$I^,k񽽽H$|R*o!1Eʄ9!Q*41&YwpJWIͿЉ!fi*x뚆JJ(Ty q:7H$ l۶ }0 ɠ&qP*K8gITU, a)w&T*ex0Ѩ1+p5ڌ,򽽽---AJRbhU A QMiJSv)MiJSPʔRYVe•u]ommu'HfJB,j^9v/Q4h"\tH4 `-HV.ՠ.@6!L2 (d2g0!Yc"Nh4D"xT|ӦMîVXgƶEM)aȌЭQ@X$0!M)` ֭[}OR\tMca>iÈ&}رG)îdu`FB$UO#7EP(!SVpNTIF˔r9҂,I@5wG=?ZRoT*&I~ %,[ B@)UnWY+O߱+'ܔ4)MiJSъ"+Qkl6kJ1È"f4[ZZ*Vu=Jids]mt$D34Dr:6Mr^B4M$h{RApq!0`Je2l)~4hgH$nww7Bzzz$ hG"$۲lwww,+ VUU#=HHs  DBUw$ {u! @ww7qP;hڎ.}8dBJD)+8w,[R.[xbY,3BԺM8P*T*0F.ׇ3Ǭ:10t]nPP("9iU)SܟmZkK:2dYVt:=v^'zFDf^+-8}?ZI'}ry#%oSKN< OޢE;&0󐳖>@iiMKQZw;B y8s}.P4G"fF{/;[Jr,>f̴zhph5)z6jĈO)d2Um\ʘ\q\6u|>Y*!p-r]P(`Y (FH$]aȇŒرc$F5MX cL0htرB|>~XT#j̓tMӰ&Vlb)#Xy{{ baZ{h_P4NK#7أQh0~89 SEIJߞ}_?_lTVS+ LA)+$K)-yJJ$QUh؎<ET*Pb,[vEQ,B|\ u]oii7nڵkU59YeYV$۷c_-~.Gfl9F5!o@j Ƙih"wJ&G}=ԡR ikk}T*!'ܲ,c2yI5лcU 1_F1~9Ew/2T,pYAMeYT &paRU5c%5>kp]=9,E@atwBL,lYB?ugf>_T*kX RV}$oZ5n6c LH2>}ڡ2ۿ\|I'My5msy.8%OZl(eu?>}YM_#_1?<V1~ݸ{p'N 6_1Cg^J?DeGO8K:e}ݟ}[v}"w=oث{5%aU2..|9פl~/Zg񳗟i:>v ـ.;z瞻O}OAmƆ9({drxmmꁟ0i會>Ճ/p]XmaM.^!hp,.Rg͍_0X~=5)EB9WdP(kv4SdŲ,(c,몪ٶq @x=" $I$ cuvvbqT2zKK }}}eN&[mz c|>;cƌ<4MԄiT<@@W԰!$Ixcqb(z @iQ븅Ap1nFX@)dYLX"+ jʔAhJHز׌ЌR"cZ[XD׫xZjEQe* ᣝC)Ut-c!yG"X,2c6"t]'HhSpt?t]WUR 2&S*IrCEB!YVrB]2T*L&%,xT UD0aD"3g͘={v*;MSUP0"#yL_\"YhDo윸~ru[/xtoޱ`Wк?>w}'pt.3_׬]_ɕm8]ʷ^Zoo=ymc9b(;ο>]W4ܼBp7?J5~ϾOoyū5g]ۀ9Nby㧮{/+.5P5Q? խjضG@;)ȮxϞ{sݛ~oPD3'/ܼ~}"I="ٷ6lx;_]7VrnT?ZЊ'cג;{oWm:o];x>~"ݫB]6 F?:H=jp=pu>v;=~ϼSQ}rƿ~[k}r['X{دaOZ?S{=w旟]/t_l2au7=І~{a~5CO<3ɋ@ܟhgLWwUYSUrYZ%t- IDAT\X*ZkiR)Lx縮zB/lʕJRB\w<./Iܖec{^g^4P焐i͒Y)c.$$L&Ǎ0D"N8d2eE M|\\.gY˲\*2LŶ,!$ <OueEIR `B0G\.f%O,P!dyݡeOaM00I „E(hLz{{%F(r1YYA'HRY Bjjs59B>7KdsyA pdLkkkbbgÈ8ML49|,K,IS55OĢQ,iʨE_{t_>efTfdg֢q #tdU"w&u_cO8y1 ]}|k=g0ʉ˷squESDHno_U9Tghrh]WDdpĸxhH,IҲ3Iew}N˕5:sb  y珧?i˱݇ӳ5:c(0h g }yM(l!sZr섢F;}@O=p,1e£Om\pw3# ӿ:^l>W45"3 ?9ateya3O\vn!٧^1zCg_IjKSE@\`msyb,c1xl2M(,F1D_UUĦ$y4e"̅@ SƏoN&IRxq45M8,_D'BH|"H$B!g2۶8UBM*$ɮXeF 猥XB ˲8mÀJ-5#qsDOi2, B! КG[>c̷,WZnw-=wAwTkx`س7Ҷc87[wcƎ44_DdIƎAGc\0 =UUmYFژ 'MJ%2FcXN1.0.֗"_>֯& #|u]dq]wc?`> d8رc@[[bÆ X׋}iQWx|ҤIAtu#R},>E*{&B1ieYmeٖ s1(B(f1TUe ;aaD, j41O; &a/^s  \.)IcLfLOOƍ7lؐfض80`B"IUF'L@RXHڏ\ps.E4i rU}w}\1 #+pN)K&ShԲ۶aCob5wJHjǯ͎U=YoA)>%H֔ Yco^6X@tm˯.͆:n}z}WļcǾqSW~Fs fVe ʛVo enC߸[]/)9t7IcOsگNY .W|oҭ\}DyG/๕~9cU̙j<#wU<}^A_|ݟ|O^8鹛^pv?#;RxnvϾ|cMVCrwЀ$}੣݋~z`㛆=;Bփ z"1f)%~qQ߷vIlYmYZe9jZ"0qErAH "N<FhΚ5kBS~`@h4N3 ~zf̙sa% y<G D"aYV>,k֭x1KVb>lH$@Ȱ6 Ӷ-[X,ޮi@OOXƀ-,+/U{c!8J$&SX0{3O?_|Qεkqt\ד$9i,+lE_;Ǎ) 5vvɟZ'v'z偱ێHVTr.\xQc*itGd)^tMu[SiiOoýhȀuec[:bclnN"{]?7;ipJ@s;nm4Mi T*=3F%I<@STE1ܠn"BJ%4)iZ[[`k4SU9yXjrh`.Xƺ.b$If=(c(Re,[?^[[;vj Rrׇ"r<L , !<4N!TL6EBUUM&ldV8 _* ~* T !iXl~MǞԦL&GB|M<[|K. ,hJm7ZB(8F%"9P@8P5@`"9’Lbqݼ NeDꦏw_)&]pd|B}O_spy1褁6(cS%V&d "aV'A @MEJxPX:u5Pȏ pA# S[p2GhG7 K)pTwQug>Ut^wkJ@n l'k zJ!Z~~3/2הJg+~xgGV2+zw֬Y#aedqGtY (+F4"p]ӄeLJJ.wV<GI]i- 0+e =n2m4M]׵yDhww7vŞ X,b5,r8N[[h?n!Ę1c4ML,jgg'aT$v{\6`%BR,`"A D%B A AWPBxl DuD0$)jG0ƘxX q֬Mwv"׍9!im;,')w . 87 cLk+#;>p?|w]_BȲZ*HDQ/+_uD,n;3A?DLt=oټ9w߲;ŒSM D䋥w׽kN<(!R'n&k{]}x ԡ enE7$# .#a/6xZHP;[·*AkfK`g2ihuDŁk ~:.x]kpyзZuP(μFa85ү_n?_mMSn=imm-Yvx1N:8D"j&I ~vM^}ܸqR$<՘r*V2$Q!r*1I{U>hc$4M=`ް,H(UU1ofH9Ji\,cxUUb|I$%qIJaBH*eYVkk+)dUW'UUEz~7B4?Z}k@չqBm5Ou9E M <ϳu]H$QsBe۾Ki9sF"Xf-)VĩTbƌ/R.STՈD(K 6k>,"ԩS߾};Ą0ʕjmir[o!rkJ!즛n:uѧ{z=&1&ʫz7TrIɖ{{zm&@̝S,2̒%3R ;}}ſ\ɂ4);# QMI5/1Mi?| ֬bW*X,J0%$8`hu=EQ"iE&QXbydT*m8ٶ # a)BvJ%MicƌBqjMӰaZ ]ף(Ry"&lP*+b1yeYr NY7q@#DQJ#H6L]UjR 9v*DXe`!; FP}-$BA`[ظw-`,E@%!pB u|*|3 x޻"fFUx<64f-_c󟻺zyg]6F "O0>Ͻa=D\( ֿ~ض3f|y6-z0h+:/)89ӔciJSE!+ JF"vI%8w]7qd"=nܸR4f]י9sf*u]$}*SceJNCSNݾ}{\mKu{{{d+ㄅAԶJy𠯯 Cɓ'b3|[0rYEQ(T {zz82$ PV* B.JX,6f QU}op<$*C1,˲,;>s]o,EǶ9tD/ LB$ p IPqJ16f3P.@%IQaBS'BuΝU*0V5R>IpFKe++u(lݺG`'s J  9YUY$,_\$ɹs0&<+\P,V6eTI&?+ܦM?ɓ?O)MiJSҔ4W|G]QU].pl_\dI*MMUs,3#ޓewD"[o8sL&H$cd+z` іEQV\cƌIRS*dYF_mf!D&q] Y1 $ -R/Ӄ.rz"R8N&Ym몦:\&Nӳav"îd4ɓ'۶N1%y> JG$߉"ɔ~ |}JQ M"3#AzWPUu4 %준$}Q-u! 5 g4ry3t$@EA(zDP<\zU{2U!(BP7O{^{u}uuwu'@&O=Uo* } $`0Қ (Jq۞C`v\ٶK)R+a1f3@kJTIJADŽr他 ZKM@jC͂rcq$~3M/_)-wݣkGx3NxX"ֆ]תWk;v׳;n?vlcmmeZj a+ADZc(p3,prǝH]#5|W}ƿ蒷n{MW} 7pӭ/>>e>pCϢ (#M(*e@H^w20 {\MrfVTj4K˜$M`?$C;Ξ=tBrӽA4 =;z(l|G4Mp8Nj4MZ G1.NqOSJi\ư `dmUU<922qjGqƨҲ,u0fgΜϟǶ.//l}|"7@qGQZJj: 4`Wd_)% M< VW$sI)Yj}Z=w.M0[[[ d5Md !hkk 630VWW)ct-ز,%247 )eyQ e:A)gIɄLp*[d_EkH^?̆ h 49}ERtD8纶eU s}U9 IDATYYQ*S/2e4I,aUs<A$av%,/? ~UϹ=~|fr^J_{}x^xǭ_@۞q};{H?3/u'Npmw'^~lj#͍I{wэ/lg ?)P o>2~o~}&AlM_7p_+4t=_}9Wk^>?/=@v}~KQ^9=8'zJաwc?zǭSw^G׼_엿cY7{_'|/z׵'_?sWOJ?@-JR8{{{YYƉapq!n8IFRh22ARJFTj28@9(ft:fȰGCyTBŞzl6Kr^߿0Bv:6 vvv&lm)eZjF}u9)`0pBà|vmie8(.TVs q8K @f"x#y󹮿RA0^*8NߋdL`0Ǣ>ե`qd!c*3[ְa7 xf7. F`Cи߲ EB\-/}3۶=߱LvVggq EQ4,5`wgO=x' )Vh}˯|o~a/6μ{~6}ԧ>\M>ӿg'7cyGx7oyssrOhZʅ~/1_k]RZy?uŊ|O}ӟ+m~Woyoi|ow J)X"͵&@EbϾ /Z/0'=bE2 7RwjݪJ(LʗJHt4c/I(d)g0 ur!PG}l6Y:NVWkegApm8 :z{g:仏npVt_hl?D=Ouo_ox%k_r5,߿y `乯K>P57溄R8ANۋKY4i9،Pa{^z3{qG2ywGWYoymد}u`zj'_"ڏOZ @hycɮ[WR1-Yw5~AWW@l#(F1}6V&I%)nKΝ=xv,--u:q0UD~Rj4iB4T*˱yR *[?WTJu]w)-h8DBZ6 8T jXگT*8FJe:/*Uʔ3QLi6Q> r-U*(8Zqh4VVV8f8MS4QWT*%]k Cg1f,se1Z9C!s q-7>9T,u}l 3`}}6]p0gԩSy›9G*eY8-H37'cT@afj+UnI),q+m#"<eqlTs !8Z[[[=4͍< \dYZ<|M{?zv_50[-К&3Q~?~ם?țn 6QI4,(OzqD9\2\Υ}ۏomu11lU@gx3W+~|ϔ_[//]rw껖B'ax i1@\@4Jd+a{p4RRB,Bu8MK)A^Z6 `UXضR蜅3%I魷ފDǧOƚm?{}dpRt:nt:|a={V~I{___1' 0$I' Zu :ړe5͕P sLj!0$RFQ$ʁ0 J%$Nlq&\'PE"2Eheh'I= 0ٶ P 871Xk0pǙ,c9s`nmۦak ZK:;pÊ<8R繒{eeUU*yBXt R0mm`S.6֏ZM!>eYI0z`8 G,Yj (5 I$ Brɱ,2=@*e(I8/7zR}ww>zvY/=Pz|%~D$g? T.s\jQ"i)c x3=xg?sy!DEǎm;\^]2_YY)Js'=`c~ߵa AN, R$=0874mܻUCEݞ Xk00$c6H?rᄧG t4~3 Zk|i" [ X gIBMPsɌ"e/HxNJ)* q ř1 ZS iqΕcZ-umäqPd* fDSM XJ%enfTFt 2UZs8EQ('I8@yV ?uU\la|MczKMzv/ TI7UJ}_jŎꝿe@oyOͺq[.{O~wY7te޷qp5PNRvVv{{Zc4*k YP*:{۶VUs{{xj}w-ʲ̲,P:dvNJ1!7CjtڶRJ[e(֠3gh4z,*yޚӧ(MGyjcJ%lXz2HGRӉLjZEZMJná#g8(1Ƅ !X2ϕ<JԲӦu!RJ%}=?VԼ^dwlB$KӔRۖe!$M sh4l˨jQBY´֖e$t\nd2k5,~Zjۮkkg04gY$l >0pM,}r,vj|ՠ,Bh)]3M18> eRJ,2Mc}}]A)a0 aς{l4ǣ`4Vp{k:?Qo]ueAP=wT@%@;PJ*@B_$ EqmBk<ŷ_k]T8N%mY5,C)Ni}4}ggɓAqtc4 0 FñfB)!#\k͑p/KJ)BB[K qQ~[13Ŝp5,ZXzyX7<"0WqGi|(4pӸ<)$dY R H̙3QR*qf 4n}FV? ʶzl6=էڰ,˲Lqص"=ΥZ&c,˥eaTU2-%l6I\J'S˶˥mYq&0p@-(8%zi.KvʮH;ŷ_k]B?ߡ+>ޮr5#}|y}WSo0>a %ς|7ؕ7!+P@#xy zQ5q#W$MۭV R\*y-TJNSƹ NƓdL)s341.0Dq|N)9cBH1M4p⓳̲,=i@NT}<GQ[[[L0$I8+++Y\RJK(G:S,˥~n{ޱc:N}nJ2 Ёx11ȴ"j)F꼒RtMAlmmab`YQDL<4RJ$hלKi2fPL˲!hm°@)Mx<+kDRFsVTjq<ϣYLyTkJ`ܿHp,mѐdqjS3˲t.J)ee$B%vl˲"kzQ/Pøuwi5UÏ~ $/P@_m23MVg x,Mgir86L4M,c'I|Y_,˒"-IZZ^AXy\F=XζmT[VFAAFjɓw8! FR9g1"_̼jpmۨ6a0JAB[q|abgbgpYkMضmp aEQgKINF3M Ø3ҪăAM ѦiK^V*3F,PJ&8ij R*s `}AY NVНMYzRJa!4h˲ͦ( }ܹNgYt-;Mkj\PH(B)5>;,jީ/78VirJ g x2Fq]n>#?p, fRBJ~;{(+a$Ҵ^ ֎c@Y ;eJƔj"i<hiIC5~hYPVc Eb*f3j^',2N)pA0!txOMR3Bi$I$КsHsT(cJ) M53i&$b929 ocs9Rk!R*MEM]+JRfh`ϣ&@Y.S 8=^ (c[+++a4 ˲83PL 2HAVBT9œ5 ",]|K)!n Ӄ<\וRLLȤiPƒ8"ך!Qv~"r (P@jWl6ǣtO{K1FS+Eq]IQ|9QjapWl---Bzޜ8A  a/j껮k2jjp8j5IVJeV Qix 0ƒ$<<]P?˲@ri~g>dR.*رlBiEqZEF R37aٽT*2\ő__YU> sstYi"\.z= KI QR2& b+F8#* ,B4!sBe 0Ir0Z_ڿJPKR LF4p˲1lhEQFMϟ(ex6c]Hqכze.Fœ,R9iv7y.`I@q-yaJṚ$)(P@ g-?7EGY ?{aE]K8y*ϵTH+뺝NDZmBɹc;Rk)#u \(*˾k' jq s|5lx0h6JpQ_`0@!4MM7$E)]禙$Iq]4M=Lv`AXšdqfFvTx4R4`P!FE=hШՆ!ߗ~*ZeX_RR22$hƘR9ƾ b<Jr )#twwW*y'{ !Rd_ZY2ϓ$ʲl!t BmRj HQJ}zZ 3!ЁXk= Ncye<_5_~}J*~'a~Z(Jj:j Bt`uu+HiVEi*@ (%/OĻw_˿Xy1i[T\y>ʥRZMq]sǎŐڱxIp'cJI$fswwwAo6έdj !P\1q(ҏ$R^>}zgoT*y^$hU+`GQ ynBG0ta8Ne)4IB۪TkQ:֪nZ-ιeYy-jR.1f33cz}Pѣ9Ȳ, CKzdΎ8 \q]Fe"0Da$cOh4j"emnnraR>6 |!JicaLqn` ƾվ?:uPjY&@9!E1y4L<ϓCiBRHǹH,A/}F<5Հ8%OȨĔe|Bk!ha$b1Ni4fӱm)$GL2&d"ISL&aT*Uǵ !a&A`hVyk;Kmoooo3L%֮[oh6Bz{?wf@+M8(Ot=Y (P@O8c=ku$ilwg~Cju6YVf`0nooc8;NT|WU$}l=z}MS4C% N>7x1]ZZNE>^o2eyN>u]t,^ !ZV+JȕDyiJp9&gT°޲,ì ",](B.tj#!s@ZHRJeZ+6!$M!< DJI J0f @;y=L4JDJELq35W~ z:eiZ.WVVa !FQ&ļU,ϲ< #ȉ7H),^8J<ϥsU?Z4|lVI)ME>wq$x7 /Wu %8/ξ{U@ (/[NWoRj̦N}v> e($en[vG(nYuxlT2D(L 7pX.Q\.&q8.cTRu`>N,FXN0 enb0QmlldY].lȪe4 cmclGϞMDr8mnoܘf5ecT*v8zc_r !BN Mtyyygg9K)kJF1Ӵ<7m+^gB88(J8^csM)!$l6ER6= tb,/9pf00;㥔@H^o4ӧOdpg#w}׵ (WY!qq @ح0 Kk;LZF!Jj,//oFɤb5Z3ư! 5Rz: !ʨV4@"DXtϲ>4\( ZtpȄݭV[[[B:.O`7k6mQ y[/<0Jdsg{L'[BP$l*(J+Z)E&0nko@ xs~ח/{ea H^cTr8R ZMIl6'ȳtZV8β ؖaau;qp udӴquϝ;' cW6Prd}muu5`eYZ ^BibwRsΡ1.A);{p< gAVKԲ,Nr}}}uu`:fYV5rȅH,s]= 5 ^Jd 8^'~4UJَiei&qEQUJAm  @#dtB@Ns9!0Rp@__2FR\ !B!5p$! (! rE{xQ-u5\imZ|em 4,Lq?cQ l@kʘia'I pp}pdb4,-I {|I (P@/AlFRa*)8:hBp8Q8wt:ڭl"|DŽH;~HVT*Q cyM={ꙇO3\s} )o ` d2y{{{Jȑ#~9mf\ 2dԫ4ݨxvT* NR kkk !8cP\.Wj4M ƧiJq<2L4Cygjޠ/,+E 4R 2ȅ>\)@0$ke<2)/Dy\`'a4/eY)SEU @)jxaz4`BME)P@ x*CIY*,Ӝ,K+:Qmn^XZZv]GIɹ uKFPn 8 é:㽽=$l+ Bxyy2BdPΚ?n6kkk^z#G,ZOF$IjL mooofmۄRƘv[)ZOY8vXR,&IYʥpX{8~9gY2LiKZ(JYD(/ɗM\ .,Ж&Mu&dl?Ν$sZ*O>̹gyy84H0xff&IreAk]֢(]qBhueTiNOMیc6ҿ7oP-;l6rQ DcBA1FQBlN:%[ycﻔ$Ihml^6pdd}2Ғ&ފ_zQ`NXO}+t^zC=C=<q~|Qι֪hfgfgZV&ałbzj2$Ɏwm޼C]IQZ9 kK}}r i28lNRfggm0qVZ0/333LRJ)5,ˍFq^״1V\W)jv'ýVP.J|.M7jUXF=MJlsxe2v+$Lј+/X%BD8a"lfvv8J|6ö](!|>/D=Z3ƌ1R !$\om:M)R܈i,imLҧL)hx49{ƥz衇zc0fJӴl&/8+B>8IZ 9hjj^WkFakZ-vetGFFvqRB333BUVB9eرcc:sK^fZ8gq`fvVk])W0t\g``aIR !YP~j(d2V-Ba(!F`DbJ/%$ظ6eW>1cBXvG`{@=C=a:r\6Fq>pB/n޼)?Gy}}}IaX.'q8wcfQ|Xڸ~C\ƔXQ* CjbIѳBeYX?XJvBhOGFp.衇z衇rn IDATzFu~Pq]NMkD&&&BBy}ŢҪ/pH.J:C "IA ȁCV:rHR8666qp__jLfq`xxjׯ}xGy!fͨj>!dpp!y}}n7m^Jl6B6vJVGxǎs4cؑq)eFdT*;?-y6i lUWJey00B `Ҫ&LĔ?1`ɱ2S,8@6Z2D "I>333fq^$z1VZ559iٴes=? ڵkӦM{jfgg1Ɨ]vY:xƸT*ݻ9/FQW1۷{Jy׮]ǎZLh66B(I"qXĘB#nP2B4N!sJi6(2Zl\)eae?Cl`iv2'Qϴ~mݗ9}Yϸ~޶~?E?YTnKM4 p[ ۼxw>@S go{+޾ /O}" i=o_t_зf9_s7 ~g~0Jꧣo~svlڶ|Y=g\uiicg<]Sn붇za%RFca~.M4MB<>>>66F-;gjh6 ۡ2qvf(>|匛cfgg(ZcLǎ pÆ \n͚5|빵ZmffGٷo߽{mMOOW*jVҗo|czvfzv&ˍq<>>W,fy[]wU }4۶mg_a;vN=dn9vlذq%RN@o ֯_{9[o߾cǎky hqFVQU* 7oBi0}L&WE"[vR߷l c}R%1þ]<.<ޏoo} Mzuow~=䉉F:(?߻$]WVx恜.Ogσ򯋿|G~{_w~?gvޱ 9/~kɓ:sUM~ n}?:?5 pqe~ovӋYq`=yt]=Cw^Ϲ\2RJJy'z|VT*IBg:B~~n>rlCmf9?lܸR.gff1vv=88h=w7o|;w޽a.l`` Ijw{f,{=|pZ}}8M0%GFZfMјd2CCCAgپ]vo۶M)9/\r 7mtW'I8300>Tk=00daryq @,۽y/mR+']=2ҧɘ(6Β3d*Eٗ\Yo /d~c?)r~W &:w#&Ct/tׅ/}Ň99]= yWSb+;׼+_psv]zɕI| ʬ;Up %_/>]\~sCpLJ_3/r+>`оソ{[F+NLm?i=L'WP@f[^~2ݕ]>VI $F_reK?18K[dXUOImso|ѦS];/_xwcDZNP1W?p?~b4, ֮Zm= \W)q=x& z-;].ˍ[V``hppp``ݺ<2lTrww`h5badboxa=;{6 ZTlre % 뮻kgs9ghh!rz裏FQ423by[AfZw9>B9q]9|֭9VAP*6mt}}K77 BȐRjpppxx(5#׌  )䲹\ur237 ],ybv']^))rOɞkNE] Aw =ul{#}ZT3/?Uu5lI&o^vo/?_wnyk]k߱SS7VGtr?fo?[|p6 }lG{ޝ߶Oyÿqg+ԝgc \w>#_{?f[nWw_KB3P^6bk.-oO_'zo^?;r8!ځo?^UM/^׬hC @Bv[9'p_;?گƝ_,z枿g,FyTJ} [8M,nx <4Ƀ>ګL'^?|%}䕙+BrkpLPjE߽l".mn{yqDza%vk!\JyeTi~FmZiG(IJD@V- PahZkLEI"8|V9WV[-իwm 3VB} !P(ZNaVOϿ>wG_Lx[41sj*ɞ;{VxݍLc6q#cẍ#WU^ЄyFInyxYj;#^uHNO?' p „j|o|'h sx /[gi>FWRE 0ͮ2'+µ#7/?h!HN.;z̒g_i7d2٫nV׾ϿwHle.r#eg;?.MgxGjt] { ti7鲗z8ok^ZzF,O !Sf\0RJqR_POLLiB FjE58[e# ɬ^q6` WX!7oD) |wϚg|>/tlllRJJnݺT&V+ukK>' FU* h)0ˇKRQT*q$IVbyZk_ƘMiL) NrN}Rr_~0X)eJ1^JN4b0=`t%o t5>|5>~\}ӶND]nYB)sB2lvv7tYdrF7;߽+zMoݾ7nY?ra/ `̉k=&.Bc @gq!BtnH݄ӎ|(c!f1sW$gLޏ}O- 1a9p%VtHxo)Xݕ\ᥦ҉+̽*o7lqATlwkn%g_K7up'Jg[Uw l}d|C΋sl]g봢z˯b+uV]r=3%˟&@RHp\㺮q4MƹL!!Xf c8nA{8DQD,,,ڭ$ Eyw]cl6APJ&''$Ilݮ*:vxaز>wrrRk}!uFGGAk֬ٷou d2Ƙ\!Oi;wd3JHT6衇nL&ùn_1ϗ*B6nbs~UVA@rA977WT\}WJI)O495BcuX.|.L6B.sbW, JBTo2(&rR/r}b1_(䳥9٠^x\x9b1 d2`ygyZ"Xǎ>tnws,6F1YYx6e0!M*F4jɕ:ÕR:m Bia@L;SЋ,^9<ԭ=x/=7w+7ϞWMg[w`Eo|ꗮo=7n+3{}!8W_knO]iͩ_{/ r nSpr(?1;?[zחK|_>2]g~~*w>y~ ޣ駮y[^6_}[ [D@NHg_o~o~q_vcl}Z w>'~W_kӿFN)ֿ3<^~lm8qh]vo_{衇MYn$lFGFQȹS,D"0FFu*ժ}VFq\!x5zjc(fBH+rƘGyR(aojƘ0=w=4395<O)*2A&)T*P}R)\DZo90&"4M-[kg$MdYBBefffpݺuVǹGGGoRXlGa,JSSSJ4Msܹn?tpŔ'|/f͚ZVwu뺅ba2znnh@TOpγLٌtvvzڵ[lZ $f.*ggg a1 ,8!s9XdP1rޖ Bt~a s ֖ۚiڼ֋@k-X6!bLv2pN4 ZKJRhIӔsBJ5 Z#?ŒCHJF?-=b}={{ MV_􁛞+7ԭdnO2]xrN)ҵ¥z:uiːO„vNlζwT[3N벒g奻v鬖ʟ'g+y,:C?XBL&|pvnֺI$Iuf2RFNs sLIuVr]qv]Vggg-S$ 3zttTEQ&i4@L&n7:4>>n$I})&''ggJҖm[?5! IDAT&<˅BjiZ(*JRqÆ=ܝӮNOO9rlY=<:|X0Rʾ!PJc FFA@BH)m%PZkι8$I0fE& LcLf5M(jxZ<s(˷OwJ(?i~q˟&@1~ j.l&q\Y(B45`aI!L$RJ!ѱ1X&gJiQqǎsl00f^ZNOOkWZ%TULc,V*JƘcǎ^=R./h0?A!$\H=rq l߾ ػw/lxhU>}3t l}s9]xȑ=C=COHExY1:vQRecc4cLJVjpp0 CR@䜍yBpJ `qr<==h4 4"(${a$4 6Q8;7v(DvP8>1~hܴycBʁdQW4;R26z([lrZ\.GQdajRR:?7?770]nuT0N0xvnnÆ iNNNp~,2sX:BBk{;-Wmֶ R/[aaAc8eZy)%x"F<&R+( ;wҗ^oھi a !\ezgR)c,I4MZ(Rź+X=~ƚHے .z衇z8{X)yB84483=SZ!,1OӴT* &":RkG8CCC8 C;33cE(\gّT*#Vj\Oؿy\0҅BT* BC1͓RGB0B ٸq֭[:%1jH)E1X#DN8{Z-cAN\ײiB6ֺ݃ m\ 08 D O'JGRιuY^'xyC=C=&$NZj6R.˾q}pVT0OzW?(19qطvr<331]+dƦ@; !dLp>}rr 5"_.ul@O!֚jYLZcVoBb-upw:tHJA^_7 B1& f%Dcꗽ%rw=!6?Pu>RZgַZ-JȺ&ihyj41䮥+Rk4{&@=C=V/Ѩ9Jp΃ X\M.̱cDzlXXXHs>:ZT4 l,zbb"sΫjckS1T* vf.zDŒ1cLݶQ8.JiA`<דRD[uJiTJBcFEku4 \q|>O?x >>~8cccvwpÇ cBerZcT !4R( 61-f~j uEKc97zC=C=$p%%Bl9k3`zEl.(MSS|>ぁA)8vh41JQD}Yj\.;y^$Bj:44dc X>@JYՒ$SJf)ͦHS)Bj|&[5UwBVlZSAO)us1!$8UϽRk1ˮ !fk׮9-w((DX(-9cqJE%5ca~ZGR-gm]Kbf.`H)@A!!Tih6J)y໎خ )Yr9S,VjA4M)5 qk}%qSηcH4B)8R侠;;1?%X򻼇OJoBASJ8lEBad5t6=Y>4s!`t h6i1.6l?B11p )]]1:"-wbR%׋pU'I)i)ca@kcT^ne|v 0lG`!u]Bp$q&azR&Fk1ƹ\cBFA UJTWh:_40@ cFZk% #F &Rh qcTN0^UJe璽\T 0 B` F bL[Ҫ61` &R:2<ǑR1&B ,v_DlJ) Q0`aVc c.Q{c:U`W#))c,MRL1ĉ 4cERE[F+%;H)9l4c, b!1R(TN{3hPJ b$qK%% !`*#("&Z/~ZJϊ)JQB<ϋheFX) 6 R1Fie/њ2*RqJ+$FaDMf*oC;׎!iZ<gpF9a؋wzJ]vs8v\7c%Iؐ#ABv[ץR)%<@ bc/fDÕRRcL$P "=i1`@*I Jj!F]֦L%( @%iRLL!)1Mk-ٓgJiRB!)%%cVJ1*1Pj^Rn.E}_kVVSh{v0s 6!ehJ)%q"Mʨ堤TBVy&I,15x.ؽL0k aq4q7 RJ8 ZAj JceݰsR& :-BVnm J!A%c :#M( Ib1hJYJkJ%2jAcƨ5Nc(G1=UZ[mJi9cb1j&c%fBH}_Ji?aFQd݂Zgl6[*ZV6lW҆hV [eᡡ!q8_g!28l6%1[ c$p]͚5SJ18gzZT*Z /+R$}FBte%w 30!dʆ85 WP>ssssz3IR:G}X$K)%msD\ƚ0m8(8I(I; "<.N KS884' `d)K} [9{ g0@ | JTRRIA1FСÌRjqy&[i1)h `?`KylL98M$P` ؜םO;uq\[#cRXB(#RBHkEe9HD-ѥ|wbh53PZs̋c#DhW dd Բ`MmCe4Th7a$MҲV1+x[7 q٢ثq 80@sd9&4_?w_R_h¹V=PY=088~hŃ8hCeR&/,cZj)Rڠ0 UxZkaQbB(rGg+B6`L$Vl"[c e2cR!D.<=gQi $<Ya lE_C+:R8`)!B @vALE{؏%'>r8JUbP) k&!E$J c,ϼ q5[QGQUgŏl}ch4,wH0!c:;JI+ZJY}j+%T#Yb!Zimи}O)2l6c_¤#txTڲ7%k23RfUvhΝ(͖`QYT1R)0F'z,;3Aq)Kf5fI9kFͷe?(*2<"t샟(%6ff0*őrь|!R$CK@@q&RrZ))E9ˉ .{Asò՜3"M. 줟s[вy%sn !Ɛ~RB?1')ѨKo KT#2)%u_"2/ }~cl>TJ]`΅{Z/͚R.zZ<ό!xI=N"BR5FJ霳)H)a'$XkZ9gJj.xW*cAJIժO)A/HN* j9c,eGAq9Rr)Rr1I?um%ƘK!Z;1BgW9gd"r>hS.D8pz(81@/g~Xk}@o Zׯ_yf۵}C/O䜅P~m\٬]OCa?6MzxswK(wm7#Jɭx἞\B=7j\j=N9%m4m)0Q\]@D(X%H3D:0sΙJ%h<Z)$2*>oz9|~/u<_3b*XkEd6ZpU'\>mӬW})RkȈL'"۟8 @JA3"Zs-Kݰ(F99ZkcPjJ7k|zҲ/"1fv)1IM9gIO?Mcq饓R\ Wg#Aċ>s3"v2i*h//9g k clǔ&,[8B5MضMJ看.@g |K;@*ItB$5Jq%.2) ꋄhXBΘ^nB1Ɖ))rεQM>>>!K)Bcun9QJKO &ZkxJA9MRyZ鱡«W4(  c1bbp "Q !dbJ@ -U* IDATzJ-̹`V}R0R;Jdh ;"q9 GYcb9z4͌1B17ƒ/4픒mx:Zk84ZsDZfRN$徨ysɛ=m+(R)3N^2Q"M J#]pfQZsv=cHCwnJ)'=дfG!1&콣V{ߔ\ʬ )R)`,L, f!hBØK 2~09ӪEhNJiY SZyw(k#f/Dc)*NMzBo߾kUp眇H]n+jCO|>ZkKIBqR|y sc !5>}kD(( ; onnynL;OMq캕jteJi%ijMx?349j!Z Ig!zG)9Q"W6R?C&qKB5e1Fi0-=Jrj$Y<ϳ v]gH)9LG>ӥ '@H\DE4s$ BDzƦR] y45ƨ&|)5H-<)%mL6<bww1i#g2TJ]GcSkDkK!ښRJ6X0 $7K-PAkM.>S<! BsnTJ!D>C ι҂RȶKpW"&LL%~*Yk[u9G͉~3 *|It,pwʜպWRngRg}~H?/ "B~&vu3PZ;BZT)sHV}<#c%gl Up1H!!J;|މQ}K)1iӪc)TApN3IcR.4MƑ_u)/c: DIbB0D2+C]{p{bԥD.ŕu{O0q$ot`Q9Hf@řTR+,yK),jr"1i!>sΕFr+s'$@pA}N !honIFBd==zrs~dZڈ 3 S Vcl3B*#"gP.2bFodH .R/{_JqyjJRk6thZ\iljs0s.p`ΉsJ+J9cOs.9yCL@LNi&dM;3~u)my6M-3w`-C`]צzz*i&c͗_~{z}J\]Gw1ic\V:cnWr"?LL HmL(ƘO8)&ɍJ <ժB'碵V޵R΅2JjG!x4p<1H)kŗBΕRѵֆm9"')qJ8d!%(b[Lի u!fPOA,c )qDdLYK)I+UJ>ZywW$st$>>i)^ᣵg_}TWR0 WWW0^0B8c2 Cm۶og_kf&EZBv圇BƦC)Z_2ƾ;`nkdǺ:ZaZU)RWoxO1AH>z3"BL9%s$F\℔Zkd8#mX)R%*LԒs-)λ/_)}6XJ=>K糛 ogKiۆan[kxyo(9hQE@$PJeim+u:,)L~:S2bȴؗ\ÙaEJ}HcDY=WN]ב8F"edc\U!mZq$08^m<甴ZTGRi–r{O_DGeBn&BW:mt%h@InvNdLe" c.eFYJiZkϧ32v8ЊsNXCaJ*ڭ T aPJ9$Z*^M0GFzHԪ1̐RVi1F`H6VJc ,bĠlՂs&Z hri]?,~>xX-YC$6:$@ ٶM0B4a Z%e!~7ڜ3ޓ7qWǏ@&E)Lh9ccv8jٵyaS4%BY1@) s>轟tbmZ)gG ۾[Ƙa8cmclǐRRJ)rWWWq}ιL5|]y?Nq1vϥ~aHD{^e8b̜1ikX1D 4ZW`nip)IZgOCd\_JmTJܤy/j@Y Uke包 AhM tHR+#&9f^I!`%V RZƐ&$]r!mNԟ VZZVS-U)EwBVE+dzBJ)C(,s.㤝!$B@8%Z|(nz[%S?~|TK%kRɔRUkvxR/@@e QJ vq61p<\rJλ ts*bʔVR2K HYEJZ\1S$+RMY;d 2dBq39c3΅yO$B8cj΅9E"YW%!{DSrQt6d(g,oaCMkSLArRiJ*! SJMv]=>>itlp 1RƐSjAd)&ɳ(^WӉs>va>}d1EcuyPm3mb) Rk؆r*( tb<OW1qR 2RD #Rt,I18}cכwxsu RHQ_ ^8=SlyWr~U1)m{s}=4mks΂qJ#znl{4wJn´ĸRR).T(!D;OG% s.8a3.h6liYBLӤjvV*9\ :Zƴm{8($1ږ\C8簸a&x}}-)jNmsZi'Eeʙ!JpƭZ MUqt! wtBcg85]b(ClǨtRO)D7ϛx88稳ʔ(RӎTCԐTRJ㑈\%4KT6M\m4C?8R̓+J!0=0ΥJ%mA@&Tmc/^P4 m; NxNvRPO4]׍\Jwᄋ{K0خw74?ٟӠrN01a>kmv9ꫯ8J)>Ǜ+xūy)x<VQR^oBs)"8 g~M)%#I !/jC 1aM3h0#e1Ծc4A\\_g6,_sͳ"B<ǘcj~x%)h(pΡVRD b۶ww7o~_j_?Ur$\+9cMӄND\T\R 'IvR"B(%I_˧|9vƚ-^R }NH%UDPqEPJ/ڟ j * cPJJ_fJ9GRK۴WW8Ǒ*m[%4)x~HpOEJh&9爌9vўJ<< x!RD +qF).&2I!7vx)E֒sRݞ__}8zLL`Bp!A*BC#DFr^s )S0SZ9RI)%M)^3})$8kۖ LQ*{bRjJ)HU!Vs^K11Rj0<3-%#b) 0t]7TKnB|՗B%HgZvj%ZR2PDWg睇ZKwbL)cHZ CkdMp&s|>R~2ƭՔMIsƅa adTcDD])9猯7s"73@u H/۶.6}R!"y$>fC( ι\1:(P @Nm R+tbqSNZH*\JbBC /]J [Vt\ T߭B9.Gy4 6$ԋe)sNjBy!XK1Z*iqtmWRvmu\ՆҁrN0d}%czcəL^T14%笍n9oZݛ7o#R$[ժF@XT48qUœaT)ntv:z$M{^\+ e!2CFq;<t-J1Jۻۧǧ9!D^i]J^m#%nq1'%U$gHQьsX4T?==rssc~]LH1; il 1pssB\Niq0hcnooi>~4Z[cP kjx>!R)E__ÇRJŋ yba'ι`/xzz )yv2<9tssrP|SJMcj%gM IDAT4OpsuZ?|pEjEd#M[i8Rzc<777pW._W} << )QnEE'2*͔¹ֆr)%R rv~3|Rk&MUII 9{W20݁ !)-.TʴAh&(tAB׵_yL0Pmk "Ng 2BhH RڮB > w#WT|xxPZr!J1%v 㠔%!FtZKU u/~w?;u#5Z u;$)r)&)v7@;UHYK^Α )aEYBH9G989g9Q#Ieb4W@JWvP0\Д.r>o\p-)V)'DjZk!8M@攗RJT2p. 9R s/7Bpm̺_b֚Lغ;kϧScRK'kjc4Ӵz-xÃTj8s1g2mcy8\r)WWWOOO)ƻ6oFIe&z!]n)%pvNk# ! <0{j\3F_]_k9@fw稔bLZ)Y/ӎ@kt>Y[|n pj|:Lc.ib[N+6>~ri6Meb+c<5!E0RL 64MJC!J!l dL v9'< 43B٬%g!T)e\JFO2tm7MUN*0lz&s<[kBR2kSN!FyFۛPmRJNdlpޗ#piZjc @qc]#jo+<]mRn7#tΥ "c^B3maX)G2+9 }W}OKa/_IͥY0"gWZ')e4 r)'tb̹|rwqdƚ>~TK9z1ǔSev09uNg ؛/C:RY+ K.BdNi%7s1m#]M{c"8S!LZ "R~Ι=!)$rVr#8WJ`۶O84 {R0_-O>H149t:duP9RRjvm/p9ju<ǓRr{uնm׶ݻ?圕59J[RJO=Ik(9‘RH@O#fwؓRp8P5lzի_88=U5/K)޽#)\fr>OÙt#V:7|gBD.'\봤lI!Ī_Zk4s0%Iݞ9ghRȒF5MS gMY- f^ /1.?ՃsfvU4il8Γ6RcP|uk|FEKZi0gvR)ső`IIA&d\I);69UOOO1F֐cvv0 1xars) v%Hiql&M\*B^Mw!'j]l)ҟ~D5$K|)&R0dw74k[f!.qx:Bp.dV)Uk aGd1s0J*dcRq&r]\! 5g!0C@kFƂ;!{[k} tCs]۾~?pε)FAJ1HABeBМsC1fSK+ivd\.\m7/_b\ٔ3|]L R*Z+m~e;O|nN)}8잹_ZalcxCJmy Q* .CHoM)rCJ)E2B̥ C_1]o P-%xȽ wRJDJ&$BJ<^–RBHZ\kP6M(asd| )A4癢fWTJ btK,vs>?QZO)%M?~bSJ[kmLy8x~YZ4M<όqc48svHYLmjSJ<_x1 ef,@ˎSrN_yt4ҁP|)v\!ֻ.zJKZIRN`M1qI,Z+ζt< _>wwurʹr΀@+UʩReFr"hK4c͛70+ ֞'ƹz􊑻}GD !'T:wiuy4}HΜsÞj[`)wi8o߾{p ɽ n{cL:so6.Z߾~3` *+ċذS K)傈yZ&3aSw)jFӖ;B)K3.ALag @Mc۶l6hyc-EzԘR#t$$ {+ 4s9/38/"(eɋ%]sU'(/IBH 9+0ƥ 4b^SUzus~xB̺1F=v4M#aOKh0JTJ)R4uSz|KZKBwi jUye(bZ(oDxqls3&C*0`eѷ3ƴ\pTt<1&s.9 "<0.b BnZmb_v6MwZe!8^jf٬+c4";4Rɳs {""()!KFuZQ.{EkȄZ+bZ)m5IZ5ئm۔2j.]2)#bʉ^AB!yu\p=^rh(}")OXj7)'kr~ի:NiQZ1R I!FX)V!^>YIy8bJ;)eJOXj/~qrΧc_Y+sJ"B a0BnSYF>vR~;m+<}w}}g=5p8 Ep}1D .1jkR9yBa|zz|>Jyv1gB VY)YJ(~%0rM-ilH ޶-qDswwu}k6juR Qtju^ 'ӝ9Gp)EGJVsRJVSMcWWW)q8mPݭ_؋{B4?q^{hha 9#<(#;iqBR K;kk[ZTa %nnn6wb@msЬֺcE&ja?Ow~jRZ1dNDiնۻvKB2΅RŋRH!x qZkJi篾{<I|9Nzzڅg7iL۶B|1$!hY)յ]iilw*%Pjθ1zZq!(U6B0RfͅKQIJqdެVas4DVmwJm7MujinnobmH)S>Zy_V>x%1&t<*Ǐi! cM~n?Rc7WjWJp)49}ḓv!R($cC8Mn[6P8qʲLӴrP3EA%ƇG|k~D!S`f 0x<+6ۭW`N 9sQZ뫫g]WUE)9zu8TRI &{kc4\ Yv} q18>_XE^K8m><<'?aTJb~MaC'YBJdb}op[gP<4iι4MiRRc}ߧic;7 PƂwi:3 CY`-jKtZi09p/=9WJ-kc Ak\EQ6fZX,#aeQ4M޹i\pimb Z&30?c0 Y lyfE^/<͏4T4 ! syOG?Ch"giCs>Jhri6ewJ7},M,i,ONֻnZ_\w(WV sO??~w?#=I80B&?C?SJ3ߎAJ`R)JBHk}bc Ak=&Ag *tB,hVy>&bs!B!Z 2zPIw6xcJZ g#c>@M(*.I!xD B%q*sk]s^c<Ƅ9g!48gVkED`!pΝXu.Im :C t Y! c0Fs:aa|W.UUi>0# eY͛7E Q<&Dƶuiw!D%R Q20DwG Jy OC$El1!4:ȹ`Y#Y뺶} ~$IeJ)ĬA(XYg4I(!SkmlF?_UEC$ NmiX%୅S$HT'xNw SQhsYo6j}ss A'`i[rbQupw΢,^|!{m a~Ty?pޟl΅PJyƄ4mJ2 PCccC8B8NH!J%!1N)qayk0.5xf(.F  %ZCA)c,u均c/e7$K+yl&Lp3u]H) >0F?Op( (RqVJ%I"ow!R0B IDAT|o˗/C^79zN'in[hu==sp1Mqa!ͧ,Sj6X5|v;MS^JHJz@k#%zR!o^,K%${=<݌1ŢK)%(>}>8/ ֢$ bM߶=!@'kJ38W?"փP;Dh1N2*LyQei춻,^|E1l6(!B)_ŋ!ͬp4O4IMӔe(jIOi&IQB6m X;#%u!Ţ8nxu2Qv\.kҳSfXc%0އvC(O{[k#FgggKDqss3 TPYE!}cyPJqeB< fMӄs>O3( )$&+)//bzx|x1ҪONb*4-& %:Cs(Bk1qG_^>C)%B,poR2ƴvjQ}ZUgϞv;L'bقOt;1Ʀk2Ƅc'R1Q~h JSem1FVJ_m{}}=c˗!ci_AknC}ӧOm?Vgm>{1X,ii?p9@B80KS?rw0ƜS{Y ! B{O{eEB1+ bhuE1È1>:;"Bf Kq#$9("q^scSc̑z:_"|v*qcDYQ.2y׳1&YFCr4`HuƘ5@>#Ȳς" |i`I$x&hߥ!1Y]Qw H444MB4$V/aLBJ!,ieL@i1<rBlB4b QV%p8@Ƹi<[k/0] qǠ{%T)Roڞ4lBƔ2P?!,RJF9K81XTY F$9: BIqZypzm"; 9oaZdY-` `+jnBX.}'*UbYk0A 1:gE=MS1j^w bV,qZ:޽0 1& >0N1mۮmcz,noo g t`q51c! IbV54,P\iQmӛ[B A)QIgPp;ks:էO]-i 49뫺Ldq,W_}{v+ŋw<_'r"c l2Ӭ !$zO(!r<{g>}m!ƺ>~ׯ몆}~(PJ wἃ\J㳌3M&!xw4I1%^r}>ơi1FJq:4D%MgϮnnn waX,BvրM<%D qvv܅6mþ1Qryi}E( _)e@lL<"}@Oa4S34MsD(ONB mg!JWnﭳ )8`bFOKdێ6azC,)!OWQD×\L|v~1v {Mp !P!%ew(aCeY㘦__|I(NNOi믫,ig}[c!9U]~˪ԳNϺZklu<'I⽛!t{wW9&x<χa\8ky)@(BiZ-$-BJasB‡^t];ZQk<103f:1)!xZYcEQdy^u}eYJiy v~Z&i1a1LCLZϛZ{^BMiBXJRXEX,sιzҩʢ\,*IQCߟ]>ZK)uκ$Qrη]gD%d~vuzQ?au$WWWP@qv<7m+osno$i7~S6}߿~i/b 1^^^'y9mγZwہ c+%"O VPC ^(~,W sgcDAȻWWW![g->B~#Kb ZkP.#%(SJxDcyauroXqT@}dG>Ρ P5729 | OcQ@YSہR "<|c.x50B&WR:og i{|*0F0J 1x] JBH",~} >J7O4QR 8؞jC,ˮ=(c]Ee)%m 0Mkaڶz=swh1o'j1^0f=`SVX=N3t&2"}c[̲s ;(E4MsaBS00cl&3vL QiRUTkG)izAH69BT,.nd$MqJy "8<)!I,TP2FiDz7M34P%Fi AfueijRJ1QJY*^B)cZ\ZM ·ʢX1zM:{ubkۻyONVz8Bs1 i9箯w]QTR)9t軼(YUUm6;pE{Fy9c8aTi.\-!D_hi !uj=:cRfRy~s{ٙq'E&Ǐ.]^^^{3dYxxHeeLAcH)aɲ97R*Q 1l>sʩsnXдgp9(e4ZzZ4I[オ1hz}<L0 rWiB .s(g64cwiUb0ڌW_!_.޿{0xv1 c< 1R4NJJ{,$IWJ1%2qr (?|$ɫ5wM3f8eU]9X%۷8eY&Ά?}G4M>99?~G1vp?Mٳg0DZ,۶#vo\ʆqz.\) gyٶ-FQJ1t4ruRz _|9 D>@?ѳwȩ$IB8"cty!`ްnUX.έ4imuoxXԋ%}wߵM"XbXEQV;HA;L c0 YQ],1Ʈ 4u!0V^^^r@~Gk<<"0MӄNOO.1e khGI%lB0u?@ x#c~'OБ1ZYI@q p$l"!"$gJShmѠI1B`ÅX!r! 1+(Pa>Rʜs;!"F1ѹs9Jw> ` )X aTI,U V)>S1CD1c` R J +1sXUU c*eI)R!Fzą J&/_;/OӬLJG Uu}sC(V嗏!a))!#D[,?{W!v[c|/M2Zʢ8??oVI9V[emn*eDk=0Խ T2MSs)`yw2 UHj3`1B$IrZ.,RNh# )ȳL !$@QRUUUUU]uE )<"D)s΂ XUjZ@e 8ZWU;=9!TUq//N,M ?J(eB*eF(RJRꨮ!P,!G ug B<10 J%y|8 B ayYɟ,}!$ h`Zϳ֗u]?<><8q컾iya@1.,aPJ1ΤyBiȳ,!s!e1QqK :{b5Bam1u89=S0 1DLC2뺦yq~q^Uq?Ji] NJ9np`!dٳ X,z">Ӭnm}wǏq8.:( 1)CQi|}kLJ)hU>m0$xYYke8p!aT|}}}yq~yqyr8ف* IDAT|g94Ͳӓ3fʊP_Y%=<ʲ<TV%4\Kq1oPJ)k0ͦ,}PJI%su]w] ,qP@5WՕ"ƺax0 ֎H]Wy^B>==a !Bu]c=,% ɦaߏav?1>?kT΋]##|Pn†7FL)(rB{"BM!B˜@\!g-0;"s>N,%T0l)Q909g6Ң(C(EqqqC$2s~vu1zQWE^4]7NR儣SA>c N)!bCmpDQWWW.%$2jk3ֶ d}8vy98!~ȲzL^ 7ZϘL``;$RFP 9pF019H}oY&8Lk `tvZ%q{'6RLH~&hQyQz~OʹO4M8#xZcD@Ck%)6K3`㪪V/@G3rmft%z,ϳ{wpzzzw˜7ov[ a(-jjQ{%dUae)EK9CViR¼ÎR5w~Z&R{lf["|GJ)ed' 40VI>}ʋ<ĸl1!;F)L*wUu}{jXZk켪ii.4B&~(|0#t{wWaNO1!aK15eijyz^qB4s>e~J=8`1B^42MB,}^r8M\ \ YSJy猵]ם_^\\\\o$J׿bWE(]<gy]W4v,\:IS`Bg7RJJ)ЏskRӴZ.oooa9'~W!fY,˔߾/8T)!y9GRONNnnqg>}...1F!nso6ɉ` caaβl\j=eOoWbLlŢS [0CR)JqYl1}KS%BӶ8,˼ =Zey}97`i >:疫EQ lX>~8>'4 ܔE^*Bt{m 1 EiI^12FaV ]&؝1dYR~i㓽s,tX}*Q@ Ƙ2꺮kY# /RB% Bi4y#ˆ1zh܄i)z!hVy(vZZϐl/W >(L)ѳv΅x ,;74M4M4u:7c 2_."Z *wy_V5iqC PQEYEQE? (FJ(QkZ.g~򓟼}b'wi<Ҳ,0΄s!61(=Y <}?L#,TH) bh 3c:ֆlia}Q,[ѡiq!p.ത+)4~NC0B^9%iB km{h_ޫɲ,xs}2]U]nfr@$uAb` fUw̼i?~m M3#EAD?td11Bꅨ*3Ex3ZSRy1mڪ( ]6ʢHalUZ[cZιf:F#K:3V;1R*a&Oʽ7 4J}?L/e_u]!(iyűsT]UBR&GsvJ1!m1&B 52ڶQ:BEQc/mZuUm[qA:1D.p: (4J !ƣQǢRqE>RS-^ttFӽf#znhf^WUU%j`<)c7󛺩///jeonoonѯB9==ڦ)J 5)%Y~rY OBBemMӓyeIzquQ+Fe@(?t6[.Z0xi]0f3 NOY!߼yaUZJi$B L KFh MfJumfzGY)ajӵIJ_Hdq)]]wp8RVU54m꺮`@RfYHYYk8NXK M41:"y n7777]%I\.hia F[k!p=0]qKE8Jf^uLUUuwh&)eu]o6!缮+Xq)%ci! _?] !WXe!*kX] wp7FC *"[9Щ䍂aR;!4@T.1%a송a;uB~a ιgi$R몪q80!eUB*cy?WZkF(cL ЛLFQ+(!X|4u1. &U5Ti%> ˙G׫5˥*4 ! -XkLDmY@ǀZ6uX,h8cq?,;99BsbڎR'?c]EA6<& na`2ڶJ:U犉<ϟ?͛i8Q1Ƣ0r9!ʱwьP6BJ< ox6~Yk@Z0qR=ch4ߝzA˲`B޾}k,?~a^mSGq|||ڕ8}5mljzwoE-D铧- *MӋ !oߵ] (~Ium۶xcZGGG`o\"2:L˥Fb4/ߺ%O}7Uv6I?TB^܌cJ0=?<ϋ¨뺎wY:oaKvl\.[vIٻLb7'~vm+8f]׹.KڮR 8upz0B{!} k-mMmGaF!IŠL&2۷o5p~~AD)h4$ (8x,I˲<<9N"t֙Cn\`P1f00ƆPIl0LksNJO< o:~mp*)UUizvۛ[Ȳlcr|zTuqn,>y1'߸hyF&cx:C_ʘ*ɐ$ FTiEz^#`RnU%¨:ynwxx ƫQ﷛1YE3Z#H1u]q8NE!.v;9$"mtVI r$5j3\kYGb~/zt{~u 08J(A+ƣ1cϿhv~58VJQBU}Ƽ=1/ ."EƁ1fdYF1Z,\Ȫn #4NOBP)ryEXUJ}kB5M.9b3Hi.ǹ}격Bi%sk1nj0:88eYQmFa$$eYskR>z ެ7ILkݴ5!rvvv 1(MRFRr}}3pחy!pX] BeI0Z`}v!6us~q0x^]Ai];zzzlơuF[kGiy( vnykm RwJQܶ-,0(jfƁL<*1FxwwaeeQB)Qk(!4Mv2JyqUiNxD }_H6`/#v[gYY׷f:3ڮ|~۷o0Jk$yG78v{{~xy|t>\[Sh4RfYV7UP|`R6rzˋf6VqBsãuw T2ISzJGqe9@Iu5-KDq{ lo}?mo۶xn=Ah8 Swt}Tw1fkMj<8#\ͯ%t֣t<_^ڶmt^}߇4]U~|7o|RP[X1ƔTBE!̒t۹P']y{P}h<E!zam"<}Bxu5Ox8nnoVz HNOO1Fi\_/b0iZE>a6;$y'Bl9>˪(Q^e(./.Ҕb-B(I4>'#FJr``11dYJ(JQ!ƘPJzQ&QEiqRjݬ~ IDATQ#yUWZ sK%:"!BZ1=@>| >¨u4z c)rZfSBhJ匱??mvbWmAeUYa)"X;N(z346kBHJIJޞ#>uE((3*BJ\ {˪MgG8Ta(̳w|WnRށ(8%u':TQB1X$MӄQ&#}?2(QX)&i NNNe;daYA4M-8˲}GZq!@t Rͯ=ƫՊ2Z7uj3L4=vk!dsA"&1PIq/˲BEn8+Q>{sl` q0'OeQBE/nnc'ǣѨ*ǏR'''a]]^-WKBHrfYa}`XH)4 nnnn޿5zzxxxٳhզ{)/_~I,㲟̦͖sR;zSB1ITu]׵5n5lI(ln:90zs{h.0 )ꪗ2Imb 3 rN'Т,뭐2 奐7wwQ`lΝ+eor]edM>YR>K8qG'Gq ?wFau3v$I$&;uFwGc@g{{{.нnSEvY_ͯo_y{nDͯ/&N46mx,<8B\^^VUjViZk?~|{{իrM){˷o߭כ 8>쳟g٬ﻶm\=^4yC*bڕZy~8Y6yQb0:G'3B?]@~%!QBv+@cn7 q_'hq$=`Įq>c܁O7o#SJ)E+)?@ 0cbiۦ(릩B0u=;Cj}ck{BJk!0ƄJ91vő(zwx)eeYڦquݔe妕1=1 =q觓 PIeֺkKd a2M&* :λPRIs`-xKm0}Uu]Y`VmieV. @8[ D#9y)ڶm;kW63eYeh4nZ:aq}sݻEYBȏ.!TԢ);ʤB yM&x<ay({۶iڎs'Ft] .8ꪬ*$2.Lk1/`0 <jJO~eY!aKk Wjm[Ǐ{.V(B!_~UGGͺJB5<ϓ$a!<>:VJ)KFg` ?,]Qe1bn?btw;`TbWno02M^__Yk˯;~v۽߼yq?( KNk1u\^^BmumvN9DM/DQI}?#7Lةh!8!A-yCw& zvWx2vlJ0Y.w7MQL6'I,ߟz*?߾}9O@0OF'{)D(2_<qrrD5֬kg tm)ˢReYywu]7Vv50QJݮiKB" ,{vTy۶P!v 0 4?BP|~߾}6Iz~|>_u]ӧOáhm,4 9Rk˪%a4 4Xk9on!ƚo}wJ_~jnwͶm(...>ӧ*Bp'/4=99yh4ˆsJo6uF#AQ!HFkt}Gy+d%2 !01ϵ.&% CiVk!'Ap}=w.p:n\__/zR!0_xL <4]UW0˲rYWAhվ}hKuFC`t,H$s?jTo@WkZ0c#y VHn@)ţ ͏_#s"T !t'B_;"tw37xp8 `!\uYUǍMXa /2FmQwiʪnڎ2Z(RR0\{`;%sic$~cRX.yB$ R)u۶iZkvZ#TJWAo۾˲PJA}!Z(;0 & &x<ƣVAΦn$\ %8zcGRWWw!Z@]m4mכnfEQIc!MۊLjJ6۵|EE\.Vj`I7ۛ(rX=;{4 v5z#g ,Ks75a΃unz;N0H„0 TEYH!8ֶM%d9<::8܇u(ڼxiEQ񾧔x Rc$f<3a8|J)aUUw]{rrT'<988@%qo(R ǣ.s T2̘>Bu]qo߽/,]R'Ϟx0 ,Ϧ/?9;;Kd0ʒu&iD5\)1'w,|oFqˋ˲:s|rG19o[f8emy5u=ј4M0BDV%yan?z ͚`b)v;Bغgl6M&,h8&IkcMB]Qz۶FYfUUS$N,D/ʪ|Xւ aYӴkQǃ|PT2J)%@l4J`l2~y^Yceڥ.Uq> ]8LFycƣq'iãc)??=aURӧO<5m^Q-v~sm54uy,躶jB_Wu$I5eU5 @B`4`ȳRg?Cx\"ԛfB Dad[,^z]W58#}_7o_~gnYGa%qŗ_&i&VH'=8=I)Bߧay C1F _iZB n||~qy+cRЭn ccls6QwE ciڶm>}AUUyviZlۺ͘!RB2J>zLEQq`˲pl۶}ueY&ϟCh\.ww|>Fwwjn"u08}BHQ=PJI)!s*?FwK1v&wmO6ߏU Rt! ;L0!ƌP?`!ӏ[('Bt}$BZB p'wpWvt0 !tw1$0>>zXY?>?} #c#3}?`aeu]u B0(J-ei l:p%Vk$I1`yJk)QJ)ݠ.<ϛNӴx#b R֪xu_|y/_@=Q6kpi\ )!blY^]p1v R!$sLH ljjLժi[)?4LƓQUZ> VꝛBRXlĔakQdJU8}DŽPJ}f!|? ]QMWOY_1FIEl8,˦ߧj)ٌ`?8y ۷B`0Nf1akqoޛYc.hð*l:#EruVJd4Z]Rh,3ƄQ0Nth7,I4_~~eU]' ծFY/뺬A~yy+aYK!QY''UUw1B~f̲(Q1ooo[kQR5RaB/{.f* BRk$qwN+^˲DO'W_L\\1 m,BڦVRq1"hc}w} {bLdXJ gggg8^zUu>)g$;?E>}7m+ &YUUhJm1joooXDItQ4"P)-h1Qu}z7/.. UZscLDiZkۭ[y#gύB\e۶ժx7o!ʘ d#|?缝L&庪*cl6 X:$n!z(EYLskd2wQJjmgʢrQBۦJ%,\19oe!YaJNT//N08''΁T'eG qV?=O D!0DcSkcn׉!0`FIyIDZkX|lNa7;c(a įc(!kcR #1!Ak  [}Z-҉`!c2a+c$?qvW|x:MXu4}\HBi\Ve/$zLa"ʪZx<bWH!y/؉"Q0, " m;!B@KP'/;q!mӶʑU8@(NƘ@@)-RI(ILkeL)q~ٔef@%I)84Iw! %:.0!bֽ1ZvVU|4M׵]/Y Fv(J0|J8JX` D@F˲,I1y( |a׵8gg{_!ڦY.Wc뼛JvSUR=q(βvg !l`0xc1</K9n߃3a̲A+-WIf郇'1?\,^ڎ-$uݼ(ּntږm7u_xlZ!LAiUUeU⸮y߷{d2ull ,n;eRjRnk1&i(!}^YVa8β4݌4u]K./~,Ͷw xHI Ci*fiJBdPBڝ6l<ɳ,⃃}ܓ$ ܸ `41A= ~?"V܊R8`B^j%,㺎~N'᰿i7!1hv:mfi_Uy''geY4f4r]jEqd0՚L&iPJ N[&I(5ʴ,q(EYLXxާ/_Fa{P04nc4@,Mt6]%)o8q]dYAGU lnHRSgYvrl aUUI00eYjx=_,Nq\h|;aEaaYf4۔ҋsuoܼͦ7i(@c)oeۃ~#!v YRȊ,˦ ?qq37EQ>モREQǛgg=W_ ->? nq&Һv0Z.N4yY~ɧTHgY~yy5˲97 ~UW! B>w;ַ͒4˲{{o3MO>>hZzJ\k ,Xβlkkk\~$ {o޼1 ~a1f|0q0TJz=M힞N&<ϣ("p;NDJV ?99x%888RތFiQJRFb YÇ.iSlRa*m2IewMeYw-1q)1BJ q}YVUE4!paoZkHlo}nC ZVpaJ/V}ǫ* N@),^W%L%jFQr΋p$eQ@i gZ.aa[!}Rwݲ,eJ)%̠!6r&Y qskݭ+lXq]⪪'@͘`byƓǏONNRIZze[.&=j+=X3ƚFI& 'ͦ2fZ#(˪Jj9 BZ ,066NjE?'''uAZU5a]qܾ&OGaPARd4nc[L6(r0X,Ʉ1f;h0R=O'/VVJF㪪*oeBknwde;ht !4F ׮^I,;'IUUY#Do,+]=+l'eZ- ...\1 ÁUsB|>3-4lDI%mjx\,zpcH0N&B 43( jCA)RjP B8rDpDpXF>B4|A1REABMg,MF{Chggg8j[giB__~ QƬL\[V竗/_j߽{/4v͍Ap8 eUUUcYh4 tnGYQv!TU5! ?1$Iӈ( 1%ZV]UM *|fYez[YBd"qod2nw:[WWWRZk8oF1BhPJB;;;vLׯ^-VU]UuyI ! ?<<O'e^t}JbVO&{Zp8~:[gg`~1X{>9;ZO&/}WWϞ=tZS0>'YVPJOOχMfxjݞR!4*^T%xA {GIrYuYBx*xIQÇCƌ/`0woիWϟwٶgE.u]\-qUUZ!ug)5RB)E P4nnnJ)?T^{t%Yvo'PJ)$[)"f0HlΪntCgPڦq@5?%`P⦆Z[[c .iu q\GDɘ @)6M|"u]SJB$2( I%, N+&!st cJ1XeYJ(U1&BLZII.aeB4 ׵-+=ɲLI4] mxBxnԩB*$ҔbܲBPRJ!8Xk\U0(k1 ݎ?x]`$2s&d8RgϞ?/²lbz97M .m6 J$I?A 6+)8E˺c Y,{r\,R ) eLMfb3h0i$B157lۺt?XJeٓ'Ol> 0M|$b{srB˗J.=-10" }ιP1ƫF)<^o0L0/~yحViO޽{zi*/99i󣣣*?888ׯo ÇyWv]n7EUUWWהpJ,?|rvv$;0 'Ye0(!q9L /i !wwwlI$I(j,>yv !ai22Lu:4 WWW4N +oXLIi?ǔRu 8>>. <Ҷ۶˺$ 1cvn (޽ ðib(¶-JMno>ahێgg3Fͨp3N#MFNgwoo6r =I{YKj2 1r>\:BG_lm,KjWWNs4˗/Y}8q]޽{VW߾?NUUyy^R$\<=)]2|"lbs2}OJ)(0ֆaʎ1:(0K 'Rnh=PUoYy?QJe c0<ϋ,˺DeU!DsriBF+qcJ3@pݕ 眙&]ތnB4TcqR)Pƅ95E!d뺪B*7BHBHW2?|/F]_u,KF (,˲!Z-aEafY]UY e^RJHLb 3׊4M jB\ρjZF6Mc clRS*UJ(fJiu1B5!qȳȻ9oHӴi8ceRuR*àB*bp( SJ0JӔsro+RKN)J',q;???Lf_5Ji^ 4*rPp1֎4NgW0666VNw:14R+Z)!XY;nv/GyhPRRIǶ)&B6sڊN <={8۷G|>/V+6MsX3ycY4Mm^r|﹫4-˂eABeY¢wuut<8\y^yEYr]'/?ZeYF <(A(`z?359o{η~ʲ<^ǹ x !l׵m[p]L!3MP$<2UUdq!AeM4YI)adaYև&Klbj"NOO(䓲4ϧMEgaf(0־LBh˥MYƫN+(W6s IDATLtss?rt/Z-W|gggci:nV+r{{N!jmmmuvq~tt2M:=OY5cR4Ţ*KUv,( BO>3j-lo޼ZonnOr6Eu<wG h8=vDJ"4BNAV r4Xo%̲THom&ZpI Z×AfE~||MYRǶ=Yq4F뿮.g)Bk9iܤy'}cc>~4^ϱ0 ~4b1RJ7b-BJ+TB9!q)QZ^=j=,)u]MQ$ϋ,z\^^B*ivݫ8naBz^۵L+2fY@ve}mۖH)aQt:%PN&ֺ+ku>J4]!w|7 ZułR"jw:777J)f2=}R*IA>0ϗEQ 9J?QaWU5ʢ a/ϋ( :N+JiqC޽ \- 7Ms2O{6zg?hggݻws6I)?9/K!8~WK4ܲ,)#06X TJZ6<6vbnv(nOB@Y##5%B%1"c4Z#FZkmh911 C)m˲, cw F4L&u]lZQi3aNڎE1,V !,B1W l!\x<.˒&ϳlVWiЬڶ]9BJ)U&yWutX I ЌC*1R!I?~7.nnnfrJBtԜ7k?Ya\MBŘeYa6c9sgZN~ma@\0/.,˦aXy8?xş{x:N 47 cXf|07a 3˲VF⋍FJ0 ǹm1N863d2Re9_nt3ٹZsHQ/xyyB=˲`jat:%v=Gy|ͶOR$YAGeuqc<) VV2yߏn- Ji_EZ-+AJ)`l6s/"jP0:4E#֓իW'>|gg>|xݷ|_~w~.BJ \亮ex|rrr~v&?SyeI<}4ozjZڶt6+)5MSlīj777`4M׍)L xX2fjf,NOO'>|/ !۷o9q>eێcYVfu$vvJebxmQ^mEX,iQYBlBIZ^qtt$"ǵGqÇ|\^__7McYvUUŋYeჇ^/i;;;{[WZ}2B [R2ڊTUJʺnZpTqem;[EdRJ{yyY%Xk,[5BmsU 7~dZVUwo{_Ӹ cm ( rnׯ-ƿVw(w݈ZV"|¯Y ܺqH /ݝ%Uz|o@Nw%cC&p@O[n"ò`nfBfTL@ a!tZUU:">Q5)XNm#DUU1q@cF1AX@E^% #0t0 ۶טhdeh1BB;kR,KbP?58iZ?0^N1ɲ,/P(4MSf6y =Hb.wO^k]L F0KUUap) P,,K`}I%TW`:N뺖H7MS!)<s0Fb}q~Eʺoo˚'?qMȡ@!0RkN]@cUJmmmuy\.Tj0^wrr"NJgCj;;= !DI-p'g (q"mqonȧi w|???BP جeu]@68VUJM&3(j3Mկ p]7I(A7VU fc!atBȄŪnsΥlVcL: ,{io,@8˵Z$YfY}ppom<}7 Ƹn !vw ϟ___ea%U40vu] Cq.lYi `,nw2t:4ٽ{Fe?h{'I>>2"̬HfV&mWZ32h LOOwuWUםe&vm%ꡭ&22}5m><8I( UUMZ꺮WVu{{|%|6\ɴ!tEQWOEQ HG/^ sNRZ:e,xI"i!IR{sEUy(B!tvpps{qq~ZF^wE!H"BH01&Ţj|w ɲu߇qMSU&Yݮjiz700L3inpEI8:<}pǢG4=99GFㅷp]=|gY+ Cql" Z޽{~b4?PUb6N6m06 F (I0=zTkԿFqqNQJce^lNNNܹ1ȶђ|4nK)5 !9Oa:(ʫWNOOݿw`YeYi!~mXve0ubU˲ Y?F,Q(0*+a˗UITe۲fY`0C5qZ<+aEQXy3$IdQޝΦ' ZVF.FZa?(K˲'Jb.WJ>G+~x- ^Nx%>u )AE|*$ LŲ/$8p%zeeY2^uB"%(ʲ`#A\h9Y^kr\5!t_VeQ4M!qEcm`j |j!)"YxbVdYVT1&!#\:8;9SL$qjUjvf0JӔeY/84sl6 !;*^/Nbh=`dYaK}ΞaJŶmq*JQxB<,{R C7 C50&l6PBp,<|Ɠ'O66jjJż"Dx2u]*sXRXYT$I}:q sY,SYe45]WE MӀ8]Tc u]q\jUl-IUUۮ֪fh_/` 0De8T*p%QpjbB̊i2+]0^GyҢ0 CU($Q̲:MO$MAd |4ƺatr$M$%ŲmZP],KQC_ ׯ/./I_>9) 8W(β,B]:]O!4˲m{<0|{vX4繐5t]e4 (TM (Uʢ֪C3HV>F{`0׺E.d8LyBH I:`$,/(۲,vY5Mh!QJB(Mӽ=@mA!,0 =Jhx cl:˲EIDZ5+*eY6 Q0,18UhKs8^} `Y$Q@"+dKyQСGcqDÜ31-s^ ,"^!5:fTu !V YUd%AFhՕw=:Xה`JEo\=|ser J)c%QJQYk*{,+"hMjBi!*eYbDYJ$MJ !"#?0EY*gHY%[y4/ !Q(K*&VɣG$I)8̈!"IU0H c뒬Eq cH";;!MUD`Q v=w 9iCɲ$`tűn4&DiI岪|uuy.($2T]+Yi@ 13İ&aѩ `E 4M5 h Lu44 sVEQ뺮B&Yc y#IVL0MSQJX XTwjFө:iH}xxxzzz^ggga(B$#~jiId:ϲ,˾xvC$뺦 WUy ˊyXv [S )!ضm<>߷i n;NnG40}~]\^5M"/_p{{h4F *yI 5ommڶMS4UDjA(Di@ (jjt:`BUU$$9 CHg܁? 0EEA I9P7 G< g뛫,lۮJExR)qlEQ(ͣ8LQ{ffGy; ˗}h4 0 #jXzТ(`t:N,3&wPޤ4_,eJaس4ߟ| h8E4 YZgQ[U4Mfe6EE?{,˲0 4-(̦w~o޾=;;\/Yi'q|hU,?$ + @TM,Nz7 8IJ,K]y~G,InUҌ1Jd,'iG, iE.l>p2K(r=/ Ɋ Q*Afs( BpQO&|ѣ^ouQ\]]N&㢠IV*f7 |I+33s$DxbXUUEUM, z( _ lAxv=w0p=o>Ng,onn,t:0X7g/i {wy)4ZfoG!Tի~m~=|6;:Gna_ض& ӳ[rdi<ќuMf BnAiV,+<χáa+,s]WgɄEx^Hf07/8G}V8:w駟j$eEˊ!笷iFQwT7D 4%ieYD1SYQ$Y D4Aln6LJżX?h4;;;5$AQ;vuks4MQ$74$I2uuUWhQV ^omJqnYV^(IcMQ8XY76TU ooo1-l8m'q$iOzmHQ<'lmmU  Ìx20KZqZkHF𿄥ۣ #/Ei%\uHX"Xp0AAaqxՖ^e؄V(} [}YЕ/W~2hŵXs#VpgVfK 5^̖4'#P{ON-I!u,C%؀T*AXR T0.B1~˓1&Bt]40 0t/ Â4Ͳ<[JK-(0DQX !DicAfIp ieI\QJR$Ift]4$I$YE \D]*4 YQA:EUuU+9:t4)B YUU2qBHZgFj!8N灵6@i `Fހk-V04~M:; Ar$|veE1^P))2SJ #ȃ/HʲDiD!c`dt4g4}UnqeYFsq$Q :ga{z@$"˄G3sA^SUjf3yNeY?kθ빌8N(tg:ijU*]ٟmo0dIظёo޼)KzNzzxsgy<<:<ejf9-?ժqUѲ,͛7F0Ǐ `647+fQUGg)nii1V`h߿O>4cxb>xV:#+eYe0FQYFiyKS m 8iDZm;a0Ϸ6`,#HMܼшٳPX6`<ϧө뺞t:NszzWWv4͛<0N~iǀ^jXz;;;#>|p41f빌qAásFEDDZ̊i~ @D#.bYy^`Ls8/A(ʫ^1beO] g`ZaD/!c`eVM}a%FĀ@qi 7!X݅@#Ckݒ_dhk*"Iጕ%Kϥ(Z %>h~y !`pRDQR"TI6t%EQj*4hY,eS AE.˂LQdMS%Q Kj^7,/ݴA#%IX^Uբ(`˶2&atIԶ*QH$/ EI4y;h8ciJ00="iYiK19<>ŋ(rJwwv< H$V2`e) L0i{Z!q hE9R9笤efQrIƄi6H[__A `n?㥧J.c%"h Ⱦ,K W*_87 sFy9ri$)lWj?(J̑(3xAcqU0vjy~uuX,9˲zJ҂IxA`yO?w vgh8Ͳ`hێiYdJ)Nߝ mggw> $l1h==0] #TUyyK88I% nÎa6BDzF)u=/IcYV , *X)(BRi۞LeY`>giYu= h.\ΐHJ([tU*ns||Oյvh_:~?KSEӓ4+ >Ph6gAӧa(rϞ>=88Nӧ{{{v3ܥHxX|1V6hxq\QE9~s$%ϳ,˪j(b(m/oo m'RA$Zi&犢< nmGU癢(ycevMb%+ժ38RZ|p I ÅXf( Mu}ccuݿᇗ,WսF奪Yj흗/d4z%cNqwW_Vv1Eq$VYNgӧOOߞy{}}[g>8aOgwa]t6G~ׯ_zFU2vdY*V+Jvvvvs}ׯ{v]Ajَe!- ;$)MܬX={O>{ u4-]SmˎM"+RB }x24/K^\;wml5/./}?@Yu}}q^Osh6Ȳl<eblg}^_z(4L'9~0JCS}qzV9O =b s.Btɂ 1?`5(qY,a"YA 9炀UUx|(JcGεCUx֖%W%ˠX2dY՜NaQ.r\ЕRcc&KE,v]Q$pN \4-MSJ.b1Ʀ+$I4MWMӲ4ǢqEL8%+ʲ$IŲd!BHRԢ(òLhaN38=ߓ]%8'y 0E%{m9njER?Y IDATJ%QkiI>˲GD8{xUI4W539GNg(t:U4*;s{{ v˗[ٳgWWWWB: mA? pؚ9ymHnԪW׾>ov2pw }cc<]MF6s.`d&4\$EaCOz6}nfXT'O?#܏@gld*Ѝℹf`_~;u0f"NZ8u$IJ< â^`J$ $Iv7(;̀Y !੟ev( Q[$(Z v*0+0ƊA{/c va=n#@q $ .xX,@EQ(/ht:B7NuUe V9V!}aVs94vae!i !$(|:Fe6y^=/ci _~WͦyZ(JBۛ`IQJekkKՋ^7)z^?|Yp8E%Y: ,q "NSQs P+%k xɚ&pϋHӴR˱7V_u0 y,u]oGGGIJMSyp8>=}#q߇b3u߾=Fo~J /UGW{clf9A\ހg]p,]oЌOT!ɓ'mZHWWW~BTUh4TU_t:FyvvV,˛[Qonbs.4::-a,icܴB Dώ㜜hVl8yzpprw{eY$0l>w$IR<===??=<[$:irY)il\ a}DYAEi6v읦iAmj y2E*J^]]izr:Y+òv>BH+VH@Ee&(EQ `A%UUApѣ1TQNF `^>oH ^,MLRZrR$; qr~~h.!"Ƹ}_Rd47|#`1Mӌ`寧*̎9X]8^b|ެEgE(ݍ(ij`0PuwͦiQCH!Jn[r9fywwwvvzZݻwmÞ-BĖYsqqj8/IV?96M7sݿf4h4(v7~_i.IRӑ$aggg:`prrX,t]Y^.c,hY,fz=ShB% bƒ$EQb6lnn2.//(JQ0Q ,` M:`An j&iR1 EQ(0gQeYV9@?o++d%]?&"A!EAV7D0c( l ,lD i)V,Qi=X?aTX**V q׏X"ߙ9^Q'dl[`G 6A_Ѓ+f`k&Ѐp#s.ˢ(y^PJ%Iݰ豢(*[xe8N&b1KQ//40IBDX?\AȠŒ"8y[hʲd:a 1"iZeQ$Η%BY˶, >$JZ`㱦ꊦx]z2Ƃ8*RIJ,7ZfW5M,BHV ȑ$ J܊*y 4/$4 Kc#cE#`"? 8AlI4M|]jB !`%s( JRmG>x;P=q@(7oХ&5Cj999r Àx~_ۯ^@(Ǔ.E4u,koo&N2#D 4NZW(j<~xfS,noo @`j?Pܔ+XUU Bةke3xO[9¼3Vw1Lj!fcDJVu Ba(zn7$ VdXaۿOeeCH MRͩ`-ϰZ1E"2t((N(=X,QU~|>_,>n6fٻwʒ"vvv*ior677%QEGU.c1KYV1K$ ZX!I~kdiꗗqijY__EQ./?Cv Jb˲EEq{۟L&~ٴj1Vy.Aٳg !~77wz1A*jfVkṔ??}׿~?XWׯ_Eau<+?b16 O^bHPaR)$$ZF#q`jhf@'|4I$Ivww -nwSӴ_?XqHӴR1;~+jpkwww8N&t I>>~ dYj>99Jc )j$IZnITQe*SUUsz0OUk'iz}}繩{{;%U6tR1q<Zmmmz,K˲PUnF<ʲ ðlF# |(j5xCxx#0˕ IJlY˒:; _f3E9:`08 aGK(,y4MC%۶ɓDŽͭ 0i@ -MEM< 0#(>}A5F1ww8?xaEGGLQd]~mT`x5M+|?@0s!0**#MQj`eӼ( ô,۾~Sgۖ($ ƈ!^!ID,gmjUyNLUvʻZ"0F,/^Vk?ׯEQ<<(Ot 0I߾}_zmYQ(KYL4tͶ*e!˂rnn4oZ;;;Q7-)w I2;fgyQwcǶj}mUzEF#8{H+M 2UŪ"Y,NS=Oo6=34㐽3$Ud ʵAD,d 謭ݽ} n躮}&v~&IŅɓo߾=::>?8;57κt:heeEWՍlr=Vuݗ^u:r|FQժfu;jY$j J9xh4*iZ-9#Iҍ78h4krYV4Mfi+Itmm-Zv,777jZպ, 0Acb>q[ TUl6,j1C0i"eY~vvF㸓d߶KRJ:v'3hd۶iB4MBq%eK:\bQG1YuH4(E4 ۶g!DńbƂ0sJhqIe/c 0I˲Le)MSaiNGRdUU+++ $I1\4͖BЧc MJ)9* 0tJ=j@ S iZB)M̶˗l#t]/ '''_|Eߏx0q ~xp GQ8&x:ঔZF#4MsGqzvqvv69BH40}_TuYizrr2߿ g}v:lEVUSqu=YLEj7nܸ}{^߼yɓ'T,aL&R5UmMl>88x%ӨZkgOwvMˈ0bRxvqb)!&$P(wǽvO>秧n|W37M3  Z?}/ #˲ ˲`.`l6zFpTUvj>,I.Yϣ(di-:At~{fža+5R*$I#!X\9]MӀgӶZ^,=jB)se IDATZM a^\^^W*,yox|޿`'O$Y2 EN4 hy΄tt0BUg@b)BG*( i-O,˖emo !$YZYYWI~ŋ|7N(fkݻw,ZVTj`.afxOSot:W^={X,Z- /^KD3ZG,b)ׯ_fw+JELU۷o !YqFV}gJ[0lsH 095ٳgYzg-zYe|֍R鬁چYz=aDp8zr0<_~ɓjBBw~[NN!J%˲jŋn;N/..yv!FﻎyXUUY?Ccww4ͲTӴRt||3d2q]W}xzARJ J0]כΧ4Il6L1[ca5p̋> ZU5XCkfQiycoͲ4I4muu__޿8ť,Kgggӗ_N^OP嗿%0`YeZ{ns0r]͛d27uDTV:BO8Иāu߽xO60TUt:U ?~dY?7io,bDi)@acBH TY0XEC][rpf ORJr~HW6W,^25#S`i-Z\,* «Bhá.5] q(Zk;^8u]}ٔ8~@xZ&h^~eBu~e'W(029 HclyzsђuUBTUF=%|Ǡ|?$I8eI*Jj]WeY.E3Q[VUU"2\,(r2`8nykA]( b\QB,NdY5R*r1+4M x:a:L;3I4Bn nwIY~i rUcB(!TQSׄY`PEQ$a{Q%fx7Hp-? {jY6S0\[[K qpp0,o#JihA"9zBTM!Carm$I҅\ea$SpɊ?,2qF c'HDsomm79==.TeH۷n߾K0I$Q̥ͽ7,?믿Kdr\v!t޼y9Y0ƀ0TU}d2hoF 0ƚfxdl:JQV{.8JpKe1tض#N)>VEN%GmɨYaoYh4rni!(jL*c`q!2IBJBd \__o[XVAeHRU~az'dY !(!^|BUUΙ B \(XAϲ\.3'i\4MYH )l:.0Ǝ>-f\芎…y12yN![97 `6ް$qr<"AiԒ$<{4%-ɲjTTB(8rQc3y,K,Yu]_ 8'Yas Ass7`D!erCp5UAL)yn6B(IW dCpa\alƘȅBRZ׫ժib QEQj_ekHrem6θmۜ0 㩬*03<>sIjssӧ޽L&ФT* jY$*ey[0|(뵚eYis`9eٻwoL 94 CEQaD,$IvoPJD!W<8) bq6 !ګxy^w޵m;BUUKRR  8T0#ض O{`TT, <4 \D\(!$IRB-\DEB8c4MMUpBg 'I Fc.BeR*oܸA)$U:/ H RNc̳,ݻw'''`$qT@k(߇]{:J%IZ7n޼Y, <Nsm! h6f3ꜝJ%θeYvD!rJp8}?Yug6OcPiVFCTι(B)RV (A2L9&﫭W^Dei h4e߼ysvzyp8i616ONL ÏNOOPJeEW*Iz$$Itӓ|id2)Z}4^ I$ŕYáȪ;f E777XVdJȊ"I2Ea8VDYA2( r`˄g B%Ie9Oӌ =b@ !k'$1h*a$`vqmJh/ױBxeYjF$Q/!8e}Yc#TU 8~׻lἎ "E-|?^`KF{k#-MS0@9BW@t4M(I4KS뺞eY:y,3! !0*y(J"B(4JhC3 M˄9!0Bb#A'UyB`BJieijZpi8#OFUUU,C1eY$۶β,KS/W*{s,D<`(U*$YjZfcUM,K< $ cBeY_?#(@5 bB {$xG)N8 #? <% j4<4IŎP$SbTYTUSU(<$2MUQ5-N,ϡ5 8<FQȊ&g}jw,DT$P, ۛ(zj^ EVk44\ @eEUs8nDؙϩx;\eI !+j54jcYcVWW0F_$Ѝ rS*~( 0MSTMk=00L0$N MF٬V{{7߿?R\ܽ{7Zvpp0A ^yl6f{q\YVnAizZSdlGG8k>Znllt:B7q|>,}~`YVXNB0TUsgN( X/JKVVjB8 BX}۽ͦp:RB8N4c0C("ϙ$.o:s 4w(.y`2mmmݿ?g6 ,z#!fݞ;W_~/Kۿf_ B Nl޿sƞe=uͲ=ε흝(J bB0-ӤZjEn$2]i4JD$=;;zHSTIZU/FY%Z=M( fQV= @<\=::j?#w-TUP(J4TEmuaPr˲\,tMS6e* +빎Aprzzrz6N^~=BBv{5Ϲ㸜窪AEL%ZbB)Z0,pyЬ;<4ZfXLsc}͙ϒ,qjzg] dY'1b0p5!h/DQdzM/ !)Nr!2,O|]-!@[zB`b\^`~>^%JR"84&3z! -0  _?0hWk,ˌ# Y-r,$G f&R =H9T\yМRDDYWjp1Ƌ2%(F!1#EIu]=σL !*g EQ Ä\wVB5ĩ +DQ14ՐJʗq,7p۠+Y*,5Ut4B ˲iBA4!iO&S ˲h44O&,MJ7oݫ39 N_*5]Ӵpva04- I*r,I뺂bH֭[YO8Ţi% !0  !dfe0 !RPBH( `kkKnЅ,I2c :ܔ-!˗4|' z9 cr:N u]!8@aX#HT*J1VT}޽W^Õxj:˹$I5I< 0Mt1uFaYI`2<{BIPsv<ϡ8N\MY6 !a4[M{w'I!7ؤnonnya裏(!eie)XQJƍ73 'ibYV~'kkk%{t:z3N߽{{v~s8;q<׍ { IDAT(8f|>WEU8NVV}|?PB@r:F,KEN0_Y9[]]=P(P $R4bFx<}Ν;z>$Iy/Lӂq7)0niݻw_|^0$V+v{5 Ç4MU("4f$?8NVWWkjZ}/!XR}0L|t4 sǡiV$,t]w4aeEOEO$gL\deY!X`D ˊeY$K"˪h*+@BȃJ$I 9g=NSƄ@8@ttH *aLUaNF`!J */>W3EӒ: Y aee9,:W!ײBK&] .Ȃ~ML8BTw ŬY[6MիW`GA:A,5MUU+$I V00 of0_.ۍF^o~}M77666y:`#Ec,cv?~wuM9l2(SE,d9glw%_{.4oίc0dG@ ^a7 !A$>c /0H@m n%^Dzj5}z-*K,Ҳ" 0K,"/yXe, < P%s1Ix_PʲRX, D1RR pe0 P" q ]Q!HUU]Ql6Vʌen8XR&:AgYOӔP.px,SdM$I& 7$.:*-(|2\k0W$I80 EQɇf_Z^Yx<VV41o<>>8D s$US>C!Imv7dwy%A$HcuUꙞ7nV1! _B )4V/t}U)4>3߇Y~`"os~g$ #9*Bs( CEsF! s8%$YeMS#DH\V+#J,S$q^kݍwǫ+wyWP:=4q9@L}fY'(!c\QI}l2Eޒ2 bf4u]GE^EdXyQynh& x뺄$I0/`xss`EEQ [mے<eY9L&WW@yB($-eYZmRwݛ4 LaD) O?}6 .//v[x|rzz>;;a&IEf]VKܱT*1Ơ 7Af͍,+5Ai@RR%nq0zL$I<χ!cΙE1,AmۄQ! ;',!z$i+ggg 6(u:n ,{n#덢(j*d#ȲkZ-L4Rzxp8QIZU*r(NcYnnnq&YQ vRpƂ08 .4Im5M rH1Vf,Kɲ11Möm۶DA% clZ(󛛛N2}rh4j6RJY,ATU<,y~{!s &ri2޿pE%BL"ϋ%,;!#B:\m{zgî;{p$T*ٶ%IBf]joIdY~^kJ˲|sxᚦV*}Q0e a|_e6]^^yAf Qfwgwޭk(2 ӧOt]wZ{mssmWJ0EaX ljLJI1tr~~~yy5}FWtppݷ4J^~l"$㫫+I߾}͛x<`g9[.-|~~y`0oonnl6n1l6t;;;;++MMӒ$Y[kiE.BvnkGZZ}W:tKfY;w(/on纮M&40`ǔi?fnZiR4)A: ɶmMƇ~0 `ɲ&NNN ^^.'d2! j0 5MjI@ %VYEC7RP9C,PbEEs(ϊ,ISB09; ,y 86!PV`a]@#^`B/# IDAT3l|B/BH Fd}l-؂7rxX-<yǗZveDZ18J\/;pq0j ^x \ A@J !i 0)I(4@PUEQdp}1F'(Av |r y;;0\Nak,IE%ILQFeY2 *4f'(m1RR1yTBXAÄIeYZJiDa(2lDI䜳Er`Ii1WDYs($ 빔Fs:340cЃDYM4MU C/-BҒ1$ <@(aeEgyc\A~Ǟ;9iEQa'AfE8j+a[EʳL ,+2ayW_-7_MY3Γ$EQQAa~qaDdE.L TlijZ `6EQA^g$*15q9ȇd jqg{',9qrjjQIZ8q<EEζHC׷wv...(~ Bu/ @ÀK((E隮kVO~ݛR$r`I W:u:m*y1{7\G,mmmrZ,^z5(o߾<0Ay.GR !⺞(zT2(-$m69(ts*x4ǣ$I\Ij/(e{8N$kkkVYoM$JRVUYU5h (qe(cqYw]7dd2QU((`Y kWSJ&T!5YjlF|QF8΋"IbǙ=|F}M_uct0~ƶ$.Qy^BR$I4 u1GXQT($Hm4 vV2*x<ataAѣZ""bZ8qfUHxUUdY*渃a͕4MsQZz7o߾5 }?T$~޽v*+{,UF.^]]_}xHιix謯w>FU??8888<<{p>ۺsggwO߻_7jnYZ}`0t23m<}`+kYfYZ{ͦ( ii^.[,aHi( - ^yj[euBx<wfelnFqRTE#D(ROA꣪Z0U˫.;j0 q,di4Z;,EQ͛(gK=X(iJuY?V{]մ/bkί~듓ӓ;w6G̙uYfΎ0J=YsYTN眛Y*/Y~~~_v\' htzzvyyMT4ju}}lt]ÈsۋoߍN u0dža6&"ImY?{}}jcaOŅ,˕J0?޽{A߹Q۶}p("XN^ <"yy~fQQkq mOy?PU c8m+ӧO Cr`,I,K)o̲<$ +<'X򣣇{" )K [A!YQygYF\J G$j/p"]CErEL3EaŌaȏ8`c8- 2`Y=[n^bJ@ޮ"ZFl`0{ /^|rۃ G[t_jUFK?@d<,7M#J` l8PՁ |ؤI1bf8$1e&I<,/BnPfO,sapXvJ,Ae-gY8p#Y-ۼ>۽:D`9:Xz:P'I5GqB80ۃrE"w+ xSaia IV|0k`SyeDA 4€ty$Iy"*mhjZ4qC79@4-|E qcA$y8P,Yce٣G`Ś i$i~Dz$TQJi*20GjY&qZ,˾ 8c\.f"NI4ME\^ B\.K(5(buǓ,z>gYɲl68>_c OSq>dh4/΋LFaACAu]!dssSӴ,ϖ"Vn^(aWbj#(ժ$ɲ U犢fY8B Oјfзu];wY*y(eV?gDQr\\#QTr\Rfd2ё$I c3 ݻwzjޙͦXէ `8F3}˲"{`(Y]^^&ifF,˲_yq@|7o^zlll;L&7?88<8w`Çf$r0RUU "&=Ay^+ m­jq,Kr?qssZl4$Q HQ0L&`I}qq!B&~?MY666BWWW;;r9(FG={aesjӁ(J$eY fwpkuŋy~`YrY,fd2Af3QN'eEQd2eY(fYfۖiqSJ%Imj@ 8] 7le 'әeekY&A)#DET~1M/^=?GA@#ЭaL0#`0&Ƙs( B1& f_pGD0,M:F#'dY+/Nyt'fG-zQw>0 ]>$Guw"^<zK$̡B%IR A -~Z50TUKeY2)eӅmf`0O+,r4Ui6lF06Md:( L<4,rI]˲-.3Ìxp4e:hyRQdYF%YfI׵ivE$K$jE^wwfzvuul4*Jm8NElѨ^pr|"M'_UJd.a8LSFmُ aDy߿4%)g`b-Iwvw,lەA(2cLex:zuuh44]eB>qV{-TYX( $9>~_隦?K8GGGi4wR55/r渀 &o <+4H6v_[k]\\\\\@lME;3N1sƲrXai@]Txydz3L+j8R zgy2F%QK@p A\S(,alnv"U*e)"equWz$I8gi{o۶ j.d2M<!7޽"ljjsee2Pإ)Lv]؊SMSO$Y#DfEq(JdR[o5]===U5: 4\ӟ] ۽ h0i+H<+_)45 u=̱eYGGG}yA(d2|~oq=ŋ)ĖB Bh:udYSs*4aD8`>4zgxqLgQ88ٛ7o^^~믾N_|yvv6NL>:j4FRARE{{{wtлJtvvy08]wu]>sgnmnmmmR8O+$I㩦GG^zEilx\Yi 5jJf^u-3U`*Ea$(r6FȲd6cLQr `j?????Gi麮(A8x:O IQ䕕Aᇛ땕յd)x< =NOO X[[;>>>;{oYvRٿZ]]Y5Kn[29U[1gd9˲$8aX83QA$FMӖ`^^^nmm$,3JiP߇a(_u] PepKƫW( $WPJKңG>sB2 s.I ct8Ze:alvʥRF#D ØMƚa8yEaȲJ9LFqټ `6s,)vz],˝N0̲ZR\:)H`6C0< Z~g\^\yiEEQT2ʊ$Y,B>y$ cEQVWY$(I$wJ&DQ_<o2Mc~uuUkC#`&۶&=rT zz}:オJeuuRJ)O4) mEY__/WUUI$3ʊDQ lR)"0Aa"2&iz)eyoo1!o6=pfE ޽L&|yyyxx-1cleeEQN80 4GQV(L]+B(^wppi`deX6WWWa"2ƞ}ll˶o߾t:?zz<!up#3 p}ZL&W)6 M,cl6߾} zeXlfݽ$IsJ,IgϞ~ﭮb4jOO1KRiCs|2 û{ZJ{zk'|3H_Y}ŗF^jy^y^dp(⧟~f(*eqZk4<˒L0ƛ7o %?ZCD<|ɺYKzQ!\xuҖwh(Js2 DDx)K GF+L7r%_%; A彘'2 I MT^j-,#R*2/(F1· A$ GRa .Q C_O8Q>O$[2ILV&BE1u0,KeYbrP),Q%jQE^\DVmTERPbX(Q\xaJf e)1^0F1(*"{(,rJ4cPfYdyxa:nz0,K!DGۖ-榗ifyJ("h9,IEAY$QdIc@0"2 YfQBH^84(Ng!)!XdqaS zUu(dEtC@ e)b\Ixt6y($Yۮ!juݕ&hi$w6:/_It6s0yy~ɓӳJ$i <uh(׽^u-(gF8Z*o(]$M4](i|`$`0,rLUjjT*VuqqAa4E1ضzvV$I4iy1FUUd2q,Aj ,(PA ) & N?a"\u xu]ݚG<FoQ|}svs@a{@I5M{yE^T*9%I"<ۮix:sڳ&p0%F^+Du(dY65Ç`0€Jr\^mI1 884V321t!s_1bTE5]&,4+DeYŅ 0b%#KB`$ĸrbBO>zxBeyskqJ2nnn988o|oL&8I֮nzr*~0\RPUa(; t(cRRJ l@YDQٹM̭KA,kkkK>CKǏOBae`\\\i:"t:IFXQA$ˤvGQl"d -B+??9aaBG],")`w9kE0S۶?W_}SA4VR}_DyFQqq !{=Bi[w߁>0 84`2BC|<{^w|| 7`;88M?|ݻ "$AI8t: WWW NNN?ò,g?C}v!D$q(EexE㕕0eYo8}!0 '`0M/!L4V@=EQ6;kkk/_0:(00)A&Rbi ^ U(c@mM_uscI2'>={lkk+ h$Q6^>_-5Coo,IТ(vggga|+A1Zl6{%cųgFx< FRk5B(cb(J%0kh( VŽ{AtWEQܹG zw߽?=*J+N{ȧIVuͲ,̑y~ogw4ˣ8p>~x:j~xjl}^4,L&{Vh15IeY6M]E,J%s}71;d }qR7{gg۽ó3M PNpH}<˲t]UkZ-F~h4!Lh%I*[j\ymQa\]q` L`t'Ozx쳋V<ϋxuu V^(|AY2(C h %n9B- _~_ԒP,Ip $^ %/&,C0DN@8syiyIt;qiB'( #0``i$ Ȳc\ HUdJ Q`jgle( 8^F!4]V+B‚ 9'*#0!8I0eQTJ9c\\t49 tv"ft0i Ȫ/0 Ye ̵|a`?30F%Q4MUY UeUU0Z%s| MYcRyeR) p A"ou:J`d( ÒH$SF8 `ooql<ejal5UmU۲04I}aŪ$K| ݋s$c, 0 5 Q zŶEInRwUxz$VuΝ` ѻTU?(/AugV%Z1Z*Yo6g6-qܒY`uu%IZ:YV7M`i㜝]Ov{Ͷt*"p4~iƳg? $''' 98$FÇ,5MCQd$I6MwӧI@\(IQ~iaD)]YYA1VB`&o& H`ngF# ?z۷ uҾ4A (`k%xA󍍎eYiUΙT@ag-ֶ9IDQng3B766TUSW?}TUՇi~vOU-x8n Rj}`69(D HQeIjQe^oԪժHݽ="x0޿xU*Ea%q2]ύ4MYQFws ZXx DqieYϞ}9l<of]IMDYV0FѠlDQ4튝$1ݻx<9-Jl:$!lnnpge,Iy$YB?A٤C õvK%Ƙz]n2L"kݽ; L{u`0y;FZ)|ppfq ɌG>Vkk$`~WB۶'ZͲm$bf1U txpxpVܹ m4BPDT'OFQ")aQ*蓍 4kj{95MBϟpfmWlۆ H rYcsj:M,7<[[[3Mݶ-Jir jju:B60b4Ie4IS Q gYI^y! 'FuʊY2<߇iaĘե}t6 PմaU,dV4_֩{eako?<9| XRnuaRsȏS8!#B ABf` +/K>sʋDr%.c@v:Z`|򰁻Y csgg!cgY=t,4˳,â]XJlTBm9\1Kw 'IлB` ᳳ,b`8 0 $ RneCTК ^iEQ _U6FySZ-8!$E}GRABМJTç[%X,_FA1g-"18 /Bb̓82N0 B!$X$.:4CEpZT*!d<4fy~lb_8,˲4g`6EQݻ ]nnoo<MdٹPBgFFd-Xl5|΂6| nƞqAfu`uPp,nĢ,2 q;|`H)]&뺔R8Ţ(&ɓ')a(uNSQ;{̙"Ga{`t`4b8U%CvQB!||\(t]x*~Y7 jtxx8,] 1Ƹ\)}08:8BHK),y C(_YUT1V\ׅM0;\먰(^i:L$I߇^u>A+pyEap8f/QժsυM@?;;zel:L.Il6[yX\.ww$a,aO&),rJhJ)t}? :?kXV?~3 2 8TZɓGnb\~*|OZ;04Eb,ŋ_PJmwEqi%v1.J7T*vvmZT*kkk1}K4N( d<'IiZvqq B(@x<<8ؗUEUpa.7Tv=BCBjL&R!If3I 0pARTip82GQEQWmwݝ;w63-nЂ~>GQ2S}a㍍۷oan{ֻwC 4/!*JJ)-U*wnSJqA}7p>w>USt8N?u8Դ~wiz}}(ʽ$ u2x}* WEh409H}(J"BO>IŋHɓ'~˗q隑Sܿ/^p]PUU', {޽[qP˗e]^^.pn b(B\.,˿o ۷3TkkkA<~ضn YV[r(`4REUM̜1> $z(Qii'''JP(|׶mW틋 1Ƽ $`Lӌ!D:e| D7H7͟ovo?{I$#$<*NXc c 641ƘLdEQxe #X`k 9!]QVJTDBhF,$I7zb$8e؊^:#e(2B#ՠH(c,NcqUs; >^` hShUҀ[qCȜ,㚦Fٌ3YF!c,aiiB4UE1I%/X.|Ѫ|Z^28DOP}UnjJe՞lYUT] GJZʯժޫW!E ː(bQ3qUMId<T%DITtwziQJ funy6666c)d2hskSRAmô~$a IDATA]QJr<CК]]__?: v&$i)aGbA6 v kkk$wE{BH.4MUn#tvZm{6 N$9c4ʢ8 j6RA*w:]A\[4(vkccc\_]]Ngp8lZGQ,ɲ~]rw8ʲ,k:fh4tU+q,ժFQ pA*rn:}^^^wZ-ǝ=u{W$*r}cò,TڜM&emSB[ϲ>LeB85MͲ5K4E8Z G^M'N7?}ɲHR'ci3~û$$EQlF!d^]] \/(8Oo޾m\4&~jjhf%DQQ$Vw;~*a^6MSbFnw/^x~h0LaP8<<|샏>dcc0( IFB0Va<;L531|ccCUTQ&IN>oݿl:u:ݗ/_yqq 0qvEӓAh4vvv?}v{BHQ ìUdz c\էzgw\6thP(zlV.m4Mx>{A<[8$NRZ%IZ+~LSr9Ӳp8 `(l6s7MY)Xdf3DqH3,gR8)3y0D B ` NeM-"Ke\(uPeYeIӴJy{{]R?4#]` }api'Ie(i0`-|BF'؜PMAX pJ$]7`_p+m7!/2 B}xDcٺ^__W5U˲DQp]*LآQUu$|4K`0$ܘ!3AH9sqްmIqT]M2t=MS eY3`W2 Uy&7;0JǦ$41B(*AEK$m 0L}߯T+R۶>|1~=x>?íb8v׃9/c׿˗/}7 cwww}}ݶgϞd2EQaBpEI24M3M&Fp12Q\XX'O\]^aU!p/dh_,v\&(qGa0 ΢(&cEQy+4IdYUnݚL&s#td< ]eR}FY+q᠏`FK,ÜBPU-vzrz]Fy ^+/^<>>a& lTUЮ\.G%x?{q>}ʰ, ӻw 0S04Ma½X,>xRժ;;;@mǶULcTE BF㬊0 l6Bh:ض=dY h![GGw82N0N[;;;_mH\ R_[:[V $V0 Ѐg3>u]$I>/b>;==Uk{{=̲P($I8$Ij5[nZ.W0i& .y1t LǏjͭgϞa /$X,ڶ#Iyp>@Y/.iٿC)-Kkkksw.j* -0G$eah4\QV5Ngc E4,Z- Q{B<8(p ߀z7e7:\W-M __@]x!)zf0(*`B$C8[iAƔbBGgR3<->o8.;p'ȖHۆ껯l|ᑫu@zgCx2S %F 5eoe4,#K?PLf5ba62`e1!`q9@7t\ "D QVۄ`YVRnN2D0&/ c(<ϋ/=C",I#04esB0!(O#p#DFe`pUB%oYyEU,˂,NEbqV,KQ0 fiq,Qת穪|xx Rw=OV( vg+rFf\.eh4 RZc]\\|!Z͹s0t|p4y+_VF\NDAsQXjfXDJ$Ilvuu3+Bp-nN_26x^8\eyF(((q ˶GXVly|(jn .LQY? ~(7Ϟ=Md6R$ bL_~;t: Ba 8MSJ10 ;i>;1QU  aF >}^ I[Vs_ !1EaTV ݽi,`4}v6^8 $URc󭜕&LRH)1MckkRp9Ga *筼ii_E,)HDJt:LAV,B)$.aXU=>>Ͻeq$I(uw\g:IA\.QSOxl6ybkk?Gf Rǝ?z'gYf膬HO>)ai\*󖵽=Ͳ,Ä8ʒx^l~ťiAz#O>GZf:Y5UUTcj*swmm㜝fׯ_n,+\NcFk6[`$Iқ7oƢ J$V=/oAGh8wuQ,۝ˆR1&R!Pd$*V(0t`Yr_}W/^{nT&`z᧟}Eѻw 5[-E00s$Mq.$ɦ ;;;{{{j#LfѣǢ(yV DZu]hfI<~u`0Z[~wᇷo~G}͍'OrJe16Ná7r<YUGm/Bgzxpl<ZV 0٨($mFcH3t#e,IFaP)Pl<fv\Y6O(7}'c;XGFa>]aU'~շ^d' ZȮjl2 fS 7ߗ,ܽ?7$(G҂ބ2dQi,B|BaIEIZu!fҭ6clCX 1`8_?ꗅ U//Bl Jۃ#-UuqPA%|X3-SQ)VUj:gσwe H$Q40Z (qD 3J%1KShf>(V&[-B~ Fhi =B!e9Њ8K9ӹ;(Mx.4;t5\BKb0(˸(I,SJ3ǜ؆}Y0iFaiqs`/ 1}oMhiJڪ8ٝ(EQD1q|GGGp:$ܝ3ƒ4ea<Yq u>FN3mI P2@ 4UMe)b0vwvfd2k>]ǍTeUVDQ42!ع\.MShzm;NRO&,I0Em۞M5M+KT!I0TTM4q8}UUө?Rjj^y5 r!DŽ0]8OOO]圏Gc>߿*0EQ<2I!5RBy'aݍdRsnd2۷bL& _v9BHQA@}m۔Ru l6;::~ׅM2mvjw]`c߽{DumK"cX,d22رCc9DZ]hQr&ƸP(i(Aq 8 E|i_q۶ww, {>r'vAfR)EQٸN|38GGGbs{cct`#hp8X[FQdzexF eh_cb( ѸL&癦:B<˭jYqEZMWc,o߾ `>w]=??p͇r^ST<C^?VECU^wt.wwwg0AsKg&~_tIzȴ`~^QMӌT*%q"DQJ8An BHU;N>oyyN_~ `iwdY[=>>~ݛ7o+B$8w !s&l6PAdiZd>?|b//߿ $p!C2n>gC#o2NN/..ÑiO>Vee^w]+ʊbaNA|޽ŅeY˗o޼(oߜv7o8ϟ?|Pj666*{8~IXW*;Oƣ|>eYkK^1޽Scoڷm[`8r]{(fEqyu?\.xݻwBA]ٙeY},˗<|߇mRA}ga})˲L74]߼y3W9#ת5q4]E)l\״ EdEQelsk<`J< &?w{(sx+tJp MB+@sA6Ȳ XaAA(Cc )\L9U(CJQ绞F|Op0m ҄Fa1̱hSJY4 4ʒqQ'I`QƳ, DUL„RQ (0&,a0 Y&P1FDABq3(24ISBi2ؐY IȊmwdY9k4 B(ʪbr"+(QLP,ꆎPVUAq8(\_)dYV088˪j;Q9r1Yrf^׵]YA$ |j6Mӏx"`8x2D1t2[ͦ,)W}}ݜL,M3BQjqH? |4ea׿^[[!&)=Ͻ_*G̶-+/bL|U 0#8e\*ݻwoV*׾Gils=2$Iʳ:HEIR0Ƃ@dE ZDٶ ewM0 Vc,V jooO"ѣGu;=-˲ً/|k6@1h4nڌq( $aT.u]uY岮` \Q !4uvq{38.-Iew9\.W,///}t]'O<|?B>c[$,8BϛWkx|rry =󖦪1B=[zM2ywppgmm 8j5v8ba0YVZ_7tm0w;Z=sL&t: ɓ'pǑ ,׾tvBK۷nU~>ݽ{^ ,K?O>|'OsDQ"<@0,bPH0>R*nBAUUONNm{ c_\4Ft:}ٙm;۷nonlT*Ikյi(,BUUUYVYhjǟ|Et:x2yp8 }󏎎vvvdYLӤl]eۣ/+{~cL4Mz4Bt8fYL3W(l6ӟD|>_Ւ$b,=>>Y*7jU(XV~{g+v`4| > ðP$Isp~%(rТ5C èjf.iZ>gjUx+\}5sW&Y3_eGWw\aUvWO6A+tvo4CP73Mx1SB?+:HcWyufP!`4AWgK:]: L\3lI%Z7j ^_ |Y(91 s10\8nƪ ^|)^2#1`LwA$Q6۶#c* ǔ FAy\83ΥE21 T*:W_kkkdJJ@>s0U O r JE(i6?<:h4o>^kkk<!fw ? ?,tZntc6r΁:h4=JR%ٲ 9J"!& &4MoYǏk?Ea|qрh8т .+20 Q!pR˙}|20Lt `G(wTUu]j!yGAXd9~p4,j O$IE枟ew޵۝n+Բ$I7660F?Kuuŋng{{nZO?\.zΝrT*rfB(ym=;;k68#`0 ?I 7|͛~_諯*\WNg^{;w@b\.z3`k0<4.!bggg0qE!DNi a-*Ы+O*rm؊{vveY\ǖTIj534gMN$cAI cQ8@6 ?f0Z(`gf;_/ْg/g5UoWu YpAi pH;liG:J(Cd `&?:EJf7AhB)[h<9I$W,_ 'VAKxظjd l5a"a^z&i t4A7̫B`wdQ&\S*dY( =tR `2x$,Gq (NDI>I8K$ܺ){^GY@q0tْRU(C,e/*$MQ\Q,%2W&"!L28M$(8N1$8J(c*`&# ^cgʲ ,Ɋ$+( hx2A"k %X%QdQ1"Ku,Bq'I\?, Uǰo !BL8%QWuP*Ri88VTjZ egc;H>!D$x2fM&Ӌ  e[[B?|<x?n<gwoW^DrX,߼yn%Ax&ǏBX4q J ۩Tkk܃T]3t:EBgܞNN @SJ@kiD$!H( C&uuu};et6<'qƑ˩ d(W%ǙNf16zo6H"B|>{/Il6< pɊP(DQX*T go[N$A$i^ ~WV]1MSk~~xhh׃A,dqVyg(ij]Y(uݸh\bL t>z߷,+3 M-MUdIjFc<;xtv~&JR r\>fwnFQ>o7o^˲$IjǏ?yT*0TM PQ3󢄥UUM3/&e=zz}ݤXD9 }LPDR9 ͍/!F}{{R)3yKQO:hJ`0x-j"$ ggV=LmEzrrlw޼};Nw˗/TUe%߽{(!hmmͲ/bkkۻ{P5 ^|uq@1u:\Πav]מ(!z}#ImQ`0JSJbӹ<:O(-o߾T*RZÇGGaCQJ߼zᄈ&Mxyk;inmm[@Lpӝf(dYF -q3QK ([x#05Ifܽ{\y.!Yu{ٹAITM1 ö'i4k׮.c i"w}UU ,˵ZɓhDKt2is.P(lll 4n]Ueiy(+++i鋥r*?T*q7nK 0 $#pB<21uo .]jZFv= %Ir]4MQ!܋&b۱岪ʺu˲jV}ﯮQʢ(VuΒ#&EQt]ZE4M)Bض'EX.w>|h8/шwhsω8y$,C$IS7pph4<ɓ'gǏ=z1Z6??+2*`?~/~gϞ{vAFqz}yynAx͍5E$!EQUUNLӠv^n=!Yv;BX󌒳u=ˈaoyӧ;v[3嬢$IɤT*=||;ryuuE$NރZ{h1ݻ ^Td 1Aɲv_zU,MONNTM=99LƊ8cfd-,,ض}||޽[V1Ƃ B)(8>>>뜥i&i$\"eycÓ ^ٶȊ8׮n=~^[[4\.apppp||5 req ۷6663?~077oێB4sk(Ƹl\t;'I$(r12wXBH4j5ɻlY'YDQeYb1/ܹsg~~NufmqhvOOF Bxnױ%Y5UEO4z' W'r&>.˄(Ia!ZPJq'O^|t0EB i8ā>tm3-E,k<Fۿ6R1u(lׁx*Bn;k&,ya5ͯ_{Xi濾٪S8LYΝlLl K)cɹo(J"]!YΉ}{4aNE^rC| fVp_|{J45q1(vh7OE$@Y|QD$ɼ;2M B'Y'd`<6W;f9uS|AQE J&XQ4M(cfP'q, ^g fƘ H(@A|ޔ$"e62Y`3"^SY.L_,#(QQ4Ms AYUEBc2IeI[o(!PF3J1H YNu.ڌ,RkuA2ʒ p~?`tM3 #ge B!oj!+Z9C0jyضmqFR,P(trva4p08IF3 LI&Gwv/Ko}϶4ťRianllh4~;R1 #۞4y]m.rV! 9Z}iqq<YVa}͛_04Ik$FeYKK /^=(JYFS8Ojig$oo_LUU 荛o4$I^]XXsvȶNpzxcc0~QTl5;vssno~S(r9c4UͭBU%I`aaxegwJ%AS~F3.:t:YF< GRZc3|>_Y]RJS'Z-gV+q+ <[m."BOX}OӴ|>RONNTUں8\snmuP&O>=9: f.OH&JP(0t\c/yoOx駿)NӟѣG''Ǣ(0hX,ں"^u])gTU |_fnhe=Lx0g'';;Ϣ(>=;`᧿mFYkahggq7,67Fq뗟۷_~uΛFsooo2Ai??}瞳T*eY{Ǐ!BȲ$˸@EI2 ##DuIH\(tUAUU1 ilƵk !ar?{I ]O!$ FaY'g3 À(J)OLt4骖lrިi$E1 YTZq;svvF)V.,OBz}b4KgOv:]u3FrgfӱEQ.]ZCX*x8,MSYcif;TU&)y7L\E1b3gah90 r>u]4I Dj$خɉi\׵DUZzzz#I!<~283L4MUUr͕ueg}fۓ()j쬝 w ﯭX4M倥Sж(QIF#~տ;|׫+ey#6B7onaTVgϞ?NtҫW~sFan8ν{oJr|޽ZÇqӧQ:N8d2jq?|0F#:*!]54Mt:EQyj5ͻw6sۼslQa1&iq1Mh$I 8M|>oYZ: 0qIwE(G|;o.-Nu,R8LNO~ߞJe(R)"EA 0>?~!ӧC}[V\nee1~.O<^s VӴ/_LvB)!4fsiRF<;m"1[oat(eomv~߼x|}}ݶ~A8NEt:bX(0B!. D)ERgG\.Gjkkk7nw{F!l4ssMn[(XKKK(ڶEo߾UV\B)(#Yjk+ !lr*}4Ea!,> v:J`0,E) ssW^ٶ8x)eq).ʭե۷o'IŋŅf-A=/ǡ ж]7y>*z(>|_D0z-0?~/VUUDASdQyg( H;O-BF(Bf@'q uOEq(Qj&Hs}ϲ,&R) $cW $itEUeAN+M}~~T*ُ,޽Baꚾ},3/J`B0XaF$kkkU2h Be{zf޻wODQ!ިeXTFBA7p}I(0hFEAbmmnIR,s9CbeOL۶$Q DY|r4Q$ b?_q,Ir~vm۶yJq0 4[^[9:>{Z[ oͩʣG4]/ a*AgcB8{o{? mۖ$!xhPi[A,4sgϞlllzS(e>a~d2...d"aCq0A9_~?<4hIV{͓cׯ~ZmVeNWuYj!_ꓵUF!Oq$nk jqtt^.t{GǾI7}_|޼v/~pE \.ẎiUGB@i~!FQhfniYQI(`qiQu_~u l,{n쬓孂upxih4>:9 G''nZ}]x 'Q6?7? 4ÚƆ)7o60`j"Gq\*8dl6zn5 3Ip'\8x<. \c:ERU4,jT*}'/^8|>Dž@J%Mq>QǮڶùIۓAk;Ngg^I8L0 NN΂ ۣ0v'L&~3ãEQkjZ?SLgOc>u{Z B7'IW_y^ν_Uu%I<;;jm^?h8l4NgwIVij̽z^|:^i⢕ϟƓf%I)xLZsYٶpd??O>}zΝ_Ϸrahj%x{ݼysss͐CueQ}ǒض$=`g8oL$+i_`eZ9j?9٢8fq pV^O?A(.<!TdR Nk׌1N/Xs~=a8kEQ|Y?@p<üg%>/!lŷMH]ǒ8Gi*I#b)-WIcRJ8p4 y93[a>OeY^ww .pMј[\\tDQvApbL)8닋NǍ;n~jdIuYDQY$IXY<|1V,B ~8ZsJpUB!bk1%p8,q)r>6jh4<>Nb(ˢmے BMβLMҔlnnbOOO1P 6Uq$J"IFjA>77W*\%cX,/^uu]?;i_h48~?(J)<4[nAptp|tt4Gnll$ ~cB? H<㱕._0ZR/_.rxܬ N1޽{,n(VVStcʲ /_y/)QJ\YYt:ǏZ-]}׫jZ#N?s|eWw}0@g:{Z~~8+#Gӧ??u]^,˪,Z-o,Fi'Irڵ,cַK~_6O[9͛7ӧeY><<9_׽^! 2\.ɟɳgkZZhzj-~駊"iFy*`0\PA>oy (\vM~O"ꞟ_Z^t]7?wx')y۶DI^&f1Q@i_,뵺eYb n Ꚇ7cYQE) Ķ]OEMbYļUWi,[i,K4ƣQ Jx\(BQiw(H{ȶ0 b'IZzpx躮kfazou|.Qq0J)mԛ81̛۾2ܜyoqskk_N;ódbӌq⇡㺵jUQ0)(e䜪3 =I VWWdI&dƨ8(`~_.Eq7k׮ nݞ2a./z$ cYZ_.._^,k2S dY)J ܍7JO>(#Q,u]4 o7vw_ |>(,KszTs{~$)GgqG0 8梅B@|_eYE " \0jK|.g@?dbk!X]]5MS^E8Uܹsd}}=M`n)e~~0Y|Ͽ|RQE \䕴,+e9v0{G$Q\^DV+ϵJW^e<DZDZ0z㣣NóWsZ4jj (jGJEӴx{Ý'/^}~eVph|n5l6NHԶ4MؓDU4]ӿ+(ELl ϭg Aȟt qE)Cǣ\iV;8CY%G)-//Apzcw|E9>>>>>M>"$qBQ=t9J)?]̲ BceLe!Rn ~|` }#w^0BH̲ 2 +"7jv5BF)a]4MSUs1\ ("J&+,k_Z>v ?igG_u~w~_u]˲=σP @(2k[bӧH8 xMDe]dTuY,s!$@'YP~"n^ZZ١jɓ FҒeOhuVŘp\1 e,\@R^u]svƳ3.$I0ƊfY!{}ˮ?UUm6gsO߿? BF!iF(˲,SAII4i*, Rh4,͛7)x~~!3;RR˿ׯof9'dFfɇH|xr%0oNYo4''Gׯۮσ$I"$tz(5ﺗ'IBNu]7M#"BϞ=F:MS8]y^6uXk1q[ݮ"ch\ӄxG}xttwBq /{͛7_sa>ov{q`pie[[[ф!ey1_*ImX*7Bagg'j*YX F8סHAju2=3 s$OOBPČ$(J1)FL<1ƓE)lzP(\4TUdAgAiapAFH)^AP~ni~c)򋸩(2U|Q:;0'OO)!DziIfYeZ-m31NEQDh%588I!$R^MlP82Bjժ*+Ņ˗/kt\.ϷZ~FQш>Y׍DIu]ϲ dTE]7p%YJŲ,$AEԁ"덺:I5EE&3rn,h puZ*={v]YRNOʕ2/U0i% |^uHH}㭵(|i\Z\\|rZXU\. UE)Y/o4MܽIw޼s}EQoxLq\7ch h4 `ۓ d AͿI/($Rül{8nO>m{2G 4Mɓ+ M]׊E&veeemm;wfY6]MlwaGGǽ^RQ 0wvv-+˕Jvիhyի߸FQ?;;c,힜L&v_~j-?x@QTI*B+ ׯ_7MUUm6M0tNó;cMU,˒U ˲>}:>T* ה8+rQP+˹\NW*ժ R1ðVeE޽w9ׯq8_),Aprr Gq•Jnw㉦麮B)K"O~5MӶ'It:[o!"ҥKk$I*zi xN QVr93ϱ,;:>!TQTJI|>=.E)\y+h$醪(xlg(|?M8%IGX3Mh4y2/߽͛7GA9!/ yBH*q>FB~nwݞ (]~= óŅjʇK.IyNǚj,u@鵫W-џpyy !~Es{䱢93v}ϻqz$γPc`9޺!r>SO&R4n.5Jĭ9MÌXNNNlϬ893gƬBa0xS*$Ǿz}^5q0 Binj CtinA pg#.5)T~8NZ%38r*8"MQ FfmooSJ4\^^bFQ 0S (+XVP )NS, 縄fn^_sob_YFl- lj_!˝:8]UfS1o 簴+hxOy!iD(2a$Oe@Q]8$RyDEQ4Ma(d3%Ie!ZJi'%I"9I9S"<;w+s9o`JQ55IR\qQ{UUUE cኬ2fIMC@z%LQ eY8%1$ 8ɲ;PM=RUlr֋HdIUU]dYd!t.F 2oFHqe |PU$EdYfb(VE%qiF%IZw]W%Il{;;FR @Pi@BPJO 0VY7ŗ*po(oݺÇHӴxLh!#dVM&ׯGQt ]È9q9??|_Ea(d(Bmق 4 K&L@<ϛmmqբjz[xZ=~m۞EM&VO ZiB!NS~6;Cӌ$zPHNQ3 qT*1xX)!(?::V EBHv]ټ0Mq\.ɤ8}Rܾs˲|vv[4fQa5,I&a~_r40hz?==}(J կ...x+nh|xx>ٳgǰ/|t:~nnADf24B2Ke9aX9ܹAQ$ya3. rhyX4 #(k\0~rrbm; r3on6,ťE㯿ј5Me٥W^YT<8Z>z("M\=;;T>䓏}B0Nqzz2 KA=͛7.א27kGsssL}aþr:v\[L( I'IFN{4iNnV٬ggg |*%~0 ѣw+`ZOgjunn6j&۷m۹(tzz*"!DeqFLFuxZYYa?i8^8:].=zn}+ Ϟ=/ X\\\ZZں}Vm6[O>OG h۹\n4x"\.Aߏ\T}jzttɤWVVA( ]deiaqfffgggii |W76h4E-˚׫E> G>iw:O>9G?ϳE?QZm.ǖe[YYVQnKQ諯><<w,F^*mXNT(J$I;w>B؆f2>01u]wg*{n.7h d\ǝg9u]D!- \no |.-??~|vz$ɭ۷@':Eї/_>|wǣ8 +t]=/ܿ?xdmZjL޾US)wk}y=D"lGW],aScL40Ps37 h͠QV0e qru4GSLLAMShK G}BPO(@Hf  q(QY!IWs_?]w/LIB0" }BrLQ'qy6!(8VULqa)D!E3jFVTJI8BDSQ8bY84M*N$IXx^Y}ϥ(DBuÓL a(ǐ@Q!`E 8#(BCb+ HFwq|CZBb+܉ai8Vxh-aaŰiiY1 #]iz0 r^&RdIL)J\(XDCY룈L:-ϰTZ-EB * +OBDQ(*8ǮڦSvh }P(*)yqiQI) C"Ot}smi2qyA`faaa8IZIEQ$"a9 ͵$Qy?[Ea>ǂ uIx(d2nۦiƈ*eY5{q4taeOy!),A DD<'q &M,GnF1GP_7a4} ;o [7ldnB8NeXqq04! IDATaJ4 na 7ͮ08Dznarf&dX]մt: `( ',LDeEAi f ī0܂ӷ"˲~3Z( "c/u;kkkbnooml<9(2BaX-?3˲8}rwynUU4E3\nq0 ,Gkcs,Y]__Xu (J[ϟ?yF:QL4y{ۖEQQ̵ ò4juIY-A#{w }ݻaݲt:{?ŅJǘhMLؐe$r$)It:۶a$n7xɓ`$e[FVq7BcUFQ$\|{^$$IkAD>??,1v+ ÅNh4 t:8A5M֭[QE40U$ |ATUt:Ǻ󹍍 H,knnT*`gg(uYeEes( Bd $  BWO<;;pqe9 h< |UU p\E@=Pm{N$xh{B8cp0t]˲>A p8x{{{@J%e}߿wG}h4XEM&7oض-+29t]MX[YYq=g{{{qq1NA!h{{[0옝u0]I`YbNNNeY{{{_(^{n6}6s"JAFDb)^ZZVL&NϿzj4u:L:^X\m|ŋ绻%( B*ZXXfQ1/l\wvv/_.//;dVVV{_?ݥ(n4$^^^4mf&nnn:#IG}xhKDY__}e* '||BeYeA鴿曋wyZlɎッ$IrVZyNU5EQ޼y8.!ӳ3f"VaO߈%&~p`S=) q.q?s1ñ8De wai + "\$7c05ch. C& C($T $M4üaAQ!1Ԃ^ ث!},^)+ c$IXnbZm#DCIeP$'I2 !1UFDQc,Hƀ]!I4T EEEQ,i8za5.//sQŵZ hrEQIRnOH( XhYbV:lT*};wUU=o^pN-O82 QgYQV|?::yBҒ(N"0VV;y>ā(aR;gNgk4I$IҴ`0Pa34VmODe=ǭjhȲצ ޽kׯ!!4_ZZv/Af)%+YUA$44|T՚s0rUA84$ffggm>88z3ZNQdzeY^]]]ZZ$n ˲ zJQU-t'I/..>}atZ,nyYzP'O<{ūxduu(NOObf;JZ-յ5A=σoa(uKjh$W=iewwWf д(H  Je,:8znX8A_ uhVL6FA,y$jq(3ȮAh4hZA=.EQ}AGQTaeeY9|q)hZQ׬~k#U>c;7q tԹ eZ_ 'x(&}q|mzN!<$ \e"07b@]MVpF āqTBEQy?ky2'IAc8!$d!PB0-4|Nɾ绞E1s !33Y^02!G,ð>"STB~.`q 4@3\ ̋ QMӂ#Df\EQE!,Z^^ann\.oll*"p/s|gggG<E n>3f }w`0Y&Y^^NRZݶmH\^__e^I4M+b1Q ޲}EQ4Mqff&CgggjǕr9"]4fјZrΝ8!-:33dIh:ibѶ,skz\fhIJa'''o=jYQ8gff! C8l&5\.i?~j6R1NOOGA$%p&y ˜ضjNQU֭[zc pAVN>ӓ\.G)ggn/[*]y^LYEQ666Jd<߽w\.kYVQ?}UU˥ʝw7T*EH|ϳ, XZZe%ˉ$"2m r(yZz0-fA- 0\.~~$ 1P|T*{(n3 -,#c(6-Cy cLtFY\5{I@6qq8% ,x4MJhXݘ;U$\>GugA۱j0[[[_}yQDQA",AcNon4VVF/l6[L&ڀx1-wQLF4Mg2~mEh6q=?{C?>>8rp0躞fJ>v}ppūAQx Me7660_|B4M`r~jR)T\.W*htzz":::Z]]Fyqhiu49yϋfalmmfNi3mT*E5 (FcefO~`L/}޽;pw0,UUIӍFŋӖjj۶H?dYիRnl򉎏(шvzj[Q=fA%'ضe2QݻS5:y(X,FQ fa(۶/.Z(v:sqTU% .//u} Cc-0Tj}}ݶNCqH:3?qjYy'<rEewygaatZu}ooq}שlZ4M a1C(TU}~qqGEIZIzrUsssD啵ht]#tٿt]^9ua qMsssR((㱪zف[6%$ǏW*' {f8a<hteY۶Fv(!5P{ J0`4Vx0V9BY$I*Gl14(8qbtrz*Wgg VJhv`nnݼHjvl5z&HR;:8Ȥ~ DI=z477׼(xB!|>_,K֭[A;˟V7FN?~7`ZkfAӢ;G7z$_~tL$x+0ch ,|vdj#ua\i5@f$˲3h}l(0,uǹ$l0!`J8?*''Ϟ=jzqqqxt8N$aW ,/,,,(J {=AdY^^^o6L͡(ˢe%r^(s3%__}& IDATql6={at:prDGx~~hdYUT*M˪ q"5;; b&>{+B۝L{{{{{{?ӧOR6c}kJ}D hYIE8UBN;<qAR: 0>9>-;fρWm(٬&iBT [Lcd2t]gvyyy2ɲwzz(yiq8/,,DQ4rUR)P(JT.wwwE3Y;nEr\.g2Aãׯ_z`>4cajF~ɓ''''KKKjѣkڤRvmvl><| ($IT*h2A˲q 508 wyy0Z;Yg^gZec%ݮm9nwJrQuvl> WU5N)b bZ5бXx^eP2%07ܙ|nkQ1& PTBiZAQxNө0 y] K[۷1M DMOIhOBRuaaOOz~w麞i?䇟gvnRӧO{?ټuWq_\4iyUn&zn:^\Ys94UǺ( Lteq scѣLn4ʊ2,d5ma~X*W^xb}c( iŖeFZ*!Ԭ?{[*Að\.C8\c|Z!h9cYVlmm cW9*nR4KEmt:ǂ›(2e[eN\B_^^vkN:<<5/_`E1!/o~Xwyx֭w~3㗗(*i2h8 x^;<7s=)KЈEѰL(q 4M'tEQYb9NDIҚNsx2ir-тY>\,j5c=/IDBPׯ$Y4S,WE գ)qADL! AHQE/뫫(xL#* Cs<BaZmY!˲%>CӃh{{KBȧ~_2<}ͳgQ`0q|qqzivX.힜4$iJWIAQDׁC0EUU) t(#|߷,aL&|wĚy"㸉>կ~K~/r0ʒ 2w5Mm puuu2Z64pj6-?'4M-//C &a`$T*T*%I]~4MΝ;+++BAQ`0XRdI+ Ġ(j033srraǺ34)nZv{kk ۷otFiI4=wxxl6`8ZvE`пCX rj<AT9^N)ΪO4=jd2qGeI׎@f@o5 p]iT-+oYB~vS?r`D}V]]U]}'oHJ=`ݿc_<x 0<޼F%n}_u_y?yN;++##3{|NSV%Yj:s'JJsOR~%@v!.|GqwaYViɓӓS4 p'äX,V*rLIMөyƍju˗/^uͶA{h42MS/_cZ,{nѰ,{87)0?W,OZMrj1i6?fggeIn4A(I2poooooL&cerVzeѢ'OvvvVWW Øжmwreʕ+_AhN{xxiڋ/ øvaNhz7?E%IAG1!@QBHݞNTW%0$N$½OfYR(%ImuEQ4 jQl0 css0򛛛eݽ{ײFp8R UeY iVչy@RL&i.--k׮ZMaY5f&4O0r ÄQ,*D0I Lp=O h\UU8U8Nm~V,wlkYV" pA ({wƍ~D!%G}$Io~Y 0Mr p2,gYyYu\!4??Of#IHig-(fYFHƲl>_,+M #g Bڑ$'BD0f-AkI۟_~eTR*DYF54ñ(?N,˲v1Jc\V(뇇qq(I ^﫯n4JrvrF)򇇇baP>}_~\ZZZ\\<;;+hwo2r EQVt:KKK1^XX bq0iO&*ud4M4m:jeTBtnR$ɲB0777qYyYNE\T*5'''/^lc biFQ)DG=~qr'8ٵdoKl6iyBhbYփt:秧BqhDA$mr!Ob1vOOOu]RgfO>%y^;wP`08>9Z\svZq]믿ݽzjT:99qMSeHcbc)vE!IS^#dPomۦ11EFA(REaEqzdEV/EAq°,rB:t7'zܾ=1!=~R8mYt}?z+82QLs__Bzݎ$IBEQR_\\s=Ig 4MI,L&Q,0$IEe~8\Z(3Lټim;qٶ8d2-;@|ߟ/ mmlgR$(3m/B( BVL˴8e[[[7o e[i1!(ȊrY7$a,J75 9cYƲ,L$I<'iEaXR.J?MyT Sk39v:YVUugOF#۱ffRիWNqOx< f:??J%*i88{BVzaR_ey8S6iC$ow]s!\"C.#Zi=ߢ*[ry ?Ά"B !BH o_14$PRo A$qeTg"Φ[N$ 2 4eY6]׉,p4.80 @i Ȳlnn.IS۶(E qQP@ i"e88~c`X6b"Q zsM iEQL@P? (dJ0fY6C QoatMCDYB)!DȲ7cBHfٞ! @d4 H04Nb梥iFB?0Pyeeq0 Ay^K4"H$˒@O<'IRNXIrs)+MӜ yt:L:s< ꮕe@mۦЀ B3,P$˶(㘅HyLU8%D"0M'>iw`24$u(Jݻw*j$hٶ74{h*˲<Ȣ(˲((H|e>I@uM jۦi^YZu,Mi*/,O&۶ }x<\ZZVy=8qǔ#kSBe fjeii(Aᇏ8m֗sF\<9=,W(jwjd4-;vV[__ۥƪzi:PU۷oJv8ggi,'R\{>O>\zިߺ}#or9Qo\]=:<yEU8ll?zư"qDI}?<ϵ,n+b9QJ2!ZREA&Ues;;;k;NN&8-YAxhZB^)W+q.˲;ϷEAmκz8o?7/(e͵FQןL&{{8rFhlvVkumm2p8Uzu0b@999~`(Iu:A$_/Iro>+ ;۶8??}fiVs疞u:jt f05 Bi*/PkZ{{,a`NMꁀ1OL3 4M8+ B1Lɔe8Nl99;v0]FArT,Q0(7w[&7(n𽻽ko7.G t-I8]׿x$(79 T51xSDH\.GiMaL,dE!: m"biL <1DqeJt RuMd8圲tfj71B,r^JSLšyBs !?O_x1>| W_}eVE D|yYòlVm8Ɋb%z>$q8] #aEQZO,VTUzQulת<C@`zIi7f7p'8ggg`9E1z*piT*F#Xk_~-J½{_>_qxxHy/0YdR84$I\OSEQ:ueu}ffјQogff0޽E6շ}X>WV).54N-q(4KK73 ^| !TUuݍkm3 8r{lԪ~vM ,1RCQDh4FE,--N4mjsƍ# j{{i,SuttTkt:Ͳ>3Z7m' TU- aR$I"eAe`T*QZREL.M&qZEq0P,Kp0??L㸧O~B(Tj6WWWEۛJӴxlYVBt#wrr,b1 !j6,MS '9($I(e* urrF=~_R^Q4bY.,&I:|---i {iم65>E4-//'I/666666nEYkZEnWeM6!Y>:6pW,qbr0! *hQ(u]G3L8s>^} (7Bō(I\%)L&cs:0~TX]] hj0u͋>hEW**w/d2Q4uPɟp0`08k q B(jVk<Omu}vggq}#hr,K!/{JQnaajijd,PenN-Iޢ( #aP ;'F@CayP01R@BaХ?@,'` |Ic MʌFB4*B )碓$52q\ۍf7ATɔ@H?@')Nh,*rEa@5&)$!B{,9-KSeUU8B(#3A^)Hr8a$MB I1,c j<3˲ I"ˡ$Ml۝L&A\׋(c"DQTSˆ L0, &p S7$ $a%E _ũeR4MI8Eg9^8 |qj}󼪪y $"HrAU~R>>= ( #@$$In߾-{J.AMJ3<~߲zpȳ|7t#GoHB QRGRѲ#@FQE)ʼnan߹k@Lja7@A%IBa*)k8a9|ߣ"L$4LR=͒,몦Q"8$0˲R/Jea( YiǢ fBfq\R8csƑ HprlPݓӯ~&Y?g**Yf}c=Å۷orenv]|?PV_rP?|uu?}NRh:e9C$QռG ({zl?ZmNM˲ {>_8<}Td=14/׻O>s8ˈiZaU*Ǔ_SAu]NPΞF\8>>40 \ݮTJJEUǏw4Mx<$'?' $IIYm\.F/^߾~zgyQꚮN{rrF%QV˲0vTU'I08<<84~K0{O|:IȲ2'q7];fsaaƍ==<=w: UUS"7+Kj\*A$ZussqlDZ4Y^fڶc1eY׏8q=4M:NŹ\{ ܿeY;;;p:5\Y><<* ,=AUUePeQTa/-.^z qAx$FFYQ\K4I"QO[7'c;iZR34x9ts-NO$in~A SsbZkAptzJugg{iq1 ,K8B0@? Β4UU\(/Km)qFAEydN퍧Sİ'm]wލ}o%.S;0o~?|'Nw qVXaHJi8UsH#D~!0#-躎1$AԺ/@yBc8sqobhd/P2 pRA 8N>!TTuMU.FQa,0 GabB ܛ$IhGYI$(pp'd:,U! J,$$bE1m0 BIL!mȤiB8ʂj0mcrq`@Ѕ7N C80I(} f "P" UU(#IR4M_GPI4MEE `an_JUtN8 hFBݻ>=vSۢ&ϋi S q< Id:14 7^GI!d<$%!c0S]EE[&rFZ=??x!ƀZB4Mf+z>Т0YF$J$Bpv_~h:4h( <8>8BlAU(W —_~iDz"ZJbX(뛿կͦggn޵kQo**2nP(?!I)wMC۶bEQdEOyVKd2)Ϟ={b[$ɨj8_|իWϩ܄Q^レL&r<4MyX,k5dYͧ$IV??== cIXZZv;,Fh4_k m_#$ Ȳ,D' !T*ٶq~~iAl_x1ffb+%(JRAEeزU_|[փ$*ө5333;;ŋCP9Q;ɉ$I+$-O&plYV.q"Fy5Bt:FQSq\N$E@GQDn(?p76G~l6YOy8 pLsRT~, dVdYP HFb!tttiZX__x)!35 [ nݺE߿Cβ,cUU5Mw=ӧ[ԽcK%IY\\dY4??jjGGȶ @H$j(Z,#Ϟ=8uj5ᎎ<ϫTJ7nݣuqXX^^x뺮zP$maҠѣGq`8W_}+WLӬV˝NnR=o-vVuvv&I)\.68^]]E޽{'OxeUSa9= Cխt5diz%ǝ|ƪ#WѣG*qN'I69GgggZ.'(/--u[naEi' sF#ˉ_O8y^߸q#MSqiCa(}m}\.˲HU=1!YYje+++j>#^vիWvB&1q^ 4q3!+\ӧ^9@Pl.,,%IBqԶ8gYJ!, Y!T.B,M8Jߏ8B iP;t7, ,EbPlw ƘBE^A()aBh n2/˲|  i68"u c8M4Ä/Ƃ a4M+ gg Y*_q<`c jͶnܼ~-p ÎFp9ёeK B233S.W (j3'VӲLsbʒvRÏ^xNeu]o6j13l<3< v$ y5ǶmgYvpp4b^xy|x!b`\fV, BYznZ ׮_+ dYv{xh65+KKg,KRZ`:5+at]>99 իWϞ=mױxZ*_x?q>oY$βt:kIgfŢiNF`Ȳ44GiNIvZQd%_ȷZyDZL00ɚesY%Iy^$&qvgjbX.y#7˕j:c[QQ|5$I.K,}BzFcӴJ fGn޼>t]EO~,Kt2ax6-^ icRl|u;}{:ðAN1@0.H9 f3! ,: d\QG7 ereYe IKSpA? c 0" D%p(JiBH;j =oM8 ".DRLu`c0 h/JY!e)Lcb -&!-ES$I$F]@H&I,--9c6u"B,#LӤ  1<&IB!a&qDA8==,S\;Г(jXT0 EQ,k6go߾(2&Q8(򃀪(< a*MS]>xsq|>Ƙ6(8޲LqXr !Wsg2Fx<;4Ms:Zsa}!Niy89=>w,Ǚi(孭m{nwl<ϵ (я~9qƉד$y)6z=UU*tb4{a;99X]׵mgvvֶ g,FqReF#XS?@c/_"%]nmm}O<9o0<>Ibjެ(0o033c5߁1 8vQUuss0r(I( |q0 G͛7766,mq ]7 a )vSMӮ\bYؕJ;A,‚((z㍍oFʕxLm㸴/q$r Dzp8nnnt~e˲ èV+FWVGw=;;T*zѣG| CUU,SUmssRn۝ϟSHXZ\XUťE*#뺮fggo޼ZT 03|ZI/-.1 eyh2IvWeACݛ}7rwƾ" I}ˍhIeJR3=vW?9==>ˮRJJ+L2@y$RIry=:U0yBVWWoݺidQ,HS5Q$RUu2Ȳ<33Eyq^_QN#IR\Avj6ny^UU}1LUUlys|||vv&IRo7,B#`0r5[r_JJgd,Q>YeI}'OF({b}G*(r?L\i/ϗeUE1M Yiz~ I|f{q8a9q0ܬVkv۶xY߻NpF$[RuqFq{^w{{;\.G3"1F^X(wiq^Τ&w8ZŋV$IIQVk 7?o]׋x}^4-U{5'_gҟz*waIB² !KcGB ð ˰'"s0 !d BpTV0 LN:BOiq|]#.qD%E\N+MMɥ4o L. 2D CY @J.#@"1F<ϳ!8_(P:seq˜@y.@piR$IĔJlϱ$r,K!61!^\9ÀiI\}? #974XINmHe2yX:gX/oB<ω@iG4$qJȥ+ϳO=74 @UUI-X7 40`@& bB2M4}q˗c9,kfj& e#mۮjqCv1@'$4zlƢ3,CNËvs<8ۖeZv <_\\@Ȟ40rÃϟw.?xjN8$K7nPnm8i[ְrݮijUxu=ϣ wc$ꫯ(c`0y KVadIm]\BO~yǽ~nn. Ü9Nﴻ'I'2PU{w$Z;חA Κ-9b9LVtsc#_(0 yn,{w0_tmXak4t{x~(Ǐ{8 ,q&qxq ȃ7HӔzVߝ~'ow߯OzO& 75+:>ʁݍ9@ DUL Ա>c81dz@ (1_W 4tLݴ1#!aR'5N* &ɣUj]hMLi @R̲ !@ uLj`Dq4xzz#DtEwG>U2w 8N$*pE!# 6#Q}+K0,Ç"ѮtjUy @=A$YE M2Cza"vY*f>O *4m~NMXeXv8:۷\Ʈq*C]E^PUUu8$%QUx*8G`28'۶Vrħ]Q0E!MӜa/~_J *jj!UUM0UUu'nEBBH8R2i:55%p0 !eY/=Sdʲ,iʰ,p8RI8L~#\)i(*:?/~/y~H$8'''SS%Pp. < fIv8Pl6u]7$I(1i޼yӶFA2qL3i<88<2{cc-p(JPE)IãOl68^ZZDW\.>33Mo0nlXx nݒeIUU!@in\.߻w\._\\LOO///om`Yvn^V\NT*-,,HT(,;??ma8u9 ˋ˅B,2ð\.rLfB^E ,˚}s@ RdUӴw81Ms}c]uw]LrܶEu?Y,;??rܸqs4Q(DA8NJW_eYj9eY+++vB@X}3p4rmvyNQqh}Ѯ>2o߾j5o?)'Q04UQhtv~ZT5M[_uEZ(IrTje*WnݺvyZJu]4pg>Ocp,UP(mz0rN'ː㸵۷dUŋgggV*7oΖzť;ϣ(VVVg鳚*g՚:p!GDQ$G<ܼqemۑ$q\!ӓi˰=.gx^XXX#`@O6NN9S4u0(,bEzݲrϟ??;?ϲL44M 0+GQd4M7nlq >??.,IREQ ?Ǐ[}T,,..aMm'c]E-~p@"0Amh`776,ݻ7ۿ/q!Q\*>ǛY ^hol?y[o}]0c_U8I 7674v4պiSOtŎR 68JR/(Js{Q(q=۾\uuUz[7>-~zȕw] 7RO}L׌|0ZeYc$i`AXBYApq,8MSZ+ә4d#zʘ"h!,!B0}??0J,I,CF/DO'` &XeY$t2$B>yBCQ$B#"I!!r,e)2aX@0LIA@XUT2 \z50 C 5U0Ce!/yXNQ54}#i~ppJQy^Z$Iwi,˔`}~I!,x|% IDAT( "nK!Hq5d2=OQ 8}zOx$#ev=Vk e[7o\%j*y4hy3_U!)mxfyyzAzІB$IbhFy1GtMt$ ("\iVBtU裏s՗/ zryu}c!X4e äiFIo Q|vl4iJ~$$a@Ckoe$8Ͳ$Mr'bI8}p!!@ x^(8ZqϲUfxad;} h8"r9sqqj]fIl5[#fXXNWŗ/_rJ҄ swrܘϙ8`aZn2\י}86 M۷mt:0 s-`0@U*(:n<`ێ=v<'IeYE=!;;> ]U|>ϳlUdMݗ.L{^eVԾ֝jzƭrT4 qumnjoF^Z6333666Bkkwy뺮uhhM$ Ӑ%QxdI" ' BNm+2E1L(Z3rk5Eu]9]x.^8(QzN嗿k6X=;;˲a70 Xfbaa`}qAqرyH0Ӥu:]ם`8:>nzmIQUkh4nVW9&p4 Ps88jk++IP(4"NϔqR)KLB`ێn(.I/jv{=A>W_5U]m!4Gh4{2x kk++TP1i;aضi9JLFc5^4NA<||Bc~yp\.WmKDQn\*k_P_DA}Bpr|j?sx|?oi.8t-j}ߗi<Cr@io Ie (%2秦J~hZm2LMMc{<>::ydǟ fl{ !_W~ojjڔg?#=E5;[pyjRǶӥ%a~EQ0}cZ\E,˲fgʒ,AiОiTHJo!0x<F(<{ldtT&H']\\xG'tp$y(~zl| |^8a< Gaxqq8Vj2 cYCƲl5 C'O>o4 vn$m{8v:Ƕ8. p,Kj5˲孭~||<NSU8 lF۶ ]4M2==Il6NO9;~~~nFp033sw}??|=aܾ}ޣXat i<89 r94m;=VUP(P{!˸;[.;cv$d>˲z䖖7ٳg4*j5YV tز$e,K$1FʖTHՕR---EaQEQqGA$(IQA$r$mo~sF~ϟGQ$q\Kvc&{^&iP$Kr\D@?:>`ueޒu*B7Yv:GG===,'@@A!X*KnáEssuES_z8,Ce |~;;;aR|4h !} uj诣w󿕐qW" g;C#1 + B)0I0 FeIp,K% b@EQRz B)u"$q!08I(=:` @z>yauk)F@ji2e$&ȲJjI(ۇ@0 <4C#)"W  4M}rHeW@%I54 :—eH~2e4IӌP4˙9MӲ,4Ux@`dIP䣏 I3QUU 0ĥm<0Mc0O&BHdS Ys +˲iaϝ,--ݽ{F޼QA B308⳦i +heʬi4gg+#M#cEVaV dY$_۶Vjf>/UrW^U*76{t:pd/''IaYM~bnܸ1&i50SZ|y>{aFijG'( ajMs¢(rZGN:nu(*:NEG$qv,jשk4,kH%h4|uv[)ollJq.CȬTUh<β̶,VWggaavvvZ$M4]jFyisn߼svZy睇b`2,&T*--̣,d0rв򴦩,Yh6&dii ea5M?iDQtz0¡5yիWiaa,..NM$ɓ'rx,4i.$78(ZYe4NƢ(j*WGglooi/_ @x|ttdƽ{4M999v qon ::BtXeY^àݺu'McYO|ͻ[[a7 `\.r,+>|4l60|>|xme<7hLShPyA?m; CBH!_ #]S337oޘ-$IN/<<|!96 ~4ĝxM$y޶meg] 88 if;6}7Wۿ===LFr߶~~Kx2;( 1SSS9#G4!*aLf篺sv۶5QD*3 e 0mk8Y?z뭷(Dass4Ir\@!vm?~~ttL!V/,#JD LGǖ5TaKs}~﹪.ќ\ \v'!d:Oӄw1,Q)u˲T{05CucvŹhJac !T /%Ti+eKNBXQ1tEQ,4M $QeUL&ԘV۔xCI'^4M4eY !3K7aehiBTGB1 d%.rE݇/%& Wm[**e ,xjJLF}x.gYFi*˒iEq{YFLTʛfǝvA@48cp8 ðRPw:eQj !d`ʹ$Dq }>,GQP}^q5z$IIfYLDU5BWu |lۦiQ$f qtPDO)TTL<! T@!Kq!n3qd(~ByqififYky^{F.!D%!òtPMO @(AQUU4@\ם. '|2 =zTTs|8nZ^ZZ]]vy.z}xA}G?f\UU:ٵKx4B4Mm4͵ϟw0, mI h<EQ4>DAr'#\uTlF:Q2M3ZDZEw2TuaaA8wvvNOOmۦC~珎g>} ,ի۶S(I7n躞) DQizT2 ݻ06o I EEQF>;;˲44VfV8E)j{ٕ۷EQP0ngg[J<ϲ eY=oaaAﯭ HWq=<$Ie^8~wie:ZVffVכ1 C]LӔd 8x^y~BÈ(r?e_qR}XK]O)JP%ϲTGs(t{{T \$;h\Tk($I| u^]/y}Q0s]7 Ck4O|?xurl:::J,BS"cAQP :Oj(I `QwT, Wտ'k:Z:qFy}%U_w`^\! >BeBd!Y ιA:!t7 rYW^(h d 9@@e4MEAPd58N1F*@0F ϱr 2iqJGAi5McX2j2ZZ !EI0M2e)&Dj4L<4K <!05siLikzSDƠ4\\Fq 0dQij,XaESSAfY!XXXs62DQ?jrh0?5]pBu0, 2(Yְ<|j< ƘN$1n6[wV YAԸu4Mx^`Bd$ pf(O8ƑI4Bݸ0dE12cmۦ/H.cfY)sU,+8n.mnnGy#W, Gqӳ?3Y={&d24Ҵfgssdr{몾qCV&]XXZ\\x7M38x<:99&Rqh4ZYY/T*)ݻ?Ν ޽U,JwFi\fRhYngٙZ 0Pdapxm4ff !kYϲF..rRYXX`YP(LM޽Ȳ\Z]ZZ :Nf4B??<<< sڏ?0F8R$Ȇacm۹hL øuBĻaeY!gO>L&zJrrvvK"7MDZGQGq0J,+R.gi: v{HQe5OL4L7n7;[Ř`M5LulAV֪2myyYseYCdYO B@$$KQ0^0el]ϟ0p u K{/_ j2={fFF.j0aɂP- ~RIU$ X S4,ð Jf_umG.c|B*Rʿ'|]g=7vZ )쵝  %(S(r9+( 5Kxi18v ;EQ_Ngێ`E2PM $)e e̲i!HΆBeE"2 1,˔ʰlB< pyѯM^]4 :W g08ei4ܛ5bSP)nbB8U2 ò~ay !"T*q(BnKPBH֓$bY6ϳ,y(J*jyf&gz!Iӧ_It-*H,۶A*Jq #kѣvC EQp8~i.lnn4Ma>쳕C=77G 'Q,AT*X$Y$IEh44bMc$;^w֑YwuU_Un^Mrcd 砼سc؏`m`2!,` %Cb9 iNy]UuYy?FZ>dv拈q.uPU޽{,,,LNN1Y^^> Y,!dffÇ ooX[oMLɲOh4Z_h4h8kUo6BT*AX>ޡ#˦wK/~ ^J7۷oAA v3,vMm4z `mۈc $¹\0\&w3y= 8Jrynz8 :òRt:GGG %B! iI#('!./XLeR<ܜ8''' T*ULR(*@, =!dh!L$F?%닠b $<8×;c7ơ<3Qy@ ^`BDn( ǀaðyG}v po J("QG$5UDq 95 È4Ee 80q\q0H ǀ@ ˱˲ !dzо0 b!"aL. ƘU3y.EQH'} ht,2%EabL8a)LP8a(IsʂL$xG)iYcBe(=߇ bY0,!q@Deho}k}}c,:8lrRz1kNsQ1 A$p<2z~A #rt:ϵZ-*A 0 ^t{V;jF# iL$ pX(rT9UUgEeDz (v|UMK$u 84Me,2>1q' HlvrR;9A,;2Mkd)Ju s"˚ "&8W2a,fl6뭮jjZ4j0e7s<2QY-)"433v2$yK+|wޭTR)۝h2̠?99X^@SUNMC= TJ?w32|>?33+AP`Ywnnn"0FF"^ٹrJ՚Fv'+I"護߮N~.1~LMU\3Mk0heeonnpw˶m]:;W%IF,{nnիA  E{~E^vg(z420&zcqݾJlmm#8ڶU*li4=zhggguun:%"???Q"/n7MdT˟ɧq\.O|N$[uڵ}@ǺnZ-@2ONNjQW5:KŢRdN!D1J>rTW?1H$jiJijGaardR%c2!c41+ ]7M(K/GGR1Z㹑LJR\.iJ p04|?Ћ+Iڋ\_R8>O_>?_5> O7T*d4Ƙ}#!`@>"` _ u8mPq}IeYfEt[QDa}axO&lw"Bd=I!!8ܝ Ts(je=a$IR3s5aLja4MK&)Y"HAE&ts,B> BH0`Ȳ,˜)8Yev(@ db86CIU\T$zU:G@!xKRΝ;]LJxG(=ڄEQ(|^9 E؟c %ql:JX ayXǒ(.//WUAм ˲(JvB4 Axq?[E p*Eh%(+R81,DLlۦ\802IaFqL(cX%EQlǹ}Ԕ(jRߓ_(vvvC!D|8 a$INMMiF-BVVVyje\ضQ:yW!'''aQ0 $ "vl1MaeeY@m`(/2Ŕj1^ZZuB !e%%+K\.'6T{UUE J%a nmmٶmYZ__Zc= @͇0LMǣ(~a//DQOnT` dY6 ju!19u\>Xu{;ju?ݾ}{|$"qӃ0(e0!`dF^LT*%p<0ޙ)=>,XO)˲v&˲@JyGsd>DƂ y͛eb-ˊYˁap=ӀL9ʣ mG`c8lz~ ]8ciXe Dzcϱ$&Iu9(ͪ>" N;0GCiK!@)}a,8>塂CAPdC v8ʕ33ׯ]_a\.+ A0V9eYǦ?(Jd2y.B,ˮм8ˌa1I8U555Uv;V8? A!":5;4B!u0]$ ! iMQ"ºnPz=R۶CSY& DBnB3,+ B"1$KrB!Ԇv}&?T\(!)aFy ZȊria=ӽnh SL>H2t<B8cBٺ ,ONNC#W^}nBn~lf}cC7`0ד%)>c?0OHtҥ[O>FFaڢ4j5ׯ7f5==cjÏ>hsѭ[O4̓Eiy25xgK!a!DQEuI套ͧ&mREI `i!pzz@D4Eg+r&u-!ϱ(]t\T8nVlۅ\cD2 ZVFe4n_=<: Z%T*?}W m=rLӂFQ$ITU}o@(r(rLKl>Zzaaac q;;Z}uu\.c,Kv+ƸX8iU%8nm IDATA2ͽW^ p,!!$\6K%A`vJU] (W.-. !2,'BMS{n[7ojp,m5[P{b\eҚfFZ-ziڎ+JJV֪'nr2] DABy ??_KF/(Sc9<0\/Lןa~)pAg81ƼQ(4z͒7MIjtE.oov:muEQv]' h 5UKRj!9eYYYȰ,}pGFNmQ3 B"5cA@ CKA0 Z$}pPQ(q1 q(i:D~#g>-,%",8  RdRItjzt:j^yq 1-+ Rg-# PzԉD"Q1EQ<+ARzo۶:nRU>="zaD C+MW C4\v ьH$oo?5ٶ1}_K(]10 %!BWێ,^GQD@ j{Fia`Ye XDOyT;v0+t1}c}饗uAHӴ\.w||,IDy^P0MKeB8W\;<|p42._冺ot:0 5--HDq˴nwu /@8>eN_l.//ϲl/;sR=W_r`{^"P5T2d {[[ZMUՕWۭ^1d2x8.w7 }4u:RDY=i"mYV,2 vǡﻶ3 dE. ?D۶a=nR?)!4zV*ҚiѨV!}0|wtSˊ`^F|~}b`zXOa3J~1^2}ps.L|~rq2!E)BBx{h#{ ?11A 9de(O\zYi-("Ş?l8OhpSS?{% >=rrxу/jˮ9g]I[?^kӋ\\ x_/e ~m9X'O~)w.N΃ǮOS|A>}G&> p L%xg۶LPpxtTE]Y-pprrbF\ ԏ)BZt[֛?~K/QmTZ__ދƍ\.;Rȍe|ߧW|Y t[oEe1sݫ7[hO^nr9ϋǵZR437w޽陙Ƀmg2O~~pg=>>_YYt:P(@p8Lӗ.-lllP~OSnl6kԷwSt P:á$I{{;˲if}X/ lvllb8{>t]wff͛ ;;;~̐eSh kF:d)L~.--{M jQollLL>>461Mc,ONNAl6 LL.//oٟnwbb41N?ԃŶm$Itah40_2>R}mSoj ٫_u9y^3<.TtثX\0$]zHZDB8EX qC9!Atq铝NN~qSqt:"J$ Jr85)áaY 88}?,BQ BTi,z=LD^ e@qizpJRY!3lE^BDQL&$ٶ !4gMb!W*(X*˲ǡ϶z|=__A05F ku:q #ϭ}\VνS2nq7>|1^\\:<3ᇂ 3=kIZdra C4- 0UU0U㸽=e..]>,+7oެT*^|33sra㌏}qҟ7;;"iZѬT*Nvd.;::t:drzzֶBj7iaZQ=cY6NB3 svd2DT BqT.]F\[[EAׇW^][[O<935ytt BSQU^~j:dw&T*0?06V1&$$T*5??OH|l./ܻwﭷJӲ,*˲vcaoݺ {DZ nĸ˲ƍw}饗R*TTFZF~r<>y\;!,--mooFVIJlJŢnZv:-奥ZqܥK qLnݺ: LrppL& $ p8 ")Iͣ!cccœ !@1~e|DgsE)ǯr27B ?I_|?6ϳ83ߺ[l\(TPH,l֪ϿxIyB! ]ZZb<˲x]4 qcujj?KYd( (CP99Nx饗Fmǖemnn&+W5-ܴ,owwbb,CV>xaau8q*JSeРŽ^lvYcK NoDx'2}F(T.'"g2GDA"}4tXmcO\g~.{㍟j aafiJy^XA$ ,rWUQ+`7m^z^6!ԛd2E|!dk<ϯ_(kUI6.J}GOr֭D*3O/IQ??p<rr m<ܤX3 GB 4Ot+$Ydy!8Qd2NC2Ax<^< g[Y2dDv?<9_O0~Vt3Yq[&t"aY}Vb3eYY(T奥0 SixN:/,KX~OMR(  (1AД Xx  HȲ뺎*"bEBRZ4M/.Pc1B,D8:']"*}CS_e) BHQ,f2شE331čAh6!!S;;hIg''"baw89X,^w߿_+JUɻ; <_e4MIRqm{{{ jQjD>oZ/󎎎.]vY١AU*:Vބ꺡t:].777*cVL&/]Z~Uk֓d.YZZ{LeY\ PĉQB//MOO7C *Jf xbb\QVwiZO>TZM|>~3B xr]OjzQ*\3Mģ}I4Mk6ۂ7oxɧw7j,˶Z-]׳Z:?xH$Dr^|>ϰ0 }hDQD xfgAhZd1L&EQ2QJ 4Ms\^N,S mY$s}PY8/&q\/2,?_>n_i]Я,TS' tN8QBDG EAĘ0bX?p&HXb<Qy^@y@IJ*Q D1FA 8D@Xg lۍcI I L 7337oLNWVVVV#dYd2j___vcY3<òB -4l6: ƤRZmԯ_RݦH0lIIȊ cP3}h# cREAjZ0p=1aX L&ficÁ ,bZjڝfM 7&Sxi\)0pmnolWOw:Ʉ!~p؟L(3hhbL0&F8U*~I':8V5])Q ðs9aF57?`ށ: 磑^*{ `BɤI5sl.N&LPȻdG#3aǵ,۶PO&S9v V fkdZQ C5N@HU!m%Pd9ǣOO%Ǘ\|g;`ge䌅|U|'jigge@HmYx"R(c&YEu]״ \׎c@b 8q OM10|!APϳRIZF5w#؎G>?I 6@0(ȞaH ,ӌHE<,T9ϳ@n/(&.fLn; !LOM2_t_˜Uտ@{w^[Z^ҙZ}l޻?wGƒad*qeG_mʫph֯%0i+YA s]; `^>/Jq'%)QYy/_n6Jڵkccc}G#hA뺦evww]~gjKB''p3== BhvvV䫯J%&'+=s(EuVX8<<k46%|d2Jѣ|>~a`0YjZƍsP(\|ʕ+^wQUl68558Vӑ$ÇwnVs]hAf>,u}qE+Aa(b,V*׿]q04-%I-^Z^\XXjݻo&B( IDATϏzVK0!U[]]%=e`uuummla ( ƣ("8tJ pdKKKD4*-aK"U0L,R Ѝ'V9K% jUr]^;88*VKJR1GՓ;w(J ,Brt:-IR٪_וɉph[[[[[f1m0rł eyä9`b6%wbgge_{8+ #]Po=qjp8v$Q{:a@%"ļ;BA~Y!I\<),|떎85s"NU-?_u' 9;WB'6 >-g!(Z 0*8G,Px=T*y^F\C9!(^A@e?(ȇ ($Jٶ+Q§ DbH7=ʆ E1f:1 =ׁ( Y xg23L2IB]׍1,{_8a8XeYàJK?G0xIo#*|yԶWM_~!/`;0 7d_'~4Gs՗L,^Z=_?yS/8۽iYs,sF|A,c,IjMt9PEV"Oj5y+׮qTc9B !b@0}?08-v< nE<]vMVAhW\Iw EɶmEBmGңol<J}X~7|rpH$>|xjy۶S0SSS67q`SA:jb>\./"0%)ӺV(vvvBמx QVhlnnn8_E8B:h]'hƲ`S=N˲< I9-rr|pp~٤^Te#z,'TUk `ҥrݥzu|>nA.Kj2 }UU;ε+|~(L]}E7G# ξknmmz^L&_xᅟ纮EQR({{{qv) iMOO#{^٠"|eY֭OLNNr"?yPT nY#/]t~j^xᅿ|z="Z,ʩT*J<ѣJejg|amv{A G9f-" ]\\<>>v]P(j\.o|ݻw>?55uʕW k׮<#J;T666jZE>f"?YOՙDQzojFl1~ p.Qybx>p/'|ւ|O,cbD:~1L:BX;gie8ZYJ*d2>P(A.at*Nu),bLZ G1,ˆa`G>eVc=v°f'xbӦMjGJA:_UQ_җz!Q_y'~SN{TjEq{Np8d, "DR4kjX.ʊi?r!<{\02|T*uoKKl#(33G_;Wk1A/@H04me9i7 "`2lVGPߖ--gG0h4~0L_LFQCCCR 4nYeUUws5x:.' '1uaYcX^rx|~~^׌xt||< 8p`lbyrrڥ8m=6n<::k!lr)7nv:ܾ}0BhppGPnO?TUT*͛z7\R___t̙yәH-kl .5B!Ñd:PŜvB8v\cccXLUr261fn===EM{-bbJ-9It]W`0XT6lꫯvvv#f{9t]VB) J`(3336 ce!la2:w TB#A0 j$!gy0o~&^ǿʚD r٣l48`N(d0 $+Ie1&RLDbk+<ʹvZR4]زcR !c̭!(Xв, XXe%K'b+W*pB`+($yd0l6Y9,n^JiZϵN<?1V4L0c $dai!ݷЃ>0bX };thf!!HY <8V2R.Bz^x~ԉcW _;dtذ R DA!D<3`kJF) 2F( m~ߎsj{O 8J۳cǙ!]UU>u޽{;qr, o۶mff0zr K/300ϗe2;;}7t@VEߛf%I:}4ڴiSTJvz87Oc1~X%'˳]]]ӳoqlƍ㎎ӧOL-/Dx<4رR:==ɓK69\pa~~EUuUUc>@,ۼqkNg࣏>jBV%}n׫+169=U*fdvitVDQ2x<D)Iv)gϞ5B`enk֭3ssĂ 5-@ 0;;k$trio5M3 JKIJ[v|t:;??_T;<<޽knnT*pJӧO/-&?9899Kfgg dkfzj4M] !͛7WO$;:d&c,i,KRHMÁ`Z5 ,"˲ GCCCTjlw˞L&C0- xr2`Px\TR ø\YIζ`a?vk145]jw~wҩe,Eg~~1Ңf#h(Ǔ+bQZp˚ ,vdZjZ'FATPWVØXJ eU`k.O;-Jǯj h2{ 翁"q͕_.$\v/gn̲, "Y00(*Jª k釡%Y0 P*˲1 #c!|cN ĢbO0V- ˲iYLA( b]tܴ .@>`MgYBBn[Qr*@$. ꎅ1,q;x[;tle.Ɵ߻BH WI5qd8o~M -ݝ0*%eˎo=:vI 7VCC0GNI!rku,?<}/D{ߪj_}~;eݮ??0M~7Y6:pŋc$Zwx=?p?O&&&Jҽ #{WU|;wV-%*3) 䉬>0p8:2%TUUUUsnmr9^hV.w(\>[x`#(0(c"8XW=]smoeOz*@e KJ'[P){ۻKE h@zvP kzI"(P '"80L)&P)%bB12\o4F؅1šlw|ߟ5M;~8EP~`tvkyxj,vмJͶ}k 6Ilzvl6xBpĉg϶6s=A1335551 B$,--1v{8lmmFHE|qQ4M{g!He||rI499 Ax{zz"H{{ǎ4mff3@>};9r1Br)}c(X,lٲeffwuvl6X"xgu]okk㙞GGG{z8O|>d2^RpjP( kEI0`0 _<\*r|X44]h$(;E9zR $l6Yfff#~WW(J p.jDo{"2 P0%@@$B!x.d@ I(%3L`L3<ńn$Ijpo~A*XXQ[P@0.T`cLDUV1a $ bhnw8nˎZ[AIA04]"R*U0 " !&RJ hf"BgGiZ<ݯU<"2iKT+0a3B ڥoۃfeCYbRonU+=@S|>|ѦPPz7K7da_kQ4mnn-v5h5mv/ +ej;{( ChR_;l0콫~~޽{߾}_y an^t A8/^loo߲eHK'l 0lܶP֭pA1V'Ɗ2cA5c凇KKz}~a!X]gfRbqoڸyKV͎ʸo 0MCT.bN Jز[!dѢ]-2 lpz#B@T-K>_p8D&.]h$-($H$BID a-Ly@O AeiBћMBsy]W*BaƜnwT*W/R4immK&O۷o~~qee3\xQp$p8n#G$[;;;_]VquL|cK(ھ-}}}~O~jvil\UT*vGF.^>\>_ܻ1ʥG~Ύl:}ɶd2953f^e1뎿yz~}w\t:L&BNq\0bREIK k׮X,vR8==e8|O~JiSSSR4$rn\`0866V.u -.96OŒBPTn,&t>ymݶ}7|X.9wQJqN8JfU]7,jSkY\1nki."8{u_ò~%׻_5-W={ޱgU 3W3wglSn3f$ duƘ6- 'Dey XUU eQ&5Ţ.!Qs2+Af<üi"Z2cs6|o 0 &JnQU!DVSx@{[\C 7mԯL?jHqT :%PF.>oC֖JD 4k"Q_?UXt_/^bLΏ {}ϩTQ5TZMmsQQ p D\헺 e$侼U[n *]kLHKk+=]vY|婧:u._eB w:DlQ}-1-1f].g[[ۏd2r7y}(\~IY"677Bax\h ; IDAT.^WjP,rRf 0 7W!~5^t㉒$I&dNwhqvAAaka~Wz:;;]./plњ !YDD@(3 sTis*܊ (eBD (ɷߴ#{wΗKBQ\.W.58qD8r9JSJMLLiI.\@Hljj*˚jUQd2@ٹcn\.z===;wnmmmDB<9@$=6Ml퉥$%,1j'555 P*a=== ۷/.. j'~gnhf'\UUXkke~Æ ۶og6?qDggn|`p|||jf3u\.ٳg```###s.!c\.CܹJ^_VyR)x\d2hSRقk,NC,RDQlnn&{2zks4VS9z>I%Iz|>{CEn7LSմZj^9P7iV|@5m41 8x˿Jr2޸JaJ6. R#dY0TKsk0FEBDUG]y `FW4M(nѴz258c@ѵޚfaf(&H@[,rBDdH1a,Bm6V5fc !2l6ɲ'䪅#v]i4G}nO._sӀ?/O{DNl9懀̀  ܛ([Ʃ]L[A,ɲo߭o @(^' lͭeΏJ ƺ.[hlNqlZr& ]M&[ArMD_޹"RGGGSSRj>P[4$Y=W#JFO}S+˩Cyoy5\J,-(<(s}3d1&2PrlVѕv0jg;lBy@fs[6mS144Duvv)4?r1Luɩ8ei?ebl #J&^Êk`>C׾|2$I0fMb_ Z[[AݦkB@)D Ѫ@$B)&i?y<$"$`(t5ML.fedbk[;vX:=9mbT?8wnXp81T⢄};zT\еySoGGGGG'!tzvnffƥ8"Ȇ BlĉHpX=b]]]v˥86LNN&@ {n]Emɦs/~IHpPJ~Ą隙}Ng,ti",--8fJG(``x]Ν9s뭷;E9}(*ŋ].4i{f344$I NrjsP(S3z#H" -7gĔ.UU'''1ƑHA dY\lzjj !<{avT*j5έaÆ楥%#ITKK$IJX[[[RvKR\YZJO躞\^bZ9P4-U*'NɬtwwsXv{. oor DCΎ XK$IǏEb6,\Ȼݮ;-BKB,KG ]=sss۷NOOڱc~~~i~'@`H2u0l6K(mGhD}"BlaҿH f?.TJI.gE[m}[wweBU,jwy)Wk,4AydB(eR^ٷCTy!R1)Ccv֯]vj !BV7`6w9R0 EQLӤxf 66779sUmlm޼_l޼iZ?ҥKMr\\OLL77E7n444 H\aɧ[B 2G@Rp1&rT*283 s{| IH)3-Sb`@GQDBAЕLVSLZ.²aCg*j^i-A`8FQB9i:)A@)h̄( wON0 O6W$HT]A B\wcwe2A nJATz~_L:X,"^fkqvv0m߾w㣏~XtVJVVVV~Ç[[o@ Æy^JacccNgrrٳ7oZAhnn޸qㆍǏ,X,67G~eYzZJlvuǟ~龾-UVA---Jp8LfW G(JR|^+pxƍp'rqM}@R׫tzq͖c1lin߾=Qy?%I0)R,s=^W---iEQ:0M[6t:^8\*N fEJiOOO\v:[GFF͛˕RR,_8( F$Se#mrG?QB#1p͕J\( J"IBww|pp kٽ{ʊi333{n36:fx<ҕBSO=%ܝw;VVV(KKKZ*Ufǧ&;#(...fSSODjJ4moo?yt&)z][ nuAfgjvG";11Q*W*BhȑWj\ ٺˍ+] o wte\V*gk 6W\ x HJz/fkZpP@ܽU2=`"dY@桱1BV $ J)&&BD3tBB!_xelYݪ(ɓ+[Hq2hYB R8wI2D"jH0no[`R`!$(rIeB,˲ p)tZUwer Y\z4}pyGKd`;H1A IZN@7-'-,MA?p3r.dU9oB`Qm6 xNOܐVS87:c2 `\F|޽{``Bcvm_ߖ+'|S7Bk"> ӧN|_׿0-?>?eK/u}xxxdx#v常HB)B"A˰L~,F`cccd[V"0 *` p&`La!2J0J_CQXpx~ë] 3!Tx񍑇JvEDcQ"'jum7@r GwpzB1&L1U=.հ(F%=@nfcչ|u˙t88jesIa^,nlRu\.sh^,ڒ$!Ͽim^c9r$+LOm۶uuu655QJ ԩSOMLNN```ppϟ??44iOgϞl6NMMQJgggv{?Νcm7|\.CUUbOݸQڿi&49IǣٳgWssiXLlB[[bsOSScl|r|yy9 l6˲駟\~dzn@=O&~g>uFh4u=_߸q`,ҲXz7\cNWVVVV,r{Sr\5M۽{w)޾L&#sF΋D)EHLRJڱ3D"XXE.KΎJbܡWڷ<iL7lǎ+J~ ׊;NrfXlkk BmvR*Y(gũ*7s16mn[0۫Rq8]۳떽}_TTFÚJi`` ˜ZEf&'b(p8p8@Z0 p---ƧIxPPĄ qŘ! _%n]Aĉޢ)p:\s7#vsmgC+lZ.aSH?2BH^o,˄9 pZDLO8JmiifAIꪮ7Bzjc'1^Jah &.ځY$I"@._Y.Wx44M%Ϗw!he&pe}5_-ʘ!dUpPk3{IoAb~c-Wfi (Ε 7.wlr&&T&`ծp9lBkj&41cUU^F\n ȚUEQ]- SŃ^/ר`r\n?p}Ylg˲};vOq9Mܼy㙟_J$Tjj#~ԩd2Y*Jf糡Phy9]TߟoNinnXXX8ybV:NXT ٳxt\Vw}7{4#G_y0h4ڶO?mFx={t]FrtT4 #H}>O@)MMMxmԺjL&!q'N'd2k׮`Я(ݻwkX`0(˲ZCň|>߉'rǏ2Msjj… ۷>CЇ>\.ɤeYJr$ "njp~EQ*$YۻT>M XaK&b1 ˲@-.!dYnnD" d\s$XPYH HEdYH}DQ䠟乸% :\ | BAi 4勅wg󋤽 cU7GϬ peed8Y1f " O Wwm(UeF* 82JcF) Q3Ԁw(ȍpڜ ܺvݩ8kj ( }3ٚkf0pJ!rnڶm'?#Go4u:Z %4. M).C IDAT,@z,ՠȆZnkk?l2ـ_̠Rui(F H#Jz?!hO!-V1x^q75 h4HqnBhf>qQBxdY$[[[/7Ie5I4M j< ].?EpB8*g4p.l&f Pժժ!Yӭ(J\4m\wwzǏۿûw0^5&3 OŇ~o|d 6VrQ_1x&ʚye>s1611/՗_EAt c!;ٹ??o^v4HRJm6rRI2l! =_w~~uƍssszнJ#_<>=g}hZ%y%@  )/-mSno{K?-m)e}{/MHHHlvl˶$K]Ig{gf?|]vϫ.tΝiP_ҾGnz^-sټZ4 JW_K(%$ mܸq˖-cHcD6/I(ڷD" .,.>}z۶m@xk]4P9 648l6jƊVl+(J}m8@@\UU0x PsZBHe0$$1F$I 0MBjr3!Iv|$r;vntCOt2{m,6y*CěNmA1Kݑk!}t"P@hI#"7Fa<%JF1P̨(A5&æk%}C{ l2E/Le}s׆ 6Ȋi,kKZw/58n>M瀄RJ)np>wQXaXdY^RU.0]࣏> r:kgqpXӴW^yezzOG_dž7*7Msvfz4Udixl!!ɺݮd:x}qT}}}(y<Qˎ|ߠ 7_L: =r|zr~C@XTVFD_g~/l20,VEQR&Q](Ge"1MBYS$('a}Bn拆$L,}^o4HZDl6;w110j!Ds!/h@ 1`f_~I,%F0!㔛ּҹidb~~~Æ&RT,$!Xj٬$Iv}6M3655MUUeY|WSL>?tСC.\066V(Z[[$IX,NL&r,?q~֭[VwY /,D1sOKp8E ҥ-[pWZáiZWW{3gxf'O{nA:;7=ug >_MCCիW;::VkX\0x +3ssYYGFF:;;\Phǎv횛fl -Hr9YzUUYZZַ59PWŖ"[mB|>ǧvBQ|6t:}wo޼]xsƶP(tȑd293=+bccȗGyx'dp`b8`0Ο;fggL|x MEQظqtt4p;4x<rjq:\axxjU>O'?)~wnnĉwŋu[֓'Otvv&Zr94ۭzI\((W g0u0aB+W,ˎ]vbO(UQTV ƀR@ԥ{ت~ [3kMT>qib97'8W?x㒯ૄp+QJ&Q+5a 0@x@}Η4 .b&S ݕ t؊5݄"LEfJ+W:RT^ !$IӪ!!dYEz(7Lj|64@}~95 q"9r+Cz &P inQ q?\?6ƀMJLS[OD~H&0kKuٵ;x<u(I #Sn#[1\nbQEEQ~7~ _^zߨ|f8Zv^Vvén->"Ȉaw;/ ? >|Ν;=vE'|iGcp8\S㧔Y0.ٳ7mR,|3>={|f l6QnEf_{ie-LqrZdYes7YYAD6olke߫fQԢzgwc<}*G"d"}wm%߉?ұ!%\@7p^5 }i|N8"˼|.kO8uJS0EV \yI=o[b`FJJs< QL(ajD"QG|rr2 ʲbNEQRᘝ/ x"9ylٶR@cp\с˗٬H6s#B%J0,K8DK/݈bbw=nohd$ Ǐb\&)NJR? 6/۷/y<ѫW"01`GG‚ܺuX,BtNLL]rEUD"Q,cFP({ァ^?=33ES33v=  9(ӳnLOSd"zccKcכ7w/H469!bϻ !435aF0Tu=mܸqÆffr˱IA(N$r6CQ,蟚fl۶ٱc:t8 K}x0u_MMWWammgff'v;cP(H4:2vÇx |na!B *5@fZr]׹Bs{С~wtt<_>HS]v>|?O>_Ͷp<s \e`S@z޿)KJ2w[M6m؈ʪ)Vcܴ1|N$nC_Y)d/n"E,ɹ|˘,Vbςf"@ VˆYt9Wi:vu:X{ٴ "1Pf^L\aVRK@2LS744Jbw޾#g $&1"H;Ju\/~o{QF)ӖaB RĄQ~0DL:u*Jvt:Ϟ=rv}BcccKTJ3J*Ņ%Ў;vM[fff`yiIT*B|ss ƔADnllnhhؼyW M˱h&u=Pnaa_"L:ΣG;w\.C 4tu|l&1M"AD,\X|bbXʕ+(B}-$A& b4\]P,+LNY E.@B^v $Z,]9E1iFT:E$fb1)jbA $AYUT-B^#g'$I*3cT c]4]d!$_K)pKYպ=z"zl.!h2Fo2 !Aܷw'4#_JkrjR 0$ FPcMf444AAט#MgtD 7n.erwftdYcdApAX+Q5iFXx<.\ʋ>sŋWVsXzS8CbU!7%B ˏ??><<ޞHw~7 ;uA$ddYz06"J&R5ȡClXtP(H]Z>#9r4ٙ9UٶeG}4Jtvv.-->}ommm]B!L644l۾ԩSZ)m%L666 j2Ϝ9ܺexx6\-xo3YfR MM OfB[6wrLJ1!!nw" Pp 3B)Ru \.GPuJP"aêRJ"4|>?441^*}`1.7^A|ȡTknk@"-Q`!:Y`kd p[{'Xz0eJg*U$0PI%@*5fAבa 0 j;܄+ϻt`c{|!O)1?z>חC<({pMĘ$YVhoooF"*eK*blb%qՖsKW 5u?r|su:x„twwE3jṓ;xx>>ۧZ0- nٺ"!B,_җ \.'Boo/aCs&ҏ[vY I8T+/ͫ9f"[񎭵Vxr`PP06N, n jFE֩b QPQdqՉnr Xx2eRB##}}kUS vvy[7JKBiJi(c[>2J10Bn 352EP.' ϋBpźښaZZBHwwwmmP(\reÆ W^k!r~zgffxt:{yhN27GF[lZ1791,jCpgϞd2vsnn̙3\pطK%G^la!JS6mTWWnsNUUO>}_PJڲx.MRi<(>kRi`B<c>oy)Z]744˳v;Jy\h40#GO߾<8ꫯ.G&&&boooرc ADD鴇˵C .)vIb^s#"`WG7W76??mlX9Ϫ6cb6F&:K$Ċvu޿hݹ\M=%ٳoY,q3K;m"bLFU!x]Ij`ij .2w}z4#b 0cT7I!$ Iozσ'F G*l ~10ÄQx @74ر#Ϟ?mϞ=|1t:G0f9\K. Rw܉)ٳgp24myyjU흛۴i(.\=piã{4Md2dAzY7|b nb) w#ccp8|}4M+D"#˲f4mbbȑ#;vlo?|(ɽxMo8`XΜ9׎۷駟###L#Q:u -Ξ=K)ݶm>d9U|~b{K,˺|@hddd)Ue-|gffX,eE7vd"pl333MMM8ܮ⋋f557& Iv3b/!S٪DbH0]ZZZ.y^K/)u˭&UBz#=g(۳gw,yh0i1F,."dY4M$MLLJbo馁 j]. X-DQL26dtz2*NTE5B@%$@/dQG꫰ 0s#;zhn` Q IWp9p XQfE#v=DW^wVskH8nmXVWQ9՚B2۰Ws |U3<Ϝ]6+U;TqV@HZbSu@4ଈnȂ `z]jF󖈢d$I!qEC RD\td,n=iCykܶc0A-h k5s"C#``Qť*#B+80E@(5i 7]FT .ob4*tUU7wz;ڷbH.Nu)皀*Gz{TUcbǫW0v36:j!޸f $IޱcGo_ߚ%.{ M NGc}82?;=9NSd>\4ևO<ο f1Bh9| E\55S2 H).V醡|X= /|_eHjj{"(1J Fˆ&(xpeT~U8.fe Py1FD$٬2BBV9kj@{=@?\SȎh2S˿f@)NJؚ`nT  T);.G>_]c@` Fԑӡo?ٰT:RQ0P(vLwSf0 [8*gb᷽m.:66NcΝ0v 1%y||r8{',//e?044 UUx UUUU ZV‚ժd2 ssP(dvޭi,l6Qfz{{GFZZZ|DӴ3Nׇ\w<onnXZ46466u]iŋV+Y433vP]]7Mg bO>-[EE1HD"Jw=s. .-.;l6 *s(~oaaAQŒE }}bP۽{icӧ^o |yz]=??OIf骚L%k­\.:f!]YYeY$&N ]Z;x]}h:&al:u*ݻX, H xCpM0ƱX  HEQEXggg,9Q !l'@\ &W@JE$y<HFY+nnjSu'./I|*WWtS ^9P͡}@ji39ֈ2{}5W=VjꫫsKW~ikrzQ_ dVʶ4*+ fju\STʲlX5T*c<\|>O0յ$˲,k!20@!R8#@)O(2heb`I7*:Eᙘ4޸у.گH\ 1hDP:3n1< B唳e*O("BK =0OS&6 E$!T&)rcI"9}lg?-*Ӊd=pŕB0!15ծT+ :r;:==54tx䅟@Gcf=YW{&ҋ/7\f |__'~7NH(Ile 4Ꮮ7~_l)OB099L&vg({e}?lݺ=ݻiZ_e_?pY+~tp` sٶ$I=/Dr|I^o0XL&h.s \$RF7cl(ֽ{~(:lg...RP(J Mq@@Ӵ9gXV~× ON֚>|tŗl4իnfP͛٣u3?ؿ"[5'O4BŅ︳ԩ3Lu<FKs H$ ޹b`J::UUy</'O߿F(333>OU] 4N{\.y^Z<R*\,j m%B ˢ$Is󺮇:;"?PORx$b;ֆ &X,P(cH9M2TMJ{xt6TK)TMdݸyh/-ca Ƴ%$NOԐY Yu8Ɛ)3D0GFfR0%X?wXA5h66 { %KjDP(7 0͜+. [HcyJ# ߷j*8157 rE`˾v옮`CtdQ^'V2)Yrz TMJ H3ƾz/`ӛZʲ^XwKɱs tBt!3t55T=xҿ{M?ƓI ?-eQd*/VUQt&B&$L@8t82sϽ꫟'>8qرw#w/]NƬl3 !d(!QD8WL:jٿw,&ҭl% -ʒjvcx[VzKoA HWKBE?_fGӛ7&XGX<>=9(rk`crxLMf'g&~/G>ўo^(>_={]3&30)tMRd4ȵ MMMMSSS6m"(J.W(Jmm;lBanoijs8l~{KK˶m3g.^f;vlw:}}#H`0xСYͦ$ v kgff"IY\v{>9W tumR r9GFFBTpeõ~zwCCCdx"qF套^ƒD&W^ ](Btnrzj|l"Z,Aj%I=(\Rp[ZkA9dGFF@w/3;wj6==]dY||^Q,###D, ۷1ryhgX,k',9 9ruttMNN )b>x38U4m{W-}q3g|>cvz2ϘdrO~߸?ӟ3&hddhjjBcǎ}$IR菖R6f,E׍l6hkM$)"Rdq1*4L`ZKEriJ)BaL0pm!4;3Gu:\d:;UP1^3r+HE~J0vsBG&uF? IDAT{;sˋl&ED UC׸i-L^ܿw_S.1.#\ȕ$<4" aDe08{}85g'M80d[ո9L6MϿLgܹsNsŅY4;;3Ig2tOOO>Wب(ƦP(t=g2s7vBa#HU;v~C#._fs:7H UU%_iin޳k]{===|gl(677:thCCCc>5ivb1bXV1&JHDV*T$IR0iܔWh,vc2[#"Hf$N,LD킱 Ŭ@yS:ўc miOAi^$g-Hyq(-5 c@0S)n D(c2su#Lf-07I4qz;=}SgAx==SO\U)]nK)T4O/!RR(VU?y{޽[0` .Mq Pvz]ޝbQ}}ԧ>㏏ G?oۏ>={y駝/3QOCCCǎ wyg?Yn5f$Yt:dEQBbQ4@ $I"TO8MTr t5\vp*0ȊA4ydٗd2) r1_e-߾>x'@z/=mNLHeUkw٬b1H)e̴횝ohhn_"D&"&&-c`7# "!VOc[C&9f&[8jftSB(GI3#6;]uferT*r: ][6_w} (\;#HbNnB!~@] 60Fx滾HssQJ8$Q׍_|_zuNvww=zryM3 ݠ477&T*Fxm0 ٳ/266Xm(R)ǝ(NOr7BH]}nNSx"UTU011r|R?\.G)YR % LHPWUB :bPXX8T&o;7uX,NLO Z$r HմT*inWUʕ灩)0"+cX8Ft:Nv;"LLL8p8,|^CQͶg>ˍ1V ՅO|=KȒDܹ\NӲ(+]1 PU*`'ZcUKG* a T4ՈSurz`pkܹ5؆gzZ½%o2 -kMs7 xTXam+,{%BgR XYo+8N嬮rH Bbdq!dYTU?sr|Ng*00Qd (a'_P/ FۺFa޺&J!7"  C\(z ]'LQ CvKs?N 0uUfz0 *pj P (bB"f7/emp%ٿD]8,IR Hdo~WOou4MR,PaWVV8 bPЯ}S8T&@gZuݱw(xK?{o$q ~H$(ɢ:f] e{5c^1cdž3vxBmR1IQ-AWU]]UdFdi:̗8|?jĐỹ37*R]܅ 6@rϡGb^y}ɃMу"Nij|s' G}d~_em?^/,,L&gg<\4~GZibc{//28\>ͦ'|2KLLL^r婧Ei'NDMLLťK yX|>HLNNql6H$pܹg||\k~ﻥ:??_(>|oW 5ػwoMHČEQL(&z.Qynړ%Em>"BPfo9 6)T@D3#ٶ]*Y@i=+m?ZM׾ҸI?$Ywe%mL Z|~aZ7X_dooڠpQMy.Xx3^۶~l 3Ћ?s_OC3ÿed ( |XMr7:?^;ަf_7~WuttQv|3qosʯĢG===O?h6ug}^*RrN%KeVMMO|0snm=BY!#vRaԷܱwήLsضqǶ,۶-aQ  HDRƵ٩{_?PeUS5vBFGGl}jo߾O}aokZUD#5dZx,\`@M @A)UG@fGGfVBtx>ȚuwwO}j~~~fffeee~~>33冇711Hs ._캮$Z-j5yEB‚8Lfrr%q]b=|҅}e>c=q5 _tg4M8n~~Z>B(um۶a6xeeV($q7'&j:TuETRNCuwwR EQ* D!x|ttn޳kƻa[Z8mY=5@̉fRaJ)Ƃey]*lYs\0Ʈ!AeWUUjݙLnyytGI%T@Q(9(rQ<zM4/2=tj!lM[#𚪀N$@ o1<B]J|}BxNI:>=wi89@n)ZL]*D0d"./}u'fFkk.P09juV>Ly#XB@Kyɗ˅ǒOwsw6UQdO Yo,wN__7__mM%b ݻoll㸯GG?o_b_b/M'㄂㺎ò%x6b7y YQdAl0Vzdzz(!eYm;eχB!9%EAP8J'Zmc؏u| DEAj(LB(RUx> Sz} TCjH R@((h>]dC:p_O az{)QGFF^u֥~4,;L]wm^(1^]-X\p}奥7|ciڇ?t:}ܹZf-J$}}}.\pRKy޽{sE˲4Je9}Ȳۧ_y啞L&dŋV˲._[ PEm۶޸qc9g Dg續D"Q.mێvrW]_Y) n<kn [+J+cZxjucL)jnA*!${o⋞8^ñhRiX< $Z2==-ʲ I<ǯʯղZ=mf5MFr>-v욦&''{2RTQ9idfvc[8Ay3޽ouuu׌F`@8 m`f C)A͠X!]ڎm!0^+M25gHE͘4z H~  E Ά `b;ܿ+L˵'Zvڴ2syqY6n|;Ϻcӎؾx;wg :џ~_ZƄc=OfځGq]!(P\.ˌ$ Rc y^B]XecӴmǽpؖr޹Ha 3pƦE0\3|_ J`UwM*Q(Zu DZW 7:8;J2?HEQߎA -˜Fc[M !_|}yJE6s}w4bsW.opB (ĵbSF UԿ&wg7gAR$bYg48דo~B*O&{_*TD<|YĢQx?曉D?JLZQdYcѐR8xfusyX֊7_zMK;Bo<=51 m~w9c?P@胏<ҕ?oYٙvi"H!hoIU׃Ƀ{q{{{~»{~H(I۶\211oߞt(KKK SO=% K/yޙ3gz__;ueYLii˩d:NߚY^^8f{VVr̙3̎%Hw}<EUT*Ӵ•JSh={%_~qd2L&?33sD,=95qcQ,ۮTT*H,//w-Vy.<#':99i@jRLO./Q(RZ y,$--o6/py J6Nwٽ7VD?ē'O~(zIE/7n۶EQERZYUUBHRI$Gu8b^/ J%N P׮ݸy"Xݱ@e]|5ʗ}kgϞ,KqRq|Iy Cj#ygq0g<>i)c'1¯ ,/mj0A7'$^w~ 7|\Goؗo~mw5z?u&]a+EE miUˋsX\.:cp5Gxq]yv$ Dm81h4nYVn$"O1AN)e bxs]Rb֣8 +m'MG|pI?FS2sUŸ l31;RJzSqb*%3 aaFe! By8T= H ,[7vb4 ﴺ!1ϪwJyz7O_߲N hO.׭9u?@_x#_ݵ]_PНҩOx DMnܼ#ǎzWj1K4:|d߾}Z-^vT*)+=ӧu]$) v2t瞳=BISaα0vq`ȁ(`B`#l `_e!p(JM)H8QLh%)umz?[\-VqH*eYgܹsp?JBNxZbB!8p`~q)Wd2~ڵw]xqTz5gϞC\ZZ* RqddQឞJ2mTBoy~/ӕ\GXVW.\,X,~s+귞{̙j:E@1Fd##Cju~~~֭C_B,aV%ѭ[R4  dSc\A ЃM9}rڰ 0nȳ9)"8RL+I(yx44taovvf/,bXqyyRQ=`~`4:!M؄ x<. sk-4i( ش@{1ӼTX\ns%7> ;i^u0=tmh) _L&;vD4Mݲe̙svaai=='4Mr7n\xR6iڻgޙ:;DQv]a亮jj^vmn~ayM Zv_R5 EQ:BHQBLAu݆,4qIIa3ƶY{va繮y 1JiowFkקf+՚QI2-#?\>;xG ]0D" [FG8033D,8>;v\86`˗oN=zO~PVUAJŲ,q @,#) f:MZ w0ae$IRPHO)ѵ2S/ζby]i9Sjj3*y;v py鷸鮾{k4A:߯gWO ~la  g9J[0zS$f=NKRP"̶m۶-Hwoi.XOG`6һJcc6N`Bq(j"4e9,'kZqWs`ʲ#/--ݻX,^xq;wsW,1]N>mf8p0ix0~xqc#<8Y(D2H#Uq]5|IJc qEq.;XP"HS2MJ!Hv F mY޾>y3JAJ|v@4V֬IuWQ$UU~__9m۶OG"S7_m[Ϗ3.i]ڙ3gggg]ňvZ,[^cjs۶h4zT*511ٟJGf$j4UR*TZu뺢(R4? K9J) I(a8u]4Udzzڶ]pWUUF,Z3X-mJ5G\ץ xoؒ\(xstgg~'?iZ^.ff&'L6M1 (Wλ8͛7o9222<<<::Jb3< /\t/b4==]%BH+C)f !n;hc/W4vO zAA_s;8>E1ܜQG;``׮{ܹP(r UU`;h{R^M6F{R^CnH6#v|\{Kx^Zlx:x#ՑmN&qblYnpZf ^[D)z-B^-E]fŀTVz{&gۗL&r 2&1:JƢuRHaC6jx7:QFn0#!}1Q{v!E"6e$IJB-JKKK˲AK' RBDB5t0B("kժa=M[+ q\8)oܪek% EJ&%r&+:@($֌["FxtdP(zAIR};9fo.Bnyydx$L_ժӋ1r\vZ2::q\>~  Ƀj:}#=E @&5P8,p,'nD)PJPf&P0L0MCLݫ)x\dz(Ry$z.X-j-!@I1?F͋ }6#UcчڭkWKiEsLBjD"JU4Ͳq\Z[>}zVK%B v@P˲: {Aر"( H+i+x!l♹g052JT\~iYTgfd>ϯp8s\E,W,6 caaq֭^?vؘeY33Lϳ=o\tgƯ_pƍbqfj'''?O---eCCCGbIUǶo*IɄ W\u0u:b1)x͏-&?۠;>D-õ`6v&B,MG[zZhԾr(۶+J:L$⚦$x{'P#gQA ahڙpo;D*EnR-?1:X_{ž78Q3܍$I8c#AD=}ޑ潇ٵ^_`-pg9^Hpo_REBXF7k?CV#yݾl@UH-{Y@=1!U$Rqlusťm0?i"_8H) 6󺄨Muۮ&BDQD69ضa[FN䗦'a @_|90JШ~n<}~ݱ>ϥ^B]CX 54*BXB4 ;(o_p<k]嬺!A,ˢB"tqiu\0b%tpppiiIťE))!O>|mΜ=K ,-.. ܸqc6Py^$),IRJz 3˘Gt(euӆgێnccmFqVjeU )$q{i[?>[*{Gi, 믿qZ*Umuu5+~EuҥKȑ?NiX Y]]uݬD<^(μO}'EW.T*L..F]߻D)^fRT՚-ڶyP(TT<׭~`8\J)`g SȳcHddd$N&=1eY:RG k%QOܺ ׯ4-㸄6t*(ۧOy@Y^^+[_|^E^X7np8<;qıcwww }xdO?!˜33*V71zX˱g `~u= -8-RӴpX9x𠪪q)NP0 n( `j~ VXڨ5;6>Hus׎[3u널O~XQH$Fgfgo޸dzzzRR MMMB!x<.$\Z׍jRBq\ugLg>Smݺ5Ƿn:11qߎDb}q]7&&n:a5U]*VIj)H(J%˶)T`t@ A) ,Ђ1F˨2N){m۾xض-Gx|~qҕjPSP2*x.InɃ*ZRBBaw9)Z(C##@G1n ˳{qkfR ɋ#`xhH2渹]Jiɒ\ՁBgaVٮtJG鈶?)iqx7T H)˶LSǜB #u-sd1B븖$B!a. Gb,y1 lvhh8rKKHdllBp={*x7448ysu%p8tPWkX@zmuuudddvv['>%x޲l&!8_*I1"c$}2 ض8mb"DuwY&S|Y QlVK[Saf ̐Y?:2kdIܹs;uOQjzܹbH1*3ۺM)]C[lump/_|Z)Z4-H837n(:!׶ RZg߿8B͉j[PJ P6PQ"p< H$jVUJ}^zfJ57ywR,*v" ĩ`*A#up.//;Bt]0F"0n޼ ?0qgϞ}G?c+3F!4=e<駟$ʕ+,OZM&{{Cxql/Q0 cߴb%}> `ote -(h˰|+h?- bWd<ØT& PD`FAV 1ؼx6cV^6ek4ﮜ [x;֎Z;R4\>G~%ݤ ķ 3C,v˪8]]W)ӶzRa uEy8 )ڔT0X58.N %xH(%0چz$xP3ۦ(Bip[loD( ;`+)&J$dVPJm|2Յ x:aQJz{R]s@4FcE! t,F)d2(ժZO$c۷UBq:nY[,#}nh5C-Xq\qRЪEr^v]۱R\(zzzx:9E$֭b8:Ŷܲ 𫫫33S+<4rnMNE9vrЍp$* ]]]JU;>J4Ɯ8I%QxA0aq<DZc<SJ RS4ghQÈ ]H൥ʭIHfGUsss}=/xAh'" IJ4K'SC}gM|ClrtW"?exdqq!nnn'HӎLNNVK/jz8hvǓd^K&qBZ[])XV]ޗ_z}CǏwd&Ǝ?ݻW˪UNu<ϳZM z T Me,?7<#ӤB--e vi HOîcٯEղȳ4[$UU#ݙ̃>X,5M+|4mcd"?#SSSs]8{l2x++eb2B,؈Sͯ-y7X,8>"XgkoRjowP33I؂(l8< qhk78SU( y*ݘZ}m?vd4dz;+_O *wc7[ڬ( q Xf'~/fv)i<[ǧp`3 !yߚy]Ib5=s߷? }$P<ZJB“dFOp݂ArIxć>ZdնG=w,W_y5]JqӲ.,pU۶.r,X,;wKoDш ''gz{vvv prrrddzzzlǩ*S] }V]Ge\='hԘ>vtffgV,U(⩧~gyy R9nMx`_ =pf^7-|$oK`Q{@ȓXP"d+rLqx7ʼnl' q8e *1E:;;wyƘHu9%=]_{B$[ZZRU\@4cy$?>uX(+]{٬瑙ӧ+rT,c `!D8 \ðֲg['$N +]80J;C0** X,lZ[۹|4I&;:qqveE_q7777B#RG˗>w•+Wt]+[;;e ]|ƍmyTr;669so +1/?8w~: L[n!2%ƸP捡X,vΝZ}ՙmP*=ߗD\`}''4PhjB*!h0xhRU׷I_5R0 *Jf܏bFͶ]((/TUUB%;K hᬫSêi###KKK)ѣ㍍LXUۤ^.BOOiG(zaRN)-*kѥ%'x,WH_hdH1i6ij/v;###;]9*cL&կe$v] HD, =E0TKD~F]ץ}M
© 2011 Dirk-Jan C. Binnema
#+begin_html #+end_html mu-1.6.10/www/old-news.md000066400000000000000000000111731414367003600151250ustar00rootroot00000000000000--- layout: default permalink: code/mu/old-news.html --- # Old news - 2011-07-31: mu *0.9.7-pre* is now available with a number of interesting new features and fixes, many based on user suggestions. `mu` now supports /mail threading/ based on the [JWZ-algorithm](http://www.jwz.org/doc/threading.html); output is now automatically converted to the user-locale; `mu view` can output separators between messages for easier processing, support for X-Label-tags, and last but not least, `mu` now has bindings for the [Guile](http://www.gnu.org/s/guile/) (Scheme) programming language - there is a new toy (`toys/muile`) that allows you to inspect messages and do all kinds of statistics - see the [README](https://gitorious.org/mu/mu/blobs/master/toys/muile/README) for more information. - 2011-06-02: after quite a bit of testing, *0.9.6* has been promoted to be the next release -- forget about the 'bèta'. Development continues for the next release. - 2011-05-28: *mu-0.9.6* (bèta). A lot of internal changes, but also quite some new features, for example: - wild-card searching for most fields: mu find 'car*' - search for message with certain attachments with 'attach:/a:': mu find 'attach:resume*' - color for `mu find`, `mu cfind`, `mu extract` and `mu view` Everything is documented in the man-pages, and there are examples in the [[file:cheatsheet.org][mu cheatsheet]]. - 2011-04-25: *mu-0.9.5* a small, but important, bugfix in maildir-detection, some small optimizations. - 2011-04-12: *mu 0.9.4* released - adds the `cfind` command, to find contacts (name + e-mail); add `flag:unread` which is a synonym for `flag:new OR NOT flag:seen`. Updates to the documentation and some internal updates. This is a *bèta-version*. - 2011-02-13: *mu 0.9.3*; fixes a bunch of minor issues in 0.9.2; updated the web page with pages for [mu cheatsheet](file:mug.org][mug]] (the experimental UI) and the [[file:cheatsheet.org). - 2011-02-02: *mu 0.9.2* released, which adds support for matching on message size, and various new output format. See [NEWS](http://gitorious.org/mu/mu/blobs/master/NEWS) for all the user-visible changes, also from older releases. - [2010-12-05] *mu version 0.9.1* released; fixes a couple of issues users found with a missing icon, the unit-tests. - [2010-12-04] *mu version 0.9* released. Compared to the bèta-release, there were a number of improvements to the documentation and the unit tests. Pre-processing queries is a little bit smarter now, making matching e-mail address more eager. Experimental support for Fedora-14. - [2010-11-27] *mu version 0.9-beta* released. New features: searching is now accent-insensitive; you can now search for message priority (`prio:`), time-interval (`date:`) and message flags (`flag:`). Also, you can now store ('bookmark') often-used queries. To top it off, there is a simple graphical UI now, called `mug`. Documentation has been update, and all known bugs have been fixed. - [2010-10-30] *mu version 0.8* released, with only some small cosmetic updates compared to 0.8-beta. Hurray! - [2010-10-23] *mu version 0.8-beta* released. The new version brings `mu extract` for getting the attachments from messages, improved searching (matching is a bit more 'greedy'), updated and extended documentation, including the `mu-easy` manpage with simple examples. All known non-enhancement bugs were fixed. - [2010-02-27] *mu version 0.7* released. Compared to the beta version, there are few changes. The maildir-matching syntax now contains a starting `/`, so `~/Maildir/foo/bar/(cur|new)/msg` can be matched with `m:/foo/bar`. The top-level maildir can be matched with `m:/`. Apart from that, there are so small cosmetic fixes and documentation updates. - [2010-02-11] *mu version 0.7-beta* released. A lot of changes: - Automatic database scheme version check, notifies users when an upgrade is needed - Adds the `mu view` command, to view mail message files - Removes the 10K match limit - Support for unattended upgrades - that is, the database can automatically be upgraded (`--autoupgrade`). Also, the log file is automatically cleaned when it gets too big (unless you use `--nocleanup`) - Search for a certain Maildir using the `maildir:`,`m:` search prefixes. For example, you can find all messages located in `~/Maildir/foo/bar/(cur|new)/msg` with `m:foo/bar`. This replaces the search for path/p in 0.6 - Fixes for reported issues #17 and #18 - A test suite with a growing number of unit tests - Updated documentation - Many internal refactoring and other changes This version has been tagged as `v0.7-beta` in repository, and must be considered a code-complete preview of the upcoming release 0.7. Please report any problems you encounter with it.

`_1| P a0\pB`鉖i0> 4R{T-(HRB*h"l; i Hhtppбm gMPU*poRBhOK!YH]דY GGVĊB)lq^*mϵmѣ+P۶u}(rx\J8D*Dd]Ie+lj~Vw^.{gz>oxxP(߹sVQB⋣ccdrxh8iftl4hI˄'*Kt>-n&f뺢(ѾKiYՙ LNNjǧO# \.Wn޼)Bo7-YQYo@bّwﮯC e111122fO>ݽ{g>֨Ϟ}hF岮k\\.xW_ya^*RuFS,s?/_|qffgΜqF.4MtzussO?M}24<$0 c``ߞ:իիWUUsl{;77H$z{{o\.lld6|/Jׯ_X~W\ۿ7n$7|SUU]}ߧ/.-UrhZ-L 9sfLww~ZTQƆmc+uz8vsk\.hñ#0:ƘRZ<s]4x ÐVeIMt̐ĐA`= betxɝϏmcmNزtJ(cLJPqλ:#0kkkapeo5j溞4 eA&@/ ,36PPxa-rp1w?W,̩Sfi[NXr< h eg799qs)P,Fcx"nZ'^HR^{vwqsi⋶m';w 􏎎6tzbhƍkb0ɉ_~errgjjԩӃCJ::<2w/ol ~ɯz?Vi3ϼ+ɣSׯ]_\\9˥J*Y.>Ñё˗+///-RJ9H$8e8m_v5ˏ ߹s_cvv?{JQ'>boSB_}5ѝ?Vp="GB_l6zoo3̞a#t#ia 48έ[Ţy| 23F}ߗ9$Bp*B f5 !V-X,B_ԗ fS@yr_plŶx(HDYIR++>[+cs&E}P hs6·kxm!tk[pD|j"}ނ ۚ\D¶yB/S-=@`>%H(18xt ``D$,vpR06֗a6r[cOi1&綥_\`ܨmyŃ0Wq|Ģ*ꪪ:-屦ir25MCXu]wلb1y!qR2K! B*HuB$!tS@!gD. cLӴ`cS&gf#9&NYS40c 蠄8eϚi!MUvRJxT5 !c$x$XYYA@ hTp89zVZQaJ| qIhO&;;B C]**& ]+J<w]Rʘ98#t]?w|:6hRȼ\nR01*cرcccc|PeYJ`i`_??ozz~X,j^,IDcCC#t1VA>,˪7o޼po{{\.+ٝ?J3.}}jk׮UUE3'SJtR{m@Ҋe=s KB^tԩS'JU .,d2NNNZu}#|Qr8QJUEbRL&jcpp?J/?=iɔdAa"pl6z.ML29h>O4M@j)cL NB0ҢAL1n8!Lr*V14ML |%AxJ Ci${ ASiɪL !1ʇMA "ړ;w@@Y+wSdK^lRDQ%, sGB(…|l ܆ogpSp8 *K11q\uyأRzTUUU%s <$0B< |4~z{`Aݿm϶k!IsW_zOq~Y1ŽmT ܿmZ}a#Cx@q1~j%ګ,3iFn'D2BQ{H8:,]]]J!!LzOOwGGG^GȚccz}/G*@@)!BBUPsj[;>J5bj@ !r!]Ֆ\A ]3嚴JpH&ٍRn2!J\׫T\O1NiJO!X-#`?%[–X(jT5ZF}B)<ˍFZ}8c)G-2B9cyB0U[ !3dw7/k1j:@JWgS'O͛)bzwWW\6-ԱT3^G N}r}¹R_iR)q9R>eԲCզlr!b&j1v;wܾunO~:?g%Wđ񣓓RԹsMSO:\˥bWg*B|_Z^vuwwĉKKǎK$b/kgݝӟ}R~7gN?aGu}txrvůl8yBԙE2RF&Non~yvsSӧRPh3s,/KZqdb4TUy7z"Ѩؾ?kk;\>^\\uc_Ɩ@|۶c:!dvT4&R"ȝhs(%ZM|#,8(clltcy>kB@ClZQT!8np.TUYIt-9<!`蚦*0'(X"5nh-H[ K`)tZrBTUQcFHօFkX(KI~Z92l{+4ޟ)͗ ϓwn{@%z(A#$Ll6J5U!b cs9 h  R'wPr_C)bΒ*/Z@w0Rʓ??h:yS .SGG|8la ih,t JRA%-I˻D"F*m[;Wb,S`yNMUq+1.^eUB|n9y^'tuw.@@^UUɤ ABHq$p!$ZuWِz)U s{%3ap>i6~J[fDH?au]/{$6@t0_!rɜRH,91&DE@d2zN)cB!TQT*yґjBrz󁁾D^lT1#Fse/Lr%dGoo/X~7Wպ{Fy睳gϖoݲ,k_~]CRJE۴VRr F#M'4f3˩GWU+񥪪1<3 s^H$ͺ755NEi‚eJuM1/./EJh.LvF1Msee婧Ke0MӴl2;::,~ 鵵BkD".[;8vX4L&384TVFRVZd2lZh4>aٶm |>/BjK6::(J,;vX$UPؽya++al[555=333;;+T*xrӧO;yW\(<0I=w<<ϱTgg<ݽ~z:^KЈD|睭>V%jmai!HZ߲,!fBB)/4LF١{'=C+4C .r!V`]%\ B MӤV8  +&$ zepR t]" BDB iÐN@?T?m}^m 3Y`,VUu=`}VӅH,Ҋ֋Z@$ zD:[5uӴDK>22b EQ29x<.hE=&|go>&|X==ln j?s Vi|гV0@h ܭO!5m5<|'jX%9 O>ep9*H.͂ADL'?#UEOr.${/t:3U+;o BUQBJ1F!D C`!gDm[p;鞃@D"FI|ʸLu"P0VUQ1VEtqs9I%>@%_b/ne ࠦ)#IzEQU%">bjFC5 #$Q{NQMS"CQpWWx2a!"Htv|) Vy.=81#J&M!!`{O!}p0B򔪪FшaD\ǯ*x<&FfLRX4GYۋ֨}~V^o&}0>|R۶ \ՄPJd'q$`\Q*M- aLM14 ;ضӫ(J~wdWWw&Sz)8bƆG'&sh P,W| y2ZVږ|oaaJ/u{,筷ފѵtӶWi#k[[[ƢJi|X Վ4Mq'N<}; Mg^x;)!N쳅b4)XR.fs3/tuu%F377a WUDZ?_>y_}R7D[mY~N/..'W~O_GGG{{{gOku[gkjAŢ 9gdٳ/~ ӴƏ7c>uK1;;X[kgΝcSJ]׭>aQ!OV`MWbшah4c8DH B` 9gsh3"Ȍq<#v}Db{!!@+dι`8+ <" v.-߆[VuPa>[uO0dXIF֪9o23pHy >Ԃ߶>ɇIO!?|iTjc#ZHX>xF {>p8s iO?_>|n~5l9ɻmOY0 |6E~Y'-eI?mEI HǶ_$ɤ빌1)Gy>@ s#Bt!S r!q(a.KB([0`FGTQ뺾O-)K.EIn!a329R$-B:H[^&i $y (D"f$|ōLh0z^VͦOh fRŢh^uV9OF׮^j6-DQ˜}}}CCCD^Sʆ V, N$@utf2Bf7]qd|l#aZ^꺮*cLQT8T*5ꍕT*{>dBf}?LbqUS}׏эL$b ;@۲W67nܸs[nݺ-WE"ǧ^xu !~BHww,W F\EUffj>M߿744dkkݻRq{gX,6zxP9F#Z}zzرcG4ڵk$O>gxl&{ois;{ZU8]]^ZW*k׮_vg7rl^_YYdZuX,;ΞF#333ߚꞟs+;J,GcvB$܌D"#H%O>"d6B)!"DlہF"ιO!DsH2!|UM|&5l֤"S!wV:wb7E"|aj كLBJ\%rUMoӮBAavɁQu[(QT|ӐB(q' e\\3<l\Z3ВьWr8|MD,eBJ&R "Dz1vKKIENDB`mu-1.6.10/www/mug-thumb.png000066400000000000000000001256221414367003600154730ustar00rootroot00000000000000PNG  IHDR,bZGRzTXtRaw profile type exifxڥi$9n)t$8\t_"s_ufL/r pws_Tf,W{yw~S7䐦c(‘~KɟR?}}'(-]XQͽZsmr>E^bJ?x#VS*?Go J^!zw[I|?:h{G==22w[= ~;ZZ?wn:7{ȯ: AW1]@c3䵈}&Wlr9%e|2 9/ y񒛽K#+`yEy;ߒ[4 ;#2O֕#/,#\F~oeG!'pUVCTI&ЋQ9l$hHj<ɀ"s-M1=.Y8D&Rtr Y*ТUUM]vV>f6xuuwڴAFϽ}ڭ{o1s0gp̳:ufcU.[kˮ[mm=tN?サ[nzm+k-?7kT\翲QA"gd,W!9rԚ#s3UEjfKbG^?2ynH3soޞ?7Y!ا #P}gUIwLC{v[M=*Jmt9Dꪷ,) z@HglaD2ױY:n?)op\Fe9kcg'j"rBWmu2Jmcے?7\`'GV"@9o={'UZaa9%{CYj t@;}CK. YFJdS8n6CT|9-€[_c׵|fz#?Y꒳LHgvQr$MhF찵 V96Ng]Z j%6f PiBH٢>( ևk"Y;2}qr] Xg=7u(ljŇůDo6Q*Oa @DW ! Hbl)r~sܹ1m{{zvAP ,qؒQ~`@` sk$->"jk( 0LB6!c].mm^+Kg[>؟lDf`pu]0v(2aoG2iMei@4# &H{/$yQP fߓG}& a$pX`NIQM&0 ,px!Eg֥& ; obfU"<&h'&F`֬cp:5|fC`kc~RW+3֍a'< ҍ/FB.w@rѨs H a+r;O͠X܅= 4 C~6XDHZst"QxnOl$*0nO8EULUڶл:!3@)l]=ڝke9B-4/Q$o3?p-c.xjI+,"on0 Z2Ŋ嘂BaXgUc]l_ ! `R'ằ>g@K,+6AZHtZFT $A#JYNaah%4cd,4-$^mt{&zWVe MOLΜ=S4;b]ZtVfqPZ brzjF;v[Y3* q3cgX!SD^d8 @< *#pAuԶ h1jQϢG ^2Gz)Y0;}Xi &c:jZ5UZ<* Ejhj$ڗe |J7t hc|p79xHs4"*rW+KPD =QlHX?HKFW ]Q+qRa%sa^ qC;%t"D !౼.Y^ x -ȎWad@t\+I> ;2º 4di0NvN8wW4"4HDI83Qx!8'D L.4|f7 ?3U h OBPҪ5 Ff [bȵ!4Tn~54DF;!e+vG3WcRta c#Ipjj?lnn^}x?!Rg Zk^OG, ܬ$Iw4M9 TU CHB~K/0drA"X,p 5M !q\cc7F,Y>0`RMUo8UBXUTnWBI뚶~(Y6Jp(bq@t^E5B("Oz􅂪 ݛ0cx4V]3OǗ^:xdg?RJ;!c[L&`Rz9EQQ5FiMm(x*cLtEQ1FeǢx`~^f;q 2ql08 p.R{??iW&xpSwYZdZa`h’_1Msc9.{r<֢mQJUM[Fxig3\*eâ(?QZQB:5M8Z-yy1]?DeЩn/.vU0#gGmo{pӮcOPרNS 8U\CO{:/>3CzQF`c?s?3cs7o]Ͽt;.Fn& 9Q NfkCQ0 TUz.P*%CAƌ᧦j۳s{{uMӲeH` íE֒| NX2y?Ɓ.A("~#!+IIkMSsFSxHf\Y}kkM?X˦)F5Όcg1rrp !Ƙ?[Z^~?e~@l\x^N&+* nj1UӈF8(e裀3sv4멠6 5厯3L1`狶voKג*L+EK9caGnđ!*uUfK)ye`z|ǦbgRv ^`m6u91XV9j0Yl/Z-@8[ ,b&?=i1}XUUG/KNoG,O\w}>WT+򑃒!qzs{Z (յcǖOoÄ1R/,1&d?,JRF7L%,Y<$qm/Q˔%sq*U, VשAUȫL[8ǮnW$59L%zG& N9¶]aeAhUw6zOq5#Ojw#=z1KK6NIco!įQ 7=]&]@SUO)Ibbuyں7KNJ cѭ*LIoQ'ZKt}w`RqH'|pW]6CKmLTӈ1n޲U(*FTӃ=ʃG%cB#x16ML&0lrjL`zOXj$IV8._N.Ɋn.`+GUAN-ܖzhTԿܢβ,bd+U[j06$BH5jӼj:0LLIS=>;˜B,g ^׋eUgP\h#ɾ[o؎<$=qWÂBBFhi~o\b3XRn=JMn*D0WOv2_cAWN'|%5帖RSD7K ͼ19bPc2J Zf;DL!Ӗv cz3ΔRCb ,Rq!'KX2]|-+˿6h:YW w}BFːWߑKK:P~m?t߸ɤ8Bq Th()f᷎j:`z_9a_A mܝۛLrDpP,M;h5)~Z{VBpy&E:H*&lc"9$cNKp@lޫfxsw עSl2c}귩LUM_!FTIC !N_?;VBfbyqw"=`p{ow 9knxGlرEʒqۄ#)ژɒ!vU%%'Z,Y( V p4Q2[lڣ˒%c0Bqg.BNFm!@^j%Kn"c` nt*YX\t@deq[yh~IfQ3'g~!p$'S]:O7&GtLnk2qgO'Avn꼚DIҴwO5rJ#N>M t`W]_o}S&s?Jfm>_淿;'w8<1P7y~7>H|8. '7]Ym//;>c[͉n;‘ V"s3<V5M4@F;!wÚQdwp=7ύ Wu[HaK`Ϛ[H LV5TL[pˋNZ=f*Z cxN`7Yks:N{郫HW"(UUSr,Dcgaב*0蚦jHPp)A=!Gaֺ q%,$196j/V1Vϊt!55544Ŝ$1>2l.jR=Ԯ"S较 Bqƪ#/_Yob~jrjaƚ][k{*1&q^|QLDsBS_rh󚳼&R&}ynQ`% E ŦMy-^TUۺnjrn晹墲M-cɳ@ѽS]xe wܺ8c!r`WpPR/Yd88;H+z]N0F)M M` +F30%9<ͶA2H&0l"f9c2>:1DB0EBD #C rϠփ*/ܻ{Ôbzc~T<TLu2Sxiq\zyvaQEDŽ0-_X^t{';!~-1*Zz JرO5Wrr?öD,:<ԇ1&NUqv"sO?ƩZ~Gv dd0üq&na)iED0^w 1Ӣ$:cl}(pFef =N$NC>mԚQ1vߨ9C>nDFf}=ܨ USBFH&N'ۊcES~;E$ Q'ic&yV7w`2.+cXu7B8FuqƢ1I,d(*DҠi|ac#kaĘSDi* V3|+sq%3%FbR5*J;2j=a5SaEWUEQ8BƺbB TtQ2UU)ƘuGBH4YƄuQQtCE%FsgUt$Jr,Jtv:&8c= g#VufYUY}Hڞ[]ۈú\"xv|t%yýJd674.N ˚nrx0"#%m6I,,9==e//·"qI6s{g4"sAa^dqV>ivƯhNg6_ƒP8lqB333m5[1H!ɂTB tbaq.:D\n76DScʤHmBT 63\y"M&unʘإYBN~&&5+ ڭR*b=UeOmނꉞk̜r8kKrZ@o#P`ٹ;!ԑJV-vkOFHüDR0zXZ&8/C 1zi[e7?pDVY_C_}W{/Zذy-Tۇ<c:px'y;ό`ܗ竨 +Kaqf+s<@SՕ+(QB 35[ K'nɇs=[vĜӲr0N:+=-CKNd$s&3蟯y&bute<'˕[ ⱹ4&;._j+*-/6.x)'zjܜ`&n_A^5?Iݫ3BVVUg!90;әdv{ٴ¦ZY01<+X u NKMya*̱9(IO(`}kI( Otn*hơHN{&ul3t!ڬ{m0= aGB.Ӂf'~SͦKD)rx- ӛ6y&gC[11-oinoaZǓ-_$uՁf1o͵*$ kfG0('ep:9yǁR1J Din-xZivԴUE@6sb1FbItrD`qنedwK"q:,Jd]1]B3w96Uc^ml/xs"/9<&fkBa(j"՗cZMYEBHQE$Kf0bdsbUf2bD,0=:]jAD(+ +?*m.6OCӑ#{ʯS-1o&E7*)dz$Ru"ģ"Ո#P%Rf !lwsL*22r_OH$8USHҒP|_gGt%wT"KWyǓ)3 V%$5If5MbME%n?8#L%#"LK'Ӛl2ƆG/VӁ+fKtM[j4E9r"yltZ4x#M,];nk'}ښ<nf4?ff1F8D}d<XZeҲj0 3C+ @WS1Ҕ0ר=2B -P(*5%)caeJJm;kB P9`^1FF夲F jq0e h"X<e+dB&Hws:&b^ U!s )r(JZ5%XS5zM $4VӲRɄᠯͰ.e#QRby>`U<`m,U@3uVFUp';+fc7֍-Jcs cJ&ҫڀ1ecS4`Jf2U5n+2cp}`჻e+'woaj G1Ƈ| jd+cLUU1q?X4F)iBB8B <:==]SS*˜8뚞Y;5Mtڨ׆ jhhޘy-@@մOm a_<#<ճ>!tx{Qb PTXf?^wNJn9N_N+0^X WgV^iquc8*IҦ͛?QGUUdjY+[n\ONonŮ\rkkkkl[Pq:1X>O$h~ƹ݇_ g}.&<<|m|I}gRFl$Ix\w.v&:{z~)s'j326+໧9<=4Tgzrhl$8;E<͟ċϷz3(~ܞ"=~cz;]jxg[[Hf7xCM?1--notdp^adnF>qW[fU\em'CS!S}m.llp7I<|MmmJ?RTu4Us!D I&C@C#v KdžαF;\y^=%7?poL956b!v=Gw:+?lH~ET|̋h'~:O쨴lLF@><~"SZ3pcPLb0{c7^& 8E N'PP֖'Ǜ:@,/EtbD ;dS{]tYlm$SNMvSQuMc G~< wl .t^vUoju,-Oh e_CZ/{,LnO,Mf˯vǂ)WA=g6'ϧ.9—FΟ}L n}؏+ٴʼn/}jyxdZR-Zy杁@ʕW_yyu:4g+pݔ-SrjD-ꢅEWZWb{ã;|:K:سGWq~;{8ɭU&c_w}GKU[$+14hIjo&J՛ ]9{ZGgY]"7nb,\x<.3Sxr#? f%?82wxL>B3>PFm 'EOF[09Jl1YiSsђ|WY<b+.m&D:BDu}C8ɼԒ?LzrEÐL&`~j0HǵNǛ.Ft4eRQ281C=m^[9ř̽9eM3mu 5k7BK9_NEav8๑pbWRN{ٙƢ=;Gt67>\VdU״~{"'Қ͛s]OӃ}IuDBxB߹gOquvf:NG;#.]"Hӊ(iF!:ssVoMDvCbr sp0,vtI-UTKGVP# ux۰34j{4ڱd=D+Aՙ(>q KnE"i߱zrRU3ɪJ[+\Z=[pTuMQ3:x/z{V8cTl PU&G<586&w=GWw?<g2߲MLxC/>`9Oojq3Srr&GqzTqspeʯ4F9e)pYs R+M~d ωNՔWt3Y gE1NwMy6]QIM3.>5Hs\c/*͎ں3 u,5Mb!eN6+1TQ]!"js3/T&U>/r |(n(u '[[7Zjxޝo\#Դѡ*FiDUU@]]r82v/%z{dd套^4`(w}" U+uf7+aؾᮯEMyNL/-ν_itS|TSutZ4HWƓJp~ژ¡;UQրjf@5gg,J`zPQ@Mcs {[XQK/&2;k0cT#qTRyS>qae5Sؐ$ɕg}Bc0"j]L ]VgL8&$ǯW_9Dw_9^w6ʉ 4&آ`ExoaaL? @Jq$|lQ؎-M{6=1dxH<<4I1ZNӔޜ[,6mk׺׮U3.⽇ `0)sܻ[]M&7,–撋 0r21?7[PX`eu=e,ַo5c$I3;=,K&5 ӓc -;}:7uG1Ƙ*Rʞz&eO`>vƑmq]KaYY<u !jXUU]9^٤*iEQ,VlXcijs@,xl"^4"I!`2LkJrs}*wlVdh$'Z7oݱZiЍmv{^bZ5j.Mb oy/8)e10ypZ6"jj˟wD8.3 CaaoufD+qM8B8l#0!Md@s ~ &pO?iXb#0`s" QUv 0)5I䍞 ^0+AnAɦKKd|e~sk˽'D[_Hp³voqb00r;UK~0ƫ>xGͿs駿"{KKx_f,CϵwtIØռCn+Py4piJZny?W֎E%W:ϝM#딲Yxd.Р6FwUk"|{( Ljآ#J)Bܱ_|}Ka"h ^05N[r6|m~wcP]tk$iR -{q̌Qx5dίJ]4xő((Ϋ#%2Yczd9O R.txs 3:mSZzGZs vDzrk=p]:5p2~:[ׯ\JԷvq"\ڽX|v`9ڼw__W_p)%ޖ"r^z *,+װf!ȱawebO|?S&hT$ M&Ts[.]9y\-.,p-՜xh"/x$c +%4ײw}Dr=cƗh6f9Zzmh1ڊv4]. \ݷ;Ƌ~q+ֽV&_\抜Jgrhb߁g^ #Gw#njqagYgγJ#uuuhhw98 D+j!>'G^>\?sg)&ﺗ>d4U//.ɇ{[Y2XR׼m%Ozשιw4_^G8I39/&`zWǍ=LT'"o:z}V3.tkoݵ$t͍7:>Z<ʪƲ\OA/iB7n,FRBK3CVG~EڙDNWܮ5=3 s}p" zJ ,VqH_畎>lY(Z}Oo; 6a|9GTLO:vo6s؉o@Cjʼgy}ڋdճޢsv w\QީY)wBQf>glT{36ps[U|@ ڜU*%ޯR2TVRl#lըRw z7z*U6H$Ū7*U1h#^bii UgU4Z*Q2c88.a$I*JB7'bNơWu9*C[na:u^sv蚚[! ud2b8S j&@ hd]B (3/߭PW9!F$IQ iBg`W@$ZgW-: UժCU~{ba8M ԟ{I=AցMRJG 0'p#;rO* ?_B B5n*6ɥb@TpQ#8SW(@@$A1a$IJ5F>CA VVQaR  9^2+:rY|X*cI`*çHJeTJ$j:d-UBd?d$bsy)/ǔy'ʗşR?_<+yVyY~w" Z,@ Ô7rߠ iyů?*RY!6J;sundx16[PXh,,#{IXzPJ89`$^|tbˋYL rDC/b8gbDiiz& ,ݹ?RHE̘lhaxg2(*Cpv$58361ϧ>728.Cz}y|hn5ju8|[?<<294\Κ2N™šcnȔRQpqrtjޛMKS>V9\^$(Yʆ.' cybDTL:@2ŐŤ7R Ѯ)ȥc9W*5jPl0mQ1pL9! ?95 1REӄ1~?>IoyƽlufMl"^ ˊpvok8}N:}Y5\>-b涝[YvOkkc_2؝v|ZM :/cڿ?6_BqHR;ҵej"wItDNK)wT,,A1{K]|5x9TGsoo` Tpcm:]صcĩk5صp.' $YL=~-=py3[6*pY_ `ͩ'®& 3K)Gem%.XL8Q+%Rc$r(3f7\|mm5-tR>gCWa/Fhmj5HVg2PFq.ͻNFis5>6j-k݋.n8m#DnoP)Hv[cniopBShe-Aicws:zk,zJc z$N*JA )VDe*PKp#Zifl8RLb9Z۬a8N`68+Ж!Fg ح)R`AzƮhuSQָ\  |6]Im5$*ӪY&N ^㱙VkcC0.LѦo"l8n6[tZ,oo+bbg_eG{#h'ۍ#K2 pL9  ,TR@hI ;{IIKP, HJJ$1( +hAQyQ(,rU*2d%@2E^X%_$q CTpw&IyAI%DVh%2,T<0"01 $L1I.M/h(e$8W*H ȍǟǨbX^ 1 A$K@,/*iZX$EJA%9VESJ1Qp%e$%@21PaX"#kF JIDC"q(J2B7Ɠ'[0 $EdRl=4`#e BLJAH0g0$A$HZGJe b8@%X$#Y 1cеSI~*/VF)( <b<#)K2KUݶɓGߔ% q p $<(ٌvwvڹ $xQS%oT ",\ JlZvuiN,e+Tb<1iߒ7WK#2)fגedk$<73+"ƫDCwMMM.lL'\)[@\}銬Q W䕓aj_ [m.1N- Њr f4,g1rk3?8yuNR:ǖV#F`&X熮KF*R)1 +&L&Oc|!S⒩1,S\\jk/DVs, ŕVbkYISc}K|EVWX]Y]3Mt’_kWWO6Y^'2JRF[fʼɠ>>SsMo0h:FP( M <۹,.Y/_0|yucl&1XqN߻kKCb_ /TN'9nxj;hտ]>{s%Fdr)ΨZ{`߶Lf3Ѡ+Ǔ8I.^XFrjj2 M6~'! ⹓] K/_8|jy#6WfR.gJ٨˴V(-Zm\2)uVn湜vkGbb\!ƹc1j2Fs)QYʱD`˕VBhjYds XɘK&ZIdH,S`63S0P,Wb#r*v&, aŀ'Hk@ ./[N%\EkUs9\V@._FM2[P($Ryé |6P(8PB>[,+5FB,P!E*P\Q*t:)j I2cO %2%%Ȝgw!3 j6/9xnfY^*( QD00J.?Lܷ0h+9s+D@LFP*cl$$ >_Qo$#a}|"d6]~ſUTsoW`մUl"UUl!PT< @ {_٬D̈W5w]#&q"Z^"I& 1QD,ʲnd,BX(*qH@NxAEdJa6Ѹr"I" TQq +28.߂&H*@XRPZM $,/J [|p#KdʼnzFs  ZZ1t]Wnǿ8zK$G_fw^kH<?*[<ϱ{nՅ_ ľ_^i57Oux$e7jzCFtNճs_ENp|0y~QrH擱+/Nܿ{ۣ;:÷R=kؽkeX6N\KE׿_kdNWi1eo蒹F{/Τ\TV3֙AiY?3Ƿ\{,'n i27ѓI wS_;mi~ɛgn޵i!=G pʏ} (+Qeاw7fNG!~/R u[a%!Y$wcWSWdR(o`@6 &S&,>}ܠRtpVWmKU"-m5l"yQޱS4HJ9'\xm 駟rR1f4:T.2"@RguiTYҿȝ}͹ځs#{1KeE31x>]w &iZeML<"DōM_궐# `j55wmkO}G6SD Hcw1n+I`'+ zviq8;k6zص(K[![*L2}[NwюoಘN%se~}ɱqgKw<L}/*>Sg|߶! (sӢs<pvlpHJi0j\hD%lln7YlMr+%j64JŰzʱ&+:j[=z9UF݈nn&^Ohy== fbx@NB,״tXf˾ͿihuZӶkE)HL:٤f9yoxD68faY '[0hmɱ|疞NzdSQ՘_JHTnr̯H9N)m'vL0RffhR..85 yh̹^2=!o_O1»Ƌ\ľI75Y~'Izg[ BHd7Lg oe~F%Y$Z?lW %;@do߮>Xx=!% %fwȲd՛BAiA1y $ItKjiO9W>JտYZaI QkљL9xR1Ұ8Hb9{Μ/%㛮H k&fr-[a% x?}sFmk2Vt \fhQM"ʣ3n] C?Ovٟ~%43xyIJ3V ӟ{BQH2B<++VRn IFҙN{#;/޹v5nF(& Jd?yxFmz㹵d}v^YKJBiع_%\Q%&= Hkx;`*h}l:jlofhdm`q,, #@&gvF ;#KSqVu`[j`г׎NM׵76W]_Zlu[P<7 xa5׬zKrj B* [vVc/;vhrS[ךg{g pM+/}Gz%m Lcik- 9,&gcK 0dҪpBv;.Fk(ط`t6#iU78m&ey׵6yLNnp颌$D9&LilhsNZLy5uqG1=hǶkTb M-zfa2:mSGox{auja՛m59ԕ[ǟ}aGW#I6K68즎z۔/ɧTL8+>Hs1447T4v7L-ޫQBBu@ejM'bƎֶ~AdD:ƙ7}ouV]uGwm=~Q0k'C3UNh>aBWn9f~1Hv>&Xى2anoT\H^s5wj8@d 3ֆN#(̮:z?e9sjF IRYf _eʊ?SDŽ& , IDAT`Y`,( G@4K?$\~0P?٬ݘsor(ãszK"w-OFSʊA FFf2?wfv{Ż'8sR⥓o*-VlQWgҘhپ^?=:.Zfzhb.R4mN]:};gRչWVZ85)Hih׾lScƭ.ZMl{pbz$@Ȓ!_ꭞV}iŒ[;. / ?06/VSb#ˈ6ɦZsE]zel[ N5Gpa^ |aJoc@#d8H% b,t_$NMδvoI PN:6ܷWK}CD=p7Mٲ(DfVco7:fNZ(3zxa řp,SZkJ /,a8!xr/+K =Ė (uچ[k ~na#ؽGyH&c1h,ZƲeڊI2/N.`><'! 5RaN__XyEl8~Ȳ((݃~:cwmji{j0-X°:v%H_xz:.9e,r$d&___Y?sz͹%d|sooݳOM.oLll囸μ8DJHrbj5?w񊾾 :{MySZ92 ޺34No'Qgvy.hm7^{ȼ̀?)^/-+)ٛƒ%YHG2赛wVܟ ,M20tz0-H ׮Be`^ÙY;2kϟ8qC/t%Z}s ߿QNK@[ L^u_aXK' 즷~f2woh>Pj㋑酩)RܾpvqhgݛZ뷁i7nJo-VY^P*,˒$}O J/R%>w$ppg'SOf嫗o;L'-8D+e&|kܟ)KesoF&<;tgxu5MŲ{64¤?8/=3:~r,N]bx.K1u^qtQ"h[;jl; ,EQF8X(3Ih,lѺ苷tC#;ttp8*mNE<\|ips@^m\)3 C-ݽnǼc]$),!.?Vecqs9]Hs3ؤ˃?A.L\tHD{MS]1K[-fY.-vZԸv t;qbB~Od@QJe7j=u@˴TF`O]s׎Ʒ bd2zZ{=/ Q}g:P;;8e qj95+[nI]]w=H d7 KSR;LѪT$8Ap׸j4j(ڱmӧ.r@QPOZԺjԐ:X:]4 #QpJI7ȋh ]3TvܹU΁aXLeQܜnI`h4-2@L#|?°ugVɢ$gzw-2HS\6_(ʿg駎~W." ?W>~ A ,ZVS7ϼ Ju舀OXuN̦b'f*)c˫NR3Y8=*B*XM%rjbnl6&cEY +cs0znL[_@%$uBgWԹxwHMRP`X.T[/}EKCQ˗=9ѦJ66:|҃XL/h7+ќֱTq-MZFIF>?HuեX]],":<-`T1x¸U}F`Pb3RBt|nWUV׋NYJ#B'Ƚֈ&\4WN| TEZ%R._OSx^Cs% XX]"Y]gz/MM7$&6wuhZLA34Ց8lw߻u_J-YJWk]D[j=z/J&󃡙J-MW_~ͤ.Ǘ¡,p*3's[(Hʪ`m@[X,,O j_rP$I$yׄB|q֢0Qg\7`Di4zx(ժ?0WCQ1P8mu6å)V_sݑ{"v:v?X[kX"κ,!I+P]gT,*x)eEIMfB,FeZtFKsKYg5SSsuu."q!.JTLBXU:SIv6 &^Rh;=]IZk4.g.JgvjUޥ%+ciABA:ǶtvGf&Ju u`%Ë"mxZdz]r9U N.>)dX[O(੭%J(j9kvm鬿ywzd%] TUA@$!jӍ৶2!gE(e5,j" 돏tC8%g:x\;qjq ˜^$D+) K|IN6BNZeNo48,(ktBJ)aĐBAr1`@"pJdJ z2$YXWZ TAi2RJ\. &$-*BMA!_$D#I@x6WY-+|6SԹ J Z"3FgKEAIWnc !F*)(9٠׈"Ny)z | 9biV (b.#BB`0bN&:q(f9Vu4Z=ACmNQsxV>^ZѦM1ŸE$a}ػez'?{/C?@$ApƑ(l=$y#]}?(Ͽ7R"|X Ye1U~)jo8tzn2h}G&@^3%IES61tgqa*R&jw:ںZk  3%A@$CLQʊLDJP9AåJ9KjʷvsoҵE.ݿNbJ쥗~8x[1ah6 e 赍׻ʮ/hݹvu2Wr+dSտ3o!NSEakY?< MG@y@.o[] )skMdô% ʤGMRԾ.sOx05tc N̊ ^z8lkzfk9\_JR/|p/@@ 믚wjuKߌ^<+7NJ4&Mז4).Nx2wĩG:OSѽ;ʫ П~koxxC]>3QSxuKu@{k] ܾ?VΦ*i֍VN:j=IFсs:+! ޫMiXZJWx(3QPkՅ}Fbb3ޠʖdaC`iji $#@(hӭWS8N@(ZO`HBŦW8dqfv Unn4Z)1F@ĵZ5CBݹ؎ײwUh4[p@ RYrtH8%TkÉĕ0? 8P>r`{Roc*Y|o$=.Efh !cAޅюoI ]6mt[L}84q J`8!;jizlGj'$I,r9ҨoMXB(f.OZAYJAĒOJs:0#FK\"$T<j^M5 T`D,JBL2[PmFT cyyt l^CZCJ`$ (cɢ詵<^[-OY HZE8PJG ͪ!қT ="[1M\d"W[W#1`$ews$Th*REh]ze}ctzz0@5&(A٢hl&BU5k,zU1ZHk4 t(uqXQ"6=B* ZfҊl)!6q fsV Zo7\_/YxܖRYq>."ٜߡ|ǟ8_<_j4d2olw$K/;o$A$QĉM-[ýoT*qP?ywiCN@ +Bk)W#Lp/k?~Ȥ2Q:518TFPBTBtZT,C.Mzyqy>]{Lx1`@F]JU]\\=]]mn#t2 "E4`0`񶽯̼(JAJPEDG̬()T)h -(` iej$YljjFU_O1ƘPFcRЊxpfB[[Oy]lTߕ8_z^6#{?B5R+O~v77^czXŝ_xw.uh+\Y*D_ryyoYdk'_>u(ޚZz9o%dzᙎ|XYͥ0{m%Y:[T\8{ _r9#7nd.1GA{J+-Q*w7Ts(E9>qҩo|ޥ2bDd^v|L,ufWάo>sd/-wu{}*lcvn?s6#o m%su\_N/ˣvXn..y74n..P[ZGOyDc'/ I pU5k1 TާO=7T:+u{eݞ2Sg^x0P[GN#VK͍F$%j;W.Ttz{i=OT" VA67?x]_~՗a!>G4hm,~唁R6aUʘX[/QVUZ uaW^:5e&dtA7zCR2+*a }`7Kx_J-C^i$S@?y0q'a `qN Q#6뿢ruxhq10Bp;zn!qޚM=V9ag06 ^} 0@ FkBc$2}O>]l)G0-p$Kۍq4>1bftl*OHL&&&,VcQ[J_~;6-^';ow_hVѱg_9߻.o;Vc'N7<'~|謬5'޶WNv~|`0djdlcWe[o khyris5zɹIv|O6_yqi~& kS/}/ʞ_ BP0,>E9"c`z|8XJLP8T t"AW}CؙZ 1`{eo:"C 4M~Z"'[>|XBT-Ӊx`GLʺV]8B5$9jmBTV:L"n 7l6IF pc253{@99p숚؜_dmSkyH}]]-Uq3'&Jiq=ԓVV 6;pEQ:k39Snh8pnVo[H=!#Og߻;h=bOR}mO=D,#F8[90U~&ǭKgVPXr{7.$yE3r\ϊ&7.zf+?س2(/]I[7t+'fǦd|G68l7o칅 ۶]3]q؍޿pLCKC1O]ފH {԰:qt&9%fJ3Asad3ר%7ׯ8+Fn6[\kAg^s|f7jvߞͷo57~72Ɗ2o߸?q-PU 4ᵔkqgd16jc,!+wVPf'g޾X2G.ƽ{~ֹvHk^]g/NNMbՍFb^x-(s16<20 @?X+IH\e8[,JΝ'J&UUMszp{Gw'7V_%!LJ%(N_92<;0]Z_nvzEZݔQ6.%wb&1i8.(lֶwv2̠*ܶʹ_YeMlMMWbs)_DZ"╻X+HXottb2**ZLbIZk}7fX2$Ab2ު}ZV5hLX腫=ph1ȗ$eJ -ua-m{n  [ZJ/DG[{G]M}scy3skx?r& T׸vJ]Ꟈj!߶lsw$.\{ĩgvYUJL}Z_LR`0 -Her~?,YAnTKnw9VpEaXHA "7_ \˹v%f*9 r|kx'zoĕZrqY>'Pn:E;<^ݎF1N5 @y3R9'3!pƘy;c 裣O*(IRaAcja((1EQ׏W/h[c !d Hf1%suQQ"#DArZ-(t&Z!T~/MîFX%oKM0F,IոnNNխWOA zSB&P(}9@# !X7BIp0fJJ&馥,I@gB}BA)8D#,I"!݋ (JDV,0F@j3 tg*c|J9WR`LLFCXTU cDPDQBYUUuL&\4\ "1ArAsUE}0"ҽQ&J"jZXf D}cuP>)9&`0PJ5M6},&Q}PA!pN! 41 ]sCcR!!"11TB!MU0qc('jsc@DG2i$l9Ø K$ !,˒dZBVw ?fMl6GB!Cq]GYC (g (""Ƙ=y"6jB=#@L&3T*2!a~&@,"Rj*TQUB0B(PT>F2(("c1FiVfr "@(A1JeYE˜R8a #+style: * Mug /Mug/ is a toy/demo user-interface for =mu=. It is not installable, you'll need to run it from its source directory. Mug comes in two flavors: - =mug= (in toys/mug), old simple UI, only adding dependency to GTK+ - =mug2= (in toys/mug2), the new UI, which requires GTK+, Webkit and a recent GLib. The plan for =mug= is to be a testing ground for the widget-code which will slowly evolve into a full-featured UI. #+html: =mug2= supports: - HTML email - attachments (including in-place opening, drag & drop to desktop) - bookmarks (see the =mu-bookmarks= man page, the UI will load these in the left pane) - view source #+html: