From: Michael Rasmussen Date: Mon, 17 Jun 2013 00:53:35 +0000 (+0200) Subject: initial upload X-Git-Url: http://git.datanom.net/vfolder.git/commitdiff_plain/2b242bb3daa8942981993bd9733d72da5e9516cd initial upload --- 2b242bb3daa8942981993bd9733d72da5e9516cd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdd3a5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +/ChangeLog +/.claws-mail +/claws-mail +/run +/debug +/vfolder.geany +/*.tar.gz +/*.tar.bz2 +/aclocal.m4 +/autom4te.cache +/confdefs.h +/config.cache +/config/config.guess +/config/config.sub +/config/depcomp +/config.guess +/config.h +/config.h.in +/config/install-sh +/config.log +/config/ltmain.sh +/config/Makefile +/config/Makefile.in +/config/missing +/config/mkinstalldirs +/config.status +/config.sub +/configure +/.cproject +/depcomp +/libtool +/ltmain.sh +/Makefile +/Makefile.in +/patches +/*.patchset +/.project +/.settings +/src/.deps +/src/.libs +/src/Makefile +/src/Makefile.in +/src/*.o +/src/Makefile +/src/Makefile.in +/src/*.swp +/src/tags +/src/TAGS +/src/ylwrap +/stamp-h +/stamp-h1 +/stamp-h2 +/stamp-h.in +/*.swp +/tags +/TAGS +/tools/Makefile +/tools/Makefile.in +/version diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..58936cb --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Michael Rasmussen + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..8660d23 --- /dev/null +++ b/INSTALL @@ -0,0 +1,153 @@ +Installation +============ + +This program requires GTK+ 2.16 or higher to be compiled. + +Build it +======== + +If you're using a package-based system, please make sure that gtk-devel +and glib-devel (or similar) packages are installed before the compilation +(you may also require flex (lex) and bison (yacc)). + +To compile and install, just type: + +% ./configure +% make +% su +Password: [Enter password] +# make install + +To run, just type: + +% claws-mail + +Additional libraries or programs +================================ +only needed if you require the additional functionality + +Bogofilter + for Bogofilter plugin support + http://bogofilter.sourceforge.net/ +Bsfilter + for Bsfilter plugin support + http://sourceforge.jp/projects/bsfilter/ +Canberra (>= 0.6) + for some sound features of Notification plugin + http://0pointer.de/lennart/projects/libcanberra/ +compface + for X-Face support + http://freshmeat.net/projects/compface/ +Clam AntiVirus daemon + for Clamd plugin support + http://www.clamav.net/ +D-Bus (>= 0.60) + for interprocess communication support + http://www.freedesktop.org/wiki/Software/dbus +Enchant (and dictionaries) (>= 1.0.0) + for spell-checker support + http://www.abisource.com/enchant/ +GData (>= 0.6.4) + for GData plugin support + https://live.gnome.org/libgdata/ +GnuPG (>= 1.2.1) and GPGME (>= 0.4.5) + for GnuPG and S/MIME plugin support + http://www.gnupg.org/ + ftp://ftp.gnupg.org/gcrypt/gpgme/ +GnuTLS (>= 0.4.2) + for SSL support + http://www.gnu.org/software/gnutls/ +GTK+ WebKit (>= 1.0) + for Fancy plugin support + http://trac.webkit.org/wiki/WebKitGTK/ +Gs tool + for PostScript feature of PDF Viewer plugin + http://pages.cs.wisc.edu/~ghost/ +Indicate (>= 0.3.0) + for indicator feature of Notification plugin + https://launchpad.net/libindicator/ +J-Pilot + for J-Pilot support + http://www.jpilot.org/ +LCDproc daemon + for lcdproc feature of Notification plugin + http://www.lcdproc.org +libEtPan! (>= 0.57) + for IMAP4, NNTP and Mailmbox plugin support + http://www.etpan.org +Network Manager (>= 0.6.2) + for support for detection of network connection changes + http://www.gnome.org/projects/NetworkManager/ +OpenLDAP (>= 2.0.7) + for LDAP support + http://www.openldap.org/ +Perl (>= 5.8.0) + for perl plugin support + http://www.perl.org/ +Poppler (>= 0.4.2) + for PDF Viewer plugin support + http://poppler.freedesktop.org/ +GnuTLS (>= 0.4.2) + for SSL support + http://www.gnu.org/software/gnutls/ +PyGTK (>= 2.10.3) + for Python plugin support + http://www.pygtk.org/ +Python + for Python plugin support + http://python.org/ +SpamAssassin + for SpamAssassin plugin support + http://spamassassin.apache.org/ + +Options for configure script +============================ +Most options are automatically enabled if the dependencies +are matched. + + --with-config-dir=RCDIR local config dir (default: ~/.claws-mail) + --disable-compface disable compface (X-Face) support + --disable-ipv6 disable IPv6 support + --disable-gnutls disable GnuTLS support + --disable-libetpan disable IMAP4 and NNTP support + --disable-gnomeprint disable libgnomeprint support + --disable-enchant disable Enchant support + --disable-ldap disable LDAP support + --disable-jpilot disable JPilot support + --disable-startup-notification disable startup notification support + --disable-valgrind disable valgrind support for debugging + --enable-crash-dialog enable crash dialog + --disable-startup-notification disable startup notification support + --disable-trayicon-plugin do not build System Tray Icon plugin + --disable-spamassassin-plugin do not build SpamAssassin plugin + --disable-pgpcore-plugin do not build PGP/Core plugin + --disable-pgpmime-plugin do not build PGP/MIME plugin + --disable-pgpinline-plugin do not build PGP/Inline plugin + --disable-smime-plugin do not build S/Mime plugin + --disable-bogofilter-plugin do not build Bogofilter plugin + --disable-address_keeper-plugin do not build Address Keeper plugin + --disable-archive-plugin do not build Archive plugin + --disable-att_remover-plugin do not build Att Remover plugin + --disable-attachwarner-plugin do not build Attachwarner plugin + --disable-bsfilter-plugin do not build BSfilter plugin + --disable-clamd-plugin do not build Clamd plugin + --disable-fancy-plugin do not build Fancy plugin + --disable-fetchinfo-plugin do not build Fetchinfo plugin + --disable-gdata-plugin do not build GData plugin + --disable-mailmbox-plugin do not build MailMbox plugin + --disable-newmail-plugin do not build NewMail plugin + --disable-notification-plugin do not build Notification plugin + --disable-pdf_viewer-plugin do not build PDF Viewer plugin + --disable-perl-plugin do not build Perl plugin + --disable-python-plugin do not build Python plugin + --disable-rssyl-plugin do not build RSSyl plugin + --disable-spamassassin-plugin do not build SpamAssassin plugin + --disable-spam_report-plugin do not build Spam Report plugin + --disable-tnef_parse-plugin do not build TNEF Parse plugin + --disable-vcalendar-plugin do not build vCalendar plugin + --disable-manual do not build user manuals + --disable-nls do not use Native Language Support + +For other options, refer to ./configure --help . + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..80ac3cd --- /dev/null +++ b/Makefile.am @@ -0,0 +1,12 @@ +SUBDIRS = config src + +EXTRA_DIST = \ + config/config.rpath \ + AUTHORS \ + COPYING \ + ChangeLog \ + INSTALL \ + NEWS \ + README \ + TODO + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..9e11bb5 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +No news is good news:-) diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/TODO b/TODO new file mode 100644 index 0000000..e69de29 diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..66c2119 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +aclocal \ + && libtoolize --copy --force \ + && autoheader \ + && automake --add-missing --foreign --copy \ + && autoconf \ + && ./configure --enable-maintainer-mode $@ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..57c651f --- /dev/null +++ b/configure.ac @@ -0,0 +1,136 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.60) +AC_INIT(src/vfolder.h) +AC_CONFIG_AUX_DIR(config) +AM_MAINTAINER_MODE + +PACKAGE=vfolder + +dnl plugin version + +MAJOR_VERSION=0 +MINOR_VERSION=1 +MICRO_VERSION=0 +EXTRA_VERSION=0 + +if test \( $EXTRA_VERSION -eq 0 \); then + if test \( $MICRO_VERSION -eq 0 \); then + VERSION=${MAJOR_VERSION}.${MINOR_VERSION} + else + VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION} + fi +else + if test \( $MICRO_VERSION -eq 0 \); then + VERSION=${MAJOR_VERSION}.${MINOR_VERSION}cvs${EXTRA_VERSION} + else + VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}cvs${EXTRA_VERSION} + fi +fi + +AC_DEFINE_UNQUOTED(VFOLDERVERSION, "$VERSION", [plugin version]) +AC_DEFINE_UNQUOTED(VFOLDERPACKAGE, "Claws Mail vfolder", [package]) + +AM_INIT_AUTOMAKE($PACKAGE, $VERSION, no-define) +AC_DEFINE_UNQUOTED(PLUGINVERSION, "$VERSION", [plugin version]) + +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC +AM_PROG_CC_STDC +AC_LANG_C +AC_ISC_POSIX +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +# Checks for libraries. +# +# Find pkg-config +# +AC_PATH_PROG(PKG_CONFIG, pkg-config, no) +if test x$PKG_CONFIG = xno ; then + AC_MSG_ERROR([*** pkg-config not found. See http://www.freedesktop.org/software/pkgconfig/]) +fi + +# +# Check for claws-mail +# +PKG_CHECK_MODULES(CLAWS_MAIL, claws-mail >= 3.8.0.0) +AC_SUBST(CLAWS_MAIL_CFLAGS) +AC_SUBST(CLAWS_MAIL_LIBS) +if test -z $prefix || test "${prefix}" = "NONE" ; then + prefix=$( $PKG_CONFIG --variable=prefix claws-mail ) + CLAWS_MAIL_PLUGINDIR=$( $PKG_CONFIG --variable=plugindir claws-mail ) +else + CLAWS_MAIL_PLUGINDIR='${libdir}/claws-mail/plugins' +fi +AC_SUBST(CLAWS_MAIL_PLUGINDIR) + +if test $USE_MAINTAINER_MODE = yes; then + CFLAGS="-g -Wall -DGTK_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -DG_DISABLE_DEPRECATED" +else + CFLAGS="$CFLAGS -O2 -Wall" +fi + +ALL_LINGUAS="" +AC_DEFINE_UNQUOTED(TEXTDOMAIN, "$PACKAGE", [Gettext textdomain]) +AM_GNU_GETTEXT_VERSION([0.15]) +AM_GNU_GETTEXT([external]) + +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.12]) + +GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` +AC_SUBST(GLIB_GENMARSHAL) + +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.16) + +# Checks for header files. +AC_HEADER_STDC + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T + +AC_SUBST(VERSION) +AC_SUBST(PLUGINVERSION) +AC_SUBST(MAJOR_VERSION) +AC_SUBST(MINOR_VERSION) +AC_SUBST(MICRO_VERSION) +AC_SUBST(EXTRA_VERSION) + +AC_CONFIG_COMMANDS( + [summary], + [[echo "" + echo "Vfolder will be compiled with these settings:" + echo "" + echo -e "CFLAGS: ${CFLAGS}" + echo -e "Vfolder version: ${VERSION}" + echo "" + echo -e "Now run make to build vfolder plugin" + echo "" + echo -e "Please send bugs or feature requests to the maintainer(s)." + echo -e "Email addresses can be found in the AUTHORS file." + echo ""]], + [ + CFLAGS="$CFLAGS". + VERSION="$VERSION". + ] +) + +AC_OUTPUT([ + Makefile + config/Makefile + src/Makefile +]) + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..48a0a3b --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,34 @@ +plugindir = $(CLAWS_MAIL_PLUGINDIR) + +INCLUDES = @GLIB_CFLAGS@ \ + -I$(top_srcdir) -I$(top_builddir) + -I$(top_srcdir)/src -I$(top_builddir)/src + +plugin_LTLIBRARIES = vfolder.la + +vfolder_la_SOURCES = \ + vfolder.c \ + vfolder_init.c \ + vfolder_gtk.c \ + vfolder_prop.c + +nodist_include_HEADERS = \ + gettext.h \ + vfolder_gtk.h \ + vfolder_prop.h + +vfolderincludedir = $(includedir)/claws-mail/plugins/@PACKAGE@ +vfolderinclude_HEADERS = \ + vfolder.h + +vfolder_la_LDFLAGS = \ + -avoid-version -module \ + $(GTK_LIBS) + +vfolder_la_LIBADD = + +AM_CPPFLAGS = \ + $(CLAWS_MAIL_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GTK_CFLAGS) \ + -DLOCALEDIR=\""$(localedir)"\" diff --git a/src/gettext.h b/src/gettext.h new file mode 100644 index 0000000..c21c25e --- /dev/null +++ b/src/gettext.h @@ -0,0 +1,85 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002, 2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library 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 _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#define _(str) dgettext(TEXTDOMAIN, str) + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Many header files from the libstdc++ coming with g++ 3.3 or newer include + , which chokes if dcgettext is defined as a macro. So include + it now, to make later inclusions of a NOP. */ +#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) +# include +# if (__GLIBC__ >= 2) || _GLIBCXX_HAVE_LIBINTL_H +# include +# endif +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#define _(str) str + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +#define N_(str) str + +#endif /* _LIBGETTEXT_H */ diff --git a/src/vfolder.c b/src/vfolder.c new file mode 100644 index 0000000..1ea7f54 --- /dev/null +++ b/src/vfolder.c @@ -0,0 +1,1046 @@ +/* + * $Id: $ + */ +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "alertpanel.h" +#include "main.h" +#include "localfolder.h" +#include "statusbar.h" + +#include "vfolder.h" +#include "vfolder_gtk.h" +#include "vfolder_prop.h" + +typedef struct { + LocalFolder folder; +} VFolder; + +FolderClass vfolder_class; +static gboolean existing_tree_found = FALSE; +static GList* vfolders = NULL; + +static void vfolder_get_last_num(Folder *folder, FolderItem *item) +{ + gchar *path; + DIR *dp; + struct dirent *d; + gint max = 0; + gint num; + + g_return_if_fail(item != NULL); + + debug_print("vfolder_get_last_num(): Scanning %s ...\n", item->path); + path = folder_item_get_path(item); + g_return_if_fail(path != NULL); + if( change_dir(path) < 0 ) { + g_free(path); + return; + } + g_free(path); + + if( (dp = opendir(".")) == NULL ) { + FILE_OP_ERROR(item->path, "opendir"); + return; + } + + while( (d = readdir(dp)) != NULL ) { + if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) { + if( max < num ) + max = num; + } + } + closedir(dp); + + debug_print("Last number in dir %s = %d\n", item->path, max); + item->last_num = max; +} + +static void vfolder_init_read_func(FolderItem* item, gpointer data) { + g_return_if_fail(item != NULL); + + if (! IS_VFOLDER_FOLDER_ITEM(item)) + return; + + existing_tree_found = TRUE; + + if (folder_item_parent(item) == NULL) + return; + + vfolder_folder_item_props_read(VFOLDER_ITEM(item)); + vfolder_set_msgs_filter(VFOLDER_ITEM(item)); + +/* if (! IS_VFOLDER_FROZEN(VFOLDER_ITEM(item))) { + folder_item_scan(VFOLDER_ITEM(item)->source); + }*/ +} + +static void vfolder_make_rc_dir(void) { + gchar* vfolder_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, VFOLDER_DIR, NULL); + + if (! is_dir_exist(vfolder_dir)) { + if (make_dir(vfolder_dir) < 0) { + g_warning("couldn't create directory %s\n", vfolder_dir); + } + + debug_print("created directorty %s\n", vfolder_dir); + } + + g_free(vfolder_dir); +} + +static void vfolder_create_default_mailbox(void) { + Folder* root = NULL; + + vfolder_make_rc_dir(); + + root = folder_new(vfolder_folder_get_class(), VFOLDER_DEFAULT_MAILBOX, NULL); + + g_return_if_fail(root != NULL); + + folder_add(root); +} + +static gboolean vfolder_scan_required(Folder* folder, FolderItem* item) { + return TRUE; +} + +static Folder* vfolder_new_folder(const gchar* name, const gchar* path) { + VFolder* folder; + + debug_print("VFolder: new_folder\n"); + + vfolder_make_rc_dir(); + + folder = g_new0(VFolder, 1); + FOLDER(folder)->klass = &vfolder_class; + folder_init(FOLDER(folder), name); + + return FOLDER(folder); +} + +static void vfolder_destroy_folder(Folder* _folder) +{ + VFolder* folder = VFOLDER(_folder); + + folder_local_folder_destroy(LOCAL_FOLDER(folder)); +} + +static gint vfolder_remove_folder(Folder* folder, FolderItem* item) { + GDir* dp; + const gchar* name; + gchar* cwd; + gint ret = -1; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->path != NULL, -1); + g_return_val_if_fail(item->stype == F_NORMAL, -1); + + debug_print("VFolder: removing folder item %s\n", item->path); + + gchar* path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + VFOLDER_DIR, G_DIR_SEPARATOR_S, item->name, NULL); + remove_numbered_files(path, item->last_num - item->total_msgs + 1, item->last_num); + + folder_item_remove(item); + + cwd = g_get_current_dir(); + + if (change_dir(path) < 0 ) { + g_free(path); + return ret; + } + + if( (dp = g_dir_open(".", 0, NULL)) == NULL ) { + FILE_OP_ERROR(path, "g_dir_open"); + change_dir(cwd); + g_free(cwd); + return ret; + } + + while ((name = g_dir_read_name(dp)) != NULL) { + debug_print("Removing: %s\n", name); + claws_unlink(name); + } + g_dir_close(dp); + change_dir(cwd); + if (g_rmdir(path) == 0) + ret = 0; + + g_free(cwd); + g_free(path); + + return ret; +} + +static void vfolder_remove_hashtables(VFolderItem* item) { + if (item->me_to_claws) { + g_hash_table_destroy(item->me_to_claws); + item->me_to_claws = NULL; + } + if (item->claws_to_me) { + g_hash_table_destroy(item->claws_to_me); + item->claws_to_me = NULL; + } +} + +static gboolean vfolder_remove_msg_from_bridge(VFolderItem* item, + guint src, + guint dest) { + gboolean ret = FALSE; + + debug_print("Removing from hashtable: src->%u dest->%u\n", src, dest); + ret = g_hash_table_remove(item->me_to_claws, GUINT_TO_POINTER(dest)); + ret = g_hash_table_remove(item->claws_to_me, GUINT_TO_POINTER(src)); + + return (ret == FALSE); +} +/* +static gboolean vfolder_remove_msg_bridge_claws(VFolderItem* item, guint num) { + return vfolder_remove_msg_from_bridge_rel(item, num, TRUE); +} + +static gboolean vfolder_remove_msg_bridge_vfolder(VFolderItem* item, guint num) { + return vfolder_remove_msg_from_bridge_rel(item, num, FALSE); +} +*/ +static void vfolder_free_hashtable_data(gpointer data) { + g_free(data); +} + +static MsgBridge* vfolder_create_bridge(guint src, guint dest) { + MsgBridge* bridge; + + bridge = g_new0(MsgBridge, 1); + bridge->my_num = dest; + bridge->claws_num = src; + + return bridge; +} + +static GHashTable* vfolder_hashtable_new() { + return g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, vfolder_free_hashtable_data); +} + +static gboolean vfolder_add_msg_to_message_bridge(VFolderItem* item, + guint src, + guint dest) { + gboolean ret = FALSE; + MsgBridge* bridge; + + if (! item->me_to_claws) + item->me_to_claws = vfolder_hashtable_new(); + if (! item->claws_to_me) + item->claws_to_me = vfolder_hashtable_new(); + + debug_print("Adding to hashtable: src->%u dest->%u\n", src, dest); + bridge = vfolder_create_bridge(src, dest); + g_hash_table_replace(item->me_to_claws, GUINT_TO_POINTER(dest), bridge); + + bridge = vfolder_create_bridge(src, dest); + g_hash_table_replace(item->claws_to_me, GUINT_TO_POINTER(src), bridge); + + return ret; +} + +static FolderItem* vfolder_item_new(Folder* folder) { + VFolderItem* vitem; + + debug_print("VFolder: item_new\n"); + + vitem = g_new0(VFolderItem, 1); + + gchar* id = folder_get_identifier(folder); + gchar* cmp = g_strconcat("#", VFOLDER_DIR, G_DIR_SEPARATOR_S, NULL); + if (id && strcmp(cmp, id) != 0) { + debug_print("Creating folder item: %s\n", id); + g_free(id); + vfolders = g_list_prepend(vfolders, vitem); + } + g_free(cmp); + + return FOLDER_ITEM(vitem); +} + +static void vfolder_item_destroy(Folder* folder, FolderItem* item) { + VFolderItem* vitem = VFOLDER_ITEM(item); + + g_return_if_fail(vitem != NULL); + + debug_print("Number of elements in 'vfolders': %d\n", g_list_length(vfolders)); + vfolders = g_list_remove(vfolders, vitem); + debug_print("Number of elements in 'vfolders': %d\n", g_list_length(vfolders)); + + g_free(vitem->filter); + + vfolder_remove_hashtables(vitem); + vitem->source = NULL; + + g_free(item); + item = NULL; +} + +static FolderItem* vfolder_create_folder(Folder* folder, + FolderItem* parent, + const gchar* name) { + gchar* path = NULL; + FolderItem* newitem = NULL; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + if (parent->no_sub) + return NULL; + + path = g_strconcat((parent->path != NULL) ? parent->path : "", ".", + name, NULL); + newitem = folder_item_new(folder, name, path); + newitem->no_sub = TRUE; + + folder_item_append(parent, newitem); + g_free(path); + + return newitem; +} + +static gchar* vfolder_item_get_path(Folder* folder, FolderItem* item) { + gchar* result; + result = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, VFOLDER_DIR, + G_DIR_SEPARATOR_S, item->name, NULL); + return result; +} + +static gint vfolder_get_num_list(Folder* folder, FolderItem* item, + MsgNumberList** list, gboolean* old_uids_valid) { + gchar* path; + DIR* dp; + struct dirent* d; + gint num, nummsgs = 0; + + g_return_val_if_fail(item != NULL, -1); + + debug_print("VFolder: scanning '%s'...\n", item->path); + + *old_uids_valid = TRUE; + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, -1); + if (change_dir(path) < 0 ) { + g_free(path); + return -1; + } + g_free(path); + + if( (dp = opendir(".")) == NULL ) { + FILE_OP_ERROR(item->path, "opendir"); + return -1; + } + + while ((d = readdir(dp)) != NULL ) { + if( (num = to_number(d->d_name)) > 0 ) { + debug_print("Adding %d to list\n", num); + *list = g_slist_prepend(*list, GINT_TO_POINTER(num)); + nummsgs++; + } + } + closedir(dp); + + return nummsgs; +} + +static gint vfolder_create_tree(Folder* folder) { + FolderItem* rootitem; + GNode* rootnode; + + vfolder_make_rc_dir(); + + if (!folder->node) { + rootitem = folder_item_new(folder, folder->name, NULL); + rootitem->folder = folder; + rootnode = g_node_new(rootitem); + folder->node = rootnode; + rootitem->node = rootnode; + } else { + rootitem = FOLDER_ITEM(folder->node->data); + rootnode = folder->node; + } + + debug_print("VFolder: created new vfolder tree\n"); + return 0; +} + +static gint vfolder_scan_tree(Folder *folder) { + g_return_val_if_fail(folder != NULL, -1); + + folder->outbox = NULL; + folder->draft = NULL; + folder->queue = NULL; + folder->trash = NULL; + + debug_print("VFolder: scanning tree\n"); + vfolder_create_tree(folder); + + return 0; +} + +static gchar* vfolder_get_new_msg_filename(FolderItem* dest) { + gchar* destfile; + gchar* destpath; + + destpath = folder_item_get_path(dest); + g_return_val_if_fail(destpath != NULL, NULL); + + if( !is_dir_exist(destpath) ) + make_dir_hier(destpath); + + for( ; ; ) { + destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR, + dest->last_num + 1); + if( is_file_entry_exist(destfile) ) { + dest->last_num++; + g_free(destfile); + } else + break; + } + + g_free(destpath); + + return destfile; +} + +static gint vfolder_add_msgs(Folder* folder, FolderItem* dest, GSList* file_list, + GHashTable* relation) { + gchar* destfile; + GSList* cur; + MsgFileInfo* fileinfo; + gint r = -1; + VFolderItem* vitem; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + + vitem = VFOLDER_ITEM(dest); + if( dest->last_num < 0 ) { + vfolder_get_last_num(folder, dest); + if( dest->last_num < 0 ) return -1; + } + + for( cur = file_list; cur != NULL; cur = cur->next ) { + fileinfo = (MsgFileInfo *)cur->data; + + destfile = vfolder_get_new_msg_filename(dest); + if (! destfile) + goto bail; + +#ifdef G_OS_UNIX + if (! vitem->deep_copy) + r = symlink(fileinfo->file, destfile); + + if (r < 0) { + if (! vitem->deep_copy) { + g_warning("Requested sparse folder is not possible.\n" + "Using plain file copy instead\n"); + } +#endif + if (copy_file(fileinfo->file, destfile, TRUE) < 0) { + g_warning("can't copy message %s to %s\n", fileinfo->file, destfile); + g_free(destfile); + destfile = NULL; + goto bail; + } + vitem->deep_copy = TRUE; + +#ifdef G_OS_UNIX + } +#endif + + if( relation != NULL ) + g_hash_table_insert(relation, fileinfo, + GINT_TO_POINTER(dest->last_num + 1) ); + g_free(destfile); + vfolder_add_msg_to_message_bridge(vitem, + fileinfo->msginfo->msgnum, dest->last_num + 1); + dest->last_num++; + vfolder_folder_item_props_write(VFOLDER_ITEM(dest)); + } + +bail: + + return (destfile) ? dest->last_num : -1; +} + +static gint vfolder_add_msg(Folder* folder, FolderItem* dest, const gchar* file, + MsgFlags* flags) { + gint ret; + GSList file_list; + MsgFileInfo fileinfo; + + g_return_val_if_fail(file != NULL, -1); + + fileinfo.msginfo = NULL; + fileinfo.file = (gchar *)file; + fileinfo.flags = flags; + file_list.data = &fileinfo; + file_list.next = NULL; + + ret = vfolder_add_msgs(folder, dest, &file_list, NULL); + return ret; +} + +static gint vfolder_copy_msgs(Folder *folder, FolderItem *dest, + MsgInfoList *msglist, GHashTable *relation) { + MsgInfo *msginfo; + FolderItem *src; + GSList* cur = NULL; + gint last_num = 0; + + g_return_val_if_fail(dest != NULL && IS_VFOLDER_FOLDER_ITEM(dest), -1); + + if (! VFOLDER_ITEM(dest)->deep_copy) + return -1; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if( dest->last_num < 0 ) { + vfolder_get_last_num(folder, dest); + if( dest->last_num < 0 ) return -1; + } + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo->folder != NULL, -1); + + statusbar_print_all(_("Copying messages...")); + + src = msginfo->folder; + if (src->folder != dest->folder) { + GSList *infolist = NULL, *cur; + int res = -1; + for (cur = msglist; cur; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1); + fileinfo->file = procmsg_get_message_file(msginfo); + fileinfo->flags = &(msginfo->flags); + infolist = g_slist_prepend(infolist, fileinfo); + } + infolist = g_slist_reverse(infolist); + res = folder_item_add_msgs(dest, infolist, FALSE); + for (cur = infolist; cur; cur = cur->next) { + MsgFileInfo *info = (MsgFileInfo *)cur->data; + g_free(info->file); + g_free(info); + } + g_slist_free(infolist); + statusbar_pop_all(); + return res; + } + + for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) { + MsgInfo* msginfo = (MsgInfo *)cur->data; + + /* put the local file in the folder cache, so that we don't + * have to fetch it back later. */ + if (msginfo) { + gchar *cache_path = folder_item_get_path(msginfo->folder); + debug_print("cache_path: %s\n", cache_path); + gchar *real_file = g_strconcat( + cache_path, G_DIR_SEPARATOR_S, + itos(msginfo->msgnum), NULL); + debug_print("real_file: %s\n", real_file); + gchar *cache_file = NULL; + g_free(cache_path); + cache_path = folder_item_get_path(dest); + debug_print("cache_path: %s\n", cache_path); + cache_file = vfolder_get_new_msg_filename(dest); + debug_print("cache_file: %s\n", cache_file); + if (!is_dir_exist(cache_path)) + make_dir_hier(cache_path); + if (is_file_exist(real_file) && is_dir_exist(cache_path)) { + g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(dest->last_num)); + if (dest->last_num > last_num) + last_num = dest->last_num; + copy_file(real_file, cache_file, TRUE); + debug_print("copied to cache: %s\n", cache_file); + } + g_free(real_file); + g_free(cache_file); + g_free(cache_path); + } + } + statusbar_pop_all(); + + //dest->cache_dirty = 1; + //folder_item_update_thaw(); + + return last_num; +} + +static gint vfolder_copy_msg(Folder* folder, FolderItem* dest, MsgInfo* msginfo) { + GSList msglist; + + g_return_val_if_fail(dest != NULL && IS_VFOLDER_FOLDER_ITEM(dest), -1); + + if (! VFOLDER_ITEM(dest)->deep_copy) + return -1; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return vfolder_copy_msgs(folder, dest, &msglist, NULL); +} + +static gchar* vfolder_fetch_msg(Folder* folder, FolderItem* item, gint num) { + gchar* snum = g_strdup_printf("%d", num); + gchar* file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, VFOLDER_DIR, + G_DIR_SEPARATOR_S, item->name, G_DIR_SEPARATOR_S, snum, NULL); + + debug_print("VFolder: fetch_msg: '%s'\n", file); + + g_free(snum); + + return file; +} + +static MsgInfo* vfolder_get_msginfo(Folder* folder, FolderItem* item, gint num) { + MsgInfo* msginfo = NULL; + gchar* file; + MsgFlags flags; + + debug_print("VFolder: get_msginfo: %d\n", num); + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + file = vfolder_fetch_msg(folder, item, num); + if (! g_file_test(file, G_FILE_TEST_EXISTS)) { + g_free(file); + return NULL; + } + + flags.perm_flags = MSG_NEW | MSG_UNREAD; + flags.tmp_flags = 0; + + msginfo = procheader_parse_file(file, flags, TRUE, TRUE); + + if (msginfo) + msginfo->msgnum = num; + + if (!msginfo->folder) + msginfo->folder = item; + + g_free(file); + + return msginfo; +} + +static gint vfolder_remove_msg(Folder* folder, FolderItem* item, gint num) +{ + gboolean need_scan = FALSE; + gchar *file, *tmp; + + g_return_val_if_fail(item != NULL, -1); + + file = vfolder_fetch_msg(folder, item, num); + g_return_val_if_fail(file != NULL, -1); + + need_scan = vfolder_scan_required(folder, item); + + /* are we doing a folder move ? */ + tmp = g_strdup_printf("%s.tmp", file); + if (is_file_exist(tmp)) { + claws_unlink(tmp); + g_free(tmp); + g_free(file); + return 0; + } + g_free(tmp); + + if( claws_unlink(file) < 0 ) { + FILE_OP_ERROR(file, "unlink"); + g_free(file); + return -1; + } + + MsgInfo* msginfo = vfolder_find_msg_from_vfolder_num(VFOLDER_ITEM(item), num); + if (msginfo) { + vfolder_remove_msg_from_bridge(VFOLDER_ITEM(item), msginfo->msgnum, num); + vfolder_folder_item_props_write(VFOLDER_ITEM(item)); + } + else { + MsgBridge* bridge = g_hash_table_lookup(VFOLDER_ITEM(item)->me_to_claws, GUINT_TO_POINTER(num)); + if (bridge) { + vfolder_remove_msg_from_bridge(VFOLDER_ITEM(item), bridge->claws_num, num); + vfolder_folder_item_props_write(VFOLDER_ITEM(item)); + } + } + + if( !need_scan ) + item->mtime = time(NULL); + + g_free(file); + + return 0; +} + +static gint vfolder_remove_all_msg(Folder* folder, FolderItem* item) { + GSList *msg_list, *list; + + g_return_val_if_fail(item != NULL, -1); + + msg_list = folder_item_get_msg_list(item); + if (msg_list) { + for (list = msg_list; list; list = g_slist_next(list)) { + MsgInfo* msginfo = (MsgInfo *) list->data; + folder_item_remove_msg(item, msginfo->msgnum); + } + } + g_slist_free(msg_list); + + vfolder_remove_hashtables(VFOLDER_ITEM(item)); + + return 0; +} + +static gboolean vfolder_subscribe_uri(Folder* folder, const gchar* uri) { + return FALSE; +} + +static void vfolder_print_hash_table(GHashTable* hash) { + GHashTableIter iter; + gpointer key, value; + + if (hash) { + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next(&iter, &key, &value)) { + guint key = GPOINTER_TO_UINT(key); + MsgBridge* bridge = (MsgBridge *) value; + fprintf(stderr, "key: %u ----- me: %u -> claws: %u\n", + key, bridge->my_num, bridge->claws_num); + } + } +} + +static MsgInfo* vfolder_find_msg_from_num_rel(VFolderItem* item, + guint msgnum, + gboolean claws_to_me) { + MsgInfo* msginfo = NULL; + GHashTable* hash; + + g_return_val_if_fail(item != NULL, NULL); + if (msgnum < 1) + return NULL; + + vfolder_print_hash_table(item->claws_to_me); + vfolder_print_hash_table(item->me_to_claws); + + if (claws_to_me) + hash = item->claws_to_me; + else + hash = item->me_to_claws; + + MsgBridge* msgbridge = g_hash_table_lookup(hash, GUINT_TO_POINTER(msgnum)); + + if (msgbridge) { + if (claws_to_me) + msginfo = folder_item_get_msginfo(FOLDER_ITEM(item), msgbridge->my_num); + else + msginfo = folder_item_get_msginfo(item->source, msgbridge->claws_num); + } + + return msginfo; +} + +gboolean vfolder_replace_key_in_bridge(VFolderItem* item, guint from, guint to) { + gboolean ret = FALSE; + MsgBridge *bridge, *old; + + if (! item->me_to_claws || ! item->claws_to_me) + return TRUE; + + old = g_hash_table_lookup(item->claws_to_me, GUINT_TO_POINTER(from)); + if (!old) + return TRUE; + + debug_print("Replace key in hashtable: from->%u to->%u\n", from, to); + bridge = vfolder_create_bridge(to, old->my_num); + + ret = g_hash_table_remove(item->me_to_claws, GUINT_TO_POINTER(old->my_num)); + if (!ret) { + g_free(bridge); + return TRUE; + } + ret = g_hash_table_remove(item->claws_to_me, GUINT_TO_POINTER(old->claws_num)); + if (!ret) { + bridge->claws_num = from; + g_hash_table_replace(item->me_to_claws, GUINT_TO_POINTER(bridge->my_num), bridge); + return TRUE; + } + + g_hash_table_replace(item->claws_to_me, GUINT_TO_POINTER(bridge->claws_num), bridge); + g_hash_table_replace(item->me_to_claws, GUINT_TO_POINTER(bridge->my_num), bridge); + + return ret; +} + +gboolean vfolder_add_message_to_bridge(VFolderItem* item, MsgBridge* bridge) { + if (!item || !bridge) + return TRUE; + + return vfolder_add_msg_to_message_bridge(item, bridge->claws_num, bridge->my_num); +} + +MsgInfo* vfolder_find_msg_from_claws_num(VFolderItem* item, guint msgnum) { + return vfolder_find_msg_from_num_rel(item, msgnum, TRUE); +} + +MsgInfo* vfolder_find_msg_from_vfolder_num(VFolderItem* item, guint msgnum) { + return vfolder_find_msg_from_num_rel(item, msgnum, FALSE); +} + +GList* vfolder_get_vfolder_items(void) { + return g_list_copy(vfolders); +} + +VFolderItem* vfolder_get_vfolder_item(const gchar* name) { + VFolderItem* item = NULL; + gchar* folder_name = NULL; + gchar* this_name = NULL; + GList* list = vfolders; + + if (! name) + folder_name = g_strdup(VFOLDER_DEFAULT_MAILBOX); + else + folder_name = g_strdup(name); + + while (list && item == NULL) { + this_name = FOLDER_ITEM(list->data)->name; + if (this_name && strcmp(folder_name, this_name) == 0) + item = VFOLDER_ITEM(list->data); + else + list = list->next; + } + + g_free(folder_name); + + return item; +} + +GList* vfolder_get_vfolder_from_source(FolderItem* source) { + GList* items = NULL; + FolderItem* src = NULL; + GList* list = vfolders; + gchar* src_id = NULL; + + if (! source) + return NULL; + + src_id = folder_item_get_identifier(source); + if (!src_id) + return NULL; + + while (list) { + src = VFOLDER_ITEM(list->data)->source; + if (strcmp(folder_item_get_identifier(src), src_id) == 0) { + items = g_list_prepend(items, list->data); + } + list = list->next; + } + + return items; +} + +typedef struct { GSList* msgnumlist; guint remove; } RemoveInfo; +static void find_msg_to_remove(gpointer key, gpointer value, gpointer user_data) { + RemoveInfo* remove_info = (RemoveInfo *) user_data; + + if (remove_info->remove == 0) { + if (g_slist_index(remove_info->msgnumlist, key) < 0) { + MsgBridge* bridge = (MsgBridge *) value; + remove_info->remove = bridge->my_num; + } + } +}; + +static void vfolder_add_msg_to_folder(VFolderItem* item) { + GSList *msgnumlist, *cur; + GSList file_list; + MsgFileInfo fileinfo; + MsgInfoList msgs; + + if (! item->msg_filter_func) + return; + + msgnumlist = folder_item_get_number_list(item->source); + for (cur = msgnumlist; cur; cur = g_slist_next(cur)) { + MsgBridge* bridge = g_hash_table_lookup(item->claws_to_me, cur->data); + if (! bridge) { + msgs.data = folder_item_get_msginfo(item->source, GPOINTER_TO_UINT(cur->data)); + msgs.next = NULL; + MsgInfoList* list = item->msg_filter_func(&msgs, item); + if (list) { + fileinfo.msginfo = list->data; + fileinfo.file = procmsg_get_message_file_path(fileinfo.msginfo); + fileinfo.flags = &fileinfo.msginfo->flags; + file_list.data = &fileinfo; + file_list.next = NULL; + vfolder_add_msgs(item->source->folder, FOLDER_ITEM(item), &file_list, NULL); + FOLDER_ITEM(item)->update_flags = F_ITEM_UPDATE_ADDMSG; + g_slist_free(list); + } + } + } + g_slist_free(msgnumlist); +} + +static void vfolder_remove_msg_from_folder(VFolderItem* item) { + RemoveInfo remove_info; + remove_info.msgnumlist = folder_item_get_number_list(item->source); + remove_info.remove = 0; + g_hash_table_foreach(item->claws_to_me, find_msg_to_remove, &remove_info); + vfolder_remove_msg(FOLDER_ITEM(item)->folder, FOLDER_ITEM(item), remove_info.remove); + g_slist_free(remove_info.msgnumlist); + FOLDER_ITEM(item)->update_flags = F_ITEM_UPDATE_REMOVEMSG; +} + +void vfolder_folder_item_update_msgs(VFolderItem* item, FolderItemUpdateFlags flag) { + guint c; + + if (item->frozen || item->updating) + return; + + if (item->claws_to_me) { + item->updating = TRUE; + switch (flag) { + case F_ITEM_UPDATE_MSGCNT: + break; + case F_ITEM_UPDATE_CONTENT: + c = g_hash_table_size(item->claws_to_me); + if (c < item->source->total_msgs) { + /* add messages */ + vfolder_add_msg_to_folder(item); + } + else if (c > item->source->total_msgs) { + /* remove messages */ + vfolder_remove_msg_from_folder(item); + } + break; + case F_ITEM_UPDATE_ADDMSG: + vfolder_add_msg_to_folder(item); + break; + case F_ITEM_UPDATE_REMOVEMSG: + vfolder_remove_msg_from_folder(item); + break; + case F_ITEM_UPDATE_NAME: + break; + } + item->updating = FALSE; + } +} + +FolderClass* vfolder_folder_get_class() { + if (vfolder_class.idstr == NULL ) { + vfolder_class.type = F_UNKNOWN; + vfolder_class.idstr = "vfolder"; + vfolder_class.uistr = "VFolder"; + + /* Folder functions */ + vfolder_class.new_folder = vfolder_new_folder; + vfolder_class.destroy_folder = vfolder_destroy_folder; + vfolder_class.set_xml = folder_set_xml; + vfolder_class.get_xml = folder_get_xml; + vfolder_class.scan_tree = vfolder_scan_tree; + vfolder_class.create_tree = vfolder_create_tree; + + /* FolderItem functions */ + vfolder_class.item_new = vfolder_item_new; + vfolder_class.item_destroy = vfolder_item_destroy; + vfolder_class.item_get_path = vfolder_item_get_path; + vfolder_class.create_folder = vfolder_create_folder; + vfolder_class.rename_folder = NULL; + vfolder_class.remove_folder = vfolder_remove_folder; + vfolder_class.get_num_list = vfolder_get_num_list; + vfolder_class.scan_required = vfolder_scan_required; + + /* Message functions */ + vfolder_class.get_msginfo = vfolder_get_msginfo; + vfolder_class.fetch_msg = vfolder_fetch_msg; + vfolder_class.copy_msgs = vfolder_copy_msgs; + vfolder_class.copy_msg = vfolder_copy_msg; + vfolder_class.add_msg = vfolder_add_msg; + vfolder_class.add_msgs = vfolder_add_msgs; + vfolder_class.remove_msg = vfolder_remove_msg; + vfolder_class.remove_msgs = NULL; + vfolder_class.remove_all_msg = vfolder_remove_all_msg; + vfolder_class.change_flags = NULL; + vfolder_class.subscribe = vfolder_subscribe_uri; + debug_print("VFolder: registered folderclass\n"); + } + + return &vfolder_class; +} + +/* Local functions */ + +gboolean vfolder_init(void) { + gchar* error = g_new0(gchar, 1); + + folder_register_class(vfolder_folder_get_class()); + + if (! vfolder_gtk_init(&error)) { + alertpanel_error("%s", error); + vfolder_done(); + return FALSE; + } + + folder_func_to_all_folders((FolderItemFunc)vfolder_init_read_func, NULL); + + if (existing_tree_found == FALSE) + vfolder_create_default_mailbox(); + + return TRUE; +} + +void vfolder_done(void) { + + vfolder_gtk_done(); + + if (!claws_is_exiting()) + folder_unregister_class(vfolder_folder_get_class()); +} diff --git a/src/vfolder.h b/src/vfolder.h new file mode 100644 index 0000000..aa1f750 --- /dev/null +++ b/src/vfolder.h @@ -0,0 +1,108 @@ +/* + * $Id: $ + */ + +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifndef __VFOLDER_H__ +#define __VFOLDER_H__ + +#include + +G_BEGIN_DECLS + +#include "folder.h" +#include +#include "gettext.h" + +/* Name of directory in rcdir where VFolder will store its data. */ +#define VFOLDER_DIR "vfolder" + +/* Parent mailbox name */ +#define VFOLDER_DEFAULT_MAILBOX _("Virtual folders") + +typedef enum { + SEARCH_HEADERS = 1, /* from/to/subject */ + SEARCH_BODY = 2, /* message */ + SEARCH_BOTH = 4 /* both */ +} SearchType; + +typedef struct { + guint my_num; + guint claws_num; +} MsgBridge; + +/*typedef enum { + BEFORE_ADDING_MSG = 1, + BEFORE_DELETING_MSG = 2, + AFTER_ADDING_MSG = 4, + AFTER_DELETING_MSG = 8 +} ChangeAction; +*/ + +typedef struct _VFolderItem VFolderItem; +typedef MsgInfoList* (*MSGFILTERFUNC) (MsgInfoList* msgs, VFolderItem* item); + +struct _VFolderItem { + FolderItem item; + + gchar* filter; /* Regex used to select messages */ + gboolean frozen; /* Automatic update or not */ + gboolean deep_copy; /* Copy messages or use reference */ + SearchType search; + gboolean updating; /* Is this VFolder currently updating */ + + FolderItem* source; /* Source folder for virtual folder */ +// MsgInfoList* msginfos; /* List of MsgInfo */ + GHashTable* me_to_claws; /* Hashtable containing MsgBridge */ + GHashTable* claws_to_me; /* Hashtable containing MsgBridge */ + MSGFILTERFUNC msg_filter_func; /* Active filter function */ +}; + +gboolean vfolder_init(void); +void vfolder_done(void); + +FolderClass* vfolder_folder_get_class(void); +VFolderItem* vfolder_get_vfolder_item(const gchar* name); +GList* vfolder_get_vfolder_items(void); +GList* vfolder_get_vfolder_from_source(FolderItem* source); +gboolean vfolder_add_message_to_bridge(VFolderItem* item, MsgBridge* bridge); +gboolean vfolder_replace_key_in_bridge(VFolderItem* item, guint from, guint to); +MsgInfo* vfolder_find_msg_from_claws_num(VFolderItem* item, guint msgnum); +MsgInfo* vfolder_find_msg_from_vfolder_num(VFolderItem* item, guint msgnum); +void vfolder_folder_item_update_msgs(VFolderItem* item, FolderItemUpdateFlags flag); + +#define VFOLDER_ITEM(obj) ((VFolderItem *)obj) +#define VFOLDER(obj) ((VFolder *)obj) + +#define IS_VFOLDER_FOLDER(folder) \ + ((folder) && (folder->klass == vfolder_folder_get_class())) +#define IS_VFOLDER_FOLDER_ITEM(item) \ + ((item) && (item->folder->klass == vfolder_folder_get_class())) +#define IS_VFOLDER_MSGINFO(msginfo) \ + ((msginfo) && (msginfo->folder) && IS_VFOLDER_FOLDER_ITEM(msginfo->folder)) +#define IS_VFOLDER_FROZEN(item) ((item) && (item->frozen)) +#define IS_VFOLDER_DEEP_COPY(item) ((item) && (item->deep_copy)) + +G_END_DECLS + +#endif diff --git a/src/vfolder_gtk.c b/src/vfolder_gtk.c new file mode 100644 index 0000000..9a8f864 --- /dev/null +++ b/src/vfolder_gtk.c @@ -0,0 +1,1069 @@ +/* + * $Id: $ + */ +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gtk/menu.h" +#include "gtk/gtk.h" +#include "mainwindow.h" +#include "inputdialog.h" +#include "folder.h" +#include "folderview.h" +#include "alertpanel.h" +#include "hooks.h" +#include "utils.h" +#include "summaryview.h" + +#include "gettext.h" +#include "main.h" +#include "gtkutils.h" + +#include "vfolder_gtk.h" +#include "vfolder.h" +#include "vfolder_prop.h" + +#define CONFIG_GROUP "VFolder" + +static guint folder_hook_id; +static guint item_hook_id; +static guint msginfo_hook_id; +static GSList* widgets = NULL; + +typedef struct { + MsgInfoList* list; + VFolderItem* item; + gchar* file; +} AddMsgData; + +typedef struct { + GtkWidget* widget; + GtkAction* action; +} MenuItem; + +static char* vfolder_popup_menu_labels[] = { + N_("_Refresh folder"), + N_("Refresh _all folders"), + N_("Folder pr_operties..."), + N_("Rena_me..."), + N_("_Create new folder..."), + N_("_Delete folder..."), + NULL +}; + +static GtkActionEntry vfolder_popup_entries[] = { + {"FolderViewPopup/RefreshFolder", NULL, NULL, NULL, NULL, NULL /*G_CALLBACK(vfolder_refresh_cb)*/ }, + {"FolderViewPopup/RefreshAllFolders", NULL, NULL, NULL, NULL, NULL /*G_CALLBACK(vfolder_refresh_cb)*/ }, + + {"FolderViewPopup/FolderProperties", NULL, NULL, NULL, NULL, G_CALLBACK(vfolder_properties_cb) }, + + {"FolderViewPopup/RenameFolder", NULL, NULL, NULL, NULL, NULL /*G_CALLBACK(vfolder_refresh_cb)*/ }, + + {"FolderViewPopup/NewFolder", NULL, NULL, NULL, NULL, G_CALLBACK(vfolder_new_folder_cb) }, + {"FolderViewPopup/RemoveFolder", NULL, NULL, NULL, NULL, G_CALLBACK(vfolder_remove_folder_cb) }, +}; + +static void vfolder_add_menuitems(GtkUIManager *ui_manager, FolderItem *item) { + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RefreshFolder", "FolderViewPopup/RefreshFolder", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RefreshAllFolders", "FolderViewPopup/RefreshAllFolders", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVF1", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "FolderProperties", "FolderViewPopup/FolderProperties", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVF2", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RenameFolder", "FolderViewPopup/RenameFolder", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVF3", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "NewFolder", "FolderViewPopup/NewFolder", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RemoveFolder", "FolderViewPopup/RemoveFolder", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVF4", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) +} + +static void vfolder_set_sensitivity(GtkUIManager *ui_manager, FolderItem *item) { +#define SET_SENS(name, sens) \ + cm_menu_set_sensitive_full(ui_manager, "Popup/"name, sens) + + VFolderItem *ritem = (VFolderItem *)item; + SET_SENS("FolderViewPopup/RefreshFolder", folder_item_parent(item) != NULL && ! ritem->frozen); + SET_SENS("FolderViewPopup/RefreshAllFolders", folder_item_parent(item) == NULL && ! ritem->frozen); + SET_SENS("FolderViewPopup/FolderProperties", folder_item_parent(item) != NULL); + SET_SENS("FolderViewPopup/RenameFolder", folder_item_parent(item) != NULL); + SET_SENS("FolderViewPopup/NewFolder", TRUE); + SET_SENS("FolderViewPopup/RemoveFolder", folder_item_parent(item) != NULL); + +#undef SET_SENS +} + +static FolderViewPopup vfolder_popup = { + "vfolder", + "", + vfolder_popup_entries, + G_N_ELEMENTS(vfolder_popup_entries), + NULL, 0, + NULL, 0, 0, NULL, + vfolder_add_menuitems, + vfolder_set_sensitivity +}; + +static void vfolder_fill_popup_menu_labels(void) { + gint i; + + for (i = 0; vfolder_popup_menu_labels[i] != NULL; i++) { + (vfolder_popup_entries[i]).label = _(vfolder_popup_menu_labels[i]); + } +} + +static void gslist_menu_item_free(GSList** menu_list) { + GSList* list; + + if (! menu_list || ! *menu_list) + return; + + for (list = *menu_list; list; list = g_slist_next(list)) { + MenuItem* menu = (MenuItem *) list->data; + g_free(menu); + } + + g_slist_free(*menu_list); + *menu_list = NULL; +} + +static MsgBridge* vfolder_split_file_id(gchar* id) { + MsgBridge* resp = NULL; + + if (! id) + return NULL; + + gchar** tokens = g_strsplit(id, ":", 0); + if (g_strv_length(tokens) != 2) + goto bail; + + resp = g_new0(MsgBridge, 1); + resp->my_num = to_number(tokens[0]); + resp->claws_num = to_number(tokens[1]); + +bail: + g_strfreev(tokens); + return resp; +} + +static gboolean get_menu_widgets() { + MainWindow* mainwindow; + MenuItem* menuitem = NULL; + GtkWidget* widget; + + mainwindow = mainwindow_get_mainwindow(); + if (mainwindow && mainwindow->ui_manager) { + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Move/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Move/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else + return FALSE; + + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Trash/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Trash/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else { + gslist_menu_item_free(&widgets); + return FALSE; + } + + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Delete/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menus/SummaryViewPopup/Delete/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else { + gslist_menu_item_free(&widgets); + return FALSE; + } + + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menu/Message/Move/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menu/Message/Move/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else { + gslist_menu_item_free(&widgets); + return FALSE; + } + + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menu/Message/Trash/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menu/Message/Trash/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else { + gslist_menu_item_free(&widgets); + return FALSE; + } + + widget = gtk_ui_manager_get_widget( + mainwindow->ui_manager, "/Menu/Message/Delete/"); + if (widget) { + menuitem = g_new0(MenuItem, 1); + menuitem->widget = widget; + menuitem->action = gtk_ui_manager_get_action( + mainwindow->ui_manager, "/Menu/Message/Delete/"); + widgets = g_slist_prepend(widgets, menuitem); + } + else { + gslist_menu_item_free(&widgets); + return FALSE; + } + + } + else + return FALSE; + + return TRUE; +} +/* +static gboolean vfolder_widgets_is_visible() { + gboolean visible = TRUE; + + if (widgets && widgets->data) { + MenuItem* menu = (MenuItem *) widgets->data; + visible = gtk_widget_get_visible(menu->widget); + } + + return visible; +} + +static gboolean vfolder_hide_widgets(VFolderItem* item) { + GSList* list; + MainWindow* mainwindow; + +// if (! item->deep_copy) { + for (list = widgets; list; list = g_slist_next(list)) { + MenuItem* menu = (MenuItem *) list->data; + gtk_widget_hide(menu->widget); + gtk_action_block_activate(menu->action); + } + + mainwindow = mainwindow_get_mainwindow(); + if (mainwindow && mainwindow->toolbar) { + if (mainwindow->toolbar->trash_btn) + gtk_widget_hide(mainwindow->toolbar->trash_btn); + if (mainwindow->toolbar->delete_btn) + gtk_widget_hide(mainwindow->toolbar->delete_btn); + } +// } + return TRUE; +} +*/ +static gboolean vfolder_show_widgets(VFolderItem* item) { + GSList* list; + MainWindow* mainwindow; + +// if (! item->deep_copy) { + for (list = widgets; list; list = g_slist_next(list)) { + MenuItem* menu = (MenuItem *) list->data; + gtk_widget_show(menu->widget); + gtk_action_unblock_activate(menu->action); + } + + mainwindow = mainwindow_get_mainwindow(); + if (mainwindow && mainwindow->toolbar) { + if (mainwindow->toolbar->trash_btn) + gtk_widget_show(mainwindow->toolbar->trash_btn); + if (mainwindow->toolbar->delete_btn) + gtk_widget_show(mainwindow->toolbar->delete_btn); + } +// } + return TRUE; +} +/* +static gchar* vfolder_get_message_file_path(VFolderItem* item, MsgInfo* msg) { + gchar* path; + GSList* list = NULL, *cur; + Folder* folder; + gboolean old_uid; + guint last = 0; + + if (item->deep_copy) { + path = procmsg_get_message_file_path(msg); + } + else { + gchar* root = folder_item_get_path(msg->to_folder); + folder = msg->to_folder->folder; + guint num = folder->klass->get_num_list(folder, msg->to_folder, &list, &old_uid); + if (num >= 0) { + for (cur = list, last = 0; cur; cur = g_slist_next(cur)) { + guint tmp = GPOINTER_TO_UINT(cur->data); + if (tmp > last) + last = tmp; + } + } + g_slist_free(list); + + path = g_strdup_printf("%s%s%u", root, G_DIR_SEPARATOR_S, last + 1); + g_free(root); + } + return path; +} +*/ + +/* +static void vfolder_item_update(AddMsgData* msgdata) { + MsgInfoList* cur; + GSList update; + MsgFileInfo fileinfo; + + if (!msgdata->item || !msgdata->list->data) + return; + + for (cur = msgdata->list; cur; cur = g_slist_next(cur)) { + MsgInfo* msg = (MsgInfo *) cur->data; + if (MSG_IS_DELETED(msg->flags)) { + folder_item_remove_msg(FOLDER_ITEM(msgdata->item), msg->msgnum); + } + else { + fileinfo.msginfo = msg; + fileinfo.flags = &msg->flags; + fileinfo.file = msgdata->file; + update.data = &fileinfo; + update.next = NULL; + folder_item_scan(msg->folder); + gint n = folder_item_add_msgs(FOLDER_ITEM(msgdata->item), &update, FALSE); + gchar* p = strrchr(fileinfo.file, G_DIR_SEPARATOR); + p += 1; + guint num = to_number((const gchar *) p); + vfolder_replace_key_in_bridge(msgdata->item, msg->msgnum, num); + FOLDER_ITEM(msgdata->item)->last_num = n; + } + } + + //procmsg_message_file_list_free(list); + //item->msginfos = folder_item_get_msg_list(FOLDER_ITEM(item)); +} +*/ +/* +static void add_msg_data_free(AddMsgData** rec) { + if (rec && *rec) { + AddMsgData* data = *rec; + g_slist_free(data->list); + g_free(data->file); + g_free(data); + *rec = NULL; + } +} +*/ +/* +static void vfolder_update_affected_folder_items(MsgInfo* msginfo) { + GList *vfolders, *cur; + GSList* cur_msg; + gchar* src; + AddMsgData* data; + MsgInfo* msg; + + if (! msginfo) + return; + + if (MSG_IS_NEW(msginfo->flags) || + MSG_IS_MOVE(msginfo->flags) || + MSG_IS_COPY(msginfo->flags) || + MSG_IS_DELETED(msginfo->flags)) { + vfolders = vfolder_get_vfolder_items(); + for (cur = vfolders; cur; cur = g_list_next(cur)) { + data = g_new0(AddMsgData, 1); + VFolderItem* vitem = VFOLDER_ITEM(cur->data); + if (MSG_IS_MOVE(msginfo->flags) || MSG_IS_COPY(msginfo->flags)) + src = folder_item_get_identifier(msginfo->to_folder); + else + src = folder_item_get_identifier(msginfo->folder); + gchar* shadow = folder_item_get_identifier(vitem->source); + debug_print("cmp %s : %s\n", src, shadow); + if (src && shadow && strcmp(src, shadow) == 0) { + if (MSG_IS_DELETED(msginfo->flags)) { + msg = vfolder_find_msg_from_claws_num(vitem, msginfo->msgnum); + if (msg) + data->list = g_slist_append(data->list, msg); + else { + add_msg_data_free(&data); + g_slist_free(add_msg_data); + add_msg_data = NULL; + g_free(src); + g_free(shadow); + return; + } + } + else { + data->list = g_slist_append(data->list, msginfo); + data->item = vitem; + data->file = vfolder_get_message_file_path(vitem, msginfo); + add_msg_data = g_slist_prepend(add_msg_data, data); + } + if (data->list && MSG_IS_DELETED(msginfo->flags)) { + GSList* list = vfolder_filter_msgs_list(data->list, vitem); + if (list && list->data) { + MsgInfo* msg = (MsgInfo *) list->data; + MSG_SET_PERM_FLAGS(msg->flags, MSG_DELETED); + } + g_slist_free(data->list); + data->list = list; + data->item = vitem; + vfolder_item_update(data); + add_msg_data_free(&data); + g_slist_free(add_msg_data); + add_msg_data = NULL; + } + } + g_free(src); + g_free(shadow); + } + } + if (add_msg_data) { + for (cur_msg = add_msg_data; cur_msg; cur_msg = g_slist_next(cur_msg)) { + data = (AddMsgData *) cur_msg->data; + GSList* list = vfolder_filter_msgs_list(data->list, data->item); + g_slist_free(data->list); + data->list = list; + vfolder_item_update(data); + add_msg_data_free(&data); + } + g_slist_free(add_msg_data); + add_msg_data = NULL; + } +} +*/ +static gboolean vfolder_folder_update_hook(gpointer source, gpointer data) { + FolderUpdateData* hookdata; + + g_return_val_if_fail(source != NULL, FALSE); + hookdata = (FolderUpdateData *) source; + + if (! hookdata->folder || IS_VFOLDER_FOLDER(hookdata->folder)) + return FALSE; + + if (hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) { + /* TODO: check if the removed folder item is foundation for vfolder */ + debug_print("FOLDER_REMOVE_FOLDERITEM\n"); + } + + if (hookdata->update_flags & FOLDER_REMOVE_FOLDER) { + /* TODO: check if the removed folder is foundation for vfolder */ + debug_print("FOLDER_REMOVE_FOLDER\n"); + } + + if (hookdata->update_flags & FOLDER_TREE_CHANGED) { + /* TODO: check if the removed folder is foundation for vfolder */ + debug_print("FOLDER_TREE_CHANGED\n"); + } + + if (hookdata->update_flags & FOLDER_RENAME_FOLDERITEM) { + /* TODO: check if the removed folder is foundation for vfolder */ + debug_print("FOLDER_RENAME_FOLDERITEM\n"); + } + + return FALSE; +} + +static gboolean vfolder_folder_item_update_hook(gpointer source, gpointer data) { + FolderItemUpdateData* hookdata; +// gint save_state = -1; + GList *items, *cur; +// gboolean r; +// MainWindow* mainwindow; + + g_return_val_if_fail(source != NULL, FALSE); + hookdata = (FolderItemUpdateData *) source; + + if (! hookdata->item || IS_VFOLDER_FOLDER_ITEM(hookdata->item)) + return FALSE; + + if (hookdata->update_flags & F_ITEM_UPDATE_REMOVEMSG ) { + debug_print("F_ITEM_UPDATE_REMOVEMSG\n"); + items = vfolder_get_vfolder_from_source(hookdata->item); + if (items) { + for (cur = items; cur; cur = g_list_next(cur)) { + vfolder_folder_item_update_msgs(VFOLDER_ITEM(cur->data), F_ITEM_UPDATE_REMOVEMSG); + } + g_list_free(items); + } + } + + else if (hookdata->update_flags & F_ITEM_UPDATE_CONTENT) { + debug_print("F_ITEM_UPDATE_CONTENT\n"); + items = vfolder_get_vfolder_from_source(hookdata->item); + if (items) { + for (cur = items; cur; cur = g_list_next(cur)) { + vfolder_folder_item_update_msgs(VFOLDER_ITEM(cur->data), F_ITEM_UPDATE_CONTENT); + } + g_list_free(items); + } + //mainwindow = mainwindow_get_mainwindow(); + //summary_execute(mainwindow->summaryview); + } + + else if (hookdata->update_flags & F_ITEM_UPDATE_ADDMSG) { + debug_print("F_ITEM_UPDATE_ADDMSG\n"); + items = vfolder_get_vfolder_from_source(hookdata->item); + if (items) { + for (cur = items; cur; cur = g_list_next(cur)) { + vfolder_folder_item_update_msgs(VFOLDER_ITEM(cur->data), F_ITEM_UPDATE_ADDMSG); + } + g_list_free(items); + } + } + + else if (hookdata->update_flags & F_ITEM_UPDATE_MSGCNT) { + debug_print("F_ITEM_UPDATE_MSGCNT\n"); +/* if (IS_VFOLDER_FOLDER_ITEM(item)) { + + if (! (VFOLDER_ITEM(item))->deep_copy) { + if (! (VFOLDER_ITEM(item))->active) { + r = vfolder_hide_widgets(VFOLDER_ITEM(item)); + if (r) + VFOLDER_ITEM(item)->active = TRUE; + } + else { + r = vfolder_show_widgets(VFOLDER_ITEM(item)); + if (r) + VFOLDER_ITEM(item)->active = FALSE; + } + + if (r) + save_state = 1; + else + save_state = 0; + } + else + vfolder_show_widgets(VFOLDER_ITEM(item)); + } + else { + if (!vfolder_widgets_is_visible()) + vfolder_show_widgets(VFOLDER_ITEM(item)); + }*/ +/* + items = vfolder_get_vfolder_from_source(hookdata->item); + if (items) { + for (cur = items; cur; cur = g_list_next(cur)) { + vfolder_folder_item_update_msgs(VFOLDER_ITEM(cur->data), F_ITEM_UPDATE_MSGCNT); + } + g_list_free(items); + } +*/ + } + + else if (hookdata->update_flags & F_ITEM_UPDATE_NAME) { + /* TODO: need update? */ + debug_print("F_ITEM_UPDATE_NAME\n"); + items = vfolder_get_vfolder_from_source(hookdata->item); + if (items) { + for (cur = items; cur; cur = g_list_next(cur)) { + vfolder_folder_item_update_msgs(VFOLDER_ITEM(cur->data), F_ITEM_UPDATE_NAME); + } + g_list_free(items); + } + } + + else { + /* Unhandled callback */ + debug_print("Unhandled FolderItem callback\n"); + } +/* + if (!save_state) { + MainWindow* mainwindow = mainwindow_get_mainwindow(); + alertpanel_error(_("%s: Could not hide dangerous actions"), hookdata->item->name); + summary_lock(mainwindow->summaryview); + } +*/ + return FALSE; +} +/* +static gboolean vfolder_msg_info_update_hook(gpointer source, gpointer data) { + MsgInfoUpdate* hookdata; + MainWindow* mainwindow; + MsgInfo* msginfo; + + g_return_val_if_fail(source != NULL, FALSE); + hookdata = (MsgInfoUpdate *) source; + msginfo = hookdata->msginfo; + + g_return_val_if_fail(msginfo != NULL, TRUE); + + if (IS_VFOLDER_MSGINFO(msginfo)) + return FALSE; + + debug_print("\n\nPermflag: %u Tmpflag: %u (scanned: %u)\n\n\n", + (guint32) msginfo->flags.perm_flags, (guint32) msginfo->flags.tmp_flags, 1U << 31); + if (MSG_IS_NEW(msginfo->flags)) { + debug_print("MSG_IS_NEW\n"); + vfolder_update_affected_folder_items(msginfo); + mainwindow = mainwindow_get_mainwindow(); + summary_execute(mainwindow->summaryview); + } + + if (MSG_IS_DELETED(msginfo->flags)) { + debug_print("MSG_IS_DELETED\n"); + vfolder_update_affected_folder_items(msginfo); + mainwindow = mainwindow_get_mainwindow(); + summary_execute(mainwindow->summaryview); + } + + if (MSG_IS_MOVE(msginfo->flags)) { + debug_print("MSG_IS_MOVE\n"); + vfolder_update_affected_folder_items(msginfo); + mainwindow = mainwindow_get_mainwindow(); + summary_execute(mainwindow->summaryview); + } + + if (MSG_IS_COPY(msginfo->flags)) { + debug_print("MSG_IS_COPY\n"); + vfolder_update_affected_folder_items(msginfo); + mainwindow = mainwindow_get_mainwindow(); + summary_execute(mainwindow->summaryview); + } + +// if (MSG_IS_POSTFILTERED(msginfo->flags)) { +// debug_print("MSG_IS_POSTFILTERED\n"); +// vfolder_update_affected_folder_items(msginfo); +// mainwindow = mainwindow_get_mainwindow(); +// summary_execute(mainwindow->summaryview); +// } + + + return FALSE; +} +*/ +static gchar* vfolder_get_rc_file(VFolderItem* item) { + gchar* (*item_get_path) (Folder* folder, FolderItem* item); + gchar* path; + gchar* rc_file; + + item_get_path = FOLDER_ITEM(item)->folder->klass->item_get_path; + + path = item_get_path(FOLDER_ITEM(item)->folder, FOLDER_ITEM(item)); + rc_file = g_strconcat(path, G_DIR_SEPARATOR_S, "folderitemrc", NULL); + g_free(path); + + return rc_file; +} + +FolderPropsResponse vfolder_folder_item_props_write(VFolderItem* item) { + gchar* rc_file; + GKeyFile* config; + gchar* id; + gchar* data = NULL; + FILE* fp; + gsize len = 0; + FolderPropsResponse resp = FOLDER_ITEM_PROPS_NO_ITEM; + gchar* numstr; + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail(item != NULL, resp); + + rc_file = vfolder_get_rc_file(item); + config = g_key_file_new(); + + if (item->filter) + g_key_file_set_string(config, CONFIG_GROUP, "filter", item->filter); + + g_key_file_set_integer(config, CONFIG_GROUP, "searchtype", item->search); + g_key_file_set_boolean(config, CONFIG_GROUP, "frozen", item->frozen); + g_key_file_set_boolean(config, CONFIG_GROUP, "deep_copy", item->deep_copy); + + if (item->source) { + id = folder_item_get_identifier(item->source); + if (id) { + g_key_file_set_string(config, CONFIG_GROUP, "source", id); + g_free(id); + } + } + + if (item->claws_to_me && item->me_to_claws) { + numstr = NULL; + g_hash_table_iter_init(&iter, item->claws_to_me); + while (g_hash_table_iter_next(&iter, &key, &value)) { + len++; + MsgBridge* bridge = value; + if (numstr) { + gchar* tmp = g_strdup(numstr); + g_free(numstr); + numstr = g_strdup_printf("%s, %u:%u", + tmp, bridge->my_num, bridge->claws_num); + g_free(tmp); + } + else + numstr = g_strdup_printf("%u:%u", bridge->my_num, bridge->claws_num); + } + + g_key_file_set_string(config, CONFIG_GROUP, "file_id_list", numstr); + g_free(numstr); + } + + if (g_file_test(rc_file, G_FILE_TEST_EXISTS)) { + gchar* bakpath = g_strconcat(rc_file, ".bak", NULL); + if (g_rename(rc_file, bakpath) < 0) { + g_warning("%s: Could not create", bakpath); + resp = FOLDER_ITEM_PROPS_BACKUP_FAIL; + } + g_free(bakpath); + } + +// g_key_file_set_integer(config, CONFIG_GROUP, "filter-function", item->filter_func); + + data = g_key_file_to_data(config, &len, NULL); + if (len < 1) { + g_warning("Could not get config data"); + resp = FOLDER_ITEM_PROPS_READ_DATA_FAIL; + } + else { + fp = g_fopen(rc_file, "w"); + if (fp == NULL) { + gchar* dir_path_end = g_strrstr(rc_file, G_DIR_SEPARATOR_S); + gchar* rc_dir = g_strndup(rc_file, dir_path_end - rc_file); + debug_print("rc_dir: %s\n", rc_dir); + int r = g_mkdir_with_parents(rc_dir, 0700); + if (r != 0) + resp = FOLDER_ITEM_PROPS_MAKE_RC_DIR_FAIL; + g_free(rc_dir); + if (resp == FOLDER_ITEM_PROPS_MAKE_RC_DIR_FAIL) + goto error; + fp = g_fopen(rc_file, "w"); + if (fp == NULL) { + resp = FOLDER_ITEM_PROPS_MAKE_RC_DIR_FAIL; + goto error; + } + } + fwrite(data, len, 1, fp); + fclose(fp); + resp = FOLDER_ITEM_PROPS_OK; + } + +error: + g_free(data); + + g_key_file_free(config); + g_free(rc_file); + + return resp; +} + +FolderPropsResponse vfolder_folder_item_props_read(VFolderItem* item) { + gchar* rc_file; + GKeyFile* config; + GError* error = NULL; + gchar *id, *msgnums; + gchar **list, **head; + FolderPropsResponse resp = FOLDER_ITEM_PROPS_NO_ITEM; + gint lastnum; + + g_return_val_if_fail(item != NULL, resp); + + rc_file = vfolder_get_rc_file(item); + config = g_key_file_new(); + + if (g_file_test(rc_file, G_FILE_TEST_EXISTS)) { + g_key_file_load_from_file(config, rc_file, G_KEY_FILE_KEEP_COMMENTS, &error); + if (error) { + g_warning("%s. Using defaults", error->message); + g_error_free(error); + resp = FOLDER_ITEM_PROPS_READ_USING_DEFAULT; + } + else { + item->filter = g_key_file_get_string(config, CONFIG_GROUP, "filter", NULL); + item->search = g_key_file_get_integer(config, CONFIG_GROUP, "searchtype", NULL); + item->frozen = g_key_file_get_boolean(config, CONFIG_GROUP, "frozen", NULL); + item->deep_copy = g_key_file_get_boolean(config, CONFIG_GROUP, "deep_copy", NULL); +// item->filter_func = g_key_file_get_integer(config, CONFIG_GROUP, "filter_function", NULL); + + id = g_key_file_get_string(config, CONFIG_GROUP, "source", NULL); + if (id) { + item->source = folder_find_item_from_identifier(id); + g_free(id); + } + msgnums = g_key_file_get_string(config, CONFIG_GROUP, "file_id_list", NULL); + if (msgnums) { + list = g_strsplit(msgnums, ",", 0); + head = list; + lastnum = -1; + while (*list) { + gchar* anum = g_strdup(*list++); + g_strstrip(anum); + MsgBridge* bridge = vfolder_split_file_id(anum); + g_free(anum); + if (lastnum < (gint) bridge->my_num) + lastnum = bridge->my_num; + if (bridge->my_num > 0) { + vfolder_add_message_to_bridge(item, bridge); + } + g_free(bridge); + } + FOLDER_ITEM(item)->last_num = lastnum; + g_strfreev(head); + g_free(msgnums); + } + resp = FOLDER_ITEM_PROPS_OK; + } + } + + g_key_file_free(config); + g_free(rc_file); + + return resp; +} + +gboolean vfolder_gtk_init(gchar** error) { + vfolder_fill_popup_menu_labels(); + folderview_register_popup(&vfolder_popup); + + folder_hook_id = hooks_register_hook(FOLDER_UPDATE_HOOKLIST, + vfolder_folder_update_hook, NULL); + if (folder_hook_id == -1) { + *error = g_strdup(_("Failed to register folder update hook")); + return FALSE; + } + + item_hook_id = hooks_register_hook(FOLDER_ITEM_UPDATE_HOOKLIST, + vfolder_folder_item_update_hook, NULL); + if (item_hook_id == -1) { + *error = g_strdup(_("Failed to register folder item update hook")); + hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id); + return FALSE; + } + +/* msginfo_hook_id = hooks_register_hook(MSGINFO_UPDATE_HOOKLIST, + vfolder_msg_info_update_hook, NULL); + if (msginfo_hook_id == -1) { + *error = g_strdup(_("Failed to register message info update hook")); + hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id); + hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, item_hook_id); + return FALSE; + }*/ + + if (! get_menu_widgets()) { + *error = g_strdup(_("Failed to get menu widgets")); + hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id); + hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, item_hook_id); + hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, msginfo_hook_id); + return FALSE; + } + + return TRUE; +} + +void vfolder_gtk_done(void) { + MainWindow *mainwin = mainwindow_get_mainwindow(); + FolderView *folderview = NULL; + FolderItem *fitem = NULL; + + hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, folder_hook_id); + hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, item_hook_id); + //hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, msginfo_hook_id); + + if (mainwin == NULL || claws_is_exiting()) + return; + + folderview = mainwin->folderview; + fitem = folderview->summaryview->folder_item; + + if (fitem && IS_VFOLDER_FOLDER_ITEM(fitem)) { + vfolder_show_widgets(VFOLDER_ITEM(fitem)); + gslist_menu_item_free(&widgets); + + folderview_unselect(folderview); + summary_clear_all(folderview->summaryview); + } + + folderview_unregister_popup(&vfolder_popup); +} + +void vfolder_properties_cb(GtkAction* action, gpointer data) { + FolderView *folderview = (FolderView *)data; + FolderItem *item; + + g_return_if_fail(folderview != NULL); + + item = folderview_get_selected_item(folderview); + + g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); + g_return_if_fail(item->folder != NULL); + + if (vfolder_edit_item_dialog(VFOLDER_ITEM(item))) { + /* TODO: update */ + if (debug_get_mode()) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init(&iter, VFOLDER_ITEM(item)->me_to_claws); + while (g_hash_table_iter_next(&iter, &key, &value)) { + gchar* buf = g_new0(gchar, BUFSIZ); + MsgInfo* msginfo = vfolder_find_msg_from_vfolder_num( + VFOLDER_ITEM(item), GPOINTER_TO_UINT(key)); + FILE* msg = procmsg_open_message(msginfo); + while (fread(buf, 1, BUFSIZ - 1, msg) > 0) { + fprintf(stderr, "%s", buf); + g_free(buf); + buf = g_new0(gchar, BUFSIZ); + } + fprintf(stderr, "\n"); + if (buf) + g_free(buf); + fclose(msg); + } + } + vfolder_folder_item_props_write(VFOLDER_ITEM(item)); + } +} + +void vfolder_new_folder_cb(GtkAction* action, gpointer data) { + FolderView *folderview = (FolderView *)data; + GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); + FolderItem *item; + FolderItem *new_item; + gchar *new_folder; + gchar *name; + gchar *p; + + if (!folderview->selected) return; + + item = gtk_cmctree_node_get_row_data(ctree, folderview->selected); + if (! item) { + item = FOLDER_ITEM(vfolder_get_vfolder_item(NULL)); + } + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if (item->no_sub) { + alertpanel_error(N_("Virtual folders cannot contain subfolders")); + return; + } + + new_folder = input_dialog(_("New folder"), + _("Input the name of new folder:"), + _("NewFolder")); + if (!new_folder) return; + AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + + p = strchr(new_folder, G_DIR_SEPARATOR); + if (p) { + alertpanel_error(_("'%c' can't be included in folder name."), + G_DIR_SEPARATOR); + return; + } + + name = trim_string(new_folder, 32); + AUTORELEASE_STR(name, {g_free(name); return;}); + + /* find whether the directory already exists */ + if (folder_find_child_item_by_name(item, new_folder)) { + alertpanel_error(_("The folder '%s' already exists."), name); + return; + } + + new_item = folder_create_folder(item, new_folder); + if (!new_item) { + alertpanel_error(_("Can't create the folder '%s'."), name); + return; + } + + if (! vfolder_create_item_dialog(new_item)) { + //VFolderItem* vitem = VFOLDER_ITEM(new_item); + new_item->folder->klass->remove_folder(new_item->folder, new_item); + new_item = NULL; + return; + } + + folder_write_list(); +} + +void vfolder_remove_folder_cb(GtkAction* action, gpointer data) { + FolderView *folderview = (FolderView *)data; + GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); + FolderItem *item; + gchar *message, *name; + AlertValue avalue; + gchar *old_path = NULL; + gchar *old_id; + + /* Silence lame warnings */ + old_id = (old_path) ? NULL : old_path; + + item = folderview_get_selected_item(folderview); + g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); + g_return_if_fail(item->folder != NULL); + + name = trim_string(item->name, 32); + AUTORELEASE_STR(name, {g_free(name); return;}); + message = g_strdup_printf + (_("All folders and messages under '%s' will be permanently deleted. " + "Recovery will not be possible.\n\n" + "Do you really want to delete?"), name); + avalue = alertpanel_full(_("Delete folder"), message, + GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL, FALSE, + NULL, ALERT_WARNING, G_ALERTDEFAULT); + g_free(message); + if (avalue != G_ALERTALTERNATE) return; + + Xstrdup_a(old_path, item->path, return); + old_id = folder_item_get_identifier(item); + + if (folderview->opened == folderview->selected || + gtk_cmctree_is_ancestor(ctree, + folderview->selected, + folderview->opened)) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + if (item->folder->klass->remove_folder(item->folder, item) < 0) { + folder_item_scan(item); + alertpanel_error(_("Can't remove the folder '%s'."), name); + g_free(old_id); + return; + } + + folder_write_list(); + + g_free(old_id); +} diff --git a/src/vfolder_gtk.h b/src/vfolder_gtk.h new file mode 100644 index 0000000..a766aa8 --- /dev/null +++ b/src/vfolder_gtk.h @@ -0,0 +1,58 @@ +/* + * $Id: $ + */ + +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifndef __VFOLDER_GTK_H__ +#define __VFOLDER_GTK_H__ + +#include + +G_BEGIN_DECLS + +#include "gettext.h" +#include "vfolder.h" +#include + +typedef enum { + FOLDER_ITEM_PROPS_OK, + FOLDER_ITEM_PROPS_NO_ITEM, + FOLDER_ITEM_PROPS_BACKUP_FAIL, + FOLDER_ITEM_PROPS_READ_DATA_FAIL, + FOLDER_ITEM_PROPS_MAKE_RC_DIR_FAIL, + FOLDER_ITEM_PROPS_READ_USING_DEFAULT +} FolderPropsResponse; + +gboolean vfolder_gtk_init(gchar** error); +void vfolder_gtk_done(void); +FolderPropsResponse vfolder_folder_item_props_read(VFolderItem* item); +FolderPropsResponse vfolder_folder_item_props_write(VFolderItem* item); + +/* Callback functions */ +void vfolder_new_folder_cb(GtkAction* action, gpointer data); +void vfolder_remove_folder_cb(GtkAction* action, gpointer data); +void vfolder_properties_cb(GtkAction* action, gpointer data); + +G_END_DECLS + +#endif diff --git a/src/vfolder_init.c b/src/vfolder_init.c new file mode 100644 index 0000000..047266e --- /dev/null +++ b/src/vfolder_init.c @@ -0,0 +1,147 @@ +/* + * $Id: $ + */ +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gettext.h" +#include + +#include "common/claws.h" +#include "common/version.h" +#include "plugin.h" +#include "mimeview.h" +#include "utils.h" +#include "alertpanel.h" +#include "statusbar.h" +#include "menu.h" +#include "vfolder.h" +#include "vfolder_gtk.h" + +#define PLUGIN_NAME (_("VFolder")) + +static GtkActionEntry vfolder_main_menu[] = {{ + "View/CreateVfolder", + NULL, N_("Create virtual folder..."), + "v", N_("Create a virtual folder"), + G_CALLBACK(vfolder_new_folder_cb) +}}; + +static gint main_menu_id = 0; + +gint plugin_init(gchar** error) { + debug_set_mode(TRUE); + MainWindow *mainwin = mainwindow_get_mainwindow(); + +#ifdef G_OS_UNIX + bindtextdomain(TEXTDOMAIN, LOCALEDIR); +#else + bindtextdomain(TEXTDOMAIN, get_locale_dir()); +#endif + bind_textdomain_codeset(TEXTDOMAIN, "UTF-8"); + + if (!check_plugin_version(MAKE_NUMERIC_VERSION(0,0,1,0), + VERSION_NUMERIC, PLUGIN_NAME, error)) + return -1; + + gtk_action_group_add_actions(mainwin->action_group, vfolder_main_menu, + 1, (gpointer)mainwin); + MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/View", "CreateVfolder", + "View/CreateVfolder", GTK_UI_MANAGER_MENUITEM, + main_menu_id) + + if (! vfolder_init()) { + debug_print("vfolder plugin unloading due to init errors\n"); + plugin_done(); + return -1; + } + + debug_print("vfolder plugin loaded\n"); + + return 0; +} + +gboolean plugin_done(void) { + MainWindow *mainwin = mainwindow_get_mainwindow(); + + vfolder_done(); + + if (mainwin == NULL) + return FALSE; + + MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "View/CreateVfolder", main_menu_id); + main_menu_id = 0; + + debug_print("vfolder plugin unloaded\n"); + + debug_set_mode(FALSE); + return TRUE; +} + +const gchar* plugin_licence(void) { + return "GPL3+"; +} + +const gchar* plugin_version(void) { + return PLUGINVERSION; +} + +const gchar* plugin_type(void) { + return "GTK2"; +} + +const gchar* plugin_name(void) { + return PLUGIN_NAME; +} + +const gchar* plugin_desc(void) { + return _("This plugin adds virtual folder support to Claws Mail.\n" + "\n" + "1) Select one or more mail folder(s) to use as the basic mail pool\n" + "2) Define a filter\n" + "3) Specify name for virtual folder\n" + "4) Press create and wait until the scanning of the mail pool finishes\n" + "\n" + "The VFolder will be updated periodically and when claws-mail is initially opened\n" + "Manual update is available from the context menu of the VFolder.\n" + "\n" + "The supported folder types are MH and IMAP.\n" + "Messages in a VFolder cannot be updated.\n" + "\n" + "To activate the archiving feature go to /View/Create virtual folder\n" + "\n" + "Default options can be set in /Configuration/Preferences/Plugins" + "/vfolder" + ); +} + +struct PluginFeature* plugin_provides(void) { + static struct PluginFeature features[] = + { {PLUGIN_UTILITY, N_("VFolder")}, + {PLUGIN_FOLDERCLASS, N_("VFolder")}, + {PLUGIN_NOTHING, NULL} }; + return features; +} diff --git a/src/vfolder_prop.c b/src/vfolder_prop.c new file mode 100644 index 0000000..a7028a2 --- /dev/null +++ b/src/vfolder_prop.c @@ -0,0 +1,451 @@ +/* + * $Id: $ + */ +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gtk/gtk.h" +#include "glib.h" + +#include "gettext.h" +#include "gtkutils.h" +#include "mainwindow.h" +#include "foldersel.h" +#include "alertpanel.h" + +#include "vfolder_gtk.h" +#include "vfolder.h" +#include "vfolder_prop.h" + +#define HEADERS N_("Message _Headers") +#define BODY N_("_Message body") +#define BOTH N_("_Both") + +typedef struct { + GtkWidget* filter; + GtkWidget* frozen; + GtkWidget* deep_copy; + GtkWidget* source; + GtkWidget* label_btn; + GtkWidget* message_btn; + GtkWidget* both_btn; +} PropsDialog; + +static void add_current_config(VFolderItem* item, PropsDialog* props) { + if (item->filter) + gtk_entry_set_text(GTK_ENTRY(props->filter), item->filter); + if (item->source) { + gchar* id = folder_item_get_identifier(item->source); + if (id) { + gtk_entry_set_text(GTK_ENTRY(props->source), id); + g_free(id); + } + } + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(props->frozen), item->frozen); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(props->deep_copy), item->deep_copy); + switch (item->search) { + case SEARCH_BODY: + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(props->message_btn), TRUE); + case SEARCH_BOTH: + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(props->both_btn), TRUE); + case SEARCH_HEADERS: + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(props->label_btn), TRUE); + } +} + +static gboolean is_source_widget(GtkWidget* widget) { + const gchar* name = gtk_widget_get_name(widget); + + return (name && strcmp("source", name) == 0); +} + +static void foldersel_cb(GtkWidget *widget, gpointer data) { + FolderItem *item; + gchar *item_id; + gint newpos = 0; + GtkWidget* entry = GTK_WIDGET(data); + + item = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE); + if (item && IS_VFOLDER_FOLDER_ITEM(item)) { + /* Cannot create virtual folder from virtual folder */ + alertpanel_error(_("%s: Cannot create virtual folder from virtual folder"), item->name); + return; + } + else { + if (item && (item_id = folder_item_get_identifier(item)) != NULL) { + gtk_editable_delete_text(GTK_EDITABLE(entry), 0, -1); + gtk_editable_insert_text(GTK_EDITABLE(entry), + item_id, strlen(item_id), &newpos); + g_free(item_id); + } + debug_print("Source Folder: %s\n", gtk_entry_get_text(GTK_ENTRY(entry))); + } +} + +static GtkWidget* vfolder_prop_row(GtkWidget* widget, + const gchar* label_markup, + gint width, gboolean center) { + GtkWidget* row = gtk_hbox_new(FALSE, 5); + GtkWidget* label = gtk_label_new(NULL); + + gtk_widget_set_size_request(label, width, -1); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), label_markup); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget); + gtk_box_pack_start(GTK_BOX(row), label, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(row), widget, TRUE, FALSE, 5); + + if (is_source_widget(widget)) { + GtkWidget* btn = gtk_button_new_from_stock(GTK_STOCK_OPEN); + g_signal_connect(G_OBJECT(btn), "clicked", G_CALLBACK(foldersel_cb), widget); + gtk_box_pack_start(GTK_BOX(row), btn, FALSE, FALSE, 5); + } + + return row; +} + +static gboolean vfolder_set_search_type(VFolderItem* item, GtkWidget* list) { + GSList *btn_list, *btns; + gboolean active = FALSE; + GtkToggleButton* btn = NULL; + + btn_list = gtk_radio_button_get_group(GTK_RADIO_BUTTON(list)); + for (btns = btn_list; btns && !active; btns = g_slist_next(btns)) { + btn = GTK_TOGGLE_BUTTON(btns->data); + active = gtk_toggle_button_get_active(btn); + } + if (active) { + const gchar* label = gtk_button_get_label(GTK_BUTTON(btn)); + if (label) { + if (strcmp(BOTH, label) == 0) { + if (item->search != SEARCH_BOTH) { + item->search = SEARCH_BOTH; + return TRUE; + } + } + else if (strcmp(BODY, label) == 0) { + if (item->search != SEARCH_BODY) { + item->search = SEARCH_BODY; + return TRUE; + } + } + else { + if (item->search != SEARCH_HEADERS) { + item->search = SEARCH_HEADERS; + return TRUE; + } + } + } + } + + return FALSE; +} +/* +static void vfolder_copy_msginfo_list(gpointer data, gpointer user_data) { + MsgInfo* msg = (MsgInfo *) data; + MsgInfo* new_msg; + VFolderItem* item = (VFolderItem *) user_data; + + g_return_if_fail(msg != NULL); + g_return_if_fail(item != NULL); + + new_msg = procmsg_msginfo_copy(msg); + item->msginfos = g_slist_prepend(item->msginfos, new_msg); +} +*/ +static gboolean vfolder_search_headers(MsgInfo* msg, GPatternSpec* pattern) { + return ((msg->cc && g_pattern_match_string(pattern, msg->cc)) || + (msg->from && g_pattern_match_string(pattern, msg->from)) || + (msg->fromname && g_pattern_match_string(pattern, msg->fromname)) || + (msg->inreplyto && g_pattern_match_string(pattern, msg->inreplyto)) || + (msg->subject && g_pattern_match_string(pattern, msg->subject)) || + (msg->to && g_pattern_match_string(pattern, msg->to))); +} + +static gboolean vfolder_search_body(MsgInfo* msg, GPatternSpec* pattern) { + gchar* body; + gboolean found = FALSE; + + body = procmsg_get_message_file(msg); + if (body) { + found = g_pattern_match_string(pattern, body); + g_free(body); + } + + return found; +} + +static MsgInfoList* vfolder_filter_msgs_list(MsgInfoList* msgs, VFolderItem* item) { + MsgInfoList *list = NULL, *tmp; + GPatternSpec* pattern; + MsgInfo* msg; + + if (!item || item->filter == NULL) + return list; + + pattern = g_pattern_spec_new(item->filter); + + for (tmp = msgs; tmp; tmp = g_slist_next(tmp)) { + msg = (MsgInfo *) tmp->data; + switch (item->search) { + case SEARCH_HEADERS: + if (vfolder_search_headers(msg, pattern)) + list = g_slist_prepend(list, msg); + break; + case SEARCH_BODY: + if (vfolder_search_body(msg, pattern)) + list = g_slist_prepend(list, msg); + break; + case SEARCH_BOTH: + if (vfolder_search_headers(msg, pattern)) { + list = g_slist_prepend(list, msg); + continue; + } + if (vfolder_search_body(msg, pattern)) + list = g_slist_prepend(list, msg); + break; + } + } + + g_pattern_spec_free(pattern); + + return list; +} + +static gboolean vfolder_create_msgs_list(VFolderItem* item, gboolean copy) { + MsgInfoList *msgs = NULL, *filtered = NULL; + gboolean ok = FALSE; + GSList* filelist = NULL; + + if (item->filter && item->msg_filter_func) { + item->deep_copy = copy; + ok = TRUE; + msgs = folder_item_get_msg_list(item->source); + filtered = item->msg_filter_func(msgs, item); + if (filtered) { + filelist = procmsg_get_message_file_list(filtered); + if (filelist) { + gint n = folder_item_add_msgs(FOLDER_ITEM(item), filelist, FALSE); + FOLDER_ITEM(item)->last_num = n; + procmsg_message_file_list_free(filelist); + } + g_slist_free(filtered); + } + g_slist_free(msgs); + } + return ok; +} + +void vfolder_set_msgs_filter(VFolderItem* vfolder_item) { + g_return_if_fail(vfolder_item != NULL); + + vfolder_item->msg_filter_func = vfolder_filter_msgs_list; +} + +gboolean vfolder_create_item_dialog(FolderItem* folder_item) { + gboolean created = FALSE; + VFolderItem* item = NULL; + + g_return_val_if_fail(folder_item != NULL, created); + g_return_val_if_fail(IS_VFOLDER_FOLDER_ITEM(folder_item), created); + + item = VFOLDER_ITEM(folder_item); + item->msg_filter_func = vfolder_filter_msgs_list; + + if (vfolder_edit_item_dialog(item)) { + /* save properties */ + if (FOLDER_ITEM_PROPS_OK != vfolder_folder_item_props_write(item)) + created = FALSE; + else + created = TRUE; + } + + return created; +} + +gboolean vfolder_edit_item_dialog(VFolderItem* vfolder_item) { + gboolean ok = FALSE; + PropsDialog* props_dialog; + GtkWidget* dialog; + GtkWidget* content; + GtkWidget* row; + GtkWidget* box; + gint response; + gchar* name; + const gchar* str; + gboolean frozen, deep_copy; + FolderItem* source; + gchar* old_filter = NULL; + + g_return_val_if_fail(vfolder_item != NULL, ok); + + MainWindow *mainwin = mainwindow_get_mainwindow(); + props_dialog = g_new0(PropsDialog, 1); + props_dialog->filter = gtk_entry_new(); + props_dialog->frozen = gtk_check_button_new(); + props_dialog->deep_copy = gtk_check_button_new(); + props_dialog->source = gtk_entry_new(); + props_dialog->label_btn = + gtk_radio_button_new_with_mnemonic(NULL, HEADERS); + props_dialog->message_btn = + gtk_radio_button_new_with_mnemonic_from_widget( + GTK_RADIO_BUTTON(props_dialog->label_btn), BODY); + props_dialog->both_btn = + gtk_radio_button_new_with_mnemonic_from_widget( + GTK_RADIO_BUTTON(props_dialog->label_btn), BOTH); + gtk_widget_set_name(props_dialog->source, "source"); + add_current_config(vfolder_item, props_dialog); + + dialog = gtk_dialog_new_with_buttons( + N_("Edit VFolder Properties"), + GTK_WINDOW(mainwin->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT); + content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + GtkWidget* vbox = gtk_vbox_new(FALSE, 5); + + row = vfolder_prop_row(props_dialog->source, N_("_Source folder"), 110, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), row, FALSE, FALSE, 5); + + GtkWidget* frame1 = gtk_frame_new(_("Message filter")); + GtkWidget* vbox1 = gtk_vbox_new(TRUE, 2); + gtk_container_add(GTK_CONTAINER(frame1), vbox1); + + row = vfolder_prop_row(props_dialog->filter, N_("_Filter"), 110, FALSE); + gtk_box_pack_start(GTK_BOX(vbox1), row, FALSE, FALSE, 5); + + box = gtk_hbox_new(TRUE, 2); + gtk_box_pack_start(GTK_BOX(box), props_dialog->label_btn, TRUE, TRUE, 2); + gtk_box_pack_start(GTK_BOX(box), props_dialog->message_btn, TRUE, TRUE, 2); + gtk_box_pack_start(GTK_BOX(box), props_dialog->both_btn, TRUE, TRUE, 2); + gtk_box_pack_start(GTK_BOX(vbox1), box, FALSE, FALSE, 5); + + gtk_box_pack_start(GTK_BOX(vbox), frame1, FALSE, FALSE, 5); + + row = vfolder_prop_row(props_dialog->frozen, N_("F_reeze content"), 110, TRUE); + gtk_box_pack_start(GTK_BOX(vbox), row, FALSE, FALSE, 5); + + row = vfolder_prop_row(props_dialog->deep_copy, N_("Co_py messages"), 110, TRUE); + gtk_box_pack_start(GTK_BOX(vbox), row, FALSE, FALSE, 5); + + name = g_strconcat(FOLDER_ITEM(vfolder_item)->name, N_(": settings"), NULL); + GtkWidget* frame = gtk_frame_new(name); + g_free(name); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_widget_show_all(frame); + + gtk_container_add(GTK_CONTAINER(content), frame); + + response = gtk_dialog_run(GTK_DIALOG(dialog)); + if (response == GTK_RESPONSE_ACCEPT) { + frozen = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(props_dialog->frozen)); + deep_copy = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(props_dialog->deep_copy)); + + str = gtk_entry_get_text(GTK_ENTRY(props_dialog->filter)); + if (str) { + old_filter = g_strdup(vfolder_item->filter); + if (strlen(str) == 0) { + if (vfolder_item->filter) { + g_free(vfolder_item->filter); + vfolder_item->filter = NULL; + ok = TRUE; + } + } + else { + if (!vfolder_item->filter || strcmp(vfolder_item->filter, str) != 0) { + g_free(vfolder_item->filter); + vfolder_item->filter = g_strdup(str); + ok = TRUE; + } + } + } + if (vfolder_set_search_type(vfolder_item, props_dialog->label_btn)) + ok = TRUE; + + str = gtk_entry_get_text(GTK_ENTRY(props_dialog->source)); + if (str) { + source = folder_find_item_from_identifier(str); + if (source && (source->stype != F_NORMAL && source->stype != F_INBOX)) { + alertpanel_error(_("%s: Not suitable for virtual folders\n" + "Use only folder type: Normal or Inbox\n"), str); + g_free(vfolder_item->filter); + vfolder_item->filter = g_strdup(old_filter); + ok = FALSE; + goto error; + } + + if (strlen(str) == 0) { + if (vfolder_item->source) { + vfolder_item->source = NULL; + folder_item_remove_all_msg(FOLDER_ITEM(vfolder_item)); + ok = TRUE; + } + } + else { + folder_item_remove_all_msg(FOLDER_ITEM(vfolder_item)); + gchar* id = (vfolder_item->source) ? + folder_item_get_identifier(vfolder_item->source) : NULL; + if (!id || strcmp(id, str) != 0) + vfolder_item->source = source; + if (vfolder_item->source) { + ok = vfolder_create_msgs_list(vfolder_item, deep_copy); + if (ok == FALSE) { + g_free(vfolder_item->filter); + vfolder_item->filter = g_strdup(old_filter); + goto error; + } + } + else { + g_free(vfolder_item->filter); + vfolder_item->filter = g_strdup(old_filter); + ok = FALSE; + goto error; + } + } + } + + if (vfolder_item->frozen != frozen) { + vfolder_item->frozen = frozen; + ok = TRUE; + } + + if (vfolder_item->deep_copy != deep_copy) { + vfolder_item->deep_copy = deep_copy; + ok = TRUE; + } + + } + +error: + gtk_widget_destroy(dialog); + g_free(props_dialog); + g_free(old_filter); + + return ok; +} diff --git a/src/vfolder_prop.h b/src/vfolder_prop.h new file mode 100644 index 0000000..086b208 --- /dev/null +++ b/src/vfolder_prop.h @@ -0,0 +1,44 @@ +/* + * $Id: $ + */ + +/* vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: */ + +/* + * Virtual folder plugin for claws-mail + * Claws Mail is Copyright (C) 1999-2012 by the Claws Mail Team + * + * 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 . + * + */ + +#ifndef __VFOLDER_PROP_H__ +#define __VFOLDER_PROP_H__ + +#include + +G_BEGIN_DECLS + +#include "gettext.h" +#include "folder.h" +#include "vfolder.h" +#include + +gboolean vfolder_create_item_dialog(FolderItem* folder_item); +gboolean vfolder_edit_item_dialog(VFolderItem* vfolder_item); +void vfolder_set_msgs_filter(VFolderItem* vfolder_item); + +G_END_DECLS + +#endif