From 2b0aca1bf7787abe898741ef890c0f9a51e504e5 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Wed, 25 Nov 2009 22:55:06 +0200 Subject: [PATCH] * initial import of mu - the next generation --- AUTHORS | 1 + COPYING | 674 ++++++++++++++++++++++++++++++ ChangeLog | 0 Makefile.am | 1 + NEWS | 0 README | 0 configure.ac | 88 ++++ src/Makefile.am | 36 ++ src/mu-index.c | 284 +++++++++++++ src/mu-index.h | 145 +++++++ src/mu-msg-fields.c | 260 ++++++++++++ src/mu-msg-fields.h | 88 ++++ src/mu-msg-flags.c | 199 +++++++++ src/mu-msg-flags.h | 117 ++++++ src/mu-msg-gmime.c | 849 ++++++++++++++++++++++++++++++++++++++ src/mu-msg-gmime.h | 267 ++++++++++++ src/mu-msg-str.c | 87 ++++ src/mu-msg-str.h | 94 +++++ src/mu-msg-xapian-priv.hh | 27 ++ src/mu-msg-xapian.cc | 234 +++++++++++ src/mu-msg-xapian.h | 52 +++ src/mu-msg.h | 62 +++ src/mu-path.c | 224 ++++++++++ src/mu-path.h | 74 ++++ src/mu-query-xapian.cc | 217 ++++++++++ src/mu-query-xapian.h | 96 +++++ src/mu-query.c | 263 ++++++++++++ src/mu-query.h | 10 + src/mu-result.h | 30 ++ src/mu-store-xapian.cc | 340 +++++++++++++++ src/mu-store-xapian.h | 50 +++ src/mu-util.c | 72 ++++ src/mu-util.h | 64 +++ src/mu.c | 135 ++++++ 34 files changed, 5140 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 configure.ac create mode 100644 src/Makefile.am create mode 100644 src/mu-index.c create mode 100644 src/mu-index.h create mode 100644 src/mu-msg-fields.c create mode 100644 src/mu-msg-fields.h create mode 100644 src/mu-msg-flags.c create mode 100644 src/mu-msg-flags.h create mode 100644 src/mu-msg-gmime.c create mode 100644 src/mu-msg-gmime.h create mode 100644 src/mu-msg-str.c create mode 100644 src/mu-msg-str.h create mode 100644 src/mu-msg-xapian-priv.hh create mode 100644 src/mu-msg-xapian.cc create mode 100644 src/mu-msg-xapian.h create mode 100644 src/mu-msg.h create mode 100644 src/mu-path.c create mode 100644 src/mu-path.h create mode 100644 src/mu-query-xapian.cc create mode 100644 src/mu-query-xapian.h create mode 100644 src/mu-query.c create mode 100644 src/mu-query.h create mode 100644 src/mu-result.h create mode 100644 src/mu-store-xapian.cc create mode 100644 src/mu-store-xapian.h create mode 100644 src/mu-util.c create mode 100644 src/mu-util.h create mode 100644 src/mu.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..3a546418 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Dirk-Jan C. Binnema diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + 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 +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..e69de29b diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..1bfdcf48 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=src diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..e69de29b diff --git a/README b/README new file mode 100644 index 00000000..e69de29b diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..d4154454 --- /dev/null +++ b/configure.ac @@ -0,0 +1,88 @@ +## Copyright (C) 2009 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_INIT([mu],[0.6],[http://www.djcbsoftware.nl/code/mu]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_SRCDIR([src/mu.c]) +AM_INIT_AUTOMAKE([dist-bzip2]) + +# we set the set the version of the metadata database schema here; +# it will become part of the db name, so we can automtically +# recreate the database when we incompatible changes. +# +# the Xapian and SQLite database share this number, so when +# we upgrade, we can simply update all. +# +# note that MU_DATABASE_VERSION does not necessarily follow +# MU versioning. +# +AC_DEFINE(MU_DATABASE_VERSION,["0.5"], [Schema version of the database]) + +AC_PROG_LIBTOOL + +if test x$prefix = xNONE; then + prefix=/usr/local +fi +AC_SUBST(prefix) + +AC_PROG_CC +AC_PROG_CXX +AM_PROG_CC_STDC +AC_HEADER_STDC + +# have pkg-config? +AC_PATH_PROG([PKG_CONFIG], [pkg-config], [no]) +if test "x$PKG_CONFIG" = "xno"; then + AC_MSG_ERROR([ + *** The pkg-config script could not be found. Please make sure it is + *** in your path, or set the PKG_CONFIG environment variable + *** to the full path to pkg-config.]) +fi + +# glib2? +PKG_CHECK_MODULES(GLIB,glib-2.0) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +# gmime2? +PKG_CHECK_MODULES(GMIME,gmime-2.4) +AC_SUBST(GMIME_CFLAGS) +AC_SUBST(GMIME_LIBS) + +# xapian? +AC_CHECK_PROG(XAPIAN,xapian-config,xapian-config,no) +AM_CONDITIONAL(HAVE_XAPIAN,test "x$XAPIAN" != "xno") +if test "x$XAPIAN" = "xno"; then + AC_MSG_ERROR([ + *** xapian could not be found; please install it + *** in debian/ubuntu the package wouldbe 'libxapian-dev']) +else + XAPIAN_CXXFLAGS=`$XAPIAN --cxxflags` + XAPIAN_LIBS=`$XAPIAN --libs` + have_xapian="yes" + AC_DEFINE(HAVE_XAPIAN,[1],[Whether we have Xapian]) +fi +AC_SUBST(XAPIAN_CXXFLAGS) +AC_SUBST(XAPIAN_LIBS) + + +AC_OUTPUT([ +Makefile +src/Makefile +]) + + +echo "now, type 'make' to build mu, or 'make test' to run the test suite" diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..ba20d6c9 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,36 @@ +INCLUDES=$(XAPIAN_CXXFLAGS) $(GMIME_CFLAGS) $(GLIB_CFLAGS) + +bin_PROGRAMS= \ + mu + +mu_SOURCES= \ + mu-index.c \ + mu-index.h \ + mu-msg-fields.c \ + mu-msg-fields.h \ + mu-msg-flags.c \ + mu-msg-flags.h \ + mu-msg-gmime.c \ + mu-msg-gmime.h \ + mu-msg-str.c \ + mu-msg-str.h \ + mu-msg-xapian-priv.hh \ + mu-msg-xapian.cc \ + mu-msg-xapian.h \ + mu-query.h \ + mu-query.c \ + mu-query-xapian.cc \ + mu-query-xapian.h \ + mu-path.c \ + mu-path.h \ + mu-result.h \ + mu-store-xapian.cc \ + mu-store-xapian.h \ + mu-util.h \ + mu-util.c \ + mu.c + +mu_LDADD= \ + $(XAPIAN_LIBS) \ + $(GMIME_LIBS) \ + $(GLIB_LIBS) diff --git a/src/mu-index.c b/src/mu-index.c new file mode 100644 index 00000000..781b68c7 --- /dev/null +++ b/src/mu-index.c @@ -0,0 +1,284 @@ +/* +** Copyright (C) 2008, 2009 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify +1** 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. +** +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /*HAVE_CONFIG_H*/ + +#include +#include +#include +#include +#include +#include + +#include "mu-path.h" +#include "mu-index.h" +#include "mu-store-xapian.h" + +struct _MuIndex { + MuStoreXapian *_xapian; +}; + +MuIndex* +mu_index_new (const char *xpath) +{ + MuIndex *index; + + g_return_val_if_fail (xpath, NULL); + + do { + index = g_new0 (MuIndex, 1); + index->_xapian = mu_store_xapian_new (xpath); + if (!index->_xapian) { + g_warning ("%s: failed to get xapian store (%s)", + __FUNCTION__, xpath); + break; + } + return index; + + } while (0); + + mu_index_destroy (index); + return NULL; +} + + +void +mu_index_destroy (MuIndex *index) +{ + if (!index) + return; + + mu_store_xapian_destroy (index->_xapian); + g_free (index); +} + + +struct _MuIndexCallbackData { + MuIndexMsgCallback _idx_msg_cb; + MuIndexDirCallback _idx_dir_cb; + MuStoreXapian* _xapian; + void* _user_data; + MuIndexStats* _stats; + gboolean _force; + time_t _dirstamp; +}; +typedef struct _MuIndexCallbackData MuIndexCallbackData; + +static MuResult +insert_or_update_maybe (const char* fullpath, time_t filestamp, + MuIndexCallbackData *data, gboolean *updated) +{ + MuMsgGMime *msg; + + *updated = FALSE; + + if ((size_t)filestamp <= (size_t)data->_dirstamp) { + if (!data->_force) + return MU_OK; + } + + msg = mu_msg_gmime_new (fullpath); + if (!msg) { + g_warning ("%s: failed to create mu_msg for %s", + __FUNCTION__, fullpath); + return MU_ERROR; + } + + /* we got a valid id; scan the message contents as well */ + if (mu_store_xapian_store (data->_xapian, msg) != MU_OK) { + g_warning ("%s: storing content %s failed", __FUNCTION__, + fullpath); + /* ignore...*/ + } + + mu_msg_gmime_destroy (msg); + + *updated = TRUE; + return MU_OK; +} + +static MuResult +run_msg_callback_maybe (MuIndexCallbackData *data) +{ + if (data && data->_idx_msg_cb) { + MuResult result = + data->_idx_msg_cb (data->_stats, data->_user_data); + if (result != MU_OK && result != MU_STOP) + g_warning ("%s: callback said %d", + __FUNCTION__, result); + } + return MU_OK; +} + + +static MuResult +on_run_maildir_msg (const char* fullpath, time_t filestamp, + MuIndexCallbackData *data) +{ + MuResult result; + gboolean updated; + + result = run_msg_callback_maybe (data); + if (result != MU_OK) + return result; + + /* see if we need to update/insert anything...*/ + result = insert_or_update_maybe (fullpath, filestamp, data, + &updated); + + /* update statistics */ + if (result == MU_OK && data && data->_stats) { + ++data->_stats->_processed; + if (data && data->_stats) { + if (updated) + ++data->_stats->_updated; + else + ++data->_stats->_uptodate; + } + } + + return result; +} + + +static MuResult +on_run_maildir_dir (const char* fullpath, gboolean enter, + MuIndexCallbackData *data) +{ + /* xapian stores a per-dir timestamp; we use this timestamp + * to determine whether a message is up-to-data + */ + if (enter) + data->_dirstamp = + mu_store_xapian_get_timestamp (data->_xapian, + fullpath); + else + mu_store_xapian_set_timestamp (data->_xapian, fullpath, + time(NULL)); + + if (data->_idx_dir_cb) + return data->_idx_dir_cb (fullpath, enter, + data->_user_data); + + return MU_OK; +} + +static gboolean +check_path (const char* path) +{ + g_return_val_if_fail (path, FALSE); + + if (!g_path_is_absolute (path)) { + g_warning ("%s: path is not absolute '%s'", + __FUNCTION__, path); + return FALSE; + } + + if (access (path, R_OK) != 0) { + g_warning ("%s: cannot open '%s': %s", + __FUNCTION__, path, strerror (errno)); + return FALSE; + } + + return TRUE; +} + + +MuResult +mu_index_run (MuIndex *index, const char* path, + gboolean force, MuIndexStats *stats, + MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb, + void *user_data) +{ + MuIndexCallbackData cb_data; + + g_return_val_if_fail (index && index->_xapian, MU_ERROR); + g_return_val_if_fail (check_path (path), MU_ERROR); + + cb_data._idx_msg_cb = msg_cb; + cb_data._idx_dir_cb = dir_cb; + + cb_data._user_data = user_data; + cb_data._xapian = index->_xapian; + cb_data._stats = stats; + cb_data._force = force; + + cb_data._dirstamp = 0; + + return mu_path_walk_maildir (path, + (MuPathWalkMsgCallback)on_run_maildir_msg, + (MuPathWalkDirCallback)on_run_maildir_dir, + &cb_data); +} + +static MuResult +on_stats_maildir_file (const char *fullpath, time_t timestamp, + MuIndexCallbackData *cb_data) +{ + MuResult result; + + if (cb_data && cb_data->_idx_msg_cb) + result = cb_data->_idx_msg_cb (cb_data->_stats, + cb_data->_user_data); + else + result = MU_OK; + + if (result == MU_OK) { + if (cb_data->_stats) + ++cb_data->_stats->_processed; + return MU_OK; + } else + return result; /* MU_STOP or MU_OK */ +} + + +MuResult +mu_index_stats (MuIndex *index, const char* path, + MuIndexStats *stats, MuIndexMsgCallback cb_msg, + MuIndexDirCallback cb_dir, void *user_data) +{ + MuIndexCallbackData cb_data; + + g_return_val_if_fail (index, MU_ERROR); + g_return_val_if_fail (check_path (path), MU_ERROR); + + cb_data._idx_msg_cb = cb_msg; + cb_data._idx_dir_cb = cb_dir; + + cb_data._stats = stats; + cb_data._user_data = user_data; + + cb_data._dirstamp = 0; + + return mu_path_walk_maildir (path, + (MuPathWalkMsgCallback)on_stats_maildir_file, + NULL,&cb_data); +} + + +MuResult +mu_index_cleanup (MuIndex *index, MuIndexStats *stats, + MuIndexMsgCallback msg_cb, + MuIndexDirCallback dir_cb, void *user_data) +{ + /* FIXME: implement this */ + return MU_OK; +} diff --git a/src/mu-index.h b/src/mu-index.h new file mode 100644 index 00000000..58645109 --- /dev/null +++ b/src/mu-index.h @@ -0,0 +1,145 @@ +/* +** Copyright (C) 2008 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_INDEX_H__ +#define __MU_INDEX_H__ + +#include "mu-result.h" /* for MuResult */ + +/* opaque structure */ +struct _MuIndex; +typedef struct _MuIndex MuIndex; + +struct _MuIndexStats { + int _processed; /* number of msgs processed or counted */ + int _updated; /* number of msgs new or updated */ + int _cleaned_up; /* number of msgs cleaned up */ + int _uptodate; /* number of msgs already uptodate */ +}; +typedef struct _MuIndexStats MuIndexStats; + + + +/** + * create a new MuIndex instance. NOTE(1): the database does not + * have to exist yet, but the directory already has to exist; + * NOTE(2): before doing anything with the returned Index object, + * make sure you haved called g_type_init, and mu_msg_init somewhere + * in your code. + * + * @param xpath path to the xapian db to store the results + * + * @return a new MuIndex instance, or NULL in case of error + */ +MuIndex* mu_index_new (const char* xpath); + + +/** + * destroy the index instance + * + * @param index a MuIndex instance, or NULL + */ +void mu_index_destroy (MuIndex *index); + + +/** + * callback function for mu_index_(run|stats|cleanup), for each message + * + * @param stats pointer to structure to receive statistics data + * @param user_data pointer to user data + * + * @return MU_OK to continue, MU_STOP to stop, or MU_ERROR in + * case of some error. + */ +typedef MuResult (*MuIndexMsgCallback) (MuIndexStats* stats, void *user_data); + + +/** + * callback function for mu_index_(run|stats|cleanup), for each dir enter/leave + * + * @param path dirpath we just entered / left + * @param enter did we enter (TRUE) or leave(FALSE) the dir? + * @param user_data pointer to user data + * + * @return MU_OK to contiue, MU_STOP to stopd or MU_ERROR in + * case of some error. + */ +typedef MuResult (*MuIndexDirCallback) (const char* path, gboolean enter, + void *user_data); + +/** + * start the indexing process + * + * @param index a valid MuIndex instance + * @param path the path to index + * @param force if != 0, force re-indexing already index messages; this is + * obviously a lot slower than only indexing new/changed messages + * @param result a structure with some statistics about the results + * @param cb_msg a callback function called for every msg indexed; + * @param cb_dir a callback function called for every dir entered/left; + * @param user_data a user pointer that will be passed to the callback function + * + * @return MU_OK if the stats gathering was completed succesfully, + * MU_STOP if the user stopped or MU_ERROR in + * case of some error. + */ +MuResult mu_index_run (MuIndex *index, const char* path, gboolean force, + MuIndexStats *result, MuIndexMsgCallback msg_cb, + MuIndexDirCallback dir_cb, void *user_data); + +/** + * gather some statistics about the Maildir; this is usually much faster + * than mu_index_run, and can thus be used to provide some information to the user + * note though that the statistics may be different from the reality that + * mu_index_run sees, when there are updates in the Maildir + * + * @param index a valid MuIndex instance + * @param path the path to get stats for + * @param result a structure with some statistics about the results + * @param cb a callback function which will be called for every msg; + * @param user_data a user pointer that will be passed to the callback function + * xb + * @return MU_OK if the stats gathering was completed succesfully, + * MU_STOP if the user stopped or MU_ERROR in + * case of some error. + */ +MuResult mu_index_stats (MuIndex *index, const char* path, MuIndexStats *result, + MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb, + void *user_data); + +typedef MuResult (*MuIndexCleanupCallback) (MuIndexStats*, void *user_data); + +/** + * cleanup the database; ie. remove entries for which no longer a corresponding + * file exists in the maildir + * + * @param index a valid MuIndex instance + * @param result a structure with some statistics about the results + * @param cb a callback function which will be called for every msg; + * @param user_data a user pointer that will be passed to the callback function + * + * @return MU_OK if the stats gathering was completed succesfully, + * MU_STOP if the user stopped or MU_ERROR in + * case of some error. + */ +MuResult mu_index_cleanup (MuIndex *index, MuIndexStats *result, + MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb, + void *user_data); + +#endif /*__MU_INDEX_H__*/ diff --git a/src/mu-msg-fields.c b/src/mu-msg-fields.c new file mode 100644 index 00000000..6a3846df --- /dev/null +++ b/src/mu-msg-fields.c @@ -0,0 +1,260 @@ +/* +** Copyright (C) 2008 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 +#include "mu-msg-fields.h" + +enum _FieldFlags { + FLAG_XAPIAN = 1 << 1, /* field available in xapian */ + FLAG_GMIME = 1 << 2, /* field available in gmime */ + FLAG_INDEX = 1 << 3 /* field is indexable */ +}; +typedef enum _FieldFlags FieldFlags; + +struct _MuMsgField { + const char *_name; + const char *_shortcut; + const char *_prefix; + MuMsgFieldId _id; + MuMsgFieldType _type; + + FieldFlags _flags; +}; + +static const MuMsgField FIELD_DATA[] = { + + { "body", "b", "B", + MU_MSG_FIELD_ID_BODY_TEXT, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME | FLAG_INDEX + }, + + { "bodyhtml", "h", NULL, + MU_MSG_FIELD_ID_BODY_HTML, + MU_MSG_FIELD_TYPE_STRING, + FLAG_GMIME + }, + + { "cc", "c", "C", + MU_MSG_FIELD_ID_CC, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME | FLAG_INDEX + }, + + { "date", "d", "D", + MU_MSG_FIELD_ID_DATE, + MU_MSG_FIELD_TYPE_TIME_T, + FLAG_XAPIAN | FLAG_GMIME + }, + + { "flags", "F", "G", + MU_MSG_FIELD_ID_FLAGS, + MU_MSG_FIELD_TYPE_INT, + FLAG_XAPIAN | FLAG_GMIME + }, + + { "from", "f", "F", + MU_MSG_FIELD_ID_FROM, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME | FLAG_INDEX + }, + + { "path", "p", "P", + MU_MSG_FIELD_ID_PATH, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME + }, + + { "prio", "P", "I", + MU_MSG_FIELD_ID_PRIORITY, + MU_MSG_FIELD_TYPE_INT, + FLAG_XAPIAN | FLAG_GMIME + }, + + { "size", "z", "Z", + MU_MSG_FIELD_ID_SIZE, + MU_MSG_FIELD_TYPE_BYTESIZE, + FLAG_XAPIAN | FLAG_GMIME + }, + + { "subject", "s", "S", + MU_MSG_FIELD_ID_SUBJECT, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME | FLAG_INDEX + }, + + { "to", "t", "T", + MU_MSG_FIELD_ID_TO, + MU_MSG_FIELD_TYPE_STRING, + FLAG_XAPIAN | FLAG_GMIME | FLAG_INDEX + }, + + { "msgid", "m", NULL, + MU_MSG_FIELD_ID_MSGID, + MU_MSG_FIELD_TYPE_STRING, + FLAG_GMIME + }, + + { "timestamp", "i", NULL, + MU_MSG_FIELD_ID_TIMESTAMP, + MU_MSG_FIELD_TYPE_TIME_T, + FLAG_GMIME + }, + + { NULL, NULL, NULL, 0, 0, 0 } +}; + +void +mu_msg_field_foreach (MuMsgFieldForEachFunc func, gconstpointer data) +{ + const MuMsgField* cursor = &FIELD_DATA[0]; + while (cursor->_name) { + func (cursor, data); + ++cursor; + } +} + +typedef gboolean (*FieldMatchFunc) (const MuMsgField *field, + gconstpointer data); + +static const MuMsgField* +find_field (FieldMatchFunc matcher, gconstpointer data) +{ + const MuMsgField* cursor = &FIELD_DATA[0]; + while (cursor->_name) { + if (matcher (cursor, data)) + return cursor; + ++cursor; + } + return NULL; +} + +static gboolean +match_name (const MuMsgField *field, const gchar* name) +{ + return strcmp (field->_name, name) == 0; +} + +const MuMsgField* +mu_msg_field_from_name (const char* str) +{ + g_return_val_if_fail (str, NULL); + return find_field ((FieldMatchFunc)match_name, str); +} + +static gboolean +match_shortcut (const MuMsgField *field, char kar) +{ + return field->_shortcut[0] == kar; +} + +const MuMsgField* +mu_msg_field_from_shortcut (char kar) +{ + return find_field ((FieldMatchFunc)match_shortcut, + GUINT_TO_POINTER((guint)kar)); +} + +static gboolean +match_id (const MuMsgField *field, MuMsgFieldId id) +{ + return field->_id == id; +} + +const MuMsgField* +mu_msg_field_from_id (MuMsgFieldId id) +{ + return find_field ((FieldMatchFunc)match_id, + GUINT_TO_POINTER(id)); +} + + +gboolean +mu_msg_field_is_xapian_enabled (const MuMsgField *field) +{ + g_return_val_if_fail (field, FALSE); + return field->_flags & FLAG_XAPIAN; +} + + +gboolean +mu_msg_field_is_gmime_enabled (const MuMsgField *field) +{ + g_return_val_if_fail (field, FALSE); + return field->_flags & FLAG_GMIME; +} + + +gboolean +mu_msg_field_is_xapian_indexable (const MuMsgField *field) +{ + g_return_val_if_fail (field, FALSE); + return field->_flags & FLAG_INDEX; +} + + +gboolean +mu_msg_field_is_numeric (const MuMsgField *field) +{ + MuMsgFieldType type; + + g_return_val_if_fail (field, FALSE); + + type = mu_msg_field_type (field); + + 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 (const MuMsgField *field) +{ + g_return_val_if_fail (field, NULL); + return field->_name; +} + +const char* +mu_msg_field_shortcut (const MuMsgField *field) +{ + g_return_val_if_fail (field, NULL); + return field->_shortcut; +} + +MuMsgFieldId +mu_msg_field_id (const MuMsgField *field) +{ + g_return_val_if_fail (field, MU_MSG_FIELD_ID_NONE); + return field->_id; +} + +const char* +mu_msg_field_xapian_prefix (const MuMsgField *field) +{ + g_return_val_if_fail (field, NULL); + return field->_prefix; +} + + +MuMsgFieldType +mu_msg_field_type (const MuMsgField *field) +{ + g_return_val_if_fail (field, MU_MSG_FIELD_TYPE_NONE); + return field->_type; +} diff --git a/src/mu-msg-fields.h b/src/mu-msg-fields.h new file mode 100644 index 00000000..70984793 --- /dev/null +++ b/src/mu-msg-fields.h @@ -0,0 +1,88 @@ +/* +** Copyright (C) 2008 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_MSG_FIELDS_H__ +#define __MU_MSG_FIELDS_H__ + +#include + +G_BEGIN_DECLS + +/* don't change the order, add new types at the end */ +enum _MuMsgFieldId { + MU_MSG_FIELD_ID_BODY_TEXT, + MU_MSG_FIELD_ID_BODY_HTML, + MU_MSG_FIELD_ID_CC, + MU_MSG_FIELD_ID_DATE, + MU_MSG_FIELD_ID_FLAGS, + MU_MSG_FIELD_ID_FROM, + MU_MSG_FIELD_ID_PATH, + MU_MSG_FIELD_ID_PRIORITY, + MU_MSG_FIELD_ID_SIZE, + MU_MSG_FIELD_ID_SUBJECT, + MU_MSG_FIELD_ID_TO, + MU_MSG_FIELD_ID_MSGID, + MU_MSG_FIELD_ID_TIMESTAMP, + + MU_MSG_FIELD_ID_NUM, +}; +typedef enum _MuMsgFieldId MuMsgFieldId; +static const guint MU_MSG_FIELD_ID_NONE = MU_MSG_FIELD_ID_NUM + 1; + +struct _MuMsgField; +typedef struct _MuMsgField MuMsgField; + +/* don't change the order, add new types at the end */ +enum _MuMsgFieldType { + MU_MSG_FIELD_TYPE_STRING, + + MU_MSG_FIELD_TYPE_BYTESIZE, + MU_MSG_FIELD_TYPE_TIME_T, + MU_MSG_FIELD_TYPE_INT, + + MU_MSG_FIELD_TYPE_NUM +}; +typedef enum _MuMsgFieldType MuMsgFieldType; +static const guint MU_MSG_FIELD_TYPE_NONE = MU_MSG_FIELD_TYPE_NUM + 1; + + +typedef void (*MuMsgFieldForEachFunc) (const MuMsgField *field, + gconstpointer data); +void mu_msg_field_foreach (MuMsgFieldForEachFunc func, gconstpointer data); + +const char* mu_msg_field_name (const MuMsgField *field) G_GNUC_CONST; +const char* mu_msg_field_shortcut (const MuMsgField *field) G_GNUC_CONST; +const char* mu_msg_field_xapian_prefix (const MuMsgField *field) G_GNUC_PURE; + +MuMsgFieldId mu_msg_field_id (const MuMsgField *field) G_GNUC_CONST; +MuMsgFieldType mu_msg_field_type (const MuMsgField *field) G_GNUC_CONST; + +gboolean mu_msg_field_is_numeric (const MuMsgField *field) G_GNUC_CONST; + +gboolean mu_msg_field_is_xapian_enabled (const MuMsgField *field) G_GNUC_PURE; +gboolean mu_msg_field_is_gmime_enabled (const MuMsgField *field) G_GNUC_PURE; +gboolean mu_msg_field_is_xapian_indexable (const MuMsgField *field) G_GNUC_PURE; + +const MuMsgField* mu_msg_field_from_name (const char* str) G_GNUC_PURE; +const MuMsgField* mu_msg_field_from_shortcut (char kar) G_GNUC_CONST; +const MuMsgField* mu_msg_field_from_id (MuMsgFieldId) G_GNUC_CONST; + +G_END_DECLS + +#endif /*__MU_MSG_FIELDS_H__*/ diff --git a/src/mu-msg-flags.c b/src/mu-msg-flags.c new file mode 100644 index 00000000..ec36a64e --- /dev/null +++ b/src/mu-msg-flags.c @@ -0,0 +1,199 @@ +/* +** Copyright (C) 2008 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 +#include + +#include "mu-msg-flags.h" + + +static struct { + char kar; + MuMsgFlags flag; + gboolean file_flag; +} FLAG_CHARS[] = { + {'D', MU_MSG_FLAG_DRAFT, TRUE}, + {'F', MU_MSG_FLAG_FLAGGED, TRUE}, + {'N', MU_MSG_FLAG_NEW, TRUE}, + {'P', MU_MSG_FLAG_PASSED, TRUE}, + {'R', MU_MSG_FLAG_REPLIED, TRUE}, + {'S', MU_MSG_FLAG_SEEN, TRUE}, + {'T', MU_MSG_FLAG_TRASHED, TRUE}, + {'a', MU_MSG_FLAG_HAS_ATTACH, FALSE}, + {'s', MU_MSG_FLAG_SIGNED, FALSE}, + {'x', MU_MSG_FLAG_ENCRYPTED, FALSE} +}; + + +MuMsgFlags +mu_msg_flags_from_str (const char* str) +{ + MuMsgFlags flags = 0; + while (str[0]) { + int i; + MuMsgFlags oneflag = MU_MSG_FLAG_UNKNOWN; + for (i = 0; i != G_N_ELEMENTS(FLAG_CHARS); ++i) { + if (str[0] == FLAG_CHARS[i].kar) { + oneflag = FLAG_CHARS[i].flag; + break; + } + } + if (oneflag == MU_MSG_FLAG_UNKNOWN) + return MU_MSG_FLAG_UNKNOWN; + else + flags |= oneflag; + + ++str; + } + + return flags; +} + +MuMsgFlags +mu_msg_flags_from_char (char c) +{ + char str[2]; + + str[0] = c; + str[1] = '\0'; + + return mu_msg_flags_from_str (str); +} + + +const char* +mu_msg_flags_to_str_s (MuMsgFlags flags) +{ + int i = 0, j = 0; + static char buf[G_N_ELEMENTS(FLAG_CHARS) + 1]; + + for (i = 0; i != G_N_ELEMENTS(FLAG_CHARS); ++i) + if (flags & FLAG_CHARS[i].flag) + buf[j++] = FLAG_CHARS[i].kar; + buf[j] = '\0'; + return buf; +} + + +gboolean +mu_msg_flags_is_file_flag (MuMsgFlags flag) +{ + int i = 0; + + for (i = 0; i != G_N_ELEMENTS(FLAG_CHARS); ++i) + if (flag == FLAG_CHARS[i].flag) + return FLAG_CHARS[i].file_flag; + + return FALSE; +} + + +/* + * is this a 'new' msg or a 'cur' msg?; if new, we return + * (in info) a ptr to the info part + */ +enum _MsgType { + MSG_TYPE_CUR, + MSG_TYPE_NEW, + MSG_TYPE_OTHER +}; +typedef enum _MsgType MsgType; + + +static MsgType +check_msg_type (const char* path, char **info) +{ + char *dir, *file; + MsgType mtype; + + /* 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' + */ + dir = g_path_get_dirname (path); + file = g_path_get_basename (path); + + if (!(*info = strrchr(file, ':'))) + *info = strrchr (file, '!'); /* Tinymail */ + if (*info) + ++(*info); /* skip the ':' or '!' */ + + if (g_str_has_suffix (dir, G_DIR_SEPARATOR_S "cur")) { + if (!*info) + g_message ("'cur' file, but no info part: %s", path); + mtype = MSG_TYPE_CUR; + } else if (g_str_has_suffix (dir, G_DIR_SEPARATOR_S "new")) { + /* if (*info) */ + /* g_message ("'new' file, ignoring info part: %s", path); */ + mtype = MSG_TYPE_NEW; + } else + mtype = MSG_TYPE_OTHER; /* file has been added explicitly as + a single message */ + if (*info) + *info = g_strdup (*info); + + g_free (dir); + g_free (file); + + return mtype; +} + +MuMsgFlags +mu_msg_flags_from_file (const char* path) +{ + MuMsgFlags flags; + MsgType mtype; + char *info = NULL; + + g_return_val_if_fail (path, MU_MSG_FLAG_UNKNOWN); + g_return_val_if_fail (!g_str_has_suffix(path,G_DIR_SEPARATOR_S), + MU_MSG_FLAG_UNKNOWN); + + mtype = check_msg_type (path, &info); + + /* we ignore any flags for a new message */ + if (mtype == MSG_TYPE_NEW) { + g_free (info); + return MU_MSG_FLAG_NEW; + } + + flags = 0; + if (mtype == MSG_TYPE_CUR || mtype == MSG_TYPE_OTHER) { + char *cursor = info; + /* only support the "2," format */ + if (cursor && cursor[0]=='2' && cursor[1]==',') { + cursor += 2; /* jump past 2, */ + while (*cursor) { + MuMsgFlags oneflag = + mu_msg_flags_from_char (*cursor); + /* ignore anything but file flags */ + if (mu_msg_flags_is_file_flag(oneflag)) + flags |= oneflag; + ++cursor; + } + } + } + g_free (info); + return flags; +} + diff --git a/src/mu-msg-flags.h b/src/mu-msg-flags.h new file mode 100644 index 00000000..b326d0b1 --- /dev/null +++ b/src/mu-msg-flags.h @@ -0,0 +1,117 @@ +/* +** Copyright (C) 2008 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_MSG_FLAGS_H__ +#define __MU_MSG_FLAGS_H__ + +#include + +G_BEGIN_DECLS + +enum _MuMsgFlags { + MU_MSG_FLAG_UNKNOWN = 0, + MU_MSG_FLAG_NONE = 1 << 0, + + /* these we get from the file */ + MU_MSG_FLAG_NEW = 1 << 1, + MU_MSG_FLAG_SEEN = 1 << 2, + MU_MSG_FLAG_UNREAD = 1 << 3, + MU_MSG_FLAG_REPLIED = 1 << 4, + MU_MSG_FLAG_FLAGGED = 1 << 5, + MU_MSG_FLAG_TRASHED = 1 << 6, + MU_MSG_FLAG_DRAFT = 1 << 7, + MU_MSG_FLAG_PASSED = 1 << 8, + + /* these we get from the contents */ + MU_MSG_FLAG_SIGNED = 1 << 10, + MU_MSG_FLAG_ENCRYPTED = 1 << 11, + MU_MSG_FLAG_HAS_ATTACH = 1 << 12 + + /* any new fields go here */ + /* so the existing numbers stay valid note that we're also */ + /* using these numbers in the database, so they should not change */ +}; +typedef enum _MuMsgFlags MuMsgFlags; + +/** + * convert the char-per-flag description into a MuMsgFlags value; the characters + * D=draft,F=flagged,N=new,P=passed,R=replied,S=seen,T=trashed + * a=has-attachment,s=signed, x=encrypted + * if any other characters are seen, MU_MSG_FLAG_UNKNOWN is returned. + * + * @param str a string + * + * @return a MuMSgFlags value, or MU_MSG_FLAG_UNKNOWN in case of error + */ +MuMsgFlags mu_msg_flags_from_str (const char* str) G_GNUC_PURE; + +/** + * convert the char-per-flag description into a MuMsgFlags value; NOTE, each + * of the characters must be a valid + * + * @param c a character + * + * @return a MuMSgFlags value, or MU_MSG_FLAG_UNKNOWN in case of error + */ +MuMsgFlags mu_msg_flags_from_char (char c) G_GNUC_CONST; + +/** + * get a 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_msg_flags_to_str_s returns a ptr to a static buffer, ie., + * ie, is not re-entrant. copy the string if needed. + * + * @param flags file flags + * + * @return a string representation of the flags + */ +const char* mu_msg_flags_to_str_s (MuMsgFlags flags) G_GNUC_CONST; + +/** + * get the Maildir flags from 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 name 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 + */ +MuMsgFlags mu_msg_flags_from_file (const char* pathname) G_GNUC_PURE; + +/** + * is the message flag a file flag? ie. encoded in the filename + * + * @param flag the flag to check; note, this should be a single flag + * not some flags OR'ed together + * + * @return TRUE if it is a file flag, FALSE otherwise + */ +gboolean mu_msg_flags_is_file_flag (MuMsgFlags flag) G_GNUC_CONST; + +G_END_DECLS + +#endif /*__MU_MSG_FLAGS_H__*/ + + diff --git a/src/mu-msg-gmime.c b/src/mu-msg-gmime.c new file mode 100644 index 00000000..f81767bd --- /dev/null +++ b/src/mu-msg-gmime.c @@ -0,0 +1,849 @@ +/* +** Copyright (C) 2008, 2009 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 +#include +#include +#include +#include +#include +#include +#include + +#include "mu-msg-gmime.h" + + +enum StringFields { + HTML_FIELD = 0, + TEXT_FIELD, + TO_FIELD, + CC_FIELD, + PATH_FIELD, + FLAGS_FIELD_STR, + + FIELD_NUM +}; + +struct _MuMsgGMime { + GMimeMessage *_mime_msg; + MuMsgFlags _flags; + + char* _fields[FIELD_NUM]; + + size_t _size; + time_t _timestamp; + MuMsgPriority _prio; +}; + + + + +void +mu_msg_gmime_destroy (MuMsgGMime *msg) +{ + int i; + + if (!msg) + return; + + if (G_IS_OBJECT(msg->_mime_msg)) { + g_object_unref (msg->_mime_msg); + msg->_mime_msg = NULL; + } + + for (i = 0; i != FIELD_NUM; ++i) + g_free (msg->_fields[i]); + + g_slice_free (MuMsgGMime, msg); +} + + +static gboolean +init_file_metadata (MuMsgGMime* msg, const char* path) +{ + struct stat statbuf; + + if (access (path, R_OK) != 0) { + g_warning ("%s: cannot read file %s: %s", + __FUNCTION__, path, strerror(errno)); + return FALSE; + } + + if (stat (path, &statbuf) < 0) { + g_warning ("%s: cannot stat %s: %s", + __FUNCTION__, path, strerror(errno)); + return FALSE; + } + + if (!S_ISREG(statbuf.st_mode)) { + g_warning ("%s: not a regular file: %s", + __FUNCTION__, path); + return FALSE; + } + + msg->_timestamp = statbuf.st_mtime; + msg->_size = statbuf.st_size; + msg->_fields[PATH_FIELD] = strdup (path); + + return TRUE; +} + + +static gboolean +init_mime_msg (MuMsgGMime *msg) +{ + FILE *file; + GMimeStream *stream; + GMimeParser *parser; + + file = fopen (mu_msg_gmime_get_path(msg), "r"); + if (!file) { + g_warning ("%s:cannot open %s: %s", + __FUNCTION__, mu_msg_gmime_get_path(msg), + strerror (errno)); + return FALSE; + } + + stream = g_mime_stream_file_new (file); + if (!stream) { + g_warning ("%s: cannot create mime stream", __FUNCTION__); + fclose (file); + return FALSE; + } + + parser = g_mime_parser_new_with_stream (stream); + g_object_unref (stream); + if (!parser) { + g_warning ("%s: cannot create mime parser", __FUNCTION__); + return FALSE; + } + + msg->_mime_msg = g_mime_parser_construct_message (parser); + g_object_unref (parser); + if (!msg->_mime_msg) { + g_warning ("%s: cannot create mime message", __FUNCTION__); + return FALSE; + } + + return TRUE; +} + + +MuMsgGMime* +mu_msg_gmime_new (const char* filepath) +{ + MuMsgGMime *msg; + + g_return_val_if_fail (filepath, NULL); + + msg = g_slice_new0 (MuMsgGMime); + if (!msg) + return NULL; + + if (!init_file_metadata(msg, filepath)) { + mu_msg_gmime_destroy (msg); + return NULL; + } + + if (!init_mime_msg(msg)) { + mu_msg_gmime_destroy (msg); + return NULL; + } + + return msg; +} + + +const char* +mu_msg_gmime_get_path (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + return msg->_fields[PATH_FIELD]; +} + + +const char* +mu_msg_gmime_get_subject (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + return g_mime_message_get_subject (msg->_mime_msg); +} + +const char* +mu_msg_gmime_get_msgid (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + return g_mime_message_get_message_id (msg->_mime_msg); +} + +const char* +mu_msg_gmime_get_from (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + return g_mime_message_get_sender (msg->_mime_msg); +} + + +const char* +mu_msg_gmime_get_to (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + if (!msg->_fields[TO_FIELD]) { + char *to; + InternetAddressList *recps; + recps = g_mime_message_get_recipients (msg->_mime_msg, + GMIME_RECIPIENT_TYPE_TO); + /* FIXME */ + to = (char*)internet_address_list_to_string (recps, TRUE); + if (to && strlen(to) == 0) + g_free (to); + else + msg->_fields[TO_FIELD] = to; + } + + return msg->_fields[TO_FIELD]; +} + +const char* +mu_msg_gmime_get_cc (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + if (!msg->_fields[CC_FIELD]) { + char *cc; + InternetAddressList *recps; + recps = g_mime_message_get_recipients (msg->_mime_msg, + GMIME_RECIPIENT_TYPE_CC); + + cc = internet_address_list_to_string (recps, TRUE); + if (cc && strlen(cc) == 0) + g_free (cc); + else + msg->_fields[CC_FIELD] = cc; + } + + return msg->_fields[CC_FIELD]; +} + + +time_t +mu_msg_gmime_get_date (MuMsgGMime *msg) +{ + time_t t; + + g_return_val_if_fail (msg, 0); + + /* TODO: is the GMT-offset relevant? */ + g_mime_message_get_date(msg->_mime_msg, &t, NULL); + + return t; +} + +static gboolean +part_is_inline (GMimeObject *part) +{ + GMimeContentDisposition *disp; + gboolean result; + const char *str; + + g_return_val_if_fail (GMIME_IS_PART(part), FALSE); + + disp = g_mime_object_get_content_disposition (part); + if (!GMIME_IS_CONTENT_DISPOSITION(disp)) + return FALSE; + + str = g_mime_content_disposition_get_disposition (disp); + + /* if it's not inline, it's an attachment */ + result = (str && (strcmp(str,GMIME_DISPOSITION_INLINE) == 0)); + + return result; +} + + +static void +msg_cflags_cb (GMimeObject *parent, GMimeObject *part, MuMsgFlags *flags) +{ + if (GMIME_IS_PART(part)) + if ((*flags & MU_MSG_FLAG_HAS_ATTACH) == 0) + if (!part_is_inline(part)) + *flags |= MU_MSG_FLAG_HAS_ATTACH; +} + + + +static MuMsgFlags +get_content_flags (MuMsgGMime *msg) +{ + GMimeContentType *ctype; + MuMsgFlags flags = 0; + GMimeObject *part; + + if (!GMIME_IS_MESSAGE(msg->_mime_msg)) { + g_warning ("Neeeeee!"); + return 0; + } + + g_mime_message_foreach (msg->_mime_msg, + (GMimeObjectForeachFunc)msg_cflags_cb, + &flags); + + /* note: signed or encrypted status for a message is determined by + * the top-level mime-part + */ + if ((part = g_mime_message_get_mime_part(msg->_mime_msg))) { + ctype = g_mime_object_get_content_type + (GMIME_OBJECT(part)); + if (!ctype) { + g_warning ("not a content type!"); + return 0; + } + + if (ctype) { + if (g_mime_content_type_is_type (ctype,"*", "signed")) + flags |= MU_MSG_FLAG_SIGNED; + if (g_mime_content_type_is_type (ctype,"*", "encrypted")) + flags |= MU_MSG_FLAG_ENCRYPTED; + } + } else + g_warning ("No top level mime part ?!"); + + return flags; +} + + +MuMsgFlags +mu_msg_gmime_get_flags (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, MU_MSG_FLAG_UNKNOWN); + + if (msg->_flags == MU_MSG_FLAG_UNKNOWN) { + msg->_flags = 0; + msg->_flags = mu_msg_flags_from_file (mu_msg_gmime_get_path(msg)); + msg->_flags |= get_content_flags (msg); + } + + return msg->_flags; +} + + +size_t +mu_msg_gmime_get_size (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, -1); + + return msg->_size; +} + + +static char* +to_lower (char *s) +{ + char *t = s; + while (t&&*t) { + t[0] = g_ascii_tolower(t[0]); + ++t; + } + return s; +} + + +static char* +get_prio_str (MuMsgGMime *msg) +{ + const char *str; + GMimeObject *obj; + + obj = GMIME_OBJECT(msg->_mime_msg); + + str = g_mime_object_get_header (obj, "X-Priority"); + if (!str) + str = g_mime_object_get_header (obj, "X-MSMail-Priority"); + if (!str) + str = g_mime_object_get_header (obj, "Importance"); + if (!str) + str = g_mime_object_get_header (obj, "Precedence"); + if (str) + return (to_lower(g_strdup(str))); + else + return NULL; +} + + +static MuMsgPriority +parse_prio_str (const char* priostr) +{ + int i; + struct { + const char* _str; + MuMsgPriority _prio; + } str_prio[] = { + { "high", MU_MSG_PRIORITY_HIGH }, + { "1", MU_MSG_PRIORITY_HIGH }, + { "2", MU_MSG_PRIORITY_HIGH }, + + { "normal", MU_MSG_PRIORITY_NORMAL }, + { "3", MU_MSG_PRIORITY_NORMAL }, + + { "low", MU_MSG_PRIORITY_LOW }, + { "list", MU_MSG_PRIORITY_LOW }, + { "bulk", MU_MSG_PRIORITY_LOW }, + { "4", MU_MSG_PRIORITY_LOW }, + { "5", MU_MSG_PRIORITY_LOW } + }; + + for (i = 0; i != G_N_ELEMENTS(str_prio); ++i) + if (g_strstr_len (priostr, -1, str_prio[i]._str) != NULL) + return str_prio[i]._prio; + + /* e.g., last-fm uses 'fm-user'... as precedence */ + return MU_MSG_PRIORITY_NORMAL; +} + + +MuMsgPriority +mu_msg_gmime_get_priority (MuMsgGMime *msg) +{ + char* priostr; + MuMsgPriority prio; + + g_return_val_if_fail (msg, 0); + + if (msg->_prio != MU_MSG_PRIORITY_NONE) + return msg->_prio; + + priostr = get_prio_str (msg); + if (!priostr) + return MU_MSG_PRIORITY_NORMAL; + + prio = parse_prio_str (priostr); + + g_free (priostr); + + return prio; +} + + +const char* +mu_msg_gmime_get_header (MuMsgGMime *msg, const char* header) +{ + g_return_val_if_fail (msg, NULL); + g_return_val_if_fail (header, NULL); + + return g_mime_object_get_header (GMIME_OBJECT(msg->_mime_msg), + header); +} + + +time_t +mu_msg_gmime_get_timestamp (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, 0); + + return msg->_timestamp; +} + +struct _GetBodyData { + GMimeObject *_txt_part, *_html_part; + gboolean _want_html; +}; +typedef struct _GetBodyData GetBodyData; + +static void +get_body_cb (GMimeObject *parent, GMimeObject *part, GetBodyData *data) +{ + GMimeContentDisposition *disp; + GMimeContentType *ct; + + /* already found what we're looking for? */ + if ((data->_want_html && data->_html_part != NULL) || + (!data->_want_html && data->_txt_part != NULL)) + return; + + ct = g_mime_object_get_content_type (part); + if (!GMIME_IS_CONTENT_TYPE(ct)) { + g_warning ("not a content type!"); + return; + } + + /* is it not an attachment? */ + disp = g_mime_object_get_content_disposition (GMIME_OBJECT(part)); + if (GMIME_IS_CONTENT_DISPOSITION(disp)) { + const char *str; + str = g_mime_content_disposition_get_disposition (disp); + if (str && (strcmp(str,GMIME_DISPOSITION_INLINE) != 0)) + return; /* not inline, so it's not the body */ + } + + /* is it right content type? */ + if (g_mime_content_type_is_type (ct, "text", "plain")) + data->_txt_part = part; + else if (g_mime_content_type_is_type (ct, "text", "html")) + data->_html_part = part; + else + return; /* wrong type */ +} + + +/* turn \0-terminated buf into ascii (which is a utf8 subset); + * convert any non-ascii into '.' + */ +static void +asciify (char *buf) +{ + char *c; + for (c = buf; c && *c; ++c) + if (!isascii(*c)) + c[0] = '.'; +} + + + +/* NOTE: buffer will be *freed* or returned unchanged */ +static char* +convert_to_utf8 (GMimePart *part, char *buffer) +{ + GMimeContentType *ctype; + const char* charset; + GError *err = NULL; + + g_return_val_if_fail (GMIME_IS_OBJECT(part), NULL); + + ctype = g_mime_object_get_content_type (GMIME_OBJECT(part)); + if (!GMIME_IS_CONTENT_TYPE(ctype)) { + g_warning ("not a content type!"); + return NULL; + } + + charset = g_mime_content_type_get_parameter (ctype, "charset"); + if (charset) + charset = g_mime_charset_iconv_name (charset); + + /* of course, the charset specified may be incorrect... */ + if (charset) { + char * utf8 = g_convert_with_fallback (buffer, -1, "UTF-8", + charset, (gchar*)".", + NULL, NULL, + &err); + if (!utf8) { + /* g_message ("%s: conversion failed from %s: %s", */ + /* __FUNCTION__, charset, */ + /* err ? err ->message : ""); */ + if (err) + g_error_free (err); + } else { + g_free (buffer); + return utf8; + } + } + + /* hmmm.... no charset at all, or conversion failed; ugly hack: + * replace all non-ascii chars with '.' instead... TODO: come up + * with something better */ + asciify (buffer); + return buffer; +} + + +static char* +part_to_string (GMimePart *part, gboolean convert_utf8) +{ + GMimeDataWrapper *wrapper; + GMimeStream *stream = NULL; + + ssize_t buflen, bytes; + char *buffer = NULL; + + g_return_val_if_fail (GMIME_IS_OBJECT(part), NULL); + + wrapper = g_mime_part_get_content_object (part); + if (!wrapper) { + g_warning ("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 */ + goto cleanup; + + buffer = (char*)malloc(buflen + 1); + if (!buffer) { + g_warning ("failed to allocate %d bytes", (int)buflen); + goto cleanup; + } + g_mime_stream_reset (stream); + + /* we read everything in one go */ + bytes = g_mime_stream_read (stream, buffer, buflen); + if (bytes < 0) { + free (buffer); + buffer = NULL; + } else + buffer[bytes]='\0'; + + /* convert_to_utf8 will free the old 'buffer' if needed */ + if (buffer && convert_utf8) + buffer = convert_to_utf8 (part, buffer); + +cleanup: + if (stream) + g_object_unref (G_OBJECT(stream)); + /* if (wrapper) */ + /* g_object_unref (G_OBJECT(wrapper)); */ + + return buffer; +} + + +static char* +mu_msg_gmime_get_body (MuMsgGMime *msg, gboolean want_html) +{ + GetBodyData data; + + g_return_val_if_fail (msg, NULL); + g_return_val_if_fail (GMIME_IS_OBJECT(msg->_mime_msg), NULL); + + memset (&data, 0, sizeof(GetBodyData)); + data._want_html = want_html; + + g_mime_message_foreach (msg->_mime_msg, + (GMimeObjectForeachFunc)get_body_cb, + &data); + if (want_html) + return data._html_part ? part_to_string (GMIME_PART(data._html_part), FALSE) : NULL; + else + return data._txt_part ? part_to_string (GMIME_PART(data._txt_part), TRUE) : NULL; +} + +const char* +mu_msg_gmime_get_body_html (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + if (msg->_fields[HTML_FIELD]) + return msg->_fields[HTML_FIELD]; + else { + return msg->_fields[HTML_FIELD] = mu_msg_gmime_get_body (msg, TRUE); + } +} + + +const char* +mu_msg_gmime_get_body_text (MuMsgGMime *msg) +{ + g_return_val_if_fail (msg, NULL); + + if (msg->_fields[TEXT_FIELD]) + return msg->_fields[TEXT_FIELD]; + else + return msg->_fields[TEXT_FIELD] = mu_msg_gmime_get_body (msg, FALSE); +} + + +const char* +mu_msg_gmime_get_field_string (MuMsgGMime *msg, const MuMsgField* field) +{ + MuMsgFieldId id; + + g_return_val_if_fail (msg, NULL); + id = mu_msg_field_id (field); + g_return_val_if_fail (id != MU_MSG_FIELD_ID_NONE, NULL); + + switch (id) { + case MU_MSG_FIELD_ID_BODY_TEXT: return mu_msg_gmime_get_body_text (msg); + case MU_MSG_FIELD_ID_BODY_HTML: return mu_msg_gmime_get_body_html (msg); + case MU_MSG_FIELD_ID_CC: return mu_msg_gmime_get_cc (msg); + case MU_MSG_FIELD_ID_FROM: return mu_msg_gmime_get_from (msg); + case MU_MSG_FIELD_ID_PATH: return mu_msg_gmime_get_path (msg); + case MU_MSG_FIELD_ID_SUBJECT: return mu_msg_gmime_get_subject (msg); + case MU_MSG_FIELD_ID_TO: return mu_msg_gmime_get_to (msg); + case MU_MSG_FIELD_ID_MSGID: return mu_msg_gmime_get_to (msg); + default: + g_return_val_if_reached (NULL); + } +} + +gint64 +mu_msg_gmime_get_field_numeric (MuMsgGMime *msg, const MuMsgField* field) +{ + MuMsgFieldId id; + + g_return_val_if_fail (msg, 0); + id = mu_msg_field_id (field); + g_return_val_if_fail (id != MU_MSG_FIELD_ID_NONE, 0); + + switch (id) { + case MU_MSG_FIELD_ID_DATE: + return mu_msg_gmime_get_date(msg); + case MU_MSG_FIELD_ID_FLAGS: + return mu_msg_gmime_get_flags(msg); + case MU_MSG_FIELD_ID_PRIORITY: + return mu_msg_gmime_get_priority(msg); + case MU_MSG_FIELD_ID_SIZE: + return mu_msg_gmime_get_size(msg); + default: + g_warning ("%s: %u", __FUNCTION__, (guint)id); + g_return_val_if_reached (0); + } +} + + +static int +mu_msg_gmime_get_contacts_from (MuMsgGMime *msg, MuMsgGMimeContactsCallback cb, + void *ptr) +{ + int i; + InternetAddressList *list; + + /* we go through this whole excercise of trying to get a *list* + * of 'From:' address (usually there is only one...), because + * internet_address_parse_string has the nice side-effect of + * splitting in names and addresses for us */ + + list = internet_address_list_parse_string ( + g_mime_message_get_sender (msg->_mime_msg)); + + + for (i = 0; i != internet_address_list_length(list); ++i) { + + MuMsgContact contact; /* stack allocated */ + InternetAddress *addr = + internet_address_list_get_address (list, i); + if (addr) { + int result; + + contact._name = internet_address_get_name (addr); + contact._type = MU_MSG_CONTACT_TYPE_FROM; + + /* we only support internet addresses; + * if we don't check, g_mime hits an assert + */ + contact._addr = internet_address_mailbox_get_addr + (INTERNET_ADDRESS_MAILBOX(addr)); + result = (cb)(&contact,ptr); + + /* note: don't unref addr here, as it's owned */ + /* by the list (at least that is what valgrind tells... */ + if ((result = (cb)(&contact,ptr)) != 0) { + /* callback tells us to stop */ + if (list) + g_object_unref (G_OBJECT(list)); + + return result; + } + } + } + + if (list) + g_object_unref (G_OBJECT(list)); + + return 0; +} + + +int +mu_msg_gmime_get_contacts_foreach (MuMsgGMime *msg, + MuMsgGMimeContactsCallback cb, + void *ptr) +{ + int i, result; + + struct { + GMimeRecipientType _gmime_type; + MuMsgContactType _type; + } ctypes[] = { + {GMIME_RECIPIENT_TYPE_TO, MU_MSG_CONTACT_TYPE_TO}, + {GMIME_RECIPIENT_TYPE_CC, MU_MSG_CONTACT_TYPE_CC}, + {GMIME_RECIPIENT_TYPE_BCC, MU_MSG_CONTACT_TYPE_BCC}, + }; + + g_return_val_if_fail (cb && msg, -1); + + /* first, get the from address */ + if ((result = mu_msg_gmime_get_contacts_from (msg, cb, ptr)) != 0) + return result; /* callback told us to stop */ + + for (i = 0; i != sizeof(ctypes)/sizeof(ctypes[0]); ++i) { + + MuMsgContact contact; /* stack allocated */ + InternetAddressList *list; + int i; + + list = g_mime_message_get_recipients + (msg->_mime_msg, ctypes[i]._gmime_type); + + for (i = 0; i != internet_address_list_length(list); ++i) { + + InternetAddress *addr = + internet_address_list_get_address (list, i); + if (addr) { + + contact._name = internet_address_get_name (addr); + contact._type = ctypes[i]._type; + + /* we only support internet addresses; + * if we don't check, g_mime hits an assert + */ + contact._addr = internet_address_mailbox_get_addr( + INTERNET_ADDRESS_MAILBOX(addr)); + + result = (cb)(&contact,ptr); + + if (result != 0) /* callback tells us to stop */ + return result; + } + } + } + return 0; +} + + + + +static gboolean _initialized = FALSE; + +void +mu_msg_gmime_init (void) +{ + if (!_initialized) { + g_mime_init(0); + _initialized = TRUE; + } +} + + +void +mu_msg_gmime_uninit (void) +{ + if (_initialized) { + g_mime_shutdown(); + _initialized = FALSE; + } +} diff --git a/src/mu-msg-gmime.h b/src/mu-msg-gmime.h new file mode 100644 index 00000000..345f2144 --- /dev/null +++ b/src/mu-msg-gmime.h @@ -0,0 +1,267 @@ +/* +** Copyright (C) 2008 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_MSG_READ_H__ +#define __MU_MSG_READ_H__ + +#include "mu-msg.h" +#include "mu-path.h" + +G_BEGIN_DECLS + +struct _MuMsgGMime; +typedef struct _MuMsgGMime MuMsgGMime; + +/** + * initialize the message parsing system; this function must be called + * before doing any message parsing (ie., any of the other + * mu_msg_gmime functions). when done with the message parsing system, + * call mu_msg_gmime_uninit. Note: calling this function on an already + * initialized system has no effect + */ +void mu_msg_gmime_init (void); + +/** + * uninitialize the messge parsing system that has previously been + * initialized with mu_msg_init. not calling mu_msg_uninit after + * mu_msg_init has been called will lead to memory leakage. Note: + * calling mu_msg_uninit on an uninitialized system has no + * effect + */ +void mu_msg_gmime_uninit (void); + + +/** + * create a new MuMsgGMime* instance which parses a message and provides + * read access to its properties; call mu_msg_destroy when done with + * done with it. + * + * @param path full path to an email message file + * + * @return a new MuMsgGMime instance or NULL in case of error + */ +MuMsgGMime* mu_msg_gmime_new (const char* filepath); + + +/** + * destroy a MuMsgGMime* instance; call this function when done with + * a MuMsgGMime + * + * @param msg a MuMsgGMime* instance or NULL + */ +void mu_msg_gmime_destroy (MuMsgGMime *msg); + + + +/** + * get the plain text body of this message + * + * @param a valid MuMsgGMime* instance + * + * @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_gmime_get_body_text (MuMsgGMime *msg); + + +/** + * get the html body of this message + * + * @param a valid MuMsgGMime* instance + * + * @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_gmime_get_body_html (MuMsgGMime *msg); + + +/** + * get the sender (From:) of this message + * + * @param a valid MuMsgGMime* 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_gmime_get_from (MuMsgGMime *msg); + + +/** + * get the recipients (To:) of this message + * + * @param a valid MuMsgGMime* 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_gmime_get_to (MuMsgGMime *msg); + + +/** + * get the carbon-copy recipients (Cc:) of this message + * + * @param a valid MuMsgGMime* 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_gmime_get_cc (MuMsgGMime *msg); + +/** + * get the file system path of this message + * + * @param a valid MuMsgGMime* 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_gmime_get_path (MuMsgGMime *msg); + +/** + * get the subject of this message + * + * @param a valid MuMsgGMime* 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_gmime_get_subject (MuMsgGMime *msg); + +/** + * get the Message-Id of this message + * + * @param a valid MuMsgGMime* instance + * + * @return the Message-Id of this Message or NULL in case of error or if there + * is none. the returned string should *not* be modified or freed. + */ +const char* mu_msg_gmime_get_msgid (MuMsgGMime *msg); + + +/** + * get any arbitrary header from this message + * + * @param a valid MuMsgGMime* instance + * @header the header requested + * + * @return the header requested or NULL in case of error or if there + * is no such header. the returned string should *not* be modified or freed. + */ +const char* mu_msg_gmime_get_header (MuMsgGMime *msg, + const char* header); + +/** + * get the message date/time (the Date: field) as time_t, using UTC + * + * @param a valid MuMsgGMime* instance + * + * @return message date/time or 0 in case of error or if there + * is no such header. + */ +time_t mu_msg_gmime_get_date (MuMsgGMime *msg); + +/** + * get the flags for this message + * + * @param msg valid MuMsgGMime* instance + * + * @return the fileflags as logically OR'd #Mu MsgFlags or 0 if + * there are none. + */ +MuMsgFlags mu_msg_gmime_get_flags (MuMsgGMime *msg); + + +/** + * get the file size in bytes of this message + * + * @param a valid MuMsgGMime* instance + * + * @return the filesize + */ +size_t mu_msg_gmime_get_size (MuMsgGMime *msg); + + +/** + * get some field value as string + * + * @param msg a valid MuMsgGmime 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_gmime_get_field_string (MuMsgGMime *msg, + const MuMsgField* field); + +/** + * get some field value as string + * + * @param msg a valid MuMsgGmime instance + * @param field the field to retrieve; it must be a numeric field + * + * @return a string that should not be freed + */ +gint64 mu_msg_gmime_get_field_numeric (MuMsgGMime *msg, + const MuMsgField* field); + +/** + * get the message priority for this message + * (MU_MSG_PRIORITY_LOW, MU_MSG_PRIORITY_NORMAL or MU_MSG_PRIORITY_HIGH) + * the X-Priority, X-MSMailPriority, Importance and Precedence header are + * checked, in that order. + * if no explicit priority is set, MU_MSG_PRIORITY_NORMAL is assumed + * + * @param a valid MuMsgGMime* instance + * + * @return the message priority (!= 0) or 0 in case of error + */ +MuMsgPriority mu_msg_gmime_get_priority (MuMsgGMime *msg); + +/** + * get the timestamp (mtime) for the file containing this message + * + * @param a valid MuMsgGMime* instance + * + * @return the timestamp or 0 in case of error + */ +time_t mu_msg_gmime_get_timestamp (MuMsgGMime *msg); + + +typedef int (*MuMsgGMimeContactsCallback) (MuMsgContact*, void *ptr); + + +/** + * call a function for each of the contacts in a message + * + * @param msg a valid MuMsgGMime* instance + * @param cb a callback function to call for each contact; when + * the callback returns non-0, the function stops, and this last + * callback return value is returned + * @param ptr a user-provide pointer that will be passed to the callback + * + * @return 0 if the callback was called for each recipient, -1 if there + * was an error and any other != 0 number the callback returned + */ +int mu_msg_gmime_get_contacts_foreach (MuMsgGMime *msg, + MuMsgGMimeContactsCallback cb, + void *ptr); +G_END_DECLS + +#endif /*__MU_MSG_H__*/ diff --git a/src/mu-msg-str.c b/src/mu-msg-str.c new file mode 100644 index 00000000..86667ffd --- /dev/null +++ b/src/mu-msg-str.c @@ -0,0 +1,87 @@ +/* +** Copyright (C) 2008 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 +#include "mu-msg-str.h" +#include "mu-msg-flags.h" + +const char* +mu_msg_str_date_s (time_t t) +{ + struct tm *tmbuf; + static char buf[64]; + + tmbuf = localtime(&t); + + strftime (buf, 64, "%c", tmbuf); + + return buf; +} + +char* +mu_msg_str_date (time_t t) +{ + return g_strdup (mu_msg_str_date_s(t)); +} + + +const char* +mu_msg_str_size_s (size_t s) +{ + static char buf[32]; + if (s >= 1000 * 1000) + g_snprintf(buf, 32, "%.1fM", (double)s/(1000*1000)); + else + g_snprintf(buf, 32, "%.1fk", (double)s/(1000)); + + return buf; +} + +char* +mu_msg_str_size (size_t s) +{ + return g_strdup (mu_msg_str_size_s(s)); +} + +const char* +mu_msg_str_flags_s (MuMsgFlags flags) +{ + return mu_msg_flags_to_str_s (flags); +} + +char* +mu_msg_str_flags (MuMsgFlags flags) +{ + return g_strdup (mu_msg_str_flags_s(flags)); +} + +const char* +mu_msg_str_prio (MuMsgPriority prio) +{ + switch (prio) { + case MU_MSG_PRIORITY_LOW: return "low"; + case MU_MSG_PRIORITY_NORMAL: return "normal"; + case MU_MSG_PRIORITY_HIGH: return "high"; + default: + g_warning ("%s: invalid priority %d", __FUNCTION__, prio); + return "Err"; + } +} + + diff --git a/src/mu-msg-str.h b/src/mu-msg-str.h new file mode 100644 index 00000000..39d0dae7 --- /dev/null +++ b/src/mu-msg-str.h @@ -0,0 +1,94 @@ +/* +** Copyright (C) 2008 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_MSG_STR_H__ +#define __MU_MSG_STR_H__ + +#include +#include + +#include "mu-msg.h" +#include "mu-msg-flags.h" + +/** + * get a display string for a given time_t; + * use the preferred date/time for the current locale + * (ie., '%c' in strftime). + * + * mu_msg_str_date_s returns a ptr to a static buffer, + * while mu_msg_str_date returns dynamically allocated + * memory that must be freed after use. + * + * @param t the time as time_t + * + * @return a string representation of the time; see above + * for what to do with it + */ +const char* mu_msg_str_date_s (time_t t) G_GNUC_CONST; +char* mu_msg_str_date (time_t t); + + +/** + * get a display size for a given off_t; + * uses M for sizes > 1000*1000, k for smaller sizes + * + * mu_msg_str_size_s returns a ptr to a static buffer, + * while mu_msg_str_size returns dynamically allocated + * memory that must be freed after use. + * + * @param t the size as an off_t + * + * @return a string representation of the size; see above + * for what to do with it + */ +const char* mu_msg_str_size_s (size_t s) G_GNUC_CONST; +char* mu_msg_str_size (size_t s); + +/** + * 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_msg_str_file_flags_s returns a ptr to a static buffer, + * while mu_msg_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_msg_str_flags_s (MuMsgFlags flags) G_GNUC_CONST; +char* mu_msg_str_flags (MuMsgFlags flags); + + +/** + * get a display string for a message priority; either + * High,Low or Normal + * + * @param flags file flags + * + * @return a string representation of the priority; see above + * for what to do with it, or NULL in case of error + */ +const char* mu_msg_str_prio (MuMsgPriority prio) G_GNUC_CONST; + + +#endif /*__MU_MSG_STR_H__*/ diff --git a/src/mu-msg-xapian-priv.hh b/src/mu-msg-xapian-priv.hh new file mode 100644 index 00000000..67306c58 --- /dev/null +++ b/src/mu-msg-xapian-priv.hh @@ -0,0 +1,27 @@ +/* +** Copyright (C) 2008 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_MSG_XAPIAN_PRIV_HH__ +#define __MU_MSG_XAPIAN_PRIV_HH__ + +#include + +MuMsgXapian *mu_msg_xapian_new (const Xapian::Enquire& enq, size_t batchsize); + +#endif /*__MU_MSG_XAPIAN_PRIV_HH__*/ diff --git a/src/mu-msg-xapian.cc b/src/mu-msg-xapian.cc new file mode 100644 index 00000000..f0d10ce2 --- /dev/null +++ b/src/mu-msg-xapian.cc @@ -0,0 +1,234 @@ +/* +** Copyright (C) 2008 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 +#include +#include + +#include "xapian.h" +#include "mu-msg-xapian.h" + +struct _MuMsgXapian { + + Xapian::Enquire *_enq; + Xapian::MSet _matches; + Xapian::MSet::const_iterator _cursor; + size_t _batchsize; + size_t _offset; + char* _str[MU_MSG_FIELD_ID_NUM]; +}; + +MuMsgXapian * +mu_msg_xapian_new (const Xapian::Enquire& enq, size_t batchsize) +{ + MuMsgXapian *msg; + + try { + msg = new MuMsgXapian; + memset (msg->_str, 0, sizeof(msg->_str)); + + msg->_enq = new Xapian::Enquire(enq); + msg->_matches = msg->_enq->get_mset (0, batchsize); + if (!msg->_matches.empty()) + msg->_cursor = msg->_matches.begin(); + + msg->_batchsize = batchsize; + msg->_offset = 0; + + return msg; + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + return msg; +} + +void +mu_msg_xapian_destroy (MuMsgXapian *msg) +{ + if (msg) { + for (int i = 0; i != MU_MSG_FIELD_ID_NUM; ++i) + g_free (msg->_str[i]); + + delete msg->_enq; + delete msg; + } +} + +gboolean +mu_msg_xapian_next (MuMsgXapian *msg) +{ + g_return_val_if_fail (msg, FALSE); + g_return_val_if_fail (!mu_msg_xapian_is_done(msg), FALSE); + + if (++msg->_cursor == msg->_matches.end()) + return FALSE; /* no more matches */ + + for (int i = 0; i != MU_MSG_FIELD_ID_NUM; ++i) { + g_free (msg->_str[i]); + msg->_str[i] = NULL; + } + + return TRUE; +} + + +gboolean +mu_msg_xapian_is_done (MuMsgXapian *msg) +{ + if (msg->_matches.empty()) + return TRUE; + if (msg->_cursor == msg->_matches.end()) + return TRUE; + return FALSE; +} + + +const gchar* +mu_msg_xapian_get_field (MuMsgXapian *row, const MuMsgField *field) +{ + g_return_val_if_fail (row, NULL); + g_return_val_if_fail (!mu_msg_xapian_is_done(row), NULL); + g_return_val_if_fail (field, NULL); + + try { + MuMsgFieldId id = mu_msg_field_id (field); + if (!row->_str[id]) { /* cache the value */ + Xapian::Document doc (row->_cursor.get_document()); + row->_str[id] = g_strdup (doc.get_value(id).c_str()); + } + return row->_str[id]; + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + return NULL; +} + +gint64 +mu_msg_xapian_get_field_numeric (MuMsgXapian *row, const MuMsgField *field) +{ + g_return_val_if_fail (mu_msg_field_is_numeric(field), -1); + + return Xapian::sortable_unserialise( + mu_msg_xapian_get_field(row, field)); +} + + + +static const gchar* +get_field (MuMsgXapian *row, MuMsgFieldId id) +{ + return mu_msg_xapian_get_field(row, mu_msg_field_from_id (id)); +} + + + +unsigned int +mu_msg_xapian_get_id (MuMsgXapian *row) +{ + g_return_val_if_fail (row, NULL); + const char *str; + + try { + return row->_cursor.get_document().get_docid(); + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + return 0; +} + + +const char* +mu_msg_xapian_get_path(MuMsgXapian *row) +{ + return get_field (row, MU_MSG_FIELD_ID_PATH); +} + + +const char* +mu_msg_xapian_get_from (MuMsgXapian *row) +{ + return get_field (row, MU_MSG_FIELD_ID_FROM); +} + +const char* +mu_msg_xapian_get_to (MuMsgXapian *row) +{ + return get_field (row, MU_MSG_FIELD_ID_TO); +} + + +const char* +mu_msg_xapian_get_cc (MuMsgXapian *row) +{ + return get_field (row, MU_MSG_FIELD_ID_CC); +} + + +const char* +mu_msg_xapian_get_subject (MuMsgXapian *row) +{ + return get_field (row, MU_MSG_FIELD_ID_SUBJECT); +} + +size_t +mu_msg_xapian_get_size (MuMsgXapian *row) +{ + return atoi(get_field (row, MU_MSG_FIELD_ID_SIZE)); +} + + +time_t +mu_msg_xapian_get_date (MuMsgXapian *row) +{ + return atoi(get_field (row, MU_MSG_FIELD_ID_DATE)); +} + + +MuMsgFlags +mu_msg_xapian_get_flags (MuMsgXapian *row) +{ + return (MuMsgFlags)atoi(get_field + (row, MU_MSG_FIELD_ID_FLAGS)); + +} + +MuMsgPriority +mu_msg_xapian_get_priority (MuMsgXapian *row) +{ + return (MuMsgPriority)atoi(get_field + (row, MU_MSG_FIELD_ID_PRIORITY)); +} diff --git a/src/mu-msg-xapian.h b/src/mu-msg-xapian.h new file mode 100644 index 00000000..ec34f30c --- /dev/null +++ b/src/mu-msg-xapian.h @@ -0,0 +1,52 @@ +/* +** Copyright (C) 2008 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_MSG_XAPIAN_H__ +#define __MU_MSG_XAPIAN_H__ + +#include "mu-msg.h" + +G_BEGIN_DECLS + +struct _MuMsgXapian; +typedef struct _MuMsgXapian MuMsgXapian; + +gboolean mu_msg_xapian_next (MuMsgXapian *msg); +gboolean mu_msg_xapian_is_done (MuMsgXapian *msg); +void mu_msg_xapian_destroy (MuMsgXapian *msg); + +unsigned int mu_msg_xapian_get_id (MuMsgXapian *row); +const char* mu_msg_xapian_get_path (MuMsgXapian *row); +size_t mu_msg_xapian_get_size (MuMsgXapian *row); +time_t mu_msg_xapian_get_timestamp (MuMsgXapian *row); +time_t mu_msg_xapian_get_date (MuMsgXapian *row); +const char* mu_msg_xapian_get_from (MuMsgXapian *row); +const char* mu_msg_xapian_get_to (MuMsgXapian *row); +const char* mu_msg_xapian_get_cc (MuMsgXapian *row); +const char* mu_msg_xapian_get_subject (MuMsgXapian *row); +MuMsgFlags mu_msg_xapian_get_flags (MuMsgXapian *row); +MuMsgPriority mu_msg_xapian_get_priority (MuMsgXapian *row); + +const gchar* mu_msg_xapian_get_field (MuMsgXapian *row, + const MuMsgField *field); +gint64 mu_msg_xapian_get_field_numeric (MuMsgXapian *row, + const MuMsgField *field); +G_END_DECLS + +#endif /*__MU_MSG_XAPIAN_H__*/ diff --git a/src/mu-msg.h b/src/mu-msg.h new file mode 100644 index 00000000..5feaa9d8 --- /dev/null +++ b/src/mu-msg.h @@ -0,0 +1,62 @@ +/* +** Copyright (C) 2008 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_MSG_H__ +#define __MU_MSG_H__ + +#include "mu-msg-flags.h" +#include "mu-msg-fields.h" + +/* what kind of message is this; use by the indexer */ +enum _MuMsgStatus { + MU_MSG_STATUS_NEW, /* message is new */ + MU_MSG_STATUS_UPDATE, /* message is to be updated */ + MU_MSG_STATUS_CLEANUP, /* message is to be cleaned up from db */ + MU_MSG_STATUS_CLEANED_UP, /* message has been cleaned up from db */ + MU_MSG_STATUS_EXISTS, /* message exists (will not be cleaned up) */ + MU_MSG_STATUS_UPTODATE /* message is up-to-date */ +}; +typedef enum _MuMsgStatus MuMsgStatus; + +enum _MuMsgPriority { + MU_MSG_PRIORITY_NONE = 0, + + MU_MSG_PRIORITY_LOW = 1, + MU_MSG_PRIORITY_NORMAL = 2, + MU_MSG_PRIORITY_HIGH = 3 +}; +typedef enum _MuMsgPriority MuMsgPriority; + +enum _MuMsgContactType { /* Reply-To:? */ + MU_MSG_CONTACT_TYPE_TO, + MU_MSG_CONTACT_TYPE_FROM, + MU_MSG_CONTACT_TYPE_CC, + MU_MSG_CONTACT_TYPE_BCC +}; +typedef enum _MuMsgContactType MuMsgContactType; + +struct _MuMsgContact { + const char *_name; /* Foo Bar */ + const char *_addr; /* foo@bar.cuux */ + MuMsgContactType _type; /*MU_MSG_CONTACT_TYPE_{TO,CC,BCC,FROM}*/ +}; +typedef struct _MuMsgContact MuMsgContact; + + +#endif /*__MU_MSG_H__*/ diff --git a/src/mu-path.c b/src/mu-path.c new file mode 100644 index 00000000..d1fb59de --- /dev/null +++ b/src/mu-path.c @@ -0,0 +1,224 @@ +/* +** Copyright (C) 2008 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mu-path.h" +#include "mu-msg-flags.h" + +static MuResult process_dir (const char* path, MuPathWalkMsgCallback msg_cb, + MuPathWalkDirCallback dir_cb, void *data); + + + + +static MuResult +process_file (const char* fullpath, MuPathWalkMsgCallback cb, void *data) +{ + MuResult result; + struct stat statbuf; + + if (!cb) + return MU_OK; + + /* FIXME: may remove this access, and only use 'stat'? may be faster */ + if (G_UNLIKELY(access(fullpath, R_OK) != 0)) { + g_warning ("cannot access %s: %s", fullpath, strerror(errno)); + return MU_ERROR; + } + + if (G_UNLIKELY(stat (fullpath, &statbuf) != 0)) { + g_warning ("cannot stat %s: %s", fullpath, strerror(errno)); + return MU_ERROR; + } + + result = (cb)(fullpath,statbuf.st_mtime,data); + + if (G_LIKELY(result == MU_OK || result == MU_STOP)) + return result; + else { + g_warning ("%s: failed %d in callback (%s)", + __FUNCTION__, result, fullpath); + return result; + } +} + + +/* determine if path is a maildir leaf-dir; ie. if it's 'cur' or 'new' + * (we're ignoring 'tmp' for obvious reasons) + */ +static gboolean +is_maildir_new_or_cur (const char *path) +{ + size_t len; + const char *sfx; + + /* path is the full path; it cannot possibly be shorter + * than 4 for a maildir (/cur or /new) + */ + if (!path||(len = strlen(path)) < 4) + return FALSE; + + sfx = &path[len - 4]; + + if (sfx[0] != G_DIR_SEPARATOR) /* small optimization */ + return FALSE; + else if (sfx[1] != 'c' && sfx[1] != 'n') + return FALSE; /* optimization */ + else + return (strcmp (sfx + 1, "cur") == 0 || + strcmp (sfx + 1, "new") == 0); +} + +static MuResult +process_dir_entry (const char* path,struct dirent *entry, + MuPathWalkMsgCallback cb_msg, MuPathWalkDirCallback cb_dir, + void *data) +{ + char* fullpath; + + /* ignore anything starting with a dot */ + if (G_UNLIKELY(entry->d_name[0] == '.')) + return MU_OK; + + fullpath = g_newa (char, strlen(path) + 1 + strlen(entry->d_name) + 1); + sprintf (fullpath, "%s%c%s", path, G_DIR_SEPARATOR, entry->d_name); + + switch (entry->d_type) { + case DT_REG: + /* we only want files in cur/ and new/ */ + if (!is_maildir_new_or_cur (path)) + return MU_OK; + + return process_file (fullpath, cb_msg, data); + + case DT_DIR: + return process_dir (fullpath, cb_msg, cb_dir, data); + + default: + return MU_OK; /* ignore other types */ + } +} + +static struct dirent* +dirent_copy (struct dirent *entry) +{ + struct dirent *d = g_slice_new (struct dirent); + /* NOTE: simply memcpy'ing sizeof(struct dirent) bytes will + * give memory errors*/ + return (struct dirent*)memcpy (d, entry, entry->d_reclen); +} + +static void +dirent_destroy (struct dirent *entry) +{ + g_slice_free(struct dirent, entry); +} + +static gint +dirent_cmp (struct dirent *d1, struct dirent *d2) +{ + return d1->d_ino - d2->d_ino; +} + +static MuResult +process_dir (const char* path, MuPathWalkMsgCallback msg_cb, + MuPathWalkDirCallback dir_cb, void *data) +{ + MuResult result = MU_OK; + GList *lst, *c; + struct dirent *entry; + DIR* dir; + + dir = opendir (path); + if (G_UNLIKELY(!dir)) { + g_warning ("failed to open %s: %s", path, strerror(errno)); + return MU_ERROR; + } + + if (dir_cb) { + MuResult rv = dir_cb (path, TRUE, data); + if (rv != MU_OK) { + closedir (dir); + return rv; + } + } + + /* we sort the inodes, which makes file-access much faster on + some filesystems, such as ext3fs */ + lst = NULL; + while ((entry = readdir (dir))) + lst = g_list_prepend (lst, dirent_copy(entry)); + + c = lst = g_list_sort (lst, (GCompareFunc)dirent_cmp); + for (c = lst; c && result == MU_OK; c = c->next) + result = process_dir_entry (path, (struct dirent*)c->data, + msg_cb, dir_cb, data); + + g_list_foreach (lst, (GFunc)dirent_destroy, NULL); + g_list_free (lst); + + closedir (dir); + + if (dir_cb) + return dir_cb (path, FALSE, data); + + return result; +} + +MuResult +mu_path_walk_maildir (const char *path, MuPathWalkMsgCallback cb_msg, + MuPathWalkDirCallback cb_dir, void *data) +{ + struct stat statbuf; + + g_return_val_if_fail (path && cb_msg, MU_ERROR); + + if (access(path, R_OK) != 0) { + g_warning ("cannot access %s: %s", path, strerror(errno)); + return MU_ERROR; + } + + if (stat (path, &statbuf) != 0) { + g_warning ("cannot stat %s: %s", path, strerror(errno)); + return MU_ERROR; + } + + if ((statbuf.st_mode & S_IFMT) == S_IFREG) + return process_file (path, cb_msg, data); + + if ((statbuf.st_mode & S_IFMT) == S_IFDIR) + return process_dir (path, cb_msg, cb_dir, data); + + g_warning ("%s: unsupported file type for %s", + __FUNCTION__, path); + return MU_ERROR; +} + + diff --git a/src/mu-path.h b/src/mu-path.h new file mode 100644 index 00000000..ff2f69f5 --- /dev/null +++ b/src/mu-path.h @@ -0,0 +1,74 @@ +/* +** Copyright (C) 2008 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_PATH_H__ +#define __MU_PATH_H__ + +#include +#include /* for mode_t */ + +#include "mu-result.h" /* for MuResult */ + +/** + * MuPathWalkMsgCallback -- callback function for mu_path_walk_maildir; see the + * documentation there. It will be called for each message found + */ +typedef MuResult (*MuPathWalkMsgCallback) (const char* fullpath, + time_t timestamp, + void *user_data); + +/** + * MuPathWalkDirCallback -- callback function for mu_path_walk_maildir; see the + * documentation there. It will be called each time a dir is entered or left. + */ +typedef MuResult (*MuPathWalkDirCallback) (const char* fullpath, + gboolean enter, /* enter (TRUE) or leave (FALSE) dir?*/ + void *user_data); + +/** + * start a recursive scan of a maildir; for each file found, we call + * callback with the path (with the Maildir path of scanner_new as + * root), the filename, the timestamp (mtime) of the file,and the + * *data pointer, for user data. dot-files are ignored, as well as + * files outside cur/ and new/ dirs and unreadable files; however, + * dotdirs are visited (ie. '.dotdir/cur'), so this enables Maildir++. + * (http://www.inter7.com/courierimap/README.maildirquota.html, + * search for 'Mission statement') + * + * mu_path_walk_maildir wills stop if the callbacks return something + * != MU_OK. For example, it can return MU_STOP to stop the scan, or + * some error. + * + * @param path the maildir path to scan + * @param cb_msg the callback function called for each msg + * @param cb_dir the callback function called for each dir + * @param data user data pointer + * + * @return a scanner result; MU_OK if everything went ok, + * MU_STOP if we want to stop, or MU_ERROR in + * case of error + */ +MuResult mu_path_walk_maildir (const char *path, + MuPathWalkMsgCallback cb_msg, + MuPathWalkDirCallback cb_dir, + void *data); + + +#endif /*__MU_PATH_H__*/ + diff --git a/src/mu-query-xapian.cc b/src/mu-query-xapian.cc new file mode 100644 index 00000000..7017f94f --- /dev/null +++ b/src/mu-query-xapian.cc @@ -0,0 +1,217 @@ +/* +** Copyright (C) 2008 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 +#include +#include +#include +#include + +#include "mu-query-xapian.h" + +#include "mu-msg-xapian.h" +#include "mu-msg-xapian-priv.hh" + +struct _MuQueryXapian { + Xapian::Database *_db; + Xapian::QueryParser _qparser; + Xapian::Sorter* _sorters[MU_MSG_FIELD_TYPE_NUM]; +}; + +static void +add_prefix (const MuMsgField* field, Xapian::QueryParser* qparser) +{ + if (!mu_msg_field_is_xapian_enabled(field)) + return; + + const std::string prefix (mu_msg_field_xapian_prefix(field)); + + qparser->add_boolean_prefix(std::string(mu_msg_field_name(field)), + prefix); + qparser->add_boolean_prefix(std::string(mu_msg_field_shortcut(field)), + prefix); + + /* make the empty string match this field too*/ + qparser->add_prefix ("", prefix); +} + +MuQueryXapian* +mu_query_xapian_new (const char* path, GError **err) +{ + MuQueryXapian *self; + + g_return_val_if_fail (path, NULL); + + if (!g_file_test (path, G_FILE_TEST_IS_DIR) || + g_access(path, R_OK) != 0) { + g_set_error (err, 0, 0,"'%s' is not a readable xapian dir", path); + return NULL; + } + + try { + self = new MuQueryXapian; + + self->_db = new Xapian::Database(path); + self->_qparser.set_database(*self->_db); + self->_qparser.set_default_op(Xapian::Query::OP_OR); + self->_qparser.set_stemming_strategy + (Xapian::QueryParser::STEM_SOME); + + memset (self->_sorters, 0, sizeof(self->_sorters)); + + mu_msg_field_foreach ((MuMsgFieldForEachFunc)add_prefix, + (gconstpointer)&self->_qparser); + + return self; + + } catch (const Xapian::Error &ex) { + g_set_error (err, 0, 0,"%s: caught xapian exception '%s' (%s)", + __FUNCTION__, ex.get_msg().c_str(), + ex.get_error_string()); + } catch (...) { + delete self->_db; + g_set_error (err, 0, 0,"%s: caught exception", __FUNCTION__); + } + + if (self) { + delete self->_db; + delete self; + } + + return NULL; +} + + +void +mu_query_xapian_destroy (MuQueryXapian *self) +{ + if (!self) + return; + + try { + for (int i = 0; i != MU_MSG_FIELD_TYPE_NUM; ++i) { + delete self->_sorters[i]; + self->_sorters[i] = 0; + } + + delete self->_db; + delete self; + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } +} + + + +static Xapian::Query +get_query (MuQueryXapian *self, const char* searchexpr) +{ + return (self->_qparser.parse_query + (searchexpr, + Xapian::QueryParser::FLAG_BOOLEAN | + Xapian::QueryParser::FLAG_PHRASE | + Xapian::QueryParser::FLAG_LOVEHATE | + Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE | + Xapian::QueryParser::FLAG_WILDCARD | + Xapian::QueryParser::FLAG_PURE_NOT | + Xapian::QueryParser::FLAG_PARTIAL)); +} + +MuMsgXapian* +mu_query_xapian_run (MuQueryXapian *self, const char* searchexpr, + const MuMsgField* sortfield, gboolean ascending) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (searchexpr, NULL); + + try { + Xapian::Query q(get_query(self, searchexpr)); + Xapian::Enquire enq (*self->_db); + + if (sortfield) + enq.set_sort_by_value ( + (Xapian::valueno)mu_msg_field_id(sortfield), + ascending); + + enq.set_query (q); + enq.set_cutoff (0,0); + + return mu_msg_xapian_new (enq, 10000); + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' for expr '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + searchexpr, err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + return NULL; + +} + +char* +mu_query_xapian_as_string (MuQueryXapian *self, const char* searchexpr) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (searchexpr, NULL); + + try { + Xapian::Query q(get_query(self, searchexpr)); + return g_strdup(q.get_description().c_str()); + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + return NULL; +} + +char* +mu_query_xapian_combine (GSList *lst, gboolean connect_or) +{ + GString *str; + + g_return_val_if_fail (lst, NULL); + + str = g_string_sized_new (64); /* just a guess */ + while (lst) { + const char* cnx = ""; + if (lst->next) + cnx = connect_or ? " OR " : " AND "; + + g_string_append_printf (str, "%s%s", (gchar*)lst->data, cnx); + lst = lst->next; + } + + return g_string_free (str, FALSE); +} + + + + diff --git a/src/mu-query-xapian.h b/src/mu-query-xapian.h new file mode 100644 index 00000000..6908106d --- /dev/null +++ b/src/mu-query-xapian.h @@ -0,0 +1,96 @@ +/* +** Copyright (C) 2008 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_QUERY_XAPIAN_H__ +#define __MU_QUERY_XAPIAN_H__ + +#include +#include "mu-msg-xapian.h" + +G_BEGIN_DECLS + +/* + * MuQueryXapian + */ +struct _MuQueryXapian; +typedef struct _MuQueryXapian MuQueryXapian; + +/** + * create a new MuQueryXapian instance. + * + * @param path path to the xapian db to search + * @param err receives error information (if there is any) + * + * @return a new MuQueryXapian instance, or NULL in case of error. + * when the instance is no longer needed, use mu_query_xapian_destroy + * to free it + */ +MuQueryXapian *mu_query_xapian_new (const char* path, GError **err); + +/** + * destroy the MuQueryXapian instance + * + * @param self a MuQueryXapian instance, or NULL + */ +void mu_query_xapian_destroy (MuQueryXapian *self); + + +/** + * run a Xapian query; for the syntax, please refer to the mu-find + * manpage, or http://xapian.org/docs/queryparser.html + * + * @param self a valid MuQueryXapian instance + * @param expr the search expression + * + * @return a MuMsgXapian instance you can iterate over, or NULL in + * case of error + */ +MuMsgXapian* mu_query_xapian_run (MuQueryXapian *self, + const char* expr, + const MuMsgField* sortfield, + gboolean ascending); + +/** + * create a xapian query from list of expressions; for the syntax, + * please refer to the mu-find manpage, or + * http://xapian.org/docs/queryparser.html + * + * @param lst a list of search expressions + * @param connect_or if TRUE, combine the expressions with OR, otherwise use AND + * + * @return a string with the combined xapian expression or NULL in + * case of error; free with g_free when it's no longer needed + */ +char* mu_query_xapian_combine (GSList *lst, gboolean connect_or); + +/** + * get a string representation of the Xapian search query + * + * @param self a MuQueryXapian instance + * @param searchexpr a xapian search expression + * + * @return the string representation of the xapian query, or NULL in case of + * error; free the returned value with g_free + */ +char* mu_query_xapian_as_string (MuQueryXapian *self, const char* searchexpr); + + +G_END_DECLS + +#endif /*__MU_QUERY_XAPIAN_H__*/ diff --git a/src/mu-query.c b/src/mu-query.c new file mode 100644 index 00000000..5ba12bc4 --- /dev/null +++ b/src/mu-query.c @@ -0,0 +1,263 @@ +/* +** Copyright (C) 2008 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 +#include +#include +#include + +#include "mu-util.h" +#include "mu-result.h" +#include "mu-msg-str.h" +#include "mu-msg-flags.h" +#include "mu-query-xapian.h" + +struct _FindOptions { + gboolean _links; + gboolean _print; + gboolean _query; + char* _fields; + + char* _sortfield_str; + const MuMsgField* _sortfield; + + gboolean _ascending_flag, _descending_flag; + gboolean _sortdir_ascending; + +}; +typedef struct _FindOptions FindOptions; + +static FindOptions FIND_OPTIONS; +static GOptionEntry OPTION_ENTRIES[] = { + {"links", 'l', 0, G_OPTION_ARG_NONE, &FIND_OPTIONS._links, + "generate symlinks to matching messages", NULL}, + {"print", 'p', 0, G_OPTION_ARG_NONE, &FIND_OPTIONS._print, + "print matching messages to screen (default)", NULL}, + {"query", 'q', 0, G_OPTION_ARG_NONE, &FIND_OPTIONS._query, + "print a string representation of the Xapian query", NULL}, + {"fields", 'f', 0, G_OPTION_ARG_STRING, &FIND_OPTIONS._fields, + "fields to display in output", NULL}, + {"sortfield", 's', 0, G_OPTION_ARG_STRING, &FIND_OPTIONS._sortfield_str, + "field to sort on", NULL}, + {"ascending", 'a', 0, G_OPTION_ARG_NONE, &FIND_OPTIONS._ascending_flag, + "sort ascending", NULL}, + {"descending", 'c', 0, G_OPTION_ARG_NONE, &FIND_OPTIONS._descending_flag, + "sort ascending", NULL}, + {NULL} +}; + +GOptionGroup* +mu_query_option_group (void) +{ + GOptionGroup *og; + + og = g_option_group_new ("query", + "options for quering the e-mail database", + "", NULL, NULL); + g_option_group_add_entries (og, OPTION_ENTRIES); + + return og; +} + + +static gboolean +handle_options_sort_field_dir (void) +{ + const MuMsgField *field; + + if (FIND_OPTIONS._sortfield_str) { + field = mu_msg_field_from_name (FIND_OPTIONS._sortfield_str); + if (!field && strlen(FIND_OPTIONS._sortfield_str) == 1) + field = mu_msg_field_from_shortcut + (FIND_OPTIONS._sortfield_str[0]); + if (!field) { + g_warning ("not a valid sort field: '%s'", + FIND_OPTIONS._sortfield_str); + return FALSE; + } + FIND_OPTIONS._sortfield = field; + } + + if (FIND_OPTIONS._ascending_flag && FIND_OPTIONS._descending_flag) { + g_warning ("ignoring option '--descending'"); + FIND_OPTIONS._sortdir_ascending = TRUE; + } else if (!FIND_OPTIONS._descending_flag) + FIND_OPTIONS._sortdir_ascending = !FIND_OPTIONS._descending_flag; + + return TRUE; +} + + +static gboolean +handle_options (void) +{ + //GError *err = NULL; + /* if (!mu_conf_handle_options (mu_app_conf(),OPTION_ENTRIES, argcp, argvp, */ + /* &err)) { */ + /* g_printerr ("option parsing failed: %s\n", */ + /* (err && err->message) ? err->message : "?" ); */ + /* if (err) */ + /* g_error_free (err); */ + /* return FALSE; */ + /* } */ + + /* if nothing specified, or fields are specified use print */ + if ((!FIND_OPTIONS._links && !FIND_OPTIONS._query)||FIND_OPTIONS._fields) + FIND_OPTIONS._print = TRUE; + + /* if no fields are specified, use 'd f s' */ + if (FIND_OPTIONS._print && !FIND_OPTIONS._fields) { + FIND_OPTIONS._fields = "d f s"; /* default: date-from-subject.. */ + if (!FIND_OPTIONS._ascending_flag) /* ... and sort descending */ + FIND_OPTIONS._sortdir_ascending = FALSE; + } + + if (!handle_options_sort_field_dir ()) + return FALSE; + + return TRUE; +} + +static gboolean +print_query (MuQueryXapian *xapian, const gchar *query) +{ + char *querystr; + + querystr = mu_query_xapian_as_string (xapian, query); + g_print ("%s\n", querystr); + g_free (querystr); + + return TRUE; +} + + +static const gchar* +display_field (MuMsgXapian *row, const MuMsgField* field) +{ + gint64 val; + + switch (mu_msg_field_type(field)) { + case MU_MSG_FIELD_TYPE_STRING: + return mu_msg_xapian_get_field (row, field); + case MU_MSG_FIELD_TYPE_INT: + + if (mu_msg_field_id(field) == MU_MSG_FIELD_ID_PRIORITY) { + val = mu_msg_xapian_get_field_numeric (row, field); + return mu_msg_str_prio ((MuMsgPriority)val); + } + + if (mu_msg_field_id(field) == MU_MSG_FIELD_ID_FLAGS) { + val = mu_msg_xapian_get_field_numeric (row, field); + return mu_msg_str_flags_s ((MuMsgPriority)val); + } + + return mu_msg_xapian_get_field (row, field); /* as string */ + case MU_MSG_FIELD_TYPE_TIME_T: + val = mu_msg_xapian_get_field_numeric (row, field); + return mu_msg_str_date_s ((time_t)val); + case MU_MSG_FIELD_TYPE_BYTESIZE: + val = mu_msg_xapian_get_field_numeric (row, field); + return mu_msg_str_size_s ((time_t)val); + default: + g_return_val_if_reached (NULL); + } +} + + +static gboolean +print_rows (MuQueryXapian *xapian, const gchar *query, FindOptions *opts) +{ + MuMsgXapian *row; + + row = mu_query_xapian_run (xapian, query, + opts->_sortfield, opts->_sortdir_ascending); + if (!row) { + g_printerr ("error: running query failed\n"); + return FALSE; + } + + while (!mu_msg_xapian_is_done (row)) { + const char* fields = opts->_fields; + int printlen = 0; + while (*fields) { + const MuMsgField* field = + mu_msg_field_from_shortcut (*fields); + if (!field) + printlen += printf ("%c", *fields); + else + printlen += printf ("%s", display_field(row, + field)); + + ++fields; + } + if (printlen >0) + printf ("\n"); + + mu_msg_xapian_next (row); + } + + mu_msg_xapian_destroy (row); + return TRUE; +} + + +static gboolean +do_output (MuQueryXapian *xapian, GSList *args, FindOptions* opts) +{ + gchar *query; + gboolean retval = TRUE; + + query = mu_query_xapian_combine (args, FALSE); + if (opts->_query) + retval = print_query (xapian, query); + + if (retval && opts->_print) + retval = print_rows (xapian, query, opts); + + g_free (query); + return retval; +} + + + +MuResult +mu_query_run (GSList *args) +{ + GError *err = 0; + MuQueryXapian *xapian; + MuResult rv; + + rv = MU_OK; + + handle_options (); + xapian = mu_query_xapian_new ("/home/djcb/.mu", &err); + + if (!xapian) { + if (err) { + g_printerr ("error: %s\n", err->message); + g_error_free (err); + } + return MU_ERROR; + } + + rv = do_output (xapian, args, &FIND_OPTIONS) ? 0 : 1; + mu_query_xapian_destroy (xapian); + + return rv; +} diff --git a/src/mu-query.h b/src/mu-query.h new file mode 100644 index 00000000..1e4cb7ab --- /dev/null +++ b/src/mu-query.h @@ -0,0 +1,10 @@ +#ifndef __MU_QUERY_H__ +#define __MU_QUERY_H__ + +MuResult mu_query_run (GSList *args); +GOptionGroup* mu_query_option_group (void); + +#endif /*__MU_QUERY_H__*/ + + + diff --git a/src/mu-result.h b/src/mu-result.h new file mode 100644 index 00000000..dcdc2189 --- /dev/null +++ b/src/mu-result.h @@ -0,0 +1,30 @@ +/* +** Copyright (C) 2008 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_H__ +#define __MU_RESULT_H__ + +enum _MuResult { + MU_OK, /* all went ok */ + MU_STOP, /* user wants to stop */ + MU_ERROR /* some error occured */ +}; +typedef enum _MuResult MuResult; + +#endif /*__MU_RESULT__*/ diff --git a/src/mu-store-xapian.cc b/src/mu-store-xapian.cc new file mode 100644 index 00000000..2a5e2f7c --- /dev/null +++ b/src/mu-store-xapian.cc @@ -0,0 +1,340 @@ +/* +** Copyright (C) 2008 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 +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /*HAVE_CONFIG_H*/ + +#include "mu-msg.h" +#include "mu-msg-gmime.h" +#include "mu-store-xapian.h" + +struct _MuStoreXapian { + Xapian::WritableDatabase *_db; + + /* transaction handling */ + bool _in_transaction; + int _processed; + size_t _transaction_size; +}; + +MuStoreXapian* +mu_store_xapian_new (const char* path) +{ + MuStoreXapian *store; + + g_return_val_if_fail (path, NULL); + + try { + store = g_new0(MuStoreXapian,1); + store->_db = new Xapian::WritableDatabase + (path,Xapian::DB_CREATE_OR_OPEN); + + /* keep count of processed docs */ + store->_transaction_size = 10000; /* default */ + store->_in_transaction = false; + store->_processed = 0; + + g_message ("%s: opened %s", __FUNCTION__, path); + return store; + + } catch (const Xapian::Error &err) { + + delete store->_db; + g_free (store); + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + return NULL; + + } catch (...) { + delete store->_db; + g_free (store); + g_warning ("%s: caught exception", __FUNCTION__); + return NULL; + } +} + + +void +mu_store_xapian_tune (MuStoreXapian *store, + unsigned int transaction_size) +{ + g_return_if_fail (store); + + if (transaction_size) { + g_message ("tune: setting xapian transaction size to %u", + transaction_size); + store->_transaction_size = transaction_size; + } +} + + +void +mu_store_xapian_destroy (MuStoreXapian *store) +{ + if (!store) + return; + + try { + if (store->_in_transaction) { + store->_in_transaction = false; + store->_db->commit_transaction(); + } + store->_db->flush(); + + g_message ("closing xapian database with %d documents", + (int)store->_db->get_doccount()); + + delete store->_db; + g_free (store); + + } catch (const Xapian::Error &err) { + g_free (store); + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_free (store); + g_warning ("%s: caught exception", __FUNCTION__); + } +} + + +static void +add_terms_values_number (Xapian::Document& doc, MuMsgGMime *msg, + const MuMsgField* field) +{ + const std::string pfx (mu_msg_field_xapian_prefix(field), 1); + gint64 num = mu_msg_gmime_get_field_numeric (msg, field); + const std::string numstr (Xapian::sortable_serialise((double)num)); + + doc.add_value ((Xapian::valueno)mu_msg_field_id(field), numstr); + doc.add_term (pfx + numstr); +} + + +static void +add_terms_values_string (Xapian::Document& doc, MuMsgGMime *msg, + const MuMsgField* field) +{ + const char* str = mu_msg_gmime_get_field_string (msg, field); + if (G_UNLIKELY(!str)) + return; + + const std::string value (str); + const std::string prefix (mu_msg_field_xapian_prefix(field)); + + if (mu_msg_field_is_xapian_indexable (field)) { + Xapian::TermGenerator termgen; + termgen.set_document (doc); + termgen.index_text_without_positions (value, 1, prefix); + } else + doc.add_term(prefix + value); + + doc.add_value ((Xapian::valueno)mu_msg_field_id(field), value); +} + +static void +add_terms_values_body (Xapian::Document& doc, MuMsgGMime *msg, + const MuMsgField* field) +{ + if (G_UNLIKELY((mu_msg_gmime_get_flags(msg) & MU_MSG_FLAG_ENCRYPTED))) + return; /* don't store encrypted bodies */ + + const char *str = mu_msg_gmime_get_body_text(msg); + if (!str) + str = mu_msg_gmime_get_body_html(msg); /* FIXME: html->html fallback */ + if (!str) + return; /* no body... */ + + Xapian::TermGenerator termgen; + termgen.index_text(str, 1, mu_msg_field_xapian_prefix(field)); + termgen.set_document(doc); +} + +struct _MsgDoc { + Xapian::Document *_doc; + MuMsgGMime *_msg; +}; +typedef struct _MsgDoc MsgDoc; + +static void +add_terms_values (const MuMsgField* field, MsgDoc* msgdoc) +{ + MuMsgFieldType type; + + if (!mu_msg_field_is_xapian_enabled(field)) + return; + + type = mu_msg_field_type (field); + + if (G_LIKELY(type == MU_MSG_FIELD_TYPE_STRING)) { + if (G_UNLIKELY(mu_msg_field_id (field) == + MU_MSG_FIELD_ID_BODY_TEXT)) + add_terms_values_body (*msgdoc->_doc, msgdoc->_msg, + field); + else + add_terms_values_string (*msgdoc->_doc, msgdoc->_msg, + field); + return; + } + + if (type == MU_MSG_FIELD_TYPE_BYTESIZE || + type == MU_MSG_FIELD_TYPE_TIME_T || + type == MU_MSG_FIELD_TYPE_INT) { + add_terms_values_number (*msgdoc->_doc, msgdoc->_msg, field); + return; + } + + g_return_if_reached (); +} + + +MuResult +mu_store_xapian_store (MuStoreXapian *store, MuMsgGMime *msg) +{ + static const MuMsgField* pathfield = + mu_msg_field_from_id(MU_MSG_FIELD_ID_PATH); + static const std::string pathprefix + (mu_msg_field_xapian_prefix(pathfield)); + + g_return_val_if_fail (store, MU_ERROR); + g_return_val_if_fail (msg, MU_ERROR); + + try { + const char* body; + Xapian::Document newdoc; + Xapian::docid id; + gboolean commit_now; + MsgDoc msgdoc = { &newdoc, msg }; + + // start transaction if needed + if (G_UNLIKELY(!store->_in_transaction)) { + store->_db->begin_transaction(); + store->_in_transaction = true; + } + + mu_msg_field_foreach ((MuMsgFieldForEachFunc)add_terms_values, + &msgdoc); + + /* we replace all existing documents for this file */ + const std::string pathterm (pathprefix + + mu_msg_gmime_get_path(msg)); + id = store->_db->replace_document (pathterm, newdoc); + + commit_now = ++store->_processed % store->_transaction_size == 0; + if (G_UNLIKELY(commit_now)) { + store->_in_transaction = false; + store->_db->commit_transaction(); + } + + return MU_OK; + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } + + if (store->_in_transaction) { + store->_in_transaction = false; + store->_db->cancel_transaction(); + } + + return MU_ERROR; +} + + +MuResult +mu_store_xapian_cleanup (MuStoreXapian *store, const char* msgpath) +{ + g_return_val_if_fail (store, MU_ERROR); + g_return_val_if_fail (msgpath, MU_ERROR); + + try { + return MU_OK; /* TODO: */ + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + + return MU_ERROR; + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + return MU_ERROR; + } + +} + +time_t +mu_store_xapian_get_timestamp (MuStoreXapian *store, const char* msgpath) +{ + g_return_val_if_fail (store, 0); + g_return_val_if_fail (msgpath, 0); + + try { + const std::string stamp (store->_db->get_metadata (msgpath)); + if (stamp.empty()) + return 0; + + return (time_t) g_ascii_strtoull (stamp.c_str(), NULL, 10); + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + + return MU_ERROR; + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + return MU_ERROR; + } + + return 0; +} + + +void +mu_store_xapian_set_timestamp (MuStoreXapian *store, const char* msgpath, + time_t stamp) +{ + g_return_if_fail (store); + g_return_if_fail (msgpath); + + try { + char buf[24]; + sprintf (buf, "%" G_GUINT64_FORMAT, (guint64)stamp); + store->_db->set_metadata (msgpath, buf); + + } catch (const Xapian::Error &err) { + g_warning ("%s: caught xapian exception '%s' (%s)", + __FUNCTION__, err.get_msg().c_str(), + err.get_error_string()); + } catch (...) { + g_warning ("%s: caught exception", __FUNCTION__); + } +} diff --git a/src/mu-store-xapian.h b/src/mu-store-xapian.h new file mode 100644 index 00000000..887fc08c --- /dev/null +++ b/src/mu-store-xapian.h @@ -0,0 +1,50 @@ +/* +** Copyright (C) 2008 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_STORE_XAPIAN_H__ +#define __MU_STORE_XAPIAN_H__ + +#include +#include + +#include "mu-msg-gmime.h" + +G_BEGIN_DECLS + +struct _MuStoreXapian; +typedef struct _MuStoreXapian MuStoreXapian; + +MuStoreXapian* mu_store_xapian_new (const char* path); +void mu_store_xapian_tune (MuStoreXapian *store, + unsigned int transaction_size); +void mu_store_xapian_destroy (MuStoreXapian *store); +MuResult mu_store_xapian_store (MuStoreXapian *store, + MuMsgGMime *msg); +MuResult mu_store_xapian_cleanup (MuStoreXapian *store, + const char* msgpath); + +void mu_store_xapian_set_timestamp (MuStoreXapian *store, + const char* msgpath, + time_t stamp); +time_t mu_store_xapian_get_timestamp (MuStoreXapian *store, + const char* msgpath); + +G_END_DECLS + +#endif /*__MU_STORE_XAPIAN_H__*/ diff --git a/src/mu-util.c b/src/mu-util.c new file mode 100644 index 00000000..230b1294 --- /dev/null +++ b/src/mu-util.c @@ -0,0 +1,72 @@ +/* +** Copyright (C) 2008 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 +#include + +#include "mu-util.h" + +char* +mu_util_homedir_expand (const char *path) +{ + const char* home; + + if (!path) + return NULL; + + if (path[0] != '~' || path[1] != G_DIR_SEPARATOR) + return g_strdup (path); + + home = getenv ("HOME"); + if (!home) + home = g_get_home_dir (); + + return g_strdup_printf ("%s%s", home, path + 1); +} + + +GSList * +mu_util_strlist_from_args (int argc, char *argv[]) +{ + GSList *lst; + int i; + + g_return_val_if_fail (argc >= 0, NULL); + if (argc == 0) + return NULL; + g_return_val_if_fail (argv, NULL); + + /* we prepend args in opposite direction; + * prepending is faster + */ + for (i = argc - 1, lst = NULL; i >= 0; --i) { + if (!argv[i]) + continue; + lst = g_slist_prepend (lst, g_strdup(argv[i])); + } + return lst; +} + + +void +mu_util_strlist_free (GSList *lst) +{ + g_slist_foreach (lst, (GFunc)g_free, NULL); + g_slist_free (lst); +} diff --git a/src/mu-util.h b/src/mu-util.h new file mode 100644 index 00000000..81b2b906 --- /dev/null +++ b/src/mu-util.h @@ -0,0 +1,64 @@ +/* +** Copyright (C) 2008 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 + +G_BEGIN_DECLS + +/** + * get the expanded path; currently only a starting '~/' will be + * replaced by the users home directory, ie. for a user 'bob' this could mean + * something like "~/my/path/to/something" --> "/home/bob/my/path/to/something" + * + * @param path path to expand + * + * @return the expanded path as a newly allocated string, or NULL in + * case of error + */ +char* mu_util_homedir_expand (const char* path); + + +/** + * take a char*[] and turn it into a GSList + * + * @param argc numbers of strings + * @param argv array of strings + * + * @return a newly allocated GSList of the arguments; or NULL in case + * of error. use mu_exprs_helpers_strlist_free when done with the list + */ +GSList *mu_util_strlist_from_args (int argc, char *argv[]); + + + +/** + * free a list of strings, as produced by mu_expr_helpers_strlist_from_args or + * mu_expr_helpers_strlist_from_str + * + * @param lst a list or NULL + */ +void mu_util_strlist_free (GSList *lst); + + +G_END_DECLS + +#endif /*__MU_UTIL_H__*/ diff --git a/src/mu.c b/src/mu.c new file mode 100644 index 00000000..370f4da8 --- /dev/null +++ b/src/mu.c @@ -0,0 +1,135 @@ +/* +** Copyright (C) 2008, 2009 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 +#include +#include + +#include "mu-index.h" +#include "mu-query.h" +#include "mu-util.h" + +#include "mu-msg-gmime.h" + +static MuResult +msg_cb (MuIndexStats* stats, void *user_data) +{ + char *kars="-\\|/"; + static int i = 0; + + g_print ("%s%c", (!i)?"":"\b", kars[i % 4]); + ++i; + + return MU_OK; +} + +static void +show_help (const char* cmd) +{ + if (cmd) + g_print ("Help about %s\n", cmd); + else + g_print ("General help"); +} + + +static gboolean opt_debug; +static gboolean opt_quiet; + +static GOptionEntry entries[] = { + { "debug", 'd', 0, G_OPTION_ARG_NONE, &opt_debug, + "print debug output to standard-error", NULL }, + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &opt_quiet, + "don't give any progress information", NULL }, + { NULL } +}; + + + + + +int +main (int argc, char *argv[]) +{ + const char* cmd; + + GError *error = NULL; + GOptionContext *context; + MuResult rv; + + g_type_init (); + + context = g_option_context_new ("- search your e-mail"); + g_option_context_add_main_entries (context, entries, "mu"); + g_option_context_add_group (context, mu_query_option_group()); + + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_printerr ("option parsing failed: %s\n", + error->message); + g_error_free (error); + return 1; + } + + if (argc < 2 || !( strcmp(argv[1], "index") == 0 || + strcmp(argv[1], "search") == 0 || + strcmp(argv[1], "help") == 0)) { + g_printerr ("usage: mu [options] command [parameters]\n" + "\twhere command is one of index, search, help\n"); + return 1; + } + + cmd = argv[1]; + + if (strcmp (cmd, "help") == 0) { + show_help (argc > 2 ? argv[2] : NULL); + return 0; + } + + + mu_msg_gmime_init (); + + rv = MU_OK; + if (strcmp(cmd, "index") == 0) { + MuIndex *midx; + MuIndexStats stats; + + midx = mu_index_new ("/home/djcb/.mu"); + rv = mu_index_run (midx, + "/home/djcb/Maildir", + FALSE, &stats, msg_cb, NULL, NULL); + + mu_index_destroy (midx); + + } else if (strcmp(cmd, "search") == 0) { + + GSList *args; + if (argc < 3) { /* FIXME */ + g_printerr ("error!\n"); + return 1; + } + + args = mu_util_strlist_from_args (argc-2, argv+2); + rv = mu_query_run (args); + mu_util_strlist_free (args); + } + + mu_msg_gmime_uninit (); + return rv == MU_OK ? 0 : 1; + +}