* initial import of mu - the next generation

This commit is contained in:
Dirk-Jan C. Binnema 2009-11-25 22:55:06 +02:00
commit 2b0aca1bf7
34 changed files with 5140 additions and 0 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>

674
COPYING Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
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:
<program> Copyright (C) <year> <name of author>
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
<http://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

0
ChangeLog Normal file
View File

1
Makefile.am Normal file
View File

@ -0,0 +1 @@
SUBDIRS=src

0
NEWS Normal file
View File

0
README Normal file
View File

88
configure.ac Normal file
View File

@ -0,0 +1,88 @@
## Copyright (C) 2009 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software Foundation,
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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"

36
src/Makefile.am Normal file
View File

@ -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)

284
src/mu-index.c Normal file
View File

@ -0,0 +1,284 @@
/*
** Copyright (C) 2008, 2009 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <errno.h>
#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;
}

145
src/mu-index.h Normal file
View File

@ -0,0 +1,145 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_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__*/

260
src/mu-msg-fields.c Normal file
View File

@ -0,0 +1,260 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <string.h>
#include "mu-msg-fields.h"
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;
}

88
src/mu-msg-fields.h Normal file
View File

@ -0,0 +1,88 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_FIELDS_H__
#define __MU_MSG_FIELDS_H__
#include <glib.h>
G_BEGIN_DECLS
/* don't change the order, add new types at the end */
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__*/

199
src/mu-msg-flags.c Normal file
View File

@ -0,0 +1,199 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <glib.h>
#include <string.h>
#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;
}

117
src/mu-msg-flags.h Normal file
View File

@ -0,0 +1,117 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_FLAGS_H__
#define __MU_MSG_FLAGS_H__
#include <glib.h>
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__*/

849
src/mu-msg-gmime.c Normal file
View File

@ -0,0 +1,849 @@
/*
** Copyright (C) 2008, 2009 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <gmime/gmime.h>
#include <stdlib.h>
#include <ctype.h>
#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;
}
}

267
src/mu-msg-gmime.h Normal file
View File

@ -0,0 +1,267 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_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__*/

87
src/mu-msg-str.c Normal file
View File

@ -0,0 +1,87 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <glib.h>
#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";
}
}

94
src/mu-msg-str.h Normal file
View File

@ -0,0 +1,94 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_STR_H__
#define __MU_MSG_STR_H__
#include <time.h>
#include <sys/types.h>
#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__*/

27
src/mu-msg-xapian-priv.hh Normal file
View File

@ -0,0 +1,27 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_XAPIAN_PRIV_HH__
#define __MU_MSG_XAPIAN_PRIV_HH__
#include <xapian.h>
MuMsgXapian *mu_msg_xapian_new (const Xapian::Enquire& enq, size_t batchsize);
#endif /*__MU_MSG_XAPIAN_PRIV_HH__*/

234
src/mu-msg-xapian.cc Normal file
View File

@ -0,0 +1,234 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include "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));
}

52
src/mu-msg-xapian.h Normal file
View File

@ -0,0 +1,52 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_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__*/

62
src/mu-msg.h Normal file
View File

@ -0,0 +1,62 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_MSG_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__*/

224
src/mu-path.c Normal file
View File

@ -0,0 +1,224 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <dirent.h>
#include <errno.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <glib/gstdio.h>
#include <string.h>
#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;
}

74
src/mu-path.h Normal file
View File

@ -0,0 +1,74 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_PATH_H__
#define __MU_PATH_H__
#include <glib.h>
#include <sys/types.h> /* 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__*/

217
src/mu-query-xapian.cc Normal file
View File

@ -0,0 +1,217 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <stdlib.h>
#include <xapian.h>
#include <glib/gstdio.h>
#include <string.h>
#include <string>
#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);
}

96
src/mu-query-xapian.h Normal file
View File

@ -0,0 +1,96 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_QUERY_XAPIAN_H__
#define __MU_QUERY_XAPIAN_H__
#include <glib.h>
#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__*/

263
src/mu-query.c Normal file
View File

@ -0,0 +1,263 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <glib.h>
#include <glib/gstdio.h>
#include <stdio.h>
#include <string.h>
#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;
}

10
src/mu-query.h Normal file
View File

@ -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__*/

30
src/mu-result.h Normal file
View File

@ -0,0 +1,30 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_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__*/

340
src/mu-store-xapian.cc Normal file
View File

@ -0,0 +1,340 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <xapian.h>
#include <string.h>
#include <stdio.h>
#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__);
}
}

50
src/mu-store-xapian.h Normal file
View File

@ -0,0 +1,50 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_STORE_XAPIAN_H__
#define __MU_STORE_XAPIAN_H__
#include <glib.h>
#include <inttypes.h>
#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__*/

72
src/mu-util.c Normal file
View File

@ -0,0 +1,72 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <stdlib.h>
#include <string.h>
#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);
}

64
src/mu-util.h Normal file
View File

@ -0,0 +1,64 @@
/*
** Copyright (C) 2008 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef __MU_UTIL_H__
#define __MU_UTIL_H__
#include <glib.h>
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__*/

135
src/mu.c Normal file
View File

@ -0,0 +1,135 @@
/*
** Copyright (C) 2008, 2009 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#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;
}