diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..2d2bab1 --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,22 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..0741db7 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,26 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..a343ccd --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,119 @@ +Creative Commons Legal Code + +CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES +NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE +AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION +ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE +OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS +LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION +OR WORKS PROVIDED HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive +Copyright and Related Rights (defined below) upon the creator and subsequent +owner(s) (each and all, an "owner") of an original work of authorship and/or +a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later claims +of infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further production +of creative, cultural and scientific works, or to gain reputation or greater +distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with +a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or +her Copyright and Related Rights in the Work and the meaning and intended +legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected +by copyright and related or neighboring rights ("Copyright and Related Rights"). +Copyright and Related Rights include, but are not limited to, the following: + +i. the right to reproduce, adapt, distribute, perform, display, communicate, +and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + +iii. publicity and privacy rights pertaining to a person's image or likeness +depicted in a Work; + +iv. rights protecting against unfair competition in regards to a Work, subject +to the limitations in paragraph 4(a), below; + +v. rights protecting the extraction, dissemination, use and reuse of data +in a Work; + +vi. database rights (such as those arising under Directive 96/9/EC of the +European Parliament and of the Council of 11 March 1996 on the legal protection +of databases, and under any national implementation thereof, including any +amended or successor version of such directive); and + +vii. other similar, equivalent or corresponding rights throughout the world +based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time extensions), +(iii) in any current or future medium and for any number of copies, and (iv) +for any purpose whatsoever, including without limitation commercial, advertising +or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the +benefit of each member of the public at large and to the detriment of Affirmer's +heirs and successors, fully intending that such Waiver shall not be subject +to revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account Affirmer's +express Statement of Purpose. In addition, to the extent the Waiver is so +judged Affirmer hereby grants to each affected person a royalty-free, non +transferable, non sublicensable, non exclusive, irrevocable and unconditional +license to exercise Affirmer's Copyright and Related Rights in the Work (i) +in all territories worldwide, (ii) for the maximum duration provided by applicable +law or treaty (including future time extensions), (iii) in any current or +future medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional purposes +(the "License"). The License shall be deemed effective as of the date CC0 +was applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder of +the License, and in such case Affirmer hereby affirms that he or she will +not (i) exercise any of his or her remaining Copyright and Related Rights +in the Work or (ii) assert any associated claims and causes of action with +respect to the Work, in either case contrary to Affirmer's express Statement +of Purpose. + + 4. Limitations and Disclaimers. + +a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, +licensed or otherwise affected by this document. + +b. Affirmer offers the Work as-is and makes no representations or warranties +of any kind concerning the Work, express, implied, statutory or otherwise, +including without limitation warranties of title, merchantability, fitness +for a particular purpose, non infringement, or the absence of latent or other +defects, accuracy, or the present or absence of errors, whether or not discoverable, +all to the greatest extent permissible under applicable law. + +c. Affirmer disclaims responsibility for clearing rights of other persons +that may apply to the Work or any use thereof, including without limitation +any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims +responsibility for obtaining any necessary consents, permissions or other +rights required for any use of the Work. + +d. Affirmer understands and acknowledges that Creative Commons is not a party +to this document and has no duty or obligation with respect to this CC0 or +use of the Work. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 0000000..0f3d641 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. 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. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute 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 and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the 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 +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +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. +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 convey 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)< yyyy> + +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 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This 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. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..1d80ac3 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. 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. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute 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 and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the 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 +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +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. +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 convey 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 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This 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. diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt new file mode 100644 index 0000000..e142a52 --- /dev/null +++ b/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,625 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 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/LICENSES/LGPL-2.0-only.txt b/LICENSES/LGPL-2.0-only.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-only.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library 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 2 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library 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 2 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt new file mode 100644 index 0000000..130dffb --- /dev/null +++ b/LICENSES/LGPL-2.1-only.txt @@ -0,0 +1,467 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +< one line to give the library's name and an idea of what it does. > + +Copyright (C) < year > < name of author > + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information +on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 0000000..04bb156 --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,468 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +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 library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..bd405af --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,163 @@ +GNU LESSER 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. + +This version of the GNU Lesser General Public License incorporates the terms +and conditions of version 3 of the GNU General Public License, supplemented +by the additional permissions listed below. + + 0. Additional Definitions. + + + +As used herein, "this License" refers to version 3 of the GNU Lesser General +Public License, and the "GNU GPL" refers to version 3 of the GNU General Public +License. + + + +"The Library" refers to a covered work governed by this License, other than +an Application or a Combined Work as defined below. + + + +An "Application" is any work that makes use of an interface provided by the +Library, but which is not otherwise based on the Library. Defining a subclass +of a class defined by the Library is deemed a mode of using an interface provided +by the Library. + + + +A "Combined Work" is a work produced by combining or linking an Application +with the Library. The particular version of the Library with which the Combined +Work was made is also called the "Linked Version". + + + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding +Source for the Combined Work, excluding any source code for portions of the +Combined Work that, considered in isolation, are based on the Application, +and not on the Linked Version. + + + +The "Corresponding Application Code" for a Combined Work means the object +code and/or source code for the Application, including any data and utility +programs needed for reproducing the Combined Work from the Application, but +excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without +being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility +refers to a function or data to be supplied by an Application that uses the +facility (other than as an argument passed when the facility is invoked), +then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure +that, in the event an Application does not supply the function or data, the +facility still operates, and performs whatever part of its purpose remains +meaningful, or + +b) under the GNU GPL, with none of the additional permissions of this License +applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header +file that is part of the Library. You may convey such object code under terms +of your choice, provided that, if the incorporated material is not limited +to numerical parameters, data structure layouts and accessors, or small macros, +inline functions and templates (ten or fewer lines in length), you do both +of the following: + +a) Give prominent notice with each copy of the object code that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + + 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, +effectively do not restrict modification of the portions of the Library contained +in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +c) For a Combined Work that displays copyright notices during execution, include +the copyright notice for the Library among these notices, as well as a reference +directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + +0) Convey the Minimal Corresponding Source under the terms of this License, +and the Corresponding Application Code in a form suitable for, and under terms +that permit, the user to recombine or relink the Application with a modified +version of the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + +1) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (a) uses at run time a copy of the Library +already present on the user's computer system, and (b) will operate properly +with a modified version of the Library that is interface-compatible with the +Linked Version. + +e) Provide Installation Information, but only if you would otherwise be required +to provide such information under section 6 of the GNU GPL, and only to the +extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application +with a modified version of the Linked Version. (If you use option 4d0, the +Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the +Installation Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + + 5. Combined Libraries. + +You may place library facilities that are a work based on the Library side +by side in a single library together with other library facilities that are +not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities, conveyed under the +terms of this License. + +b) Give prominent notice with the combined library that part of it is a work +based on the Library, and explaining where to find the accompanying uncombined +form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU Lesser 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 Library as you +received it specifies that a certain numbered version of the GNU Lesser General +Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of +any later version published by the Free Software Foundation. If the Library +as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public +License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether +future versions of the GNU Lesser General Public License shall apply, that +proxy's public statement of acceptance of any version is permanent authorization +for you to choose that version for the Library. diff --git a/LICENSES/LicenseRef-KDE-Accepted-GPL.txt b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt new file mode 100644 index 0000000..60a2dff --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt @@ -0,0 +1,12 @@ +This library 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) at any later version that is +accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 14 of version 3 of the license. + +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. diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt new file mode 100644 index 0000000..232b3c5 --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt @@ -0,0 +1,12 @@ +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the license or (at your option) any later version +that is accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 6 of version 3 of the license. + +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. diff --git a/LICENSES/LicenseRef-Qt-Commercial.txt b/LICENSES/LicenseRef-Qt-Commercial.txt new file mode 100644 index 0000000..11e00c7 --- /dev/null +++ b/LICENSES/LicenseRef-Qt-Commercial.txt @@ -0,0 +1,7 @@ +Commercial License Usage +Licensees holding valid commercial Qt licenses may use this file in +accordance with the commercial license agreement provided with the +Software or, alternatively, in accordance with the terms contained in +a written agreement between you and The Qt Company. For licensing terms +and conditions see https://www.qt.io/terms-conditions. For further +information use the contact form at https://www.qt.io/contact-us. diff --git a/LICENSES/MPL-1.1.txt b/LICENSES/MPL-1.1.txt new file mode 100644 index 0000000..b45d0e1 --- /dev/null +++ b/LICENSES/MPL-1.1.txt @@ -0,0 +1,422 @@ +Mozilla Public License Version 1.1 + + 1. Definitions. + +1.0.1. "Commercial Use" means distribution or otherwise making the Covered +Code available to a third party. + +1.1. "Contributor" means each entity that creates or contributes to the creation +of Modifications. + +1.2. "Contributor Version" means the combination of the Original Code, prior +Modifications used by a Contributor, and the Modifications made by that particular +Contributor. + +1.3. "Covered Code" means the Original Code or Modifications or the combination +of the Original Code and Modifications, in each case including portions thereof. + +1.4. "Electronic Distribution Mechanism" means a mechanism generally accepted +in the software development community for the electronic transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source Code. + +1.6. "Initial Developer" means the individual or entity identified as the +Initial Developer in the Source Code notice required by Exhibit A. + +1.7. "Larger Work" means a work which combines Covered Code or portions thereof +with code not governed by the terms of this License. + + 1.8. "License" means this document. + +1.8.1. "Licensable" means having the right to grant, to the maximum extent +possible, whether at the time of the initial grant or subsequently acquired, +any and all of the rights conveyed herein. + +1.9. "Modifications" means any addition to or deletion from the substance +or structure of either the Original Code or any previous Modifications. When +Covered Code is released as a series of files, a Modification is: + +Any addition to or deletion from the contents of a file containing Original +Code or previous Modifications. + +Any new file that contains any part of the Original Code or previous Modifications. + +1.10. "Original Code" means Source Code of computer software code which is +described in the Source Code notice required by Exhibit A as Original Code, +and which, at the time of its release under this License is not already Covered +Code governed by this License. + +1.10.1. "Patent Claims" means any patent claim(s), now owned or hereafter +acquired, including without limitation, method, process, and apparatus claims, +in any patent Licensable by grantor. + +1.11. "Source Code" means the preferred form of the Covered Code for making +modifications to it, including all modules it contains, plus any associated +interface definition files, scripts used to control compilation and installation +of an Executable, or source code differential comparisons against either the +Original Code or another well known, available Covered Code of the Contributor's +choice. The Source Code can be in a compressed or archival form, provided +the appropriate decompression or de-archiving software is widely available +for no charge. + +1.12. "You" (or "Your") means an individual or a legal entity exercising rights +under, and complying with all of the terms of, this License or a future version +of this License issued under Section 6.1. For legal entities, "You" includes +any entity which controls, is controlled by, or is under common control with +You. For purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (b) ownership of more than fifty percent (50%) +of the outstanding shares or beneficial ownership of such entity. + + 2. Source Code License. + +2.1. The Initial Developer Grant. The Initial Developer hereby grants You +a world-wide, royalty-free, non-exclusive license, subject to third party +intellectual property claims: + +a. under intellectual property rights (other than patent or trademark) Licensable +by Initial Developer to use, reproduce, modify, display, perform, sublicense +and distribute the Original Code (or portions thereof) with or without Modifications, +and/or as part of a Larger Work; and + +b. under Patents Claims infringed by the making, using or selling of Original +Code, to make, have made, use, practice, sell, and offer for sale, and/or +otherwise dispose of the Original Code (or portions thereof). + +c. the licenses granted in this Section 2.1 (a) and (b) are effective on the +date Initial Developer first distributes Original Code under the terms of +this License. + +d. Notwithstanding Section 2.1 (b) above, no patent license is granted: 1) +for code that You delete from the Original Code; 2) separate from the Original +Code; or 3) for infringements caused by: i) the modification of the Original +Code or ii) the combination of the Original Code with other software or devices. + +2.2. Contributor Grant. Subject to third party intellectual property claims, +each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license + +a. under intellectual property rights (other than patent or trademark) Licensable +by Contributor, to use, reproduce, modify, display, perform, sublicense and +distribute the Modifications created by such Contributor (or portions thereof) +either on an unmodified basis, with other Modifications, as Covered Code and/or +as part of a Larger Work; and + +b. under Patent Claims infringed by the making, using, or selling of Modifications +made by that Contributor either alone and/or in combination with its Contributor +Version (or portions of such combination), to make, use, sell, offer for sale, +have made, and/or otherwise dispose of: 1) Modifications made by that Contributor +(or portions thereof); and 2) the combination of Modifications made by that +Contributor with its Contributor Version (or portions of such combination). + +c. the licenses granted in Sections 2.2 (a) and 2.2 (b) are effective on the +date Contributor first makes Commercial Use of the Covered Code. + +d. Notwithstanding Section 2.2 (b) above, no patent license is granted: 1) +for any code that Contributor has deleted from the Contributor Version; 2) +separate from the Contributor Version; 3) for infringements caused by: i) +third party modifications of Contributor Version or ii) the combination of +Modifications made by that Contributor with other software (except as part +of the Contributor Version) or other devices; or 4) under Patent Claims infringed +by Covered Code in the absence of Modifications made by that Contributor. + + 3. Distribution Obligations. + +3.1. Application of License. The Modifications which You create or to which +You contribute are governed by the terms of this License, including without +limitation Section 2.2. The Source Code version of Covered Code may be distributed +only under the terms of this License or a future version of this License released +under Section 6.1, and You must include a copy of this License with every +copy of the Source Code You distribute. You may not offer or impose any terms +on any Source Code version that alters or restricts the applicable version +of this License or the recipients' rights hereunder. However, You may include +an additional document offering the additional rights described in Section +3.5. + +3.2. Availability of Source Code. Any Modification which You create or to +which You contribute must be made available in Source Code form under the +terms of this License either on the same media as an Executable version or +via an accepted Electronic Distribution Mechanism to anyone to whom you made +an Executable version available; and if made available via Electronic Distribution +Mechanism, must remain available for at least twelve (12) months after the +date it initially became available, or at least six (6) months after a subsequent +version of that particular Modification has been made available to such recipients. +You are responsible for ensuring that the Source Code version remains available +even if the Electronic Distribution Mechanism is maintained by a third party. + +3.3. Description of Modifications. You must cause all Covered Code to which +You contribute to contain a file documenting the changes You made to create +that Covered Code and the date of any change. You must include a prominent +statement that the Modification is derived, directly or indirectly, from Original +Code provided by the Initial Developer and including the name of the Initial +Developer in (a) the Source Code, and (b) in any notice in an Executable version +or related documentation in which You describe the origin or ownership of +the Covered Code. + + 3.4. Intellectual Property Matters + + (a) Third Party Claims + +If Contributor has knowledge that a license under a third party's intellectual +property rights is required to exercise the rights granted by such Contributor +under Sections 2.1 or 2.2, Contributor must include a text file with the Source +Code distribution titled "LEGAL" which describes the claim and the party making +the claim in sufficient detail that a recipient will know whom to contact. +If Contributor obtains such knowledge after the Modification is made available +as described in Section 3.2, Contributor shall promptly modify the LEGAL file +in all copies Contributor makes available thereafter and shall take other +steps (such as notifying appropriate mailing lists or newsgroups) reasonably +calculated to inform those who received the Covered Code that new knowledge +has been obtained. + + (b) Contributor APIs + +If Contributor's Modifications include an application programming interface +and Contributor has knowledge of patent licenses which are reasonably necessary +to implement that API, Contributor must also include this information in the +LEGAL file. + + (c) Representations. + +Contributor represents that, except as disclosed pursuant to Section 3.4 (a) +above, Contributor believes that Contributor's Modifications are Contributor's +original creation(s) and/or Contributor has sufficient rights to grant the +rights conveyed by this License. + +3.5. Required Notices. You must duplicate the notice in Exhibit A in each +file of the Source Code. If it is not possible to put such notice in a particular +Source Code file due to its structure, then You must include such notice in +a location (such as a relevant directory) where a user would be likely to +look for such a notice. If You created one or more Modification(s) You may +add your name as a Contributor to the notice described in Exhibit A. You must +also duplicate this License in any documentation for the Source Code where +You describe recipients' rights or ownership rights relating to Covered Code. +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Code. However, +You may do so only on Your own behalf, and not on behalf of the Initial Developer +or any Contributor. You must make it absolutely clear than any such warranty, +support, indemnity or liability obligation is offered by You alone, and You +hereby agree to indemnify the Initial Developer and every Contributor for +any liability incurred by the Initial Developer or such Contributor as a result +of warranty, support, indemnity or liability terms You offer. + +3.6. Distribution of Executable Versions. You may distribute Covered Code +in Executable form only if the requirements of Sections 3.1, 3.2, 3.3, 3.4 +and 3.5 have been met for that Covered Code, and if You include a notice stating +that the Source Code version of the Covered Code is available under the terms +of this License, including a description of how and where You have fulfilled +the obligations of Section 3.2. The notice must be conspicuously included +in any notice in an Executable version, related documentation or collateral +in which You describe recipients' rights relating to the Covered Code. You +may distribute the Executable version of Covered Code or ownership rights +under a license of Your choice, which may contain terms different from this +License, provided that You are in compliance with the terms of this License +and that the license for the Executable version does not attempt to limit +or alter the recipient's rights in the Source Code version from the rights +set forth in this License. If You distribute the Executable version under +a different license You must make it absolutely clear that any terms which +differ from this License are offered by You alone, not by the Initial Developer +or any Contributor. You hereby agree to indemnify the Initial Developer and +every Contributor for any liability incurred by the Initial Developer or such +Contributor as a result of any such terms You offer. + +3.7. Larger Works. You may create a Larger Work by combining Covered Code +with other code not governed by the terms of this License and distribute the +Larger Work as a single product. In such a case, You must make sure the requirements +of this License are fulfilled for the Covered Code. + + 4. Inability to Comply Due to Statute or Regulation. + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Code due to statute, judicial order, +or regulation then You must: (a) comply with the terms of this License to +the maximum extent possible; and (b) describe the limitations and the code +they affect. Such description must be included in the LEGAL file described +in Section 3.4 and must be included with all distributions of the Source Code. +Except to the extent prohibited by statute or regulation, such description +must be sufficiently detailed for a recipient of ordinary skill to be able +to understand it. + + 5. Application of this License. + +This License applies to code to which the Initial Developer has attached the +notice in Exhibit A and to related Covered Code. + + 6. Versions of the License. + + 6.1. New Versions + +Netscape Communications Corporation ("Netscape") may publish revised and/or +new versions of the License from time to time. Each version will be given +a distinguishing version number. + + 6.2. Effect of New Versions + +Once Covered Code has been published under a particular version of the License, +You may always continue to use it under the terms of that version. You may +also choose to use such Covered Code under the terms of any subsequent version +of the License published by Netscape. No one other than Netscape has the right +to modify the terms applicable to Covered Code created under this License. + + 6.3. Derivative Works + +If You create or use a modified version of this License (which you may only +do in order to apply it to code which is not already Covered Code governed +by this License), You must (a) rename Your license so that the phrases "Mozilla", +"MOZILLAPL", "MOZPL", "Netscape", "MPL", "NPL" or any confusingly similar +phrase do not appear in your license (except to note that your license differs +from this License) and (b) otherwise make it clear that Your version of the +license contains terms which differ from the Mozilla Public License and Netscape +Public License. (Filling in the name of the Initial Developer, Original Code +or Contributor in the notice described in Exhibit A shall not of themselves +be deemed to be modifications of this License.) + + 7. DISCLAIMER OF WARRANTY + +COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES +THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR +PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN +ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME +THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER +OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED +CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + + 8. Termination + +8.1. This License and the rights granted hereunder will terminate automatically +if You fail to comply with terms herein and fail to cure such breach within +30 days of becoming aware of the breach. All sublicenses to the Covered Code +which are properly granted shall survive any termination of this License. +Provisions which, by their nature, must remain in effect beyond the termination +of this License shall survive. + +8.2. If You initiate litigation by asserting a patent infringement claim (excluding +declatory judgment actions) against Initial Developer or a Contributor (the +Initial Developer or Contributor against whom You file such action is referred +to as "Participant") alleging that: + +a. such Participant's Contributor Version directly or indirectly infringes +any patent, then any and all rights granted by such Participant to You under +Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant +terminate prospectively, unless if within 60 days after receipt of notice +You either: (i) agree in writing to pay Participant a mutually agreeable reasonable +royalty for Your past and future use of Modifications made by such Participant, +or (ii) withdraw Your litigation claim with respect to the Contributor Version +against such Participant. If within 60 days of notice, a reasonable royalty +and payment arrangement are not mutually agreed upon in writing by the parties +or the litigation claim is not withdrawn, the rights granted by Participant +to You under Sections 2.1 and/or 2.2 automatically terminate at the expiration +of the 60 day notice period specified above. + +b. any software, hardware, or device, other than such Participant's Contributor +Version, directly or indirectly infringes any patent, then any rights granted +to You by such Participant under Sections 2.1(b) and 2.2(b) are revoked effective +as of the date You first made, used, sold, distributed, or had made, Modifications +made by that Participant. + +8.3. If You assert a patent infringement claim against Participant alleging +that such Participant's Contributor Version directly or indirectly infringes +any patent where such claim is resolved (such as by license or settlement) +prior to the initiation of patent infringement litigation, then the reasonable +value of the licenses granted by such Participant under Sections 2.1 or 2.2 +shall be taken into account in determining the amount or value of any payment +or license. + +8.4. In the event of termination under Sections 8.1 or 8.2 above, all end +user license agreements (excluding distributors and resellers) which have +been validly granted by You or any distributor hereunder prior to termination +shall survive termination. + + 9. LIMITATION OF LIABILITY + +UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING +NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY +OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF +ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES +FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY +AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE +BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY +SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH +PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. +SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL +OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO +YOU. + + 10. U.S. government end users + +The Covered Code is a "commercial item," as that term is defined in 48 C.F.R. +2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial +computer software documentation," as such terms are used in 48 C.F.R. 12.212 +(Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through +227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Code +with only those rights set forth herein. + + 11. Miscellaneous + +This License represents the complete agreement concerning subject matter hereof. +If any provision of this License is held to be unenforceable, such provision +shall be reformed only to the extent necessary to make it enforceable. This +License shall be governed by California law provisions (except to the extent +applicable law, if any, provides otherwise), excluding its conflict-of-law +provisions. With respect to disputes in which at least one party is a citizen +of, or an entity chartered or registered to do business in the United States +of America, any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of California, +with venue lying in Santa Clara County, California, with the losing party +responsible for costs, including without limitation, court costs and reasonable +attorneys' fees and expenses. The application of the United Nations Convention +on Contracts for the International Sale of Goods is expressly excluded. Any +law or regulation which provides that the language of a contract shall be +construed against the drafter shall not apply to this License. + + 12. Responsibility for claims + +As between Initial Developer and the Contributors, each party is responsible +for claims and damages arising, directly or indirectly, out of its utilization +of rights under this License and You agree to work with Initial Developer +and Contributors to distribute such responsibility on an equitable basis. +Nothing herein is intended or shall be deemed to constitute any admission +of liability. + + 13. Multiple-licensed code + +Initial Developer may designate portions of the Covered Code as "Multiple-Licensed". +"Multiple-Licensed" means that the Initial Developer permits you to utilize +portions of the Covered Code under Your choice of the MPL or the alternative +licenses, if any, specified by the Initial Developer in the file described +in Exhibit A. Exhibit A - Mozilla Public License. + +"The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for +the specific language governing rights and limitations under the License. + +The Original Code is ______________________________________ . + +The Initial Developer of the Original Code is ________________________ . + +Portions created by ______________________ are Copyright (C) ______ . All +Rights Reserved. + +Contributor(s): ______________________________________ . + +Alternatively, the contents of this file may be used under the terms of the +_____ license (the " [___] License"), in which case the provisions of [______] +License are applicable instead of those above. If you wish to allow use of +your version of this file only under the terms of the [____] License and not +to allow others to use your version of this file under the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the [___] License. If you do not delete the +provisions above, a recipient may use your version of this file under either +the MPL or the [___] License." + +NOTE: The text of this Exhibit A may differ slightly from the text of the +notices in the Source Code files of the Original Code. You should use the +text of this Exhibit A rather than the text found in the Original Code Source +Code for Your Modifications. diff --git a/LICENSES/Qt-LGPL-exception-1.1.txt b/LICENSES/Qt-LGPL-exception-1.1.txt new file mode 100644 index 0000000..d0f532e --- /dev/null +++ b/LICENSES/Qt-LGPL-exception-1.1.txt @@ -0,0 +1,21 @@ +The Qt Company Qt LGPL Exception version 1.1 + +As an additional permission to the GNU Lesser General Public License version 2.1, the object code form of a "work that uses the Library" may incorporate material from a header file that is part of the Library. You may distribute such object code under terms of your choice, provided that: + + (i) the header files of the Library have not been modified; and + + (ii) the incorporated material is limited to numerical parameters, data structure layouts, accessors, macros, inline functions and templates; and + + (iii) you comply with the terms of Section 6 of the GNU Lesser General Public License version 2.1. + +Moreover, you may apply this exception to a modified version of the Library, provided that such modification does not involve copying material from the Library into the modified Library's header files unless such material is limited to + + (i) numerical parameters; + + (ii) data structure layouts; + + (iii) accessors; and + + (iv) small macros, templates and inline functions of five lines or less in length. + +Furthermore, you are not required to apply this additional permission to a modified version of the Library. diff --git a/autotests/alwaysunloadplugin.cpp b/autotests/alwaysunloadplugin.cpp index f753027..b1603ab 100644 --- a/autotests/alwaysunloadplugin.cpp +++ b/autotests/alwaysunloadplugin.cpp @@ -1,36 +1,21 @@ /* - * Copyright 2013 Sebastian Kügler - * Copyright 2014 Alex Merry - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + SPDX-FileCopyrightText: 2014 Alex Merry + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #include "alwaysunloadplugin.h" #include #include #include AlwaysUnloadPlugin::AlwaysUnloadPlugin(QObject *parent, const QVariantList &args) : QObject(parent) { qDebug() << "Created AlwaysUnloadPlugin with args" << args; } K_PLUGIN_FACTORY(AlwaysUnloadPluginFactory, registerPlugin();) #include "alwaysunloadplugin.moc" diff --git a/autotests/alwaysunloadplugin.h b/autotests/alwaysunloadplugin.h index e70533e..1b28bcd 100644 --- a/autotests/alwaysunloadplugin.h +++ b/autotests/alwaysunloadplugin.h @@ -1,36 +1,21 @@ /* - * Copyright 2013 Sebastian Kügler - * Copyright 2014 Alex Merry - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + SPDX-FileCopyrightText: 2014 Alex Merry + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #ifndef ALWAYSUNLOADPLUGIN_H #define ALWAYSUNLOADPLUGIN_H #include class AlwaysUnloadPlugin : public QObject { Q_OBJECT public: AlwaysUnloadPlugin(QObject *parent, const QVariantList &args); }; #endif // ALWAYSUNLOADPLUGIN_H diff --git a/autotests/desktoptojsontest.cpp b/autotests/desktoptojsontest.cpp index 482f6bb..956466b 100644 --- a/autotests/desktoptojsontest.cpp +++ b/autotests/desktoptojsontest.cpp @@ -1,356 +1,344 @@ -/* This file is part of the KDE project - Copyright (C) 2014 Alex Richardson +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2014 Alex Richardson - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include #include #include #include #include #include namespace QTest { template<> inline char *toString(const QJsonValue &val) { // simply reuse the QDebug representation QString result; QDebug(&result) << val; return QTest::toString(result); } } class DesktopToJsonTest : public QObject { Q_OBJECT private: void compareJson(const QJsonObject& actual, const QJsonObject& expected) { for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) { if (expected.constFind(it.key()) == expected.constEnd()) { qCritical() << "Result has key" << it.key() << "which is not expected!"; QFAIL("Invalid output"); } if (it.value().isObject() && expected.value(it.key()).isObject()) { compareJson(it.value().toObject(), expected.value(it.key()).toObject()); } else { QCOMPARE(it.value(), expected.value(it.key())); } } for (auto it = expected.constBegin(); it != expected.constEnd(); ++it) { if (actual.constFind(it.key()) == actual.constEnd()) { qCritical() << "Result is missing key" << it.key(); QFAIL("Invalid output"); } if (it.value().isObject() && actual.value(it.key()).isObject()) { compareJson(it.value().toObject(), actual.value(it.key()).toObject()); } else { QCOMPARE(it.value(), actual.value(it.key())); } } } private Q_SLOTS: void testDesktopToJson_data() { QTest::addColumn("input"); QTest::addColumn("expectedResult"); QTest::addColumn("compatibilityMode"); QTest::addColumn("serviceTypes"); QJsonObject expectedResult; QJsonObject kpluginObj; QByteArray input = // include an insignificant group "[Some Group]\n" "Foo=Bar\n" "\n" "[Desktop Entry]\n" // only data inside [Desktop Entry] should be included "Name=Example\n" //empty lines "\n" " \n" // make sure translations are included: "Name[de_DE]=Beispiel\n" // ignore comments: "#Comment=Comment\n" " #Comment=Comment\n" "Categories=foo;bar;a\\;b\n" // As the case is significant, the keys Name and NAME are not equivalent: "CaseSensitive=ABC\n" "CASESENSITIVE=abc\n" // Space before and after the equals sign should be ignored: "SpacesBeforeEq =foo\n" "SpacesAfterEq= foo\n" // Space before and after the equals sign should be ignored; the = sign is the actual delimiter. // TODO: error in spec (spaces before and after the key??) " SpacesBeforeKey=foo\n" "SpacesAfterKey =foo\n" // ignore trailing spaces "TrailingSpaces=foo \n" // However spaces in the value are significant: "SpacesInValue=Hello, World!\n" // The escape sequences \s, \n, \t, \r, and \\ are supported for values of // type string and localestring, meaning ASCII space, newline, tab, // carriage return, and backslash, respectively: "EscapeSequences=So\\sme esc\\nap\\te se\\\\qu\\re\\\\nces\n" // make sure that the last n is a literal n not a newline! // the standard keys that are used by plugins, make sure correct types are used: "X-KDE-PluginInfo-Category=Examples\n" // string key "X-KDE-PluginInfo-Version=1.0\n" // The multiple values should be separated by a semicolon and the value of the key // may be optionally terminated by a semicolon. Trailing empty strings must always // be terminated with a semicolon. Semicolons in these values need to be escaped using \;. "X-KDE-PluginInfo-Depends=foo,bar,esc\\,aped\n" // string list key "X-KDE-ServiceTypes=\n" // empty string list "X-KDE-PluginInfo-EnabledByDefault=true\n" // bool key // now start a new group "[New Group]\n" "InWrongGroup=true\n"; expectedResult[QStringLiteral("Categories")] = QStringLiteral("foo;bar;a\\;b"); expectedResult[QStringLiteral("CaseSensitive")] = QStringLiteral("ABC"); expectedResult[QStringLiteral("CASESENSITIVE")] = QStringLiteral("abc"); expectedResult[QStringLiteral("SpacesBeforeEq")] = QStringLiteral("foo"); expectedResult[QStringLiteral("SpacesAfterEq")] = QStringLiteral("foo"); expectedResult[QStringLiteral("SpacesBeforeKey")] = QStringLiteral("foo"); expectedResult[QStringLiteral("SpacesAfterKey")] = QStringLiteral("foo"); expectedResult[QStringLiteral("TrailingSpaces")] = QStringLiteral("foo"); expectedResult[QStringLiteral("SpacesInValue")] = QStringLiteral("Hello, World!"); expectedResult[QStringLiteral("EscapeSequences")] = QStringLiteral("So me esc\nap\te se\\qu\re\\nces"); kpluginObj[QStringLiteral("Name")] = QStringLiteral("Example"); kpluginObj[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel"); kpluginObj[QStringLiteral("Category")] = QStringLiteral("Examples"); kpluginObj[QStringLiteral("Dependencies")] = QJsonArray::fromStringList (QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped")); kpluginObj[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(QStringList()); kpluginObj[QStringLiteral("EnabledByDefault")] = true; kpluginObj[QStringLiteral("Version")] = QStringLiteral("1.0"); QJsonObject compatResult = expectedResult; compatResult[QStringLiteral("Name")] = QStringLiteral("Example"); compatResult[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel"); compatResult[QStringLiteral("X-KDE-PluginInfo-Category")] = QStringLiteral("Examples"); compatResult[QStringLiteral("X-KDE-PluginInfo-Version")] = QStringLiteral("1.0"); compatResult[QStringLiteral("X-KDE-PluginInfo-Depends")] = QJsonArray::fromStringList (QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped")); compatResult[QStringLiteral("X-KDE-ServiceTypes")] = QJsonArray::fromStringList(QStringList()); compatResult[QStringLiteral("X-KDE-PluginInfo-EnabledByDefault")] = true; expectedResult[QStringLiteral("KPlugin")] = kpluginObj; QTest::newRow("newFormat") << input << expectedResult << false << QStringList(); QTest::newRow("compatFormat") << input << compatResult << true << QStringList(); // test conversion of a currently existing .desktop file (excluding most of the translations): QByteArray kdevInput = "[Desktop Entry]\n" "Type = Service\n" "Icon=text-x-c++src\n" "Exec=blubb\n" "Comment=C/C++ Language Support\n" "Comment[fr]=Prise en charge du langage C/C++\n" "Comment[it]=Supporto al linguaggio C/C++\n" "Name=C++ Support\n" "Name[fi]=C++-tuki\n" "Name[fr]=Prise en charge du C++\n" "GenericName=Language Support\n" "GenericName[sl]=Podpora jeziku\n" "ServiceTypes=KDevelop/NonExistentPlugin\n" "X-KDE-Library=kdevcpplanguagesupport\n" "X-KDE-PluginInfo-Name=kdevcppsupport\n" "X-KDE-PluginInfo-Category=Language Support\n" "X-KDevelop-Version=1\n" "X-KDevelop-Language=C++\n" "X-KDevelop-Args=CPP\n" "X-KDevelop-Interfaces=ILanguageSupport\n" "X-KDevelop-SupportedMimeTypes=text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\n" "X-KDevelop-Mode=NoGUI\n" "X-KDevelop-LoadMode=AlwaysOn"; QJsonParseError e; QJsonObject kdevExpected = QJsonDocument::fromJson("{\n" " \"GenericName\": \"Language Support\",\n" " \"GenericName[sl]\": \"Podpora jeziku\",\n" " \"KPlugin\": {\n" " \"Category\": \"Language Support\",\n" " \"Description\": \"C/C++ Language Support\",\n" " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n" " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n" " \"Icon\": \"text-x-c++src\",\n" " \"Id\": \"kdevcppsupport\",\n" " \"Name\": \"C++ Support\",\n" " \"Name[fi]\": \"C++-tuki\",\n" " \"Name[fr]\": \"Prise en charge du C++\",\n" " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n" " },\n" " \"X-KDevelop-Args\": \"CPP\",\n" " \"X-KDevelop-Interfaces\": \"ILanguageSupport\",\n" " \"X-KDevelop-Language\": \"C++\",\n" " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n" " \"X-KDevelop-Mode\": \"NoGUI\",\n" " \"X-KDevelop-SupportedMimeTypes\": \"text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\",\n" " \"X-KDevelop-Version\": \"1\"\n" "}\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::newRow("kdevcpplanguagesupport no servicetype") << kdevInput << kdevExpected << false << QStringList(); QJsonObject kdevExpectedWithServiceType = QJsonDocument::fromJson("{\n" " \"GenericName\": \"Language Support\",\n" " \"GenericName[sl]\": \"Podpora jeziku\",\n" " \"KPlugin\": {\n" " \"Category\": \"Language Support\",\n" " \"Description\": \"C/C++ Language Support\",\n" " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n" " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n" " \"Icon\": \"text-x-c++src\",\n" " \"Id\": \"kdevcppsupport\",\n" " \"Name\": \"C++ Support\",\n" " \"Name[fi]\": \"C++-tuki\",\n" " \"Name[fr]\": \"Prise en charge du C++\",\n" " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n" " },\n" " \"X-KDevelop-Args\": \"CPP\",\n" " \"X-KDevelop-Interfaces\": [\"ILanguageSupport\"],\n" " \"X-KDevelop-Language\": \"C++\",\n" " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n" " \"X-KDevelop-Mode\": \"NoGUI\",\n" " \"X-KDevelop-SupportedMimeTypes\": [\"text/x-chdr\", \"text/x-c++hdr\", \"text/x-csrc\", \"text/x-c++src\"],\n" " \"X-KDevelop-Version\": 1\n" "}\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); QVERIFY(!kdevServiceTypePath.isEmpty()); QTest::newRow("kdevcpplanguagesupport with servicetype") << kdevInput << kdevExpectedWithServiceType << false << QStringList(kdevServiceTypePath); // test conversion of the X-KDE-PluginInfo-Author + X-KDE-PluginInfo-Email key: QByteArray authorInput = "[Desktop Entry]\n" "Type=Service\n" "X-KDE-PluginInfo-Author=Foo Bar\n" "X-KDE-PluginInfo-Email=foo.bar@baz.com\n"; QJsonObject authorsExpected = QJsonDocument::fromJson("{\n" " \"KPlugin\": {\n" " \"Authors\": [ { \"Name\": \"Foo Bar\", \"Email\": \"foo.bar@baz.com\" } ]\n" " }\n }\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::newRow("authors") << authorInput << authorsExpected << false << QStringList(); // test case-insensitive conversion of boolean keys const QString boolServiceType = QFINDTESTDATA("data/servicetypes/bool-servicetype.desktop"); QVERIFY(!boolServiceType.isEmpty()); QByteArray boolInput1 = "[Desktop Entry]\nType=Service\nX-Test-Bool=true\n"; QByteArray boolInput2 = "[Desktop Entry]\nType=Service\nX-Test-Bool=TRue\n"; QByteArray boolInput3 = "[Desktop Entry]\nType=Service\nX-Test-Bool=false\n"; QByteArray boolInput4 = "[Desktop Entry]\nType=Service\nX-Test-Bool=FALse\n"; auto boolResultTrue = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": true}", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); auto boolResultFalse = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": false}", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::newRow("bool true") << boolInput1 << boolResultTrue << false << QStringList(boolServiceType); QTest::newRow("bool TRue") << boolInput2 << boolResultTrue << false << QStringList(boolServiceType); QTest::newRow("bool false") << boolInput3 << boolResultFalse << false << QStringList(boolServiceType); QTest::newRow("bool FALse") << boolInput4 << boolResultFalse << false << QStringList(boolServiceType); // test conversion of kcookiejar.desktop (for some reason the wrong boolean values were committed) QByteArray kcookiejarInput = "[Desktop Entry]\n" "Type= Service\n" "Name=Cookie Jar\n" "Comment=Stores network cookies\n" "X-KDE-ServiceTypes=KDEDModule\n" "X-KDE-Library=kf5/kded/kcookiejar\n" "X-KDE-Kded-autoload=false\n" "X-KDE-Kded-load-on-demand=true\n"; auto kcookiejarResult = QJsonDocument::fromJson( "{\n" " \"KPlugin\": {\n" " \"Description\": \"Stores network cookies\",\n" " \"Name\": \"Cookie Jar\",\n" " \"ServiceTypes\": [\n" " \"KDEDModule\"\n" " ]\n" " },\n" "\"X-KDE-Kded-autoload\": false,\n" "\"X-KDE-Kded-load-on-demand\": true\n" "}\n", &e).object(); const QString kdedmoduleServiceType = QFINDTESTDATA("data/servicetypes/fake-kdedmodule.desktop"); QVERIFY(!kdedmoduleServiceType.isEmpty()); QTest::newRow("kcookiejar") << kcookiejarInput << kcookiejarResult << false << QStringList(kdedmoduleServiceType); } void testDesktopToJson() { QTemporaryFile output; QTemporaryFile inputFile; QVERIFY(inputFile.open()); QVERIFY(output.open()); // create the file QFETCH(QByteArray, input); QFETCH(QJsonObject, expectedResult); QFETCH(bool, compatibilityMode); QFETCH(QStringList, serviceTypes); output.close(); inputFile.write(input); inputFile.flush(); inputFile.close(); qDebug() << expectedResult; QProcess proc; proc.setProgram(QStringLiteral(DESKTOP_TO_JSON_EXE)); QStringList arguments = QStringList() << QStringLiteral("-i") << inputFile.fileName() << QStringLiteral("-o") << output.fileName(); if (compatibilityMode) { arguments << QStringLiteral("-c"); } for(const QString &s : qAsConst(serviceTypes)) { arguments << QStringLiteral("-s") << s; } proc.setArguments(arguments); proc.start(); QVERIFY(proc.waitForFinished(10000)); qDebug() << "desktoptojson STDOUT: " << proc.readAllStandardOutput().data(); QByteArray errorOut = proc.readAllStandardError(); if (!errorOut.isEmpty()) { qWarning().nospace() << "desktoptojson STDERR:\n\n" << errorOut.constData() << "\n"; } QCOMPARE(proc.exitCode(), 0); QVERIFY(output.open()); QByteArray jsonString = output.readAll(); qDebug() << "result: " << jsonString; QJsonParseError e; QJsonDocument doc = QJsonDocument::fromJson(jsonString, &e); QCOMPARE(e.error, QJsonParseError::NoError); QJsonObject result = doc.object(); compareJson(result, expectedResult); QVERIFY(!QTest::currentTestFailed()); } }; QTEST_MAIN(DesktopToJsonTest) #include "desktoptojsontest.moc" diff --git a/autotests/jsonplugin.cpp b/autotests/jsonplugin.cpp index 7cda8bf..f2eff39 100644 --- a/autotests/jsonplugin.cpp +++ b/autotests/jsonplugin.cpp @@ -1,33 +1,18 @@ /* - * Copyright 2013 Sebastian Kügler - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #include "jsonplugin.h" #include JsonPlugin::JsonPlugin(QObject *parent, const QVariantList &args) : QObject(parent) { Q_UNUSED(args) } K_PLUGIN_FACTORY_WITH_JSON(jsonpluginfa, "jsonplugin.json", registerPlugin();) #include "jsonplugin.moc" diff --git a/autotests/jsonplugin.h b/autotests/jsonplugin.h index c6cd19c..1ffe6ae 100644 --- a/autotests/jsonplugin.h +++ b/autotests/jsonplugin.h @@ -1,35 +1,20 @@ /* - * Copyright 2013 Sebastian Kügler - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #ifndef JSONPLUGIN_H #define JSONPLUGIN_H #include class JsonPlugin : public QObject { Q_OBJECT public: explicit JsonPlugin(QObject *parent, const QVariantList &args); }; #endif // JSONPLUGIN_H diff --git a/autotests/jsonplugin2.cpp b/autotests/jsonplugin2.cpp index a1b0a09..c325d53 100644 --- a/autotests/jsonplugin2.cpp +++ b/autotests/jsonplugin2.cpp @@ -1,33 +1,18 @@ /* - * Copyright 2013 Sebastian Kügler - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #include "jsonplugin2.h" #include JsonPlugin2::JsonPlugin2(QObject *parent, const QVariantList &args) : QObject(parent) { Q_UNUSED(args) } K_PLUGIN_FACTORY_WITH_JSON(jsonplugin2, "jsonplugin2.json", registerPlugin();) #include "jsonplugin2.moc" diff --git a/autotests/jsonplugin2.h b/autotests/jsonplugin2.h index 7534b26..a70390a 100644 --- a/autotests/jsonplugin2.h +++ b/autotests/jsonplugin2.h @@ -1,35 +1,20 @@ /* - * Copyright 2013 Sebastian Kügler - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ #ifndef JSONPLUGIN2_H #define JSONPLUGIN2_H #include class JsonPlugin2 : public QObject { Q_OBJECT public: explicit JsonPlugin2(QObject *parent, const QVariantList &args); }; #endif // JSONPLUGIN_H diff --git a/autotests/kaboutdataapplicationdatatest.cpp b/autotests/kaboutdataapplicationdatatest.cpp index 29740ec..b86b52a 100644 --- a/autotests/kaboutdataapplicationdatatest.cpp +++ b/autotests/kaboutdataapplicationdatatest.cpp @@ -1,96 +1,84 @@ /* - * Copyright 2016 Friedrich W. H. Kossebau - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + + SPDX-License-Identifier: LGPL-2.0-only +*/ // test object #include // Qt #include #include // Separate test for reading & setting applicationData // to ensure a separate process where no other test case has // directly or indirectly called KAboutData::setApplicationData before // and thus created the global KAboutData object class KAboutDataApplicationDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testInteractionWithQApplicationData(); }; static const char AppName[] = "app"; static const char ProgramName[] = "ProgramName"; static const char Version[] = "Version"; static const char OrganizationDomain[] = "no.where"; static const char DesktopFileName[] = "org.kde.someapp"; static const char AppName2[] = "otherapp"; static const char ProgramName2[] = "OtherProgramName"; static const char Version2[] = "OtherVersion"; static const char OrganizationDomain2[] = "other.no.where"; static const char DesktopFileName2[] = "org.kde.otherapp"; void KAboutDataApplicationDataTest::testInteractionWithQApplicationData() { // init the app metadata the Qt way QCoreApplication *app = QCoreApplication::instance(); app->setApplicationName(QLatin1String(AppName)); app->setProperty("applicationDisplayName", QLatin1String(ProgramName)); app->setApplicationVersion(QLatin1String(Version)); app->setOrganizationDomain(QLatin1String(OrganizationDomain)); app->setProperty("desktopFileName", QLatin1String(DesktopFileName)); // without setting before, get KAboutData::applicationData const KAboutData applicationAboutData = KAboutData::applicationData(); // should be initialized with Q*Application metadata QCOMPARE(applicationAboutData.componentName(), QLatin1String(AppName)); QCOMPARE(applicationAboutData.displayName(), QLatin1String(ProgramName)); QCOMPARE(applicationAboutData.organizationDomain(), QLatin1String(OrganizationDomain)); QCOMPARE(applicationAboutData.version(), QLatin1String(Version)); QCOMPARE(applicationAboutData.desktopFileName(), QLatin1String(DesktopFileName)); // now set some new KAboutData, with different values KAboutData aboutData2(QString::fromLatin1(AppName2), QString::fromLatin1(ProgramName2), QString::fromLatin1(Version2)); aboutData2.setOrganizationDomain(OrganizationDomain2); aboutData2.setDesktopFileName(QLatin1String(DesktopFileName2)); KAboutData::setApplicationData(aboutData2); // check that Q*Application metadata has been updated, as expected per API definition QCOMPARE(app->applicationName(), QLatin1String(AppName2)); QCOMPARE(app->property("applicationDisplayName").toString(), QLatin1String(ProgramName2)); QCOMPARE(app->organizationDomain(), QLatin1String(OrganizationDomain2)); QCOMPARE(app->applicationVersion(), QLatin1String(Version2)); QCOMPARE(app->property("desktopFileName").toString(), QLatin1String(DesktopFileName2)); // and check as well KAboutData::applicationData itself const KAboutData applicationAboutData2 = KAboutData::applicationData(); QCOMPARE(applicationAboutData2.componentName(), QLatin1String(AppName2)); QCOMPARE(applicationAboutData2.displayName(), QLatin1String(ProgramName2)); QCOMPARE(applicationAboutData2.organizationDomain(), QLatin1String(OrganizationDomain2)); QCOMPARE(applicationAboutData2.version(), QLatin1String(Version2)); QCOMPARE(applicationAboutData2.desktopFileName(), QLatin1String(DesktopFileName2)); } QTEST_MAIN(KAboutDataApplicationDataTest) #include "kaboutdataapplicationdatatest.moc" diff --git a/autotests/kaboutdatatest.cpp b/autotests/kaboutdatatest.cpp index 9cab4b8..062d664 100644 --- a/autotests/kaboutdatatest.cpp +++ b/autotests/kaboutdatatest.cpp @@ -1,376 +1,364 @@ /* - * Copyright 2008 Friedrich W. H. Kossebau - * Copyright 2017 Harald Sitter - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2017 Harald Sitter + + SPDX-License-Identifier: LGPL-2.0-only +*/ // test object #include // Qt #include #include #include #include #include class KAboutDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testLongFormConstructorWithDefaults(); void testLongFormConstructor(); void testShortFormConstructor(); void testSetAddLicense(); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) void testSetProgramIconName(); #endif void testSetDesktopFileName(); void testCopying(); void testKAboutDataOrganizationDomain(); void testLicenseSPDXID(); void testLicenseOrLater(); }; static const char AppName[] = "app"; static const char ProgramName[] = "ProgramName"; #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) static const char ProgramIconName[] = "program-icon"; #endif static const char Version[] = "Version"; static const char ShortDescription[] = "ShortDescription"; static const char CopyrightStatement[] = "CopyrightStatement"; static const char Text[] = "Text"; static const char HomePageAddress[] = "http://test.no.where/"; static const char HomePageSecure[] = "https://test.no.where/"; static const char OrganizationDomain[] = "no.where"; static const char BugsEmailAddress[] = "bugs@no.else"; static const char LicenseText[] = "free to write, reading forbidden"; static const char LicenseFileName[] = "testlicensefile"; static const char LicenseFileText[] = "free to write, reading forbidden, in the file"; void KAboutDataTest::testLongFormConstructorWithDefaults() { KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown); QCOMPARE(aboutData.componentName(), QString::fromLatin1(AppName)); QCOMPARE(aboutData.productName(), QString::fromLatin1(AppName)); QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName)); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) QCOMPARE(aboutData.programIconName(), QString::fromLatin1(AppName)); #endif QCOMPARE(aboutData.programLogo(), QVariant()); QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1("kde.org")); QCOMPARE(aboutData.version(), QString::fromLatin1(Version)); QCOMPARE(aboutData.homepage(), QString()); QCOMPARE(aboutData.bugAddress(), QString::fromLatin1("submit@bugs.kde.org")); QVERIFY(aboutData.authors().isEmpty()); QVERIFY(aboutData.credits().isEmpty()); QVERIFY(aboutData.translators().isEmpty()); QCOMPARE(aboutData.otherText(), QString()); QCOMPARE(aboutData.licenses().count(), 1); // We don't know the default text, do we? // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); QCOMPARE(aboutData.copyrightStatement(), QString()); QCOMPARE(aboutData.shortDescription(), (QLatin1String(ShortDescription))); QCOMPARE(aboutData.customAuthorPlainText(), QString()); QCOMPARE(aboutData.customAuthorRichText(), QString()); QVERIFY(!aboutData.customAuthorTextEnabled()); QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app")); //TODO: test internalVersion, internalProgramName, internalBugAddress } void KAboutDataTest::testLongFormConstructor() { KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown, QLatin1String(CopyrightStatement), QLatin1String(Text), QString::fromLatin1(HomePageAddress), QString::fromLatin1(BugsEmailAddress)); QCOMPARE(aboutData.componentName(), QLatin1String(AppName)); QCOMPARE(aboutData.productName(), QLatin1String(AppName)); QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName)); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) QCOMPARE(aboutData.programIconName(), QLatin1String(AppName)); #endif QCOMPARE(aboutData.programLogo(), QVariant()); QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1(OrganizationDomain)); QCOMPARE(aboutData.version(), QString::fromLatin1(Version)); QCOMPARE(aboutData.homepage(), QString::fromLatin1(HomePageAddress)); QCOMPARE(aboutData.bugAddress(), QString::fromLatin1(BugsEmailAddress)); QVERIFY(aboutData.authors().isEmpty()); QVERIFY(aboutData.credits().isEmpty()); QVERIFY(aboutData.translators().isEmpty()); QCOMPARE(aboutData.otherText(), QLatin1String(Text)); QCOMPARE(aboutData.licenses().count(), 1); // We don't know the default text, do we? // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); QCOMPARE(aboutData.copyrightStatement(), QLatin1String(CopyrightStatement)); QCOMPARE(aboutData.shortDescription(), QLatin1String(ShortDescription)); QCOMPARE(aboutData.customAuthorPlainText(), QString()); QCOMPARE(aboutData.customAuthorRichText(), QString()); QVERIFY(!aboutData.customAuthorTextEnabled()); QCOMPARE(aboutData.desktopFileName(), QStringLiteral("where.no.app")); //TODO: test internalVersion, internalProgramName, internalBugAddress // We support http and https protocols on the homepage address, ensure they // give the same org. domain and desktop file name. KAboutData aboutDataSecure(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown, QLatin1String(CopyrightStatement), QLatin1String(Text), QString::fromLatin1(HomePageSecure), QString::fromLatin1(BugsEmailAddress)); QCOMPARE(aboutDataSecure.componentName(), QLatin1String(AppName)); QCOMPARE(aboutDataSecure.productName(), QLatin1String(AppName)); QCOMPARE(aboutDataSecure.organizationDomain(), QString::fromLatin1(OrganizationDomain)); QCOMPARE(aboutDataSecure.desktopFileName(), QStringLiteral("where.no.app")); } void KAboutDataTest::testShortFormConstructor() { KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version)); QCOMPARE(aboutData.componentName(), QString::fromLatin1(AppName)); QCOMPARE(aboutData.productName(), QString::fromLatin1(AppName)); QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName)); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) QCOMPARE(aboutData.programIconName(), QString::fromLatin1(AppName)); #endif QCOMPARE(aboutData.programLogo(), QVariant()); QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1("kde.org")); QCOMPARE(aboutData.version(), QString::fromLatin1(Version)); QCOMPARE(aboutData.homepage(), QString()); QCOMPARE(aboutData.bugAddress(), QString::fromLatin1("submit@bugs.kde.org")); QVERIFY(aboutData.authors().isEmpty()); QVERIFY(aboutData.credits().isEmpty()); QVERIFY(aboutData.translators().isEmpty()); QCOMPARE(aboutData.otherText(), QString()); QCOMPARE(aboutData.licenses().count(), 1); // We don't know the default text, do we? // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); QCOMPARE(aboutData.copyrightStatement(), QString()); QCOMPARE(aboutData.shortDescription(), QString()); QCOMPARE(aboutData.customAuthorPlainText(), QString()); QCOMPARE(aboutData.customAuthorRichText(), QString()); QVERIFY(!aboutData.customAuthorTextEnabled()); QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app")); //TODO: test internalVersion, internalProgramName, internalBugAddress } void KAboutDataTest::testKAboutDataOrganizationDomain() { KAboutData data(QString::fromLatin1("app"), QLatin1String("program"), QString::fromLatin1("version"), QLatin1String("description"), KAboutLicense::LGPL, QLatin1String("copyright"), QLatin1String("hello world"), QStringLiteral("http://www.koffice.org")); QCOMPARE(data.organizationDomain(), QString::fromLatin1("koffice.org")); QCOMPARE(data.desktopFileName(), QStringLiteral("org.koffice.app")); KAboutData data2(QString::fromLatin1("app"), QLatin1String("program"), QString::fromLatin1("version"), QLatin1String("description"), KAboutLicense::LGPL, QString::fromLatin1("copyright"), QLatin1String("hello world"), QStringLiteral("app")); QCOMPARE(data2.organizationDomain(), QString::fromLatin1("kde.org")); QCOMPARE(data2.desktopFileName(), QStringLiteral("org.kde.app")); } void KAboutDataTest::testSetAddLicense() { // prepare a file with a license text QFile licenseFile(QString::fromLatin1(LicenseFileName)); licenseFile.open(QIODevice::WriteOnly); QTextStream licenseFileStream(&licenseFile); licenseFileStream << LicenseFileText; licenseFile.close(); const QString copyrightStatement = QLatin1String(CopyrightStatement); const QString lineFeed = QString::fromLatin1("\n\n"); KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown, QLatin1String(CopyrightStatement), QLatin1String(Text), QString::fromLatin1(HomePageAddress), QString::fromLatin1(BugsEmailAddress)); // set to GPL2 aboutData.setLicense(KAboutLicense::GPL_V2); QCOMPARE(aboutData.licenses().count(), 1); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v2")); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 2")); // QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL2Text) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); // set to Unknown again aboutData.setLicense(KAboutLicense::Unknown); QCOMPARE(aboutData.licenses().count(), 1); // We don't know the default text, do we? // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty()); // QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); // add GPL3 aboutData.addLicense(KAboutLicense::GPL_V3); QCOMPARE(aboutData.licenses().count(), 1); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v3")); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 3")); // QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL3Text) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); // add GPL2, Custom and File aboutData.addLicense(KAboutLicense::GPL_V2); aboutData.addLicenseText(QLatin1String(LicenseText)); aboutData.addLicenseTextFile(QLatin1String(LicenseFileName)); QCOMPARE(aboutData.licenses().count(), 4); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v3")); QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 3")); // QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL3Text) ); QVERIFY(!aboutData.licenses().at(0).text().isEmpty()); QCOMPARE(aboutData.licenses().at(1).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v2")); QCOMPARE(aboutData.licenses().at(1).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 2")); // QCOMPARE( aboutData.licenses().at(1).text(), QString(GPL2Text) ); QVERIFY(!aboutData.licenses().at(1).text().isEmpty()); QCOMPARE(aboutData.licenses().at(2).name(KAboutLicense::ShortName), QString::fromLatin1("Custom")); QCOMPARE(aboutData.licenses().at(2).name(KAboutLicense::FullName), QString::fromLatin1("Custom")); QCOMPARE(aboutData.licenses().at(2).text(), QLatin1String(LicenseText)); QCOMPARE(aboutData.licenses().at(3).name(KAboutLicense::ShortName), QString::fromLatin1("Custom")); QCOMPARE(aboutData.licenses().at(3).name(KAboutLicense::FullName), QString::fromLatin1("Custom")); QCOMPARE(aboutData.licenses().at(3).text(), QString(copyrightStatement + lineFeed + QLatin1String(LicenseFileText))); } #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) void KAboutDataTest::testSetProgramIconName() { const QString programIconName(QString::fromLatin1(ProgramIconName)); KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown, QLatin1String(CopyrightStatement), QLatin1String(Text), QString::fromLatin1(HomePageAddress), QString::fromLatin1(BugsEmailAddress)); // Deprecated, still want to test this though. Silence GCC warnings. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // set different iconname aboutData.setProgramIconName(programIconName); #pragma GCC diagnostic pop QCOMPARE(aboutData.programIconName(), programIconName); } #endif void KAboutDataTest::testCopying() { KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::GPL_V2); { KAboutData aboutData2(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::GPL_V3); aboutData2.addLicense(KAboutLicense::GPL_V2, KAboutLicense::OrLaterVersions); aboutData = aboutData2; } QList licenses = aboutData.licenses(); QCOMPARE(licenses.count(), 2); QCOMPARE(licenses.at(0).key(), KAboutLicense::GPL_V3); QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-3.0")); // check it doesn't crash QVERIFY(!licenses.at(0).text().isEmpty()); QCOMPARE(licenses.at(1).key(), KAboutLicense::GPL_V2); QCOMPARE(aboutData.licenses().at(1).spdx(), QStringLiteral("GPL-2.0+")); // check it doesn't crash QVERIFY(!licenses.at(1).text().isEmpty()); } void KAboutDataTest::testSetDesktopFileName() { KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::Unknown); QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app")); // set different desktopFileName aboutData.setDesktopFileName(QStringLiteral("foo.bar.application")); QCOMPARE(aboutData.desktopFileName(), QStringLiteral("foo.bar.application")); } void KAboutDataTest::testLicenseSPDXID() { // Input with + should output with +. auto license = KAboutLicense::byKeyword(QStringLiteral("GPLv2+")); QCOMPARE(license.spdx(), QStringLiteral("GPL-2.0+")); // Input without should output without. license = KAboutLicense::byKeyword(QStringLiteral("GPLv2")); QCOMPARE(license.spdx(), QStringLiteral("GPL-2.0")); //we should be able to match by spdx too //create a KAboutLicense from enum, then make sure going to spdx and back gives the same enum for(int i = 1; i <= KAboutLicense::LGPL_V2_1 ; ++i) { /*current highest enum value*/ KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::GPL_V2); aboutData.setLicense(KAboutLicense::LicenseKey(i)); QVERIFY(aboutData.licenses().count() == 1); auto license = aboutData.licenses()[0]; auto licenseFromKeyword = KAboutLicense::byKeyword(license.spdx()); QCOMPARE(license.key(), licenseFromKeyword.key()); } } void KAboutDataTest::testLicenseOrLater() { // For kaboutdata we can replace the license with an orLater version. Or add a second one. KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version), QLatin1String(ShortDescription), KAboutLicense::GPL_V2); QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-2.0")); aboutData.setLicense(KAboutLicense::GPL_V2, KAboutLicense::OrLaterVersions); QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-2.0+")); aboutData.addLicense(KAboutLicense::LGPL_V3, KAboutLicense::OrLaterVersions); bool foundLGPL = false; for (auto license : aboutData.licenses()) { if (license.key() == KAboutLicense::LGPL_V3) { QCOMPARE(license.spdx(), QStringLiteral("LGPL-3.0+")); foundLGPL = true; break; } } QCOMPARE(foundLGPL, true); } QTEST_MAIN(KAboutDataTest) #include "kaboutdatatest.moc" diff --git a/autotests/kautosavefiletest.cpp b/autotests/kautosavefiletest.cpp index 1aa1f41..d04ab20 100644 --- a/autotests/kautosavefiletest.cpp +++ b/autotests/kautosavefiletest.cpp @@ -1,167 +1,155 @@ -/* This file is part of the KDE libraries - Copyright (c) 2006 Jacob R Rideout - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2006 Jacob R Rideout + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kautosavefiletest.h" #include #include #include #include #include #include QTEST_MAIN(KAutoSaveFileTest) void KAutoSaveFileTest::initTestCase() { QCoreApplication::instance()->setApplicationName(QLatin1String("qttest")); // TODO do this in qtestlib itself } void KAutoSaveFileTest::cleanupTestCase() { for (const QString &fileToRemove : qAsConst(filesToRemove)) { QFile::remove(fileToRemove); } } void KAutoSaveFileTest::test_readWrite() { QTemporaryFile file; QVERIFY(file.open()); QUrl normalFile = QUrl::fromLocalFile(QFileInfo(file).absoluteFilePath()); //Test basic functionality KAutoSaveFile saveFile(normalFile); QVERIFY(!QFile::exists(saveFile.fileName())); QVERIFY(saveFile.open(QIODevice::ReadWrite)); QString inText = QString::fromLatin1("This is test data one.\n"); { QTextStream ts(&saveFile); ts << inText; ts.flush(); } saveFile.close(); { QFile testReader(saveFile.fileName()); testReader.open(QIODevice::ReadWrite); QTextStream ts(&testReader); QString outText = ts.readAll(); QCOMPARE(outText, inText); } filesToRemove << file.fileName(); } void KAutoSaveFileTest::test_fileNameMaxLength() { // In KAutoSaveFilePrivate::tempFile() the name of the kautosavefile that's going to be created // is concatanated in the form: // fileName + junk.truncated + protocol + _ + path.truncated + junk // see tempFile() for details. // // Make sure that the generated filename (e.g. as you would get from QUrl::fileName()) doesn't // exceed NAME_MAX (the maximum length allowed for filenames, see e.g. /usr/include/linux/limits.h) // otherwise the file can't be opened. // // see https://phabricator.kde.org/D24489 QString s; s.fill(QLatin1Char('b'), 80); // create a long path that: // - exceeds NAME_MAX (255) // - is less than the maximum allowed path length, PATH_MAX (4096) // see e.g. /usr/include/linux/limits.h const QString path = QDir::tempPath() + QLatin1Char('/') + s + QLatin1Char('/') + s + QLatin1Char('/') + s + QLatin1Char('/') + s; QFile file(path + QLatin1Char('/') + QLatin1String("testFile.txt")); QUrl normalFile = QUrl::fromLocalFile(file.fileName()); KAutoSaveFile saveFile(normalFile); QVERIFY(!QFile::exists(saveFile.fileName())); QVERIFY(saveFile.open(QIODevice::ReadWrite)); filesToRemove << file.fileName(); } void KAutoSaveFileTest::test_fileStaleFiles() { QUrl normalFile = QUrl::fromLocalFile(QDir::temp().absoluteFilePath(QStringLiteral("test directory/tîst me.txt"))); KAutoSaveFile saveFile(normalFile); QVERIFY(saveFile.open(QIODevice::ReadWrite)); saveFile.write("testdata"); // Make sure the stale file is found QVERIFY(saveFile.staleFiles(normalFile, QStringLiteral("qttest")).count() == 1); saveFile.releaseLock(); // Make sure the stale file is deleted QVERIFY(saveFile.staleFiles(normalFile, QStringLiteral("qttest")).isEmpty()); } void KAutoSaveFileTest::test_applicationStaleFiles() { // TODO } void KAutoSaveFileTest::test_locking() { QUrl normalFile(QString::fromLatin1("fish://user@example.com/home/remote/test.txt")); KAutoSaveFile saveFile(normalFile); QVERIFY(!QFile::exists(saveFile.fileName())); QVERIFY(saveFile.open(QIODevice::ReadWrite)); const QList staleFiles(KAutoSaveFile::staleFiles(normalFile)); QVERIFY(!staleFiles.isEmpty()); KAutoSaveFile *saveFile2 = staleFiles.at(0); const QString fn = saveFile2->fileName(); // It looks like $XDG_DATA_HOME/stalefiles/qttest/test.txtXXXfish_%2Fhome%2FremoteXXXXXXX QVERIFY2(fn.contains(QLatin1String("stalefiles/qttest/test.txt")), qPrintable(fn)); QVERIFY2(fn.contains(QLatin1String("fish_%2Fhome%2Fremote")), qPrintable(fn)); QVERIFY(QFile::exists(saveFile2->fileName())); QVERIFY(!saveFile2->open(QIODevice::ReadWrite)); saveFile.releaseLock(); QVERIFY(saveFile2->open(QIODevice::ReadWrite)); delete saveFile2; } diff --git a/autotests/kautosavefiletest.h b/autotests/kautosavefiletest.h index 52bdf9f..7534153 100644 --- a/autotests/kautosavefiletest.h +++ b/autotests/kautosavefiletest.h @@ -1,43 +1,31 @@ -/* This file is part of the KDE libraries - Copyright (c) 2006 Jacob R Rideout +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2006 Jacob R Rideout - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef kautosavefiletest_h #define kautosavefiletest_h #include #include class KAutoSaveFileTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void test_readWrite(); void test_fileNameMaxLength(); void test_fileStaleFiles(); void test_applicationStaleFiles(); void test_locking(); void cleanupTestCase(); private: QStringList filesToRemove; }; #endif diff --git a/autotests/kcompositejobtest.cpp b/autotests/kcompositejobtest.cpp index 244d1e8..bb62067 100644 --- a/autotests/kcompositejobtest.cpp +++ b/autotests/kcompositejobtest.cpp @@ -1,107 +1,95 @@ -/* This file is part of the KDE project - Copyright (C) 2013 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2013 Kevin Funk + SPDX-License-Identifier: LGPL-2.0-only */ #include "kcompositejobtest.h" #include #include #include TestJob::TestJob(QObject *parent) : KJob(parent) { } void TestJob::start() { QTimer::singleShot(1000, this, SLOT(doEmit())); } void TestJob::doEmit() { emitResult(); } void CompositeJob::start() { if (hasSubjobs()) { subjobs().first()->start(); } else { emitResult(); } } bool CompositeJob::addSubjob(KJob *job) { return KCompositeJob::addSubjob(job); } void CompositeJob::slotResult(KJob *job) { KCompositeJob::slotResult(job); if (!error() && hasSubjobs()) { // start next subjobs().first()->start(); } else { setError(job->error()); setErrorText(job->errorText()); emitResult(); } } KCompositeJobTest::KCompositeJobTest() : loop(this) { } /** * In case a composite job is deleted during execution * we still want to assure that we don't crash * * see bug: https://bugs.kde.org/show_bug.cgi?id=230692 */ void KCompositeJobTest::testDeletionDuringExecution() { QObject *someParent = new QObject; KJob *job = new TestJob(someParent); CompositeJob *compositeJob = new CompositeJob; compositeJob->setAutoDelete(false); QVERIFY(compositeJob->addSubjob(job)); QCOMPARE(job->parent(), compositeJob); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); // check if job got reparented properly delete someParent; someParent = nullptr; // the job should still exist, because it is a child of KCompositeJob now QCOMPARE(destroyed_spy.size(), 0); // start async, the subjob takes 1 second to finish compositeJob->start(); // delete the job during the execution delete compositeJob; compositeJob = nullptr; // at this point, the subjob should be deleted, too QCOMPARE(destroyed_spy.size(), 1); } QTEST_GUILESS_MAIN(KCompositeJobTest) diff --git a/autotests/kcompositejobtest.h b/autotests/kcompositejobtest.h index 1404490..3e73091 100644 --- a/autotests/kcompositejobtest.h +++ b/autotests/kcompositejobtest.h @@ -1,70 +1,58 @@ -/* This file is part of the KDE project - Copyright (C) 2013 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2013 Kevin Funk + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KCOMPOSITEJOBTEST_H #define KCOMPOSITEJOBTEST_H #include #include #include "kcompositejob.h" class TestJob : public KJob { Q_OBJECT public: explicit TestJob(QObject *parent = nullptr); /// Takes 1 second to finish void start() override; private Q_SLOTS: void doEmit(); }; class CompositeJob : public KCompositeJob { Q_OBJECT public: explicit CompositeJob(QObject *parent = nullptr) : KCompositeJob(parent) {} void start() override; bool addSubjob(KJob *job) override; protected Q_SLOTS: void slotResult(KJob *job) override; }; class KCompositeJobTest : public QObject { Q_OBJECT public: KCompositeJobTest(); private Q_SLOTS: void testDeletionDuringExecution(); private: QEventLoop loop; }; #endif // KCOMPOSITEJOBTEST_H diff --git a/autotests/kdelibs4configmigratortest.cpp b/autotests/kdelibs4configmigratortest.cpp index 2fcdd16..17fc0d4 100644 --- a/autotests/kdelibs4configmigratortest.cpp +++ b/autotests/kdelibs4configmigratortest.cpp @@ -1,141 +1,129 @@ /* - * Copyright 2014 Montel Laurent - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2014 Montel Laurent + + SPDX-License-Identifier: LGPL-2.0-only +*/ // test object #include "kdelibs4configmigrator.h" // Qt #include #include #include #include #include class Kdelibs4ConfigMigratorTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void shouldNotMigrateIfKde4HomeDirDoesntExist(); void shouldMigrateIfKde4HomeDirExist(); void shouldMigrateConfigFiles(); void shouldMigrateUiFiles(); }; void Kdelibs4ConfigMigratorTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); const QString configHome = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); QDir(configHome).removeRecursively(); const QString dataHome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); QDir(dataHome).removeRecursively(); } void Kdelibs4ConfigMigratorTest::shouldNotMigrateIfKde4HomeDirDoesntExist() { qputenv("KDEHOME", ""); Kdelibs4ConfigMigrator migration(QLatin1String("foo")); QCOMPARE(migration.migrate(), false); } void Kdelibs4ConfigMigratorTest::shouldMigrateIfKde4HomeDirExist() { QTemporaryDir kdehomeDir; QVERIFY(kdehomeDir.isValid()); const QString kdehome = kdehomeDir.path(); qputenv("KDEHOME", QFile::encodeName(kdehome)); Kdelibs4ConfigMigrator migration(QLatin1String("foo")); QCOMPARE(migration.migrate(), true); } void Kdelibs4ConfigMigratorTest::shouldMigrateConfigFiles() { QTemporaryDir kdehomeDir; const QString kdehome = kdehomeDir.path(); qputenv("KDEHOME", QFile::encodeName(kdehome)); //Generate kde4 config dir const QString configPath = kdehome + QLatin1Char('/') + QLatin1String("share/config/"); QDir().mkpath(configPath); QVERIFY(QDir(configPath).exists()); QStringList listConfig; listConfig << QLatin1String("foorc") << QLatin1String("foo1rc"); for (const QString &config : qAsConst(listConfig)) { QFile fooConfigFile(QLatin1String(KDELIBS4CONFIGMIGRATOR_DATA_DIR) + QLatin1Char('/') + config); QVERIFY(fooConfigFile.exists()); const QString storedConfigFilePath = configPath + QLatin1Char('/') + config; QVERIFY(QFile::copy(fooConfigFile.fileName(), storedConfigFilePath)); QCOMPARE(QStandardPaths::locate(QStandardPaths::ConfigLocation, config), QString()); } Kdelibs4ConfigMigrator migration(QLatin1String("foo")); migration.setConfigFiles(QStringList() << listConfig); QVERIFY(migration.migrate()); for (const QString &config : qAsConst(listConfig)) { const QString migratedConfigFile = QStandardPaths::locate(QStandardPaths::ConfigLocation, config); QVERIFY(!migratedConfigFile.isEmpty()); QVERIFY(QFile(migratedConfigFile).exists()); QFile::remove(migratedConfigFile); } } void Kdelibs4ConfigMigratorTest::shouldMigrateUiFiles() { QTemporaryDir kdehomeDir; const QString kdehome = kdehomeDir.path(); qputenv("KDEHOME", QFile::encodeName(kdehome)); const QString appName = QLatin1String("foo"); //Generate kde4 data dir const QString dataPath = kdehome + QLatin1Char('/') + QLatin1String("share/apps/"); QDir().mkpath(dataPath); QVERIFY(QDir(dataPath).exists()); const QString xdgDatahome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); QStringList listUi; listUi << QLatin1String("appuirc") << QLatin1String("appui1rc"); for (const QString &uifile : qAsConst(listUi)) { QFile fooConfigFile(QLatin1String(KDELIBS4CONFIGMIGRATOR_DATA_DIR) + QLatin1Char('/') + uifile); QVERIFY(fooConfigFile.exists()); QDir().mkpath(dataPath + QLatin1Char('/') + appName); const QString storedConfigFilePath = dataPath + QLatin1Char('/') + appName + QLatin1Char('/') + uifile; QVERIFY(QFile::copy(fooConfigFile.fileName(), storedConfigFilePath)); const QString xdgUiFile = xdgDatahome + QLatin1String("/kxmlgui5/") + appName + QLatin1Char('/') + uifile; QVERIFY(!QFile::exists(xdgUiFile)); } Kdelibs4ConfigMigrator migration(appName); migration.setUiFiles(QStringList() << listUi); QVERIFY(migration.migrate()); for (const QString &uifile : qAsConst(listUi)) { const QString xdgUiFile = xdgDatahome + QLatin1String("/kxmlgui5/") + appName + QLatin1Char('/') + uifile; QVERIFY(QFile(xdgUiFile).exists()); QFile::remove(xdgUiFile); } } QTEST_MAIN(Kdelibs4ConfigMigratorTest) #include "kdelibs4configmigratortest.moc" diff --git a/autotests/kdelibs4migrationtest.cpp b/autotests/kdelibs4migrationtest.cpp index 438aa42..9ab54ce 100644 --- a/autotests/kdelibs4migrationtest.cpp +++ b/autotests/kdelibs4migrationtest.cpp @@ -1,58 +1,46 @@ /* - * Copyright 2014 David Faure - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2014 David Faure + + SPDX-License-Identifier: LGPL-2.0-only +*/ // test object #include // Qt #include #include #include #include class MigrationTest : public QObject { Q_OBJECT private Q_SLOTS: void testPaths(); }; void MigrationTest::testPaths() { // Setup QTemporaryDir kdehomeDir; QVERIFY(kdehomeDir.isValid()); QString kdehome = kdehomeDir.path(); qputenv("KDEHOME", QFile::encodeName(kdehome)); QString oldConfigDir = kdehome + QStringLiteral("/share/config/"); QVERIFY(QDir().mkpath(oldConfigDir)); QString oldAppsDir = kdehome + QStringLiteral("/share/apps/"); QVERIFY(QDir().mkpath(oldAppsDir)); // Test Kdelibs4Migration migration; QVERIFY(migration.kdeHomeFound()); QCOMPARE(migration.saveLocation("config"), oldConfigDir); QCOMPARE(migration.saveLocation("data"), oldAppsDir); } QTEST_MAIN(MigrationTest) #include "kdelibs4migrationtest.moc" diff --git a/autotests/kdirwatch_unittest.cpp b/autotests/kdirwatch_unittest.cpp index b9e5266..e4e93a0 100644 --- a/autotests/kdirwatch_unittest.cpp +++ b/autotests/kdirwatch_unittest.cpp @@ -1,860 +1,848 @@ -/* This file is part of the KDE libraries +/* + This file is part of the KDE libraries - Copyright (c) 2009 David Faure + SPDX-FileCopyrightText: 2009 David Faure - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include // ::link() #endif #include "config-tests.h" // Debugging notes: to see which inotify signals are emitted, either set s_verboseDebug=true // at the top of kdirwatch.cpp, or use the command-line tool "inotifywait -m /path" // Note that kdirlistertest and kdirmodeltest also exercise KDirWatch quite a lot. static const char *methodToString(KDirWatch::Method method) { switch (method) { case KDirWatch::FAM: return "Fam"; case KDirWatch::INotify: return "INotify"; case KDirWatch::Stat: return "Stat"; case KDirWatch::QFSWatch: return "QFSWatch"; } return "ERROR!"; } class StaticObject { public: KDirWatch m_dirWatch; }; Q_GLOBAL_STATIC(StaticObject, s_staticObject) class StaticObjectUsingSelf // like KSambaShare does, bug 353080 { public: StaticObjectUsingSelf() { KDirWatch::self(); } ~StaticObjectUsingSelf() { if (KDirWatch::exists() && KDirWatch::self()->contains(QDir::homePath())) { KDirWatch::self()->removeDir(QDir::homePath()); } } }; Q_GLOBAL_STATIC(StaticObjectUsingSelf, s_staticObjectUsingSelf) class KDirWatch_UnitTest : public QObject { Q_OBJECT public: KDirWatch_UnitTest() { // Speed up the test by making the kdirwatch timer (to compress changes) faster qputenv("KDIRWATCH_POLLINTERVAL", "50"); qputenv("KDIRWATCH_METHOD", KDIRWATCH_TEST_METHOD); s_staticObjectUsingSelf(); m_path = m_tempDir.path() + QLatin1Char('/'); KDirWatch *dirW = &s_staticObject()->m_dirWatch; m_slow = (dirW->internalMethod() == KDirWatch::FAM || dirW->internalMethod() == KDirWatch::Stat); qDebug() << "Using method" << methodToString(dirW->internalMethod()); } private Q_SLOTS: // test methods void initTestCase() { QFileInfo pathInfo(m_path); QVERIFY(pathInfo.isDir() && pathInfo.isWritable()); // By creating the files upfront, we save waiting a full second for an mtime change createFile(m_path + QLatin1String("ExistingFile")); createFile(m_path + QLatin1String("TestFile")); createFile(m_path + QLatin1String("nested_0")); createFile(m_path + QLatin1String("nested_1")); s_staticObject()->m_dirWatch.addFile(m_path + QLatin1String("ExistingFile")); } void touchOneFile(); void touch1000Files(); void watchAndModifyOneFile(); void removeAndReAdd(); void watchNonExistent(); void watchNonExistentWithSingleton(); void testDelete(); void testDeleteAndRecreateFile(); void testDeleteAndRecreateDir(); void testMoveTo(); void nestedEventLoop(); void testHardlinkChange(); void stopAndRestart(); void shouldIgnoreQrcPaths(); void benchCreateTree(); void benchCreateWatcher(); void benchNotifyWatcher(); protected Q_SLOTS: // internal slots void nestedEventLoopSlot(); private: void waitUntilMTimeChange(const QString &path); void waitUntilNewSecond(); void waitUntilAfter(const QDateTime &ctime); QList waitForDirtySignal(KDirWatch &watch, int expected); QList waitForDeletedSignal(KDirWatch &watch, int expected); bool waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path); bool waitForRecreationSignal(KDirWatch &watch, const QString &path); bool verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath); void createFile(const QString &path); QString createFile(int num); void removeFile(int num); void appendToFile(const QString &path); void appendToFile(int num); int createDirectoryTree(const QString &path, int depth = 4); QTemporaryDir m_tempDir; QString m_path; bool m_slow; }; QTEST_MAIN(KDirWatch_UnitTest) // Just to make the inotify packets bigger static const char s_filePrefix[] = "This_is_a_test_file_"; static const int s_maxTries = 50; // helper method: create a file void KDirWatch_UnitTest::createFile(const QString &path) { QFile file(path); QVERIFY(file.open(QIODevice::WriteOnly)); file.write(QByteArray("foo")); file.close(); //qDebug() << path; } // helper method: create a file (identified by number) QString KDirWatch_UnitTest::createFile(int num) { const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); createFile(m_path + fileName); return m_path + fileName; } // helper method: delete a file (identified by number) void KDirWatch_UnitTest::removeFile(int num) { const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); QFile::remove(m_path + fileName); } int KDirWatch_UnitTest::createDirectoryTree(const QString& basePath, int depth) { int filesCreated = 0; const int numFiles = 10; for (int i = 0; i < numFiles; ++i) { createFile(basePath + QLatin1Char('/') + QLatin1String(s_filePrefix) + QString::number(i)); ++filesCreated; } if (depth <= 0) { return filesCreated; } const int numFolders = 5; for (int i = 0; i < numFolders; ++i) { const QString childPath = basePath + QLatin1String("/subdir") + QString::number(i); QDir().mkdir(childPath); filesCreated += createDirectoryTree(childPath, depth - 1); } return filesCreated; } void KDirWatch_UnitTest::waitUntilMTimeChange(const QString &path) { // Wait until the current second is more than the file's mtime // otherwise this change will go unnoticed QFileInfo fi(path); QVERIFY(fi.exists()); const QDateTime ctime = fi.lastModified(); waitUntilAfter(ctime); } void KDirWatch_UnitTest::waitUntilNewSecond() { QDateTime now = QDateTime::currentDateTime(); waitUntilAfter(now); } void KDirWatch_UnitTest::waitUntilAfter(const QDateTime &ctime) { int totalWait = 0; QDateTime now; Q_FOREVER { now = QDateTime::currentDateTime(); if (now.toMSecsSinceEpoch() / 1000 == ctime.toMSecsSinceEpoch() / 1000) // truncate milliseconds { totalWait += 50; QTest::qWait(50); } else { QVERIFY(now > ctime); // can't go back in time ;) QTest::qWait(50); // be safe break; } } //if (totalWait > 0) qDebug() << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate); } // helper method: modifies a file void KDirWatch_UnitTest::appendToFile(const QString &path) { QVERIFY(QFile::exists(path)); waitUntilMTimeChange(path); //const QString directory = QDir::cleanPath(path+"/.."); //waitUntilMTimeChange(directory); QFile file(path); QVERIFY(file.open(QIODevice::Append | QIODevice::WriteOnly)); file.write(QByteArray("foobar")); file.close(); #if 0 QFileInfo fi(path); QVERIFY(fi.exists()); qDebug() << "After append: file ctime=" << fi.lastModified().toString(Qt::ISODate); QVERIFY(fi.exists()); qDebug() << "After append: metadataChangeTime" << fi.metadataChangeTime().toString(Qt::ISODate); #endif } // helper method: modifies a file (identified by number) void KDirWatch_UnitTest::appendToFile(int num) { const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); appendToFile(m_path + fileName); } static QString removeTrailingSlash(const QString &path) { if (path.endsWith(QLatin1Char('/'))) { return path.left(path.length() - 1); } else { return path; } } // helper method QList KDirWatch_UnitTest::waitForDirtySignal(KDirWatch &watch, int expected) { QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); int numTries = 0; // Give time for KDirWatch to notify us while (spyDirty.count() < expected) { if (++numTries > s_maxTries) { qWarning() << "Timeout waiting for KDirWatch. Got" << spyDirty.count() << "dirty() signals, expected" << expected; return spyDirty; } spyDirty.wait(50); } return spyDirty; } bool KDirWatch_UnitTest::waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path) { const QString expectedPath = removeTrailingSlash(path); while (true) { QSignalSpy spyDirty(&watch, sig); int numTries = 0; // Give time for KDirWatch to notify us while (spyDirty.isEmpty()) { if (++numTries > s_maxTries) { qWarning() << "Timeout waiting for KDirWatch signal" << QByteArray(sig).mid(1) << "(" << path << ")"; return false; } spyDirty.wait(50); } return verifySignalPath(spyDirty, sig, expectedPath); } } bool KDirWatch_UnitTest::verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath) { for (int i = 0; i < spy.count(); ++i) { const QString got = spy[i][0].toString(); if (got == expectedPath) { return true; } if (got.startsWith(expectedPath + QLatin1Char('/'))) { qDebug() << "Ignoring (inotify) notification of" << (sig + 1) << '(' << got << ')'; continue; } qWarning() << "Expected" << sig << '(' << expectedPath << ')' << "but got" << sig << '(' << got << ')'; return false; } return false; } bool KDirWatch_UnitTest::waitForRecreationSignal(KDirWatch &watch, const QString &path) { // When watching for a deleted + created signal pair, the two might come so close that // using waitForOneSignal will miss the created signal. This function monitors both all // the time to ensure both are received. const QString expectedPath = removeTrailingSlash(path); QSignalSpy spyDeleted(&watch, SIGNAL(deleted(QString))); QSignalSpy spyCreated(&watch, SIGNAL(created(QString))); if(!spyDeleted.wait(50 * s_maxTries)) { qWarning() << "Timeout waiting for KDirWatch signal deleted(QString) (" << path << ")"; return false; } // Don't bother waiting for the created signal if the signal spy already received a signal. if(spyCreated.isEmpty() && !spyCreated.wait(50 * s_maxTries)) { qWarning() << "Timeout waiting for KDirWatch signal created(QString) (" << path << ")"; return false; } return verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath); } QList KDirWatch_UnitTest::waitForDeletedSignal(KDirWatch &watch, int expected) { QSignalSpy spyDeleted(&watch, SIGNAL(created(QString))); int numTries = 0; // Give time for KDirWatch to notify us while (spyDeleted.count() < expected) { if (++numTries > s_maxTries) { qWarning() << "Timeout waiting for KDirWatch. Got" << spyDeleted.count() << "deleted() signals, expected" << expected; return spyDeleted; } spyDeleted.wait(50); } return spyDeleted; } void KDirWatch_UnitTest::touchOneFile() // watch a dir, create a file in it { KDirWatch watch; watch.addDir(m_path); watch.startScan(); waitUntilMTimeChange(m_path); // dirty(the directory) should be emitted. QSignalSpy spyCreated(&watch, SIGNAL(created(QString))); const QString file0 = createFile(0); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); QCOMPARE(spyCreated.count(), 0); // "This is not emitted when creating a file is created in a watched directory." removeFile(0); } void KDirWatch_UnitTest::touch1000Files() { KDirWatch watch; watch.addDir(m_path); watch.startScan(); waitUntilMTimeChange(m_path); const int fileCount = 100; for (int i = 0; i < fileCount; ++i) { createFile(i); } QList spy = waitForDirtySignal(watch, fileCount); if (watch.internalMethod() == KDirWatch::INotify) { QVERIFY(spy.count() >= fileCount); qDebug() << spy.count(); } else { // More stupid backends just see one mtime change on the directory QVERIFY(spy.count() >= 1); } for (int i = 0; i < fileCount; ++i) { removeFile(i); } } void KDirWatch_UnitTest::watchAndModifyOneFile() // watch a specific file, and modify it { KDirWatch watch; const QString existingFile = m_path + QLatin1String("ExistingFile"); watch.addFile(existingFile); watch.startScan(); if (m_slow) { waitUntilNewSecond(); } appendToFile(existingFile); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile)); } void KDirWatch_UnitTest::removeAndReAdd() { KDirWatch watch; watch.addDir(m_path); watch.startScan(); if (watch.internalMethod() != KDirWatch::INotify) { waitUntilNewSecond(); // necessary for mtime checks in scanEntry } const QString file0 = createFile(0); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); // Just like KDirLister does: remove the watch, then re-add it. watch.removeDir(m_path); watch.addDir(m_path); if (watch.internalMethod() != KDirWatch::INotify) { waitUntilMTimeChange(m_path); // necessary for FAM and QFSWatcher } const QString file1 = createFile(1); //qDebug() << "created" << file1; QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); } void KDirWatch_UnitTest::watchNonExistent() { KDirWatch watch; // Watch "subdir", that doesn't exist yet const QString subdir = m_path + QLatin1String("subdir"); QVERIFY(!QFile::exists(subdir)); watch.addDir(subdir); watch.startScan(); if (m_slow) { waitUntilNewSecond(); } // Now create it, KDirWatch should emit created() qDebug() << "Creating" << subdir; QDir().mkdir(subdir); QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), subdir)); KDirWatch::statistics(); // Play with addDir/removeDir, just for fun watch.addDir(subdir); watch.removeDir(subdir); watch.addDir(subdir); // Now watch files that don't exist yet const QString file = subdir + QLatin1String("/0"); watch.addFile(file); // doesn't exist yet const QString file1 = subdir + QLatin1String("/1"); watch.addFile(file1); // doesn't exist yet watch.removeFile(file1); // forget it again KDirWatch::statistics(); QVERIFY(!QFile::exists(file)); // Now create it, KDirWatch should emit created qDebug() << "Creating" << file; createFile(file); QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file)); appendToFile(file); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file)); // Create the file after all; we're not watching for it, but the dir will emit dirty createFile(file1); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), subdir)); } void KDirWatch_UnitTest::watchNonExistentWithSingleton() { const QString file = QLatin1String("/root/.ssh/authorized_keys"); KDirWatch::self()->addFile(file); // When running this test in KDIRWATCH_METHOD=QFSWatch, or when FAM is not available // and we fallback to qfswatch when inotify fails above, we end up creating the fsWatch // in the kdirwatch singleton. Bug 261541 discovered that Qt hanged when deleting fsWatch // once QCoreApp was gone, this is what this test is about. } void KDirWatch_UnitTest::testDelete() { const QString file1 = m_path + QLatin1String("del"); if (!QFile::exists(file1)) { createFile(file1); } waitUntilMTimeChange(file1); // Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed) KDirWatch watch; watch.addFile(file1); KDirWatch::statistics(); QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); QFile::remove(file1); QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1)); QTest::qWait(40); // just in case delayed processing would emit it QCOMPARE(spyDirty.count(), 0); } void KDirWatch_UnitTest::testDeleteAndRecreateFile() // Useful for /etc/localtime for instance { const QString subdir = m_path + QLatin1String("subdir"); QDir().mkdir(subdir); const QString file1 = subdir + QLatin1String("/1"); if (!QFile::exists(file1)) { createFile(file1); } waitUntilMTimeChange(file1); // Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed) KDirWatch watch; watch.addFile(file1); //KDE_struct_stat stat_buf; //QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0); //qDebug() << "initial inode" << stat_buf.st_ino; // Make sure this even works multiple times, as needed for ksycoca for (int i = 0; i < 5; ++i) { QFile::remove(file1); // And recreate immediately, to try and fool KDirWatch with unchanged ctime/mtime ;) // (This emulates the /etc/localtime case) createFile(file1); //QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0); //qDebug() << "new inode" << stat_buf.st_ino; // same! { QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); if(!waitForRecreationSignal(watch, file1)) { // We may get a dirty signal here instead of a deleted/created set. if (spyDirty.isEmpty() || !verifySignalPath(spyDirty, SIGNAL(dirty(QString)), file1)) { QFAIL("Failed to detect file deletion and recreation through either a deleted/created signal pair or through a dirty signal!"); } } } } waitUntilMTimeChange(file1); appendToFile(file1); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1)); } void KDirWatch_UnitTest::testDeleteAndRecreateDir() { // Like KDirModelTest::testOverwriteFileWithDir does at the end. // The linux-2.6.31 bug made kdirwatch emit deletion signals about the -new- dir! QTemporaryDir *tempDir1 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("olddir-")); KDirWatch watch; const QString path1 = tempDir1->path() + QLatin1Char('/'); watch.addDir(path1); delete tempDir1; QTemporaryDir *tempDir2 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("newdir-")); const QString path2 = tempDir2->path() + QLatin1Char('/'); watch.addDir(path2); QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), path1)); delete tempDir2; } void KDirWatch_UnitTest::testMoveTo() { // This reproduces the famous digikam crash, #222974 // A watched file was being rewritten (overwritten by ksavefile), // which gives inotify notifications "moved_to" followed by "delete_self" // // What happened then was that the delayed slotRescan // would adjust things, making it status==Normal but the entry was // listed as a "non-existent sub-entry" for the parent directory. // That's inconsistent, and after removeFile() a dangling sub-entry would be left. // Initial data: creating file subdir/1 const QString file1 = m_path + QLatin1String("moveTo"); createFile(file1); KDirWatch watch; watch.addDir(m_path); watch.addFile(file1); watch.startScan(); if (watch.internalMethod() != KDirWatch::INotify) { waitUntilMTimeChange(m_path); } // Atomic rename of "temp" to "file1", much like KAutoSave would do when saving file1 again // ### TODO: this isn't an atomic rename anymore. We need ::rename for that, or API from Qt. const QString filetemp = m_path + QLatin1String("temp"); createFile(filetemp); QFile::remove(file1); QVERIFY(QFile::rename(filetemp, file1)); // overwrite file1 with the tempfile qDebug() << "Overwrite file1 with tempfile"; QSignalSpy spyCreated(&watch, SIGNAL(created(QString))); QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); // Getting created() on an unwatched file is an inotify bonus, it's not part of the requirements. if (watch.internalMethod() == KDirWatch::INotify) { QCOMPARE(spyCreated.count(), 1); QCOMPARE(spyCreated[0][0].toString(), file1); QCOMPARE(spyDirty.size(), 2); QCOMPARE(spyDirty[1][0].toString(), filetemp); } // make sure we're still watching it appendToFile(file1); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1)); //qDebug() << "after created"; //KDirWatch::statistics(); watch.removeFile(file1); // now we remove it //qDebug() << "after removeFile"; //KDirWatch::statistics(); // Just touch another file to trigger a findSubEntry - this where the crash happened waitUntilMTimeChange(m_path); createFile(filetemp); #ifdef Q_OS_WIN if (watch.internalMethod() == KDirWatch::QFSWatch) { QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue); } #endif QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); } void KDirWatch_UnitTest::nestedEventLoop() // #220153: watch two files, and modify 2nd while in slot for 1st { KDirWatch watch; const QString file0 = m_path + QLatin1String("nested_0"); watch.addFile(file0); const QString file1 = m_path + QLatin1String("nested_1"); watch.addFile(file1); watch.startScan(); if (m_slow) { waitUntilNewSecond(); } appendToFile(file0); // use own spy, to connect it before nestedEventLoopSlot, otherwise it reverses order QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); connect(&watch, SIGNAL(dirty(QString)), this, SLOT(nestedEventLoopSlot())); waitForDirtySignal(watch, 1); QVERIFY(spyDirty.count() >= 2); QCOMPARE(spyDirty[0][0].toString(), file0); QCOMPARE(spyDirty[spyDirty.count() - 1][0].toString(), file1); } void KDirWatch_UnitTest::nestedEventLoopSlot() { const KDirWatch *const_watch = qobject_cast(sender()); KDirWatch *watch = const_cast(const_watch); // let's not come in this slot again disconnect(watch, SIGNAL(dirty(QString)), this, SLOT(nestedEventLoopSlot())); const QString file1 = m_path + QLatin1String("nested_1"); appendToFile(file1); //qDebug() << "now waiting for signal"; // The nested event processing here was from a messagebox in #220153 QList spy = waitForDirtySignal(*watch, 1); QVERIFY(spy.count() >= 1); QCOMPARE(spy[spy.count() - 1][0].toString(), file1); //qDebug() << "done"; // Now the user pressed reload... const QString file0 = m_path + QLatin1String("nested_0"); watch->removeFile(file0); watch->addFile(file0); } void KDirWatch_UnitTest::testHardlinkChange() { #ifdef Q_OS_UNIX // The unittest for the "detecting hardlink change to /etc/localtime" problem // described on kde-core-devel (2009-07-03). // It shows that watching a specific file doesn't inform us that the file is // being recreated. Better watch the directory, for that. // Well, it works with inotify (and fam - which uses inotify I guess?) const QString existingFile = m_path + QLatin1String("ExistingFile"); KDirWatch watch; watch.addFile(existingFile); watch.startScan(); //waitUntilMTimeChange(existingFile); //waitUntilMTimeChange(m_path); QFile::remove(existingFile); const QString testFile = m_path + QLatin1String("TestFile"); QVERIFY(::link(QFile::encodeName(testFile).constData(), QFile::encodeName(existingFile).constData()) == 0); // make ExistingFile "point" to TestFile QVERIFY(QFile::exists(existingFile)); QVERIFY(waitForRecreationSignal(watch, existingFile)); //KDirWatch::statistics(); // The mtime of the existing file is the one of "TestFile", so it's old. // We won't detect the change then, if we use that as baseline for waiting. // We really need msec granularity, but that requires using statx which isn't available everywhere... waitUntilNewSecond(); appendToFile(existingFile); QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile)); #else QSKIP("Unix-specific"); #endif } void KDirWatch_UnitTest::stopAndRestart() { KDirWatch watch; watch.addDir(m_path); watch.startScan(); waitUntilMTimeChange(m_path); watch.stopDirScan(m_path); //qDebug() << "create file 2 at" << QDateTime::currentDateTime().toMSecsSinceEpoch(); const QString file2 = createFile(2); QSignalSpy spyDirty(&watch, SIGNAL(dirty(QString))); QTest::qWait(200); QCOMPARE(spyDirty.count(), 0);// suspended -> no signal watch.restartDirScan(m_path); QTest::qWait(200); #ifndef Q_OS_WIN QCOMPARE(spyDirty.count(), 0); // as documented by restartDirScan: no signal // On Windows, however, signals will get emitted, due to the ifdef Q_OS_WIN in the timestamp // comparison ("trust QFSW since the mtime of dirs isn't modified") #endif KDirWatch::statistics(); waitUntilMTimeChange(m_path); // necessary for the mtime comparison in scanEntry //qDebug() << "create file 3 at" << QDateTime::currentDateTime().toMSecsSinceEpoch(); const QString file3 = createFile(3); #ifdef Q_OS_WIN if (watch.internalMethod() == KDirWatch::QFSWatch) { QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue); } #endif QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); QFile::remove(file2); QFile::remove(file3); } void KDirWatch_UnitTest::shouldIgnoreQrcPaths() { const auto oldCwd = QDir::currentPath(); QVERIFY(QDir::setCurrent(QDir::homePath())); KDirWatch watch; watch.addDir(QDir::homePath()); // This triggers bug #374075. watch.addDir(QStringLiteral(":/kio5/newfile-templates")); QSignalSpy dirtySpy(&watch, &KDirWatch::dirty); QFile file(QStringLiteral("bug374075.txt")); QVERIFY(file.open(QIODevice::WriteOnly)); QVERIFY(file.write(QByteArrayLiteral("test"))); file.close(); QVERIFY(file.exists()); QVERIFY(dirtySpy.wait()); QVERIFY(dirtySpy.count() > 0); QVERIFY(file.remove()); QVERIFY(QDir::setCurrent(oldCwd)); } void KDirWatch_UnitTest::benchCreateTree() { #if !ENABLE_BENCHMARKS QSKIP("Benchmarks are disabled in debug mode"); #endif QTemporaryDir dir; QBENCHMARK { createDirectoryTree(dir.path()); } } void KDirWatch_UnitTest::benchCreateWatcher() { #if !ENABLE_BENCHMARKS QSKIP("Benchmarks are disabled in debug mode"); #endif QTemporaryDir dir; createDirectoryTree(dir.path()); QBENCHMARK { KDirWatch watch; watch.addDir(dir.path(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles); } } void KDirWatch_UnitTest::benchNotifyWatcher() { #if !ENABLE_BENCHMARKS QSKIP("Benchmarks are disabled in debug mode"); #endif QTemporaryDir dir; // create the dir once upfront auto numFiles = createDirectoryTree(dir.path()); waitUntilMTimeChange(dir.path()); KDirWatch watch; watch.addDir(dir.path(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles); // now touch all the files repeatedly and wait for the dirty updates to come in QSignalSpy spy(&watch, &KDirWatch::dirty); QBENCHMARK { createDirectoryTree(dir.path()); QTRY_COMPARE_WITH_TIMEOUT(spy.count(), numFiles, s_maxTries * 50 * 2); spy.clear(); } } #include "kdirwatch_unittest.moc" diff --git a/autotests/kfileutilstest.cpp b/autotests/kfileutilstest.cpp index 31e2012..982c859 100644 --- a/autotests/kfileutilstest.cpp +++ b/autotests/kfileutilstest.cpp @@ -1,65 +1,53 @@ -/* This file is part of the KDE libraries - Copyright (c) 2000-2005 David Faure +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2000-2005 David Faure - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kfileutilstest.h" #include #include QTEST_MAIN(KFileUtilsTest) void KFileUtilsTest::testSuggestName_data() { QTest::addColumn("oldName"); QTest::addColumn("existingFiles"); QTest::addColumn("expectedOutput"); QTest::newRow("non-existing") << "foobar" << QStringList() << "foobar (1)"; QTest::newRow("existing") << "foobar" << QStringList(QStringLiteral("foobar")) << "foobar (1)"; QTest::newRow("existing_1") << "foobar" << (QStringList() << QStringLiteral("foobar") << QStringLiteral("foobar (1)")) << "foobar (2)"; QTest::newRow("extension") << "foobar.txt" << QStringList() << "foobar (1).txt"; QTest::newRow("extension_exists") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt")) << "foobar (1).txt"; QTest::newRow("extension_exists_1") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt") << QStringLiteral("foobar (1).txt")) << "foobar (2).txt"; QTest::newRow("two_extensions") << "foobar.tar.gz" << QStringList() << "foobar (1).tar.gz"; QTest::newRow("two_extensions_exists") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz")) << "foobar (1).tar.gz"; QTest::newRow("two_extensions_exists_1") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz") << QStringLiteral("foobar (1).tar.gz")) << "foobar (2).tar.gz"; QTest::newRow("with_space") << "foo bar" << QStringList(QStringLiteral("foo bar")) << "foo bar (1)"; QTest::newRow("dot_at_beginning") << ".aFile.tar.gz" << QStringList() << ".aFile (1).tar.gz"; QTest::newRow("dots_at_beginning") << "..aFile.tar.gz" << QStringList() << "..aFile (1).tar.gz"; QTest::newRow("empty_basename") << ".txt" << QStringList() << ". (1).txt"; QTest::newRow("empty_basename_2dots") << "..txt" << QStringList() << ". (1).txt"; QTest::newRow("basename_with_dots") << "filename.5.3.2.tar.gz" << QStringList() << "filename.5.3.2 (1).tar.gz"; QTest::newRow("unknown_extension_trashinfo") << "fileFromHome.trashinfo" << QStringList() << "fileFromHome (1).trashinfo"; } void KFileUtilsTest::testSuggestName() { QFETCH(QString, oldName); QFETCH(QStringList, existingFiles); QFETCH(QString, expectedOutput); QTemporaryDir dir; const QUrl baseUrl = QUrl::fromLocalFile(dir.path()); for (const QString &localFile : qAsConst(existingFiles)) { QFile file(dir.path() + QLatin1Char('/') + localFile); QVERIFY(file.open(QIODevice::WriteOnly)); } QCOMPARE(KFileUtils::suggestName(baseUrl, oldName), expectedOutput); } diff --git a/autotests/kfileutilstest.h b/autotests/kfileutilstest.h index 9cd1e36..425a5d7 100644 --- a/autotests/kfileutilstest.h +++ b/autotests/kfileutilstest.h @@ -1,34 +1,22 @@ -/* This file is part of the KDE libraries - Copyright (c) 2000-2005 David Faure +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2000-2005 David Faure - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KFILEUTILSTEST_H #define KFILEUTILSTEST_H #include class KFileUtilsTest : public QObject { Q_OBJECT private Q_SLOTS: void testSuggestName_data(); void testSuggestName(); }; #endif diff --git a/autotests/kformattest.cpp b/autotests/kformattest.cpp index 2aff332..96807ef 100644 --- a/autotests/kformattest.cpp +++ b/autotests/kformattest.cpp @@ -1,391 +1,379 @@ -/* This file is part of the KDE Frameworks - - Copyright (C) 2013 John Layt , - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kformattest.h" #include #include "kformat.h" #ifndef Q_OS_WIN void ignoreTranslations() { qputenv("XDG_DATA_DIRS", "does-not-exist"); } Q_CONSTRUCTOR_FUNCTION(ignoreTranslations) #endif void KFormatTest::formatByteSize() { QLocale locale(QLocale::c()); locale.setNumberOptions(QLocale::DefaultNumberOptions); // Qt >= 5.6 sets QLocale::OmitGroupSeparator for the C locale KFormat format(locale); QCOMPARE(format.formatByteSize(0), QStringLiteral("0 B")); QCOMPARE(format.formatByteSize(50), QStringLiteral("50 B")); QCOMPARE(format.formatByteSize(500), QStringLiteral("500 B")); QCOMPARE(format.formatByteSize(5000), QStringLiteral("4.9 KiB")); QCOMPARE(format.formatByteSize(50000), QStringLiteral("48.8 KiB")); QCOMPARE(format.formatByteSize(500000), QStringLiteral("488.3 KiB")); QCOMPARE(format.formatByteSize(5000000), QStringLiteral("4.8 MiB")); QCOMPARE(format.formatByteSize(50000000), QStringLiteral("47.7 MiB")); QCOMPARE(format.formatByteSize(500000000), QStringLiteral("476.8 MiB")); #if (defined(__WORDSIZE) && (__WORDSIZE == 64)) || defined (_LP64) || defined(__LP64__) || defined(__ILP64__) QCOMPARE(format.formatByteSize(5000000000), QStringLiteral("4.7 GiB")); QCOMPARE(format.formatByteSize(50000000000), QStringLiteral("46.6 GiB")); QCOMPARE(format.formatByteSize(500000000000), QStringLiteral("465.7 GiB")); QCOMPARE(format.formatByteSize(5000000000000), QStringLiteral("4.5 TiB")); QCOMPARE(format.formatByteSize(50000000000000), QStringLiteral("45.5 TiB")); QCOMPARE(format.formatByteSize(500000000000000), QStringLiteral("454.7 TiB")); #endif QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.0 KiB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1,023 B")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.1 MiB")); // 1.2 metric QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.0 KiB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1,023 B")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.1 MiB")); // 1.2 metric QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.0 KB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1,023 B")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.1 MB")); QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.0 KB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1,023 B")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.1 MB")); QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.2 MB")); QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.2 MB")); // Ensure all units are represented QCOMPARE(format.formatByteSize(2.0e9, 1, KFormat::MetricBinaryDialect), QStringLiteral("2.0 GB")); QCOMPARE(format.formatByteSize(3.2e12, 1, KFormat::MetricBinaryDialect), QStringLiteral("3.2 TB")); QCOMPARE(format.formatByteSize(4.1e15, 1, KFormat::MetricBinaryDialect), QStringLiteral("4.1 PB")); QCOMPARE(format.formatByteSize(6.7e18, 2, KFormat::MetricBinaryDialect), QStringLiteral("6.70 EB")); QCOMPARE(format.formatByteSize(5.6e20, 2, KFormat::MetricBinaryDialect), QStringLiteral("560.00 EB")); QCOMPARE(format.formatByteSize(2.3e22, 2, KFormat::MetricBinaryDialect), QStringLiteral("23.00 ZB")); QCOMPARE(format.formatByteSize(1.0e27, 1, KFormat::MetricBinaryDialect), QStringLiteral("1,000.0 YB")); // Spattering of specific units QCOMPARE(format.formatByteSize(823000, 3, KFormat::IECBinaryDialect, KFormat::UnitMegaByte), QStringLiteral("0.785 MiB")); QCOMPARE(format.formatByteSize(1234034.0, 4, KFormat::JEDECBinaryDialect, KFormat::UnitByte), QStringLiteral("1,234,034 B")); // Check examples from the documentation QCOMPARE(format.formatByteSize(1000, 1, KFormat::MetricBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 kB")); QCOMPARE(format.formatByteSize(1000, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 KiB")); QCOMPARE(format.formatByteSize(1000, 1, KFormat::JEDECBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 KB")); } void KFormatTest::formatValue() { QLocale locale(QLocale::c()); locale.setNumberOptions(QLocale::DefaultNumberOptions); // Qt >= 5.6 sets QLocale::OmitGroupSeparator for the C locale KFormat format(locale); // Check examples from the documentation QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB")); QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::IECBinaryDialect), QStringLiteral("1.0 KiB")); QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::JEDECBinaryDialect), QStringLiteral("1.0 KB")); // Check examples from the documentation QCOMPARE(format.formatValue(1000, KFormat::Unit::Bit, 1, KFormat::UnitPrefix::Kilo, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kbit")); QCOMPARE(format.formatValue(1000, QStringLiteral("bit"), 1, KFormat::UnitPrefix::Kilo), QStringLiteral("1.0 kbit")); QCOMPARE(format.formatValue(1000, QStringLiteral("bit/s"), 1, KFormat::UnitPrefix::Kilo), QStringLiteral("1.0 kbit/s")); QCOMPARE(format.formatValue(100, QStringLiteral("bit/s")), QStringLiteral("100.0 bit/s")); QCOMPARE(format.formatValue(1000, QStringLiteral("bit/s")), QStringLiteral("1.0 kbit/s")); QCOMPARE(format.formatValue(10e3, QStringLiteral("bit/s")), QStringLiteral("10.0 kbit/s")); QCOMPARE(format.formatValue(10e6, QStringLiteral("bit/s")), QStringLiteral("10.0 Mbit/s")); QCOMPARE(format.formatValue(0.010, KFormat::Unit::Meter, 1, KFormat::UnitPrefix::Milli, KFormat::MetricBinaryDialect), QStringLiteral("10.0 mm")); QCOMPARE(format.formatValue(10.12e-6, KFormat::Unit::Meter, 2, KFormat::UnitPrefix::Micro, KFormat::MetricBinaryDialect), QString::fromUtf8("10.12 µm")); QCOMPARE(format.formatValue(10.55e-6, KFormat::Unit::Meter, 1, KFormat::UnitPrefix::AutoAdjust, KFormat::MetricBinaryDialect), QString::fromUtf8("10.6 µm")); } enum TimeConstants { MSecsInDay = 86400000, MSecsInHour = 3600000, MSecsInMinute = 60000, MSecsInSecond = 1000 }; void KFormatTest::formatDuration() { KFormat format(QLocale::c()); quint64 singleSecond = 3 * MSecsInSecond + 700; quint64 doubleSecond = 33 * MSecsInSecond + 700; quint64 singleMinute = 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleMinute = 38 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 singleHour = 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleHour = 15 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 singleDay = 1 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleDay = 10 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 roundingIssues = 2* MSecsInHour + 59 * MSecsInMinute + 59 * MSecsInSecond + 900; quint64 largeValue = 9999999999; // Default format QCOMPARE(format.formatDuration(singleSecond), QStringLiteral("0:00:04")); QCOMPARE(format.formatDuration(doubleSecond), QStringLiteral("0:00:34")); QCOMPARE(format.formatDuration(singleMinute), QStringLiteral("0:08:04")); QCOMPARE(format.formatDuration(doubleMinute), QStringLiteral("0:38:04")); QCOMPARE(format.formatDuration(singleHour), QStringLiteral("5:08:04")); QCOMPARE(format.formatDuration(doubleHour), QStringLiteral("15:08:04")); QCOMPARE(format.formatDuration(singleDay), QStringLiteral("29:08:04")); QCOMPARE(format.formatDuration(doubleDay), QStringLiteral("245:08:04")); QCOMPARE(format.formatDuration(roundingIssues), QStringLiteral("3:00:00")); QCOMPARE(format.formatDuration(largeValue), QStringLiteral("2777:46:40")); // ShowMilliseconds format KFormat::DurationFormatOptions options = KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00:03.700")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:00:33.700")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08:03.700")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38:03.700")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08:03.700")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08:03.700")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08:03.700")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08:03.700")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2:59:59.900")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("2777:46:39.999")); // HideSeconds format options = KFormat::HideSeconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:01")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3:00")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("2777:47")); // FoldHours format options = KFormat::FoldHours; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:04")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:34")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:04")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:04")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:04")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:04")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:04")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:04")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180:00")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("166666:40")); // FoldHours ShowMilliseconds format options = KFormat::FoldHours; options = options | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:03.700")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:33.700")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:03.700")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:03.700")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:03.700")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:03.700")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:03.700")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:03.700")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179:59.900")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("166666:39.999")); // InitialDuration format options = KFormat::InitialDuration; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m04s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m34s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m04s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m04s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m04s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m04s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m04s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m04s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m00s")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("2777h46m40s")); // InitialDuration and ShowMilliseconds format options = KFormat::InitialDuration; options = options | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m03.700s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m33.700s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m03.700s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m03.700s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m03.700s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m03.700s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m03.700s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m03.700s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2h59m59.900s")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("2777h46m39.999s")); // InitialDuration and HideSeconds format options = KFormat::InitialDuration; options = options | KFormat::HideSeconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h01m")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("2777h47m")); // InitialDuration and FoldHours format options = KFormat::InitialDuration; options = options | KFormat::FoldHours; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m04s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m34s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m04s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m04s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m04s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m04s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m04s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m04s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180m00s")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("166666m40s")); // InitialDuration and FoldHours and ShowMilliseconds format options = KFormat::InitialDuration; options = options | KFormat::FoldHours | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m03.700s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m33.700s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m03.700s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m03.700s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m03.700s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m03.700s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m03.700s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m03.700s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179m59.900s")); QCOMPARE(format.formatDuration(largeValue,options), QStringLiteral("166666m39.999s")); } void KFormatTest::formatDecimalDuration() { KFormat format(QLocale::c()); QCOMPARE(format.formatDecimalDuration(10), QStringLiteral("10 millisecond(s)")); QCOMPARE(format.formatDecimalDuration(10, 3), QStringLiteral("10 millisecond(s)")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 10), QStringLiteral("1.01 seconds")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 1, 3), QStringLiteral("1.001 seconds")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond), QStringLiteral("1.17 minutes")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond, 3), QStringLiteral("1.167 minutes")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute), QStringLiteral("1.17 hours")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute, 3), QStringLiteral("1.167 hours")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour), QStringLiteral("1.42 days")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour, 3), QStringLiteral("1.417 days")); } void KFormatTest::formatSpelloutDuration() { KFormat format(QLocale::c()); QCOMPARE(format.formatSpelloutDuration(1000), QStringLiteral("1 second(s)")); QCOMPARE(format.formatSpelloutDuration(5000), QStringLiteral("5 second(s)")); QCOMPARE(format.formatSpelloutDuration(60000), QStringLiteral("1 minute(s)")); QCOMPARE(format.formatSpelloutDuration(300000), QStringLiteral("5 minute(s)")); QCOMPARE(format.formatSpelloutDuration(3600000), QStringLiteral("1 hour(s)")); QCOMPARE(format.formatSpelloutDuration(18000000), QStringLiteral("5 hour(s)")); QCOMPARE(format.formatSpelloutDuration(75000), QStringLiteral("1 minute(s) and 15 second(s)")); // Problematic case #1 (there is a reference to this case on kformat.cpp) QCOMPARE(format.formatSpelloutDuration(119999), QStringLiteral("2 minute(s)")); // This case is strictly 2 hours, 15 minutes and 59 seconds. However, since the range is // pretty high between hours and seconds, formatSpelloutDuration always omits seconds when there // are hours in scene. QCOMPARE(format.formatSpelloutDuration(8159000), QStringLiteral("2 hour(s) and 15 minute(s)")); // This case is strictly 1 hour and 10 seconds. For the same reason, formatSpelloutDuration // detects that 10 seconds is just garbage compared to 1 hour, and omits it on the result. QCOMPARE(format.formatSpelloutDuration(3610000), QStringLiteral("1 hour(s)")); } void KFormatTest::formatRelativeDate() { KFormat format(QLocale::c()); QDate testDate = QDate::currentDate(); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Today")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Today")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Today")); testDate = QDate::currentDate().addDays(1); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Tomorrow")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Tomorrow")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Tomorrow")); testDate = QDate::currentDate().addDays(-1); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Yesterday")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Yesterday")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Yesterday")); testDate = QDate::currentDate().addDays(2); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("In two days")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("In two days")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("In two days")); testDate = QDate::currentDate().addDays(-2); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Two days ago")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Two days ago")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Two days ago")); testDate = QDate::currentDate().addDays(-3); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat)); testDate = QDate::currentDate().addDays(3); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat)); testDate = QDate(); // invalid date QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Invalid date")); QDateTime testDateTime = QDateTime(QDate::currentDate(), QTime(3, 0, 0)); QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("Today, 03:00:00")); testDateTime = QDateTime(QDate::currentDate().addDays(8), QTime(3, 0, 0)); QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::LongFormat), QLocale::c().toString(testDateTime, QLocale::LongFormat)); } QTEST_MAIN(KFormatTest) diff --git a/autotests/kformattest.h b/autotests/kformattest.h index 24f5206..b7585da 100644 --- a/autotests/kformattest.h +++ b/autotests/kformattest.h @@ -1,43 +1,31 @@ -/* This file is part of the KDE Frameworks - - Copyright (C) 2013 John Layt , - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KFORMATTEST_H #define KFORMATTEST_H #include class KFormatTest : public QObject { Q_OBJECT private Q_SLOTS: void formatByteSize(); void formatDuration(); void formatDecimalDuration(); void formatSpelloutDuration(); void formatRelativeDate(); void formatValue(); }; #endif // KFORMATTEST_H diff --git a/autotests/kjobtest.cpp b/autotests/kjobtest.cpp index 651b651..1b106cc 100644 --- a/autotests/kjobtest.cpp +++ b/autotests/kjobtest.cpp @@ -1,411 +1,399 @@ -/* This file is part of the KDE project - Copyright (C) 2006 Kevin Ottens - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #include "kjobtest.h" #include #include #include QTEST_MAIN(KJobTest) KJobTest::KJobTest() : loop(this) { } void KJobTest::testEmitResult_data() { QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::newRow("no error") << int(KJob::NoError) << QString(); QTest::newRow("error no text") << 2 << QString(); QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; } void KJobTest::testEmitResult() { TestJob *job = new TestJob; connect(job, &KJob::result, this, &KJobTest::slotResult); QFETCH(int, errorCode); QFETCH(QString, errorText); job->setError(errorCode); job->setErrorText(errorText); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); job->start(); loop.exec(); QCOMPARE(m_lastError, errorCode); QCOMPARE(m_lastErrorText, errorText); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testProgressTracking() { TestJob *testJob = new TestJob; KJob *job = testJob; qRegisterMetaType("KJob*"); qRegisterMetaType("qulonglong"); QSignalSpy processed_spy(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong))); QSignalSpy total_spy(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong))); QSignalSpy percent_spy(job, SIGNAL(percent(KJob*,ulong))); /* Process a first item. Corresponding signal should be emitted. * Total size didn't change. * Since the total size is unknown, no percent signal is emitted. */ testJob->setProcessedSize(1); QCOMPARE(processed_spy.size(), 1); QCOMPARE(processed_spy.at(0).at(0).value(), static_cast(job)); QCOMPARE(processed_spy.at(0).at(2).value(), qulonglong(1)); QCOMPARE(total_spy.size(), 0); QCOMPARE(percent_spy.size(), 0); /* Now, we know the total size. It's signaled. * The new percentage is signaled too. */ testJob->setTotalSize(10); QCOMPARE(processed_spy.size(), 1); QCOMPARE(total_spy.size(), 1); QCOMPARE(total_spy.at(0).at(0).value(), job); QCOMPARE(total_spy.at(0).at(2).value(), qulonglong(10)); QCOMPARE(percent_spy.size(), 1); QCOMPARE(percent_spy.at(0).at(0).value(), job); QCOMPARE(percent_spy.at(0).at(1).value(), static_cast(10)); /* We announce a new percentage by hand. * Total, and processed didn't change, so no signal is emitted for them. */ testJob->setPercent(15); QCOMPARE(processed_spy.size(), 1); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 2); QCOMPARE(percent_spy.at(1).at(0).value(), job); QCOMPARE(percent_spy.at(1).at(1).value(), static_cast(15)); /* We make some progress. * Processed size and percent are signaled. */ testJob->setProcessedSize(3); QCOMPARE(processed_spy.size(), 2); QCOMPARE(processed_spy.at(1).at(0).value(), job); QCOMPARE(processed_spy.at(1).at(2).value(), qulonglong(3)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 3); QCOMPARE(percent_spy.at(2).at(0).value(), job); QCOMPARE(percent_spy.at(2).at(1).value(), static_cast(30)); /* We set a new total size, but equals to the previous one. * No signal is emitted. */ testJob->setTotalSize(10); QCOMPARE(processed_spy.size(), 2); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 3); /* We 'lost' the previous work done. * Signals both percentage and new processed size. */ testJob->setProcessedSize(0); QCOMPARE(processed_spy.size(), 3); QCOMPARE(processed_spy.at(2).at(0).value(), job); QCOMPARE(processed_spy.at(2).at(2).value(), qulonglong(0)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 4); QCOMPARE(percent_spy.at(3).at(0).value(), job); QCOMPARE(percent_spy.at(3).at(1).value(), static_cast(0)); /* We process more than the total size!? * Signals both percentage and new processed size. * Percentage is 150% * * Might sounds weird, but verify that this case is handled gracefully. */ testJob->setProcessedSize(15); QCOMPARE(processed_spy.size(), 4); QCOMPARE(processed_spy.at(3).at(0).value(), job); QCOMPARE(processed_spy.at(3).at(2).value(), qulonglong(15)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 5); QCOMPARE(percent_spy.at(4).at(0).value(), job); QCOMPARE(percent_spy.at(4).at(1).value(), static_cast(150)); delete job; } void KJobTest::testExec_data() { QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::newRow("no error") << int(KJob::NoError) << QString(); QTest::newRow("error no text") << 2 << QString(); QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; } void KJobTest::testExec() { TestJob *job = new TestJob; QFETCH(int, errorCode); QFETCH(QString, errorText); job->setError(errorCode); job->setErrorText(errorText); int resultEmitted = 0; // Prove to Kai Uwe that one can connect a job to a lambdas, despite the "private" signal connect(job, &KJob::result, this, [&resultEmitted](KJob *) { ++resultEmitted; }); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); bool status = job->exec(); QCOMPARE(resultEmitted, 1); QCOMPARE(status, (errorCode == KJob::NoError)); QCOMPARE(job->error(), errorCode); QCOMPARE(job->errorText(), errorText); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testKill_data() { QTest::addColumn("killVerbosity"); QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::addColumn("resultEmitCount"); QTest::addColumn("finishedEmitCount"); QTest::newRow("killed with result") << int(KJob::EmitResult) << int(KJob::KilledJobError) << QString() << 1 << 1; QTest::newRow("killed quietly") << int(KJob::Quietly) << int(KJob::KilledJobError) << QString() << 0 << 1; } void KJobTest::testKill() { TestJob *job = new TestJob; connect(job, &KJob::result, this, &KJobTest::slotResult); connect(job, &KJob::finished, this, &KJobTest::slotFinished); m_lastError = KJob::NoError; m_lastErrorText.clear(); m_resultCount = 0; m_finishedCount = 0; QFETCH(int, killVerbosity); QFETCH(int, errorCode); QFETCH(QString, errorText); QFETCH(int, resultEmitCount); QFETCH(int, finishedEmitCount); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); job->kill(static_cast(killVerbosity)); loop.processEvents(QEventLoop::AllEvents, 2000); QCOMPARE(m_lastError, errorCode); QCOMPARE(m_lastErrorText, errorText); QCOMPARE(job->error(), errorCode); QCOMPARE(job->errorText(), errorText); QCOMPARE(m_resultCount, resultEmitCount); QCOMPARE(m_finishedCount, finishedEmitCount); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testDelegateUsage() { TestJob *job1 = new TestJob; TestJob *job2 = new TestJob; TestJobUiDelegate *delegate = new TestJobUiDelegate; QPointer guard(delegate); QVERIFY(job1->uiDelegate() == nullptr); job1->setUiDelegate(delegate); QVERIFY(job1->uiDelegate() == delegate); QVERIFY(job2->uiDelegate() == nullptr); job2->setUiDelegate(delegate); QVERIFY(job2->uiDelegate() == nullptr); delete job1; delete job2; QVERIFY(guard.isNull()); // deleted by job1 } void KJobTest::testNestedExec() { m_innerJob = nullptr; QTimer::singleShot(100, this, SLOT(slotStartInnerJob())); m_outerJob = new WaitJob(); m_outerJob->exec(); } void KJobTest::slotStartInnerJob() { QTimer::singleShot(100, this, SLOT(slotFinishOuterJob())); m_innerJob = new WaitJob(); m_innerJob->exec(); } void KJobTest::slotFinishOuterJob() { QTimer::singleShot(100, this, SLOT(slotFinishInnerJob())); m_outerJob->makeItFinish(); } void KJobTest::slotFinishInnerJob() { m_innerJob->makeItFinish(); } void KJobTest::slotResult(KJob *job) { if (job->error()) { m_lastError = job->error(); m_lastErrorText = job->errorText(); } else { m_lastError = KJob::NoError; m_lastErrorText.clear(); } m_resultCount++; loop.quit(); } void KJobTest::slotFinished(KJob *job) { if (job->error()) { m_lastError = job->error(); m_lastErrorText = job->errorText(); } else { m_lastError = KJob::NoError; m_lastErrorText.clear(); } m_finishedCount++; } TestJob::TestJob() : KJob() { } TestJob::~TestJob() { } void TestJob::start() { QTimer::singleShot(0, this, SLOT(doEmit())); } bool TestJob::doKill() { return true; } void TestJob::setError(int errorCode) { KJob::setError(errorCode); } void TestJob::setErrorText(const QString &errorText) { KJob::setErrorText(errorText); } void TestJob::setProcessedSize(qulonglong size) { KJob::setProcessedAmount(KJob::Bytes, size); } void TestJob::setTotalSize(qulonglong size) { KJob::setTotalAmount(KJob::Bytes, size); } void TestJob::setPercent(unsigned long percentage) { KJob::setPercent(percentage); } void TestJob::doEmit() { emitResult(); } void WaitJob::start() { } void WaitJob::makeItFinish() { emitResult(); } void TestJobUiDelegate::connectJob(KJob *job) { QVERIFY(job->uiDelegate() != nullptr); } #include "moc_kjobtest.cpp" diff --git a/autotests/kjobtest.h b/autotests/kjobtest.h index 4e90bce..b831798 100644 --- a/autotests/kjobtest.h +++ b/autotests/kjobtest.h @@ -1,108 +1,96 @@ -/* This file is part of the KDE project - Copyright (C) 2006 Kevin Ottens - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KJOBTEST_H #define KJOBTEST_H #include #include #include "kjob.h" #include "kjobuidelegate.h" class TestJob : public KJob { Q_OBJECT public: TestJob(); ~TestJob() override; void start() override; protected: bool doKill() override; public: void setError(int errorCode); void setErrorText(const QString &errorText); void setProcessedSize(qulonglong size); void setTotalSize(qulonglong size); void setPercent(unsigned long percentage); private Q_SLOTS: void doEmit(); }; class TestJobUiDelegate : public KJobUiDelegate { Q_OBJECT protected: virtual void connectJob(KJob *job); }; class WaitJob; class KJobTest : public QObject { Q_OBJECT public: KJobTest(); public Q_SLOTS: // These slots need to be public, otherwise qtestlib calls them as part of the test void slotStartInnerJob(); void slotFinishOuterJob(); void slotFinishInnerJob(); private Q_SLOTS: void testEmitResult_data(); void testEmitResult(); void testProgressTracking(); void testExec_data(); void testExec(); void testKill_data(); void testKill(); void testDelegateUsage(); void testNestedExec(); void slotResult(KJob *job); void slotFinished(KJob *job); private: QEventLoop loop; int m_lastError; QString m_lastErrorText; int m_resultCount; int m_finishedCount; WaitJob *m_outerJob; WaitJob *m_innerJob; }; class WaitJob : public KJob { Q_OBJECT public: void start() override; void makeItFinish(); }; #endif diff --git a/autotests/klistopenfilesjobtest_unix.cpp b/autotests/klistopenfilesjobtest_unix.cpp index 9890ef2..8dc3eb4 100644 --- a/autotests/klistopenfilesjobtest_unix.cpp +++ b/autotests/klistopenfilesjobtest_unix.cpp @@ -1,117 +1,105 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #include "klistopenfilesjobtest_unix.h" #include "klistopenfilesjob.h" #include #include #include #include #include #include QTEST_MAIN(KListOpenFilesJobTest) namespace { bool hasLsofInstalled() { return !QStandardPaths::findExecutable(QStringLiteral("lsof")).isEmpty(); } } void KListOpenFilesJobTest::testOpenFiles() { if (!hasLsofInstalled()) { QSKIP("lsof is not installed - skipping test"); } QDir path(QCoreApplication::applicationDirPath()); auto job = new KListOpenFilesJob(path.path()); job->exec(); QCOMPARE(job->error(), KJob::NoError); auto processInfoList = job->processInfoList(); QVERIFY(!processInfoList.empty()); auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [](const KProcessList::KProcessInfo& info) { return info.pid() == QCoreApplication::applicationPid(); }); QVERIFY(testProcessIterator != processInfoList.end()); const auto& processInfo = *testProcessIterator; QVERIFY(processInfo.isValid()); QCOMPARE(processInfo.pid(), QCoreApplication::applicationPid()); } void KListOpenFilesJobTest::testNoOpenFiles() { if (!hasLsofInstalled()) { QSKIP("lsof is not installed - skipping test"); } QTemporaryDir tempDir; auto job = new KListOpenFilesJob(tempDir.path()); job->exec(); QCOMPARE(job->error(), KJob::NoError); QVERIFY(job->processInfoList().empty()); } void KListOpenFilesJobTest::testNonExistingDir() { if (!hasLsofInstalled()) { QSKIP("lsof is not installed - skipping test"); } QString nonExistingDir(QStringLiteral("/does/not/exist")); auto job = new KListOpenFilesJob(nonExistingDir); job->exec(); QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::DoesNotExist)); QCOMPARE(job->errorText(), QStringLiteral("Path %1 doesn't exist").arg(nonExistingDir)); QVERIFY(job->processInfoList().empty()); } /** * @brief Helper class to temporarily set an environment variable and reset it on destruction */ class ScopedEnvVariable { public: ScopedEnvVariable(const QLatin1String& Name, const QByteArray& NewValue) : name(Name) , originalValue(qgetenv(name.latin1())) { qputenv(name.latin1(), NewValue); } ~ScopedEnvVariable() { qputenv(name.latin1(), originalValue); } private: const QLatin1String name; const QByteArray originalValue; }; void KListOpenFilesJobTest::testLsofNotFound() { // This test relies on clearing the PATH variable so that lsof is not found ScopedEnvVariable emptyPathEnvironment(QLatin1String("PATH"), QByteArray()); QDir path(QCoreApplication::applicationDirPath()); auto job = new KListOpenFilesJob(path.path()); job->exec(); QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::InternalError)); QVERIFY(job->processInfoList().empty()); } diff --git a/autotests/klistopenfilesjobtest_unix.h b/autotests/klistopenfilesjobtest_unix.h index af78955..156e438 100644 --- a/autotests/klistopenfilesjobtest_unix.h +++ b/autotests/klistopenfilesjobtest_unix.h @@ -1,36 +1,24 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KLISTOPENFILESJOBTEST_UNIX_H #define KLISTOPENFILESJOBTEST_UNIX_H #include class KListOpenFilesJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testOpenFiles(); void testNoOpenFiles(); void testNonExistingDir(); void testLsofNotFound(); }; #endif diff --git a/autotests/klistopenfilesjobtest_win.cpp b/autotests/klistopenfilesjobtest_win.cpp index f45a234..218b2f2 100644 --- a/autotests/klistopenfilesjobtest_win.cpp +++ b/autotests/klistopenfilesjobtest_win.cpp @@ -1,36 +1,24 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #include "klistopenfilesjobtest_win.h" #include "klistopenfilesjob.h" #include #include #include QTEST_MAIN(KListOpenFilesJobTest) void KListOpenFilesJobTest::testNotSupported() { QDir path(QCoreApplication::applicationDirPath()); auto job = new KListOpenFilesJob(path.path()); job->exec(); QCOMPARE(job->error(), static_cast(KListOpenFilesJob::Error::NotSupported)); QCOMPARE(job->errorText(), QStringLiteral("KListOpenFilesJob is not supported on Windows")); QVERIFY(job->processInfoList().empty()); } diff --git a/autotests/klistopenfilesjobtest_win.h b/autotests/klistopenfilesjobtest_win.h index 6bd7868..73bdfbb 100644 --- a/autotests/klistopenfilesjobtest_win.h +++ b/autotests/klistopenfilesjobtest_win.h @@ -1,33 +1,21 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KLISTOPENFILESJOBTEST_WIN_H #define KLISTOPENFILESJOBTEST_WIN_H #include class KListOpenFilesJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testNotSupported(); }; #endif diff --git a/autotests/kmacroexpandertest.cpp b/autotests/kmacroexpandertest.cpp index b587f3b..5ff790a 100644 --- a/autotests/kmacroexpandertest.cpp +++ b/autotests/kmacroexpandertest.cpp @@ -1,287 +1,276 @@ -/* This file is part of the KDE libraries - Copyright (c) 2003,2008 Oswald Buddenhagen - Copyright (c) 2005 Thomas Braxton - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2003, 2008 Oswald Buddenhagen + SPDX-FileCopyrightText: 2005 Thomas Braxton + + SPDX-License-Identifier: LGPL-2.0-only */ #include #include #include #include class KMacroExpanderTest : public QObject { Q_OBJECT private Q_SLOTS: void expandMacros(); void expandMacrosShellQuote(); void expandMacrosShellQuoteParens(); void expandMacrosSubClass(); }; class MyCExpander : public KCharMacroExpander { QString exp; public: MyCExpander() : KCharMacroExpander(), exp("expanded") { } protected: bool expandMacro(QChar ch, QStringList &ret) { if (ch == 'm') { ret = QStringList(exp); return true; } return false; } }; class MyWExpander : public KWordMacroExpander { QString exp; public: MyWExpander() : KWordMacroExpander(), exp("expanded") { } protected: bool expandMacro(const QString &str, QStringList &ret) { if (str == QLatin1String("macro")) { ret = QStringList(exp); return true; } return false; } }; void KMacroExpanderTest::expandMacros() { QHash map; QStringList list; QString s; list << QString("Restaurant \"Chew It\""); map.insert('n', list); list.clear(); list << QString("element1") << QString("'element2'"); map.insert('l', list); s = "%% text %l text %n"; QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("% text element1 'element2' text Restaurant \"Chew It\"")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("text \"element1 'element2' Restaurant \"Chew It\"\" text")); QHash map2; map2.insert('a', "%n"); map2.insert('f', "filename.txt"); map2.insert('u', "https://www.kde.org/index.html"); map2.insert('n', "Restaurant \"Chew It\""); s = "Title: %a - %f - %u - %n - %%"; QCOMPARE(KMacroExpander::expandMacros(s, map2), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); QHash smap; smap.insert("foo", "%n"); smap.insert("file", "filename.txt"); smap.insert("url", "https://www.kde.org/index.html"); smap.insert("name", "Restaurant \"Chew It\""); s = "Title: %foo - %file - %url - %name - %"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); s = "%foo - %file - %url - %name"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\"")); s = "Title: %{foo} - %{file} - %{url} - %{name} - %"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); s = "%{foo} - %{file} - %{url} - %{name}"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\"")); s = "Title: %foo-%file-%url-%name-%"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n-filename.txt-https://www.kde.org/index.html-Restaurant \"Chew It\"-%")); s = "Title: %{file} %{url"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: filename.txt %{url")); s = " * Copyright (C) 2008 %{AUTHOR}"; smap.clear(); QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String(" * Copyright (C) 2008 %{AUTHOR}")); } void KMacroExpanderTest::expandMacrosShellQuote() { QHash map; QStringList list; QString s; list << QString("Restaurant \"Chew It\""); map.insert('n', list); list.clear(); list << QString("element1") << QString("'element2'") << QString("\"element3\""); map.insert('l', list); #ifdef Q_OS_WIN s = "text %l %n text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 'element2' \\^\"element3\\^\" \"Restaurant \"\\^\"\"Chew It\"\\^\" text")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text \"element1 'element2' \"\\^\"\"element3\"\\^\"\" Restaurant \"\\^\"\"Chew It\"\\^\"\"\" text")); #else s = "text %l %n text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 ''\\''element2'\\''' '\"element3\"' 'Restaurant \"Chew It\"' text")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text \"element1 'element2' \\\"element3\\\" Restaurant \\\"Chew It\\\"\" text")); #endif QHash map2; map2.insert('a', "%n"); map2.insert('f', "filename.txt"); map2.insert('u', "https://www.kde.org/index.html"); map2.insert('n', "Restaurant \"Chew It\""); #ifdef Q_OS_WIN s = "Title: %a - %f - %u - %n - %% - %VARIABLE% foo"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("Title: %PERCENT_SIGN%n - filename.txt - https://www.kde.org/index.html - \"Restaurant \"\\^\"\"Chew It\"\\^\" - %PERCENT_SIGN% - %VARIABLE% foo")); s = "kedit --caption %n %f"; map2.insert('n', "Restaurant 'Chew It'"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); s = "kedit --caption \"%n\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); map2.insert('n', "Restaurant \"Chew It\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \"\\^\"\"Chew It\"\\^\"\"\" filename.txt")); map2.insert('n', "Restaurant %HOME%"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant %PERCENT_SIGN%HOME%PERCENT_SIGN%\" filename.txt")); s = "kedit c:\\%f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit c:\\filename.txt")); s = "kedit \"c:\\%f\""; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\filename.txt\"")); map2.insert('f', "\"filename.txt\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\\\\"\\^\"\"filename.txt\"\\^\"\"\"")); map2.insert('f', "path\\"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\path\\\\\"\"\"")); #else s = "Title: %a - %f - %u - %n - %%"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - 'Restaurant \"Chew It\"' - %")); s = "kedit --caption %n %f"; map2.insert('n', "Restaurant 'Chew It'"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption 'Restaurant '\\''Chew It'\\''' filename.txt")); s = "kedit --caption \"%n\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); map2.insert('n', "Restaurant \"Chew It\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\\"Chew It\\\"\" filename.txt")); map2.insert('n', "Restaurant $HOME"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\$HOME\" filename.txt")); map2.insert('n', "Restaurant `echo hello`"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\`echo hello\\`\" filename.txt")); s = "kedit --caption \"`echo %n`\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"$( echo 'Restaurant `echo hello`')\" filename.txt")); #endif } class DummyMacroExpander : public KMacroExpanderBase { public: DummyMacroExpander() : KMacroExpanderBase(QChar(0x4567)) { } protected: int expandPlainMacro(const QString &, int, QStringList &) { return 0; } int expandEscapedMacro(const QString &, int, QStringList &) { return 0; } }; void KMacroExpanderTest::expandMacrosShellQuoteParens() { QHash map; QStringList list; QString s; s = "( echo \"just testing (parens)\" ) ) after"; int pos = 0; DummyMacroExpander kmx; QVERIFY(kmx.expandMacrosShellQuote(s, pos)); QCOMPARE(s.mid(pos), QLatin1String(") after")); QVERIFY(!kmx.expandMacrosShellQuote(s)); } void KMacroExpanderTest::expandMacrosSubClass() { QString s; MyCExpander mx1; s = "subst %m but not %n equ %%"; mx1.expandMacros(s); QCOMPARE(s, QLatin1String("subst expanded but not %n equ %")); MyWExpander mx2; s = "subst %macro but not %not equ %%"; mx2.expandMacros(s); QCOMPARE(s, QLatin1String("subst expanded but not %not equ %")); } QTEST_MAIN(KMacroExpanderTest) #include "kmacroexpandertest.moc" diff --git a/autotests/kosreleasetest.cpp b/autotests/kosreleasetest.cpp index 44789f1..8f99b38 100644 --- a/autotests/kosreleasetest.cpp +++ b/autotests/kosreleasetest.cpp @@ -1,57 +1,43 @@ /* - Copyright (C) 2014-2019 Harald Sitter + SPDX-FileCopyrightText: 2014-2019 Harald Sitter - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include "kosrelease.h" class KOSReleaseTest : public QObject { Q_OBJECT private Q_SLOTS: void testParse() { KOSRelease r(QFINDTESTDATA("data/os-release")); QCOMPARE(r.name(), QStringLiteral("Name")); QCOMPARE(r.version(), QStringLiteral("100.5")); QCOMPARE(r.id(), QStringLiteral("theid")); QCOMPARE(r.idLike(), QStringList({ QStringLiteral("otherid"), QStringLiteral("otherotherid") })); QCOMPARE(r.versionCodename(), QStringLiteral("versioncodename")); QCOMPARE(r.versionId(), QStringLiteral("500.1")); QCOMPARE(r.prettyName(), QStringLiteral("Pretty Name #1")); QCOMPARE(r.ansiColor(), QStringLiteral("1;34")); QCOMPARE(r.cpeName(), QStringLiteral("cpe:/o:foo:bar:100")); QCOMPARE(r.homeUrl(), QStringLiteral("https://url.home")); QCOMPARE(r.documentationUrl(), QStringLiteral("https://url.docs")); QCOMPARE(r.supportUrl(), QStringLiteral("https://url.support")); QCOMPARE(r.bugReportUrl(), QStringLiteral("https://url.bugs")); QCOMPARE(r.privacyPolicyUrl(), QStringLiteral("https://url.privacy")); QCOMPARE(r.buildId(), QStringLiteral("105.5")); QCOMPARE(r.variant(), QStringLiteral("Test = Edition")); QCOMPARE(r.variantId(), QStringLiteral("test")); QCOMPARE(r.logo(), QStringLiteral("start-here-test")); QCOMPARE(r.extraKeys(), QStringList({ QStringLiteral("DEBIAN_BTS") })); QCOMPARE(r.extraValue(QStringLiteral("DEBIAN_BTS")), QStringLiteral("debbugs://bugs.debian.org/")); } }; QTEST_MAIN(KOSReleaseTest) #include "kosreleasetest.moc" diff --git a/autotests/kpluginfactorytest.cpp b/autotests/kpluginfactorytest.cpp index c5f0288..ffe1cce 100644 --- a/autotests/kpluginfactorytest.cpp +++ b/autotests/kpluginfactorytest.cpp @@ -1,67 +1,52 @@ /* - * Copyright 2014 Alex Merry - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) version 3, or any - * later version accepted by the membership of KDE e.V. (or its - * successor approved by the membership of KDE e.V.), which shall - * act as a proxy defined in Section 6 of version 3 of the license. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - */ + SPDX-FileCopyrightText: 2014 Alex Merry + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ #include #include #include class KPluginFactoryTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreate() { KPluginLoader multiplugin(QStringLiteral("multiplugin")); KPluginFactory *factory = multiplugin.factory(); QVERIFY(factory); QVariantList args; args << QStringLiteral("Some") << QStringLiteral("args") << 5; QObject *obj = factory->create(this, args); QVERIFY(obj); QCOMPARE(obj->objectName(), QString::fromLatin1("MultiPlugin1")); QObject *obj2 = factory->create(this, args); QVERIFY(obj2); QCOMPARE(obj2->objectName(), QString::fromLatin1("MultiPlugin1")); QVERIFY(obj != obj2); delete obj; delete obj2; obj = factory->create(QStringLiteral("secondary"), this, args); QVERIFY(obj); QCOMPARE(obj->objectName(), QString::fromLatin1("MultiPlugin2")); obj2 = factory->create(QStringLiteral("secondary"), this, args); QVERIFY(obj2); QCOMPARE(obj2->objectName(), QString::fromLatin1("MultiPlugin2")); QVERIFY(obj != obj2); delete obj; delete obj2; } }; QTEST_MAIN(KPluginFactoryTest) #include "kpluginfactorytest.moc" diff --git a/autotests/kpluginloadertest.cpp b/autotests/kpluginloadertest.cpp index 837ef65..0184c8d 100644 --- a/autotests/kpluginloadertest.cpp +++ b/autotests/kpluginloadertest.cpp @@ -1,471 +1,456 @@ /* - * Copyright 2014 Alex Merry - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) version 3, or any - * later version accepted by the membership of KDE e.V. (or its - * successor approved by the membership of KDE e.V.), which shall - * act as a proxy defined in Section 6 of version 3 of the license. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - */ + SPDX-FileCopyrightText: 2014 Alex Merry + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ #include #include #include #include class LibraryPathRestorer { public: explicit LibraryPathRestorer(const QStringList &paths) : mPaths(paths) {} ~LibraryPathRestorer() { QCoreApplication::setLibraryPaths(mPaths); } private: QStringList mPaths; }; class KPluginLoaderTest : public QObject { Q_OBJECT private Q_SLOTS: void testFindPlugin_missing() { const QString location = KPluginLoader::findPlugin(QStringLiteral("idonotexist")); QVERIFY2(location.isEmpty(), qPrintable(location)); } void testFindPlugin() { const QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!location.isEmpty(), qPrintable(location)); } void testPluginVersion() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QCOMPARE(vplugin.pluginVersion(), quint32(5)); KPluginLoader vplugin2(QStringLiteral("versionedplugin")); QCOMPARE(vplugin2.pluginVersion(), quint32(5)); KPluginLoader uplugin(QStringLiteral("unversionedplugin")); QCOMPARE(uplugin.pluginVersion(), quint32(-1)); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QCOMPARE(jplugin.pluginVersion(), quint32(-1)); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QCOMPARE(eplugin.pluginVersion(), quint32(-1)); KPluginLoader noplugin(QStringLiteral("idonotexist")); QCOMPARE(noplugin.pluginVersion(), quint32(-1)); } void testPluginName() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QCOMPARE(vplugin.pluginName(), QString::fromLatin1("versionedplugin")); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QCOMPARE(jplugin.pluginName(), QString::fromLatin1("jsonplugin")); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY2(eplugin.pluginName().isEmpty(), qPrintable(eplugin.pluginName())); KPluginLoader noplugin(QStringLiteral("idonotexist")); QCOMPARE(noplugin.pluginName(), QString::fromLatin1("idonotexist")); } void testFactory() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QVERIFY(vplugin.factory()); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QVERIFY(jplugin.factory()); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY(!eplugin.factory()); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY(!noplugin.factory()); } void testErrorString() { KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QCOMPARE(eplugin.errorString(), QString::fromLatin1("there was an error")); } void testFileName() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QCOMPARE(QFileInfo(vplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(VERSIONEDPLUGIN_FILE)).canonicalFilePath()); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QCOMPARE(QFileInfo(jplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(JSONPLUGIN_FILE)).canonicalFilePath()); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY2(eplugin.fileName().isEmpty(), qPrintable(eplugin.fileName())); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY2(noplugin.fileName().isEmpty(), qPrintable(noplugin.fileName())); } void testInstance() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QVERIFY(vplugin.instance()); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QVERIFY(jplugin.instance()); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY(!eplugin.instance()); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY(!noplugin.instance()); } void testIsLoaded() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QVERIFY(!vplugin.isLoaded()); QVERIFY(vplugin.load()); QVERIFY(vplugin.isLoaded()); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QVERIFY(!jplugin.isLoaded()); QVERIFY(jplugin.load()); QVERIFY(jplugin.isLoaded()); KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin")); QVERIFY(!aplugin.isLoaded()); QVERIFY(aplugin.load()); QVERIFY(aplugin.isLoaded()); if (aplugin.unload()) { QVERIFY(!aplugin.isLoaded()); } else { qDebug() << "Could not unload alwaysunloadplugin:" << aplugin.errorString(); } KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY(!eplugin.isLoaded()); QVERIFY(!eplugin.load()); QVERIFY(!eplugin.isLoaded()); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY(!noplugin.isLoaded()); QVERIFY(!noplugin.load()); QVERIFY(!noplugin.isLoaded()); } void testLoad() { KPluginLoader vplugin(QStringLiteral("versionedplugin")); QVERIFY(vplugin.load()); KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QVERIFY(jplugin.load()); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY(!eplugin.load()); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY(!noplugin.load()); } void testLoadHints() { KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin")); aplugin.setLoadHints(QLibrary::ResolveAllSymbolsHint); QCOMPARE(aplugin.loadHints(), QLibrary::ResolveAllSymbolsHint); } void testMetaData() { KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin")); QJsonObject ametadata = aplugin.metaData(); QVERIFY(!ametadata.isEmpty()); QVERIFY(ametadata.keys().contains(QLatin1String("IID"))); QJsonValue ametadata_metadata = ametadata.value(QStringLiteral("MetaData")); QVERIFY(ametadata_metadata.toObject().isEmpty()); QVERIFY(!aplugin.isLoaded()); // didn't load anything KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin"))); QJsonObject jmetadata = jplugin.metaData(); QVERIFY(!jmetadata.isEmpty()); QJsonValue jmetadata_metadata = jmetadata.value(QStringLiteral("MetaData")); QVERIFY(jmetadata_metadata.isObject()); QJsonObject jmetadata_obj = jmetadata_metadata.toObject(); QVERIFY(!jmetadata_obj.isEmpty()); QJsonValue comment = jmetadata_obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Description")); QVERIFY(comment.isString()); QCOMPARE(comment.toString(), QString::fromLatin1("This is a plugin")); KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error"))); QVERIFY(eplugin.metaData().isEmpty()); KPluginLoader noplugin(QStringLiteral("idonotexist")); QVERIFY(noplugin.metaData().isEmpty()); } void testUnload() { KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin")); QVERIFY(aplugin.load()); // may need QEXPECT_FAIL on some platforms... QVERIFY(aplugin.unload()); } void testInstantiatePlugins() { const QString plugin1Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!plugin1Path.isEmpty(), qPrintable(plugin1Path)); const QString plugin2Path = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin")); QVERIFY2(!plugin2Path.isEmpty(), qPrintable(plugin2Path)); const QString plugin3Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2")); QVERIFY2(!plugin3Path.isEmpty(), qPrintable(plugin3Path)); QTemporaryDir temp; QVERIFY(temp.isValid()); QDir dir(temp.path()); QVERIFY2(QFile::copy(plugin1Path, dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())), qPrintable(dir.absoluteFilePath(QFileInfo(plugin1Path).fileName()))); QVERIFY2(QFile::copy(plugin2Path, dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())), qPrintable(dir.absoluteFilePath(QFileInfo(plugin2Path).fileName()))); QVERIFY2(QFile::copy(plugin3Path, dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())), qPrintable(dir.absoluteFilePath(QFileInfo(plugin3Path).fileName()))); // only jsonplugin, since unversionedplugin has no json metadata QList plugins = KPluginLoader::instantiatePlugins(temp.path()); QCOMPARE(plugins.size(), 2); QStringList classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className()) << QString::fromLatin1(plugins[1]->metaObject()->className()); classNames.sort(); QCOMPARE(classNames[0], QStringLiteral("jsonplugin2")); QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa")); qDeleteAll(plugins); //try filter plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData & md) { return md.pluginId() == QLatin1String("jsonplugin"); }); QCOMPARE(plugins.size(), 1); QCOMPARE(plugins[0]->metaObject()->className(), "jsonpluginfa"); qDeleteAll(plugins); plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData & md) { return md.pluginId() == QLatin1String("unversionedplugin"); }); QCOMPARE(plugins.size(), 0); plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData & md) { return md.pluginId() == QLatin1String("foobar"); // ID does not match file name, is set in JSON }); QCOMPARE(plugins.size(), 1); QCOMPARE(plugins[0]->metaObject()->className(), "jsonplugin2"); qDeleteAll(plugins); // check that parent gets set plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData&) { return true; }, this); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0]->parent(), this); QCOMPARE(plugins[1]->parent(), this); qDeleteAll(plugins); const QString subDirName = dir.dirName(); QVERIFY(dir.cdUp()); // should now point to /tmp on Linux LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); // instantiate using relative path // make sure library path is set up correctly QCoreApplication::setLibraryPaths(QStringList() << dir.absolutePath()); QVERIFY(!QDir::isAbsolutePath(subDirName)); plugins = KPluginLoader::instantiatePlugins(subDirName); QCOMPARE(plugins.size(), 2); classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className()) << QString::fromLatin1(plugins[1]->metaObject()->className()); classNames.sort(); QCOMPARE(classNames[0], QStringLiteral("jsonplugin2")); QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa")); qDeleteAll(plugins); } void testFindPlugins() { QTemporaryDir temp; QVERIFY(temp.isValid()); QDir dir(temp.path()); QVERIFY(dir.mkdir(QStringLiteral("kpluginmetadatatest"))); QVERIFY(dir.cd(QStringLiteral("kpluginmetadatatest"))); for (const QString &name : { QStringLiteral("jsonplugin"), QStringLiteral("unversionedplugin"), QStringLiteral("jsonplugin2") }) { const QString pluginPath = KPluginLoader::findPlugin(name); QVERIFY2(!pluginPath.isEmpty(), qPrintable(pluginPath)); QVERIFY2(QFile::copy(pluginPath, dir.absoluteFilePath(QFileInfo(pluginPath).fileName())), qPrintable(dir.absoluteFilePath(QFileInfo(pluginPath).fileName()))); } LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); // we only want plugins from our temporary dir QCoreApplication::setLibraryPaths(QStringList() << temp.path()); auto sortPlugins = [](const KPluginMetaData &a, const KPluginMetaData &b) { return a.pluginId() < b.pluginId(); }; // it should find jsonplugin and jsonplugin2 since unversionedplugin does not have any meta data auto plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest")); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].pluginId(), QStringLiteral("foobar")); // ID is not the filename, it is set in the JSON metadata QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); QCOMPARE(plugins[1].pluginId(), QStringLiteral("jsonplugin")); QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); // filter accepts none plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) { return false; }); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 0); // filter accepts all plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) { return true; }); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); // mimetype filter. Only one match, jsonplugin2 is specific to text/html. auto supportTextPlain = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QLatin1String("text/plain")); }; plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextPlain); QCOMPARE(plugins.size(), 1); QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin")); // mimetype filter. Two matches, both support text/html, via inheritance. auto supportTextHtml = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QLatin1String("text/html")); }; plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextHtml); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); // mimetype filter with invalid mimetype auto supportDoesNotExist = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QLatin1String("does/not/exist")); }; plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest"), supportDoesNotExist); QCOMPARE(plugins.size(), 0); // invalid std::function as filter plugins = KPluginLoader::findPlugins(QStringLiteral("kpluginmetadatatest")); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); // by plugin id plugins = KPluginLoader::findPluginsById(dir.absolutePath(), QStringLiteral("foobar")); QCOMPARE(plugins.size(), 1); QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); // by plugin invalid id plugins = KPluginLoader::findPluginsById(dir.absolutePath(), QStringLiteral("invalidid")); QCOMPARE(plugins.size(), 0); // absolute path, no filter plugins = KPluginLoader::findPlugins(dir.absolutePath()); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); } void testForEachPlugin() { const QString jsonPluginSrc = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!jsonPluginSrc.isEmpty(), qPrintable(jsonPluginSrc)); const QString unversionedPluginSrc = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin")); QVERIFY2(!unversionedPluginSrc.isEmpty(), qPrintable(unversionedPluginSrc)); const QString jsonPlugin2Src = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2")); QVERIFY2(!jsonPlugin2Src.isEmpty(), qPrintable(jsonPlugin2Src)); QTemporaryDir temp; QVERIFY(temp.isValid()); QDir dir(temp.path()); QVERIFY(dir.mkdir(QStringLiteral("for-each-plugin"))); QVERIFY(dir.cd(QStringLiteral("for-each-plugin"))); const QString jsonPluginDest = dir.absoluteFilePath(QFileInfo(jsonPluginSrc).fileName()); QVERIFY2(QFile::copy(jsonPluginSrc, jsonPluginDest), qPrintable(jsonPluginDest)); const QString unversionedPluginDest = dir.absoluteFilePath(QFileInfo(unversionedPluginSrc).fileName()); QVERIFY2(QFile::copy(unversionedPluginSrc, unversionedPluginDest), qPrintable(unversionedPluginDest)); // copy jsonplugin2 to a "for-each-plugin" subdirectory in a different directory QTemporaryDir temp2; QVERIFY(temp2.isValid()); QDir dir2(temp2.path()); QVERIFY(dir2.mkdir(QStringLiteral("for-each-plugin"))); QVERIFY(dir2.cd(QStringLiteral("for-each-plugin"))); const QString jsonPlugin2Dest = dir2.absoluteFilePath(QFileInfo(jsonPlugin2Src).fileName()); QVERIFY2(QFile::copy(jsonPlugin2Src, jsonPlugin2Dest), qPrintable(jsonPlugin2Dest)); QStringList foundPlugins; QStringList expectedPlugins; const auto addToFoundPlugins = [&](const QString &path) { QVERIFY(!path.isEmpty()); foundPlugins.append(path); }; // test finding with absolute path expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest; expectedPlugins.sort(); KPluginLoader::forEachPlugin(dir.path(), addToFoundPlugins); foundPlugins.sort(); QCOMPARE(foundPlugins, expectedPlugins); expectedPlugins = QStringList() << jsonPlugin2Dest; expectedPlugins.sort(); foundPlugins.clear(); KPluginLoader::forEachPlugin(dir2.path(), addToFoundPlugins); foundPlugins.sort(); QCOMPARE(foundPlugins, expectedPlugins); // now test relative paths LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); QCoreApplication::setLibraryPaths(QStringList() << temp.path()); expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest; expectedPlugins.sort(); foundPlugins.clear(); KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins); foundPlugins.sort(); QCOMPARE(foundPlugins, expectedPlugins); QCoreApplication::setLibraryPaths(QStringList() << temp2.path()); expectedPlugins = QStringList() << jsonPlugin2Dest; expectedPlugins.sort(); foundPlugins.clear(); KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins); foundPlugins.sort(); QCOMPARE(foundPlugins, expectedPlugins); QCoreApplication::setLibraryPaths(QStringList() << temp.path() << temp2.path()); expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest << jsonPlugin2Dest; expectedPlugins.sort(); foundPlugins.clear(); KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins); foundPlugins.sort(); QCOMPARE(foundPlugins, expectedPlugins); } }; QTEST_MAIN(KPluginLoaderTest) #include "kpluginloadertest.moc" diff --git a/autotests/kpluginmetadatatest.cpp b/autotests/kpluginmetadatatest.cpp index a70401b..db3849e 100644 --- a/autotests/kpluginmetadatatest.cpp +++ b/autotests/kpluginmetadatatest.cpp @@ -1,357 +1,342 @@ /* - * Copyright 2014 Alex Richardson - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) version 3, or any - * later version accepted by the membership of KDE e.V. (or its - * successor approved by the membership of KDE e.V.), which shall - * act as a proxy defined in Section 6 of version 3 of the license. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - */ + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ #include #include #include #include #include #include #include #include #include #include namespace QTest { template<> inline char *toString(const QJsonValue &val) { // simply reuse the QDebug representation QString result; QDebug(&result) << val; return QTest::toString(result); } } class KPluginMetaDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testFromPluginLoader() { QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!location.isEmpty(),"Could not find jsonplugin"); // now that this file is translated we need to read it instead of hardcoding the contents here QString jsonLocation = QFINDTESTDATA("jsonplugin.json"); QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json"); QFile jsonFile(jsonLocation); QVERIFY(jsonFile.open(QFile::ReadOnly)); QJsonParseError e; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e); QCOMPARE(e.error, QJsonParseError::NoError); location = QFileInfo(location).absoluteFilePath(); KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromKPluginLoader(KPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromFullPath(location); KPluginMetaData fromRelativePath(QStringLiteral("jsonplugin")); KPluginMetaData fromRawData(jsonDoc.object(), location); auto description = QStringLiteral("This is a plugin"); QVERIFY(fromQPluginLoader.isValid()); QCOMPARE(fromQPluginLoader.description(), description); QVERIFY(fromKPluginLoader.isValid()); QCOMPARE(fromKPluginLoader.description(), description); QVERIFY(fromFullPath.isValid()); QCOMPARE(fromFullPath.description(), description); QVERIFY(fromRelativePath.isValid()); QCOMPARE(fromRelativePath.description(), description); QVERIFY(fromRawData.isValid()); QCOMPARE(fromRawData.description(), description); // check operator== QCOMPARE(fromRawData, fromRawData); QCOMPARE(fromQPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromKPluginLoader); QCOMPARE(fromFullPath, fromFullPath); QCOMPARE(fromQPluginLoader, fromKPluginLoader); QCOMPARE(fromQPluginLoader, fromFullPath); QCOMPARE(fromQPluginLoader, fromRawData); QCOMPARE(fromKPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromFullPath); QCOMPARE(fromKPluginLoader, fromRawData); QCOMPARE(fromFullPath, fromQPluginLoader); QCOMPARE(fromFullPath, fromKPluginLoader); QCOMPARE(fromFullPath, fromRawData); QCOMPARE(fromRawData, fromQPluginLoader); QCOMPARE(fromRawData, fromKPluginLoader); QCOMPARE(fromRawData, fromFullPath); } void testAllKeys() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" " \"KPlugin\": {\n" " \"Name\": \"Date and Time\",\n" " \"Description\": \"Date and time by timezone\",\n" " \"Icon\": \"preferences-system-time\",\n" " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n" " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"Category\": \"Date and Time\",\n" " \"Dependencies\": [ \"foo\", \"bar\"],\n" " \"EnabledByDefault\": \"true\",\n" " \"ExtraInformation\": \"Something else\",\n" " \"License\": \"LGPL\",\n" " \"Copyright\": \"(c) Alex Richardson 2015\",\n" " \"Id\": \"time\",\n" " \"Version\": \"1.0\",\n" " \"Website\": \"https://plasma.kde.org/\",\n" " \"MimeTypes\": [ \"image/png\" ],\n" " \"ServiceTypes\": [\"Plasma/DataEngine\"]\n" " }\n}\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); KPluginMetaData m(jo, QString()); QVERIFY(m.isValid()); QCOMPARE(m.pluginId(), QStringLiteral("time")); QCOMPARE(m.name(), QStringLiteral("Date and Time")); QCOMPARE(m.description(), QStringLiteral("Date and time by timezone")); QCOMPARE(m.extraInformation(), QStringLiteral("Something else")); QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(m.category(), QStringLiteral("Date and Time")); QCOMPARE(m.dependencies(), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(m.authors().size(), 1); QCOMPARE(m.authors()[0].name(), QStringLiteral("Aaron Seigo")); QCOMPARE(m.authors()[0].emailAddress(), QStringLiteral("aseigo@kde.org")); QCOMPARE(m.translators().size(), 1); QCOMPARE(m.translators()[0].name(), QStringLiteral("No One")); QCOMPARE(m.translators()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QCOMPARE(m.otherContributors().size(), 1); QCOMPARE(m.otherContributors()[0].name(), QStringLiteral("No One")); QCOMPARE(m.otherContributors()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QVERIFY(m.isEnabledByDefault()); QCOMPARE(m.license(), QStringLiteral("LGPL")); QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015")); QCOMPARE(m.version(), QStringLiteral("1.0")); QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/")); QCOMPARE(m.serviceTypes(), QStringList() << QStringLiteral("Plasma/DataEngine")); QCOMPARE(m.mimeTypes(), QStringList() << QStringLiteral("image/png")); } void testTranslations() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{ \"KPlugin\": {\n" "\"Name\": \"Name\",\n" "\"Name[de]\": \"Name (de)\",\n" "\"Name[de_DE]\": \"Name (de_DE)\",\n" "\"Description\": \"Description\",\n" "\"Description[de]\": \"Beschreibung (de)\",\n" "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n" "}\n}", &e).object(); KPluginMetaData m(jo, QString()); QLocale::setDefault(QLocale::c()); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); QLocale::setDefault(QLocale(QStringLiteral("de_DE"))); QCOMPARE(m.name(), QStringLiteral("Name (de_DE)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)")); QLocale::setDefault(QLocale(QStringLiteral("de_CH"))); QCOMPARE(m.name(), QStringLiteral("Name (de)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)")); QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); } void testReadStringList() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" "\"String\": \"foo\",\n" "\"OneArrayEntry\": [ \"foo\" ],\n" "\"Bool\": true,\n" // make sure booleans are accepted "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted "\"Number\": 12345,\n" // number should also work "\"QuotedNumber\": \"12345\",\n" // number should also work "\"EmptyArray\": [],\n" "\"NumberArray\": [1, 2, 3],\n" "\"BoolArray\": [true, false, true],\n" "\"StringArray\": [\"foo\", \"bar\"],\n" "\"Null\": null,\n" // should return empty list "\"QuotedNull\": \"null\",\n" // this is okay, it is a string "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay? "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list "}", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"String\" to be a string list. Treating it as a list with a single entry: \"foo\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("String")), QStringList(QStringLiteral("foo")));; QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("OneArrayEntry")), QStringList(QStringLiteral("foo"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Bool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Bool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedBool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedBool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Number\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Number")), QStringList(QStringLiteral("12345"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNumber\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNumber")), QStringList(QStringLiteral("12345"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("EmptyArray")), QStringList()); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("NumberArray")), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("BoolArray")), QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("StringArray")), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Null")), QStringList()); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNull\" to be a string list. Treating it as a list with a single entry: \"null\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNull")), QStringList(QStringLiteral("null"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("ArrayWithNull")), QStringList() << QStringLiteral("foo") << QStringLiteral("") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Object")), QStringList()); } void testFromDesktopFile() { const QString dfile = QFINDTESTDATA("data/fakeplugin.desktop"); KPluginMetaData md(dfile); QVERIFY(md.isValid()); QCOMPARE(md.pluginId(), QStringLiteral("fakeplugin")); QCOMPARE(md.fileName(), QStringLiteral("fakeplugin")); QCOMPARE(md.metaDataFileName(), dfile); QCOMPARE(md.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(md.license(), QStringLiteral("LGPL")); QCOMPARE(md.website(), QStringLiteral("https://kde.org/")); QCOMPARE(md.category(), QStringLiteral("Examples")); QCOMPARE(md.version(), QStringLiteral("1.0")); QCOMPARE(md.dependencies(), QStringList()); QCOMPARE(md.isHidden(), false); QCOMPARE(md.serviceTypes(), QStringList(QStringLiteral("KService/NSA"))); QCOMPARE(md.mimeTypes(), QStringList() << QStringLiteral("image/png") << QStringLiteral("application/pdf")); auto kp = md.rawData()[QStringLiteral("KPlugin")].toObject(); QStringList formFactors = KPluginMetaData::readStringList(kp, QStringLiteral("FormFactors")); QCOMPARE(formFactors, QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); QCOMPARE(md.formFactors(), QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); const QString dfilehidden = QFINDTESTDATA("data/hiddenplugin.desktop"); KPluginMetaData mdhidden(dfilehidden); QVERIFY(mdhidden.isValid()); QCOMPARE(mdhidden.isHidden(), true); } void twoStepsParseTest() { QStandardPaths::setTestModeEnabled(true); const QString dfile = QFINDTESTDATA("data/twostepsparsetest.desktop"); const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile, QStringList() << typesPath); QVERIFY(md.isValid()); QStringList list = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Test-List")); QCOMPARE(list, QStringList({QStringLiteral("first"), QStringLiteral("second")})); } void testServiceTypes_data() { const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); const QString invalidServiceTypePath = QFINDTESTDATA("data/servicetypes/invalid-servicetype.desktop"); const QString exampleServiceTypePath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); QVERIFY(!kdevServiceTypePath.isEmpty()); QVERIFY(!invalidServiceTypePath.isEmpty()); QVERIFY(!exampleServiceTypePath.isEmpty()); } void testServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/example-input.desktop"); QVERIFY(!inputPath.isEmpty()); QTest::ignoreMessage(QtWarningMsg, qPrintable(QStringLiteral("Unable to find service type for service \"bar/foo\" listed in \"") + inputPath + QLatin1Char('"'))); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Example")); QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("example/servicetype") << QStringLiteral("bar/foo")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QVERIFY(md.rawData().value(QStringLiteral("KPlugin")).isObject()); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Integer")), QJsonValue(42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Bool")), QJsonValue(true)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Double")), QJsonValue(42.42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-String")), QJsonValue(QStringLiteral("foobar"))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-List")), QJsonValue(QJsonArray::fromStringList(QStringList() << QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << QStringLiteral("def")))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Size")), QJsonValue(QStringLiteral("10,20"))); // QSize no longer supported (and also no longer used) QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Unknown")), QJsonValue(QStringLiteral("true"))); // unknown property -> string } void testBadGroupsInServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/bad-groups-servicetype.desktop"); QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/bad-groups-input.desktop"); QVERIFY(!inputPath.isEmpty()); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::MissingTerminator\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[\""); QTest::ignoreMessage(QtWarningMsg, "Read empty .desktop file group name! Invalid file?"); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"\" in service type \".*/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"DoesNotStartWithPropertyDef::SomeOtherProperty\" in service type \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, "Could not find Type= key in group \"PropertyDef::MissingType\""); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Property type \"integer\" is not a known QVariant type. Found while parsing property definition for \"InvalidType\" in \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=11\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=13\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=14\""))); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Bad Groups")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkay")), QJsonValue(10)); // integer // 11 is empty group QCOMPARE(md.rawData().value(QStringLiteral("MissingTerminator")), QJsonValue(12)); // accept missing group terminator (for now) -> integer // 13 is empty group name // 14 is empty group name QCOMPARE(md.rawData().value(QStringLiteral("SomeOtherProperty")), QJsonValue(QStringLiteral("15"))); // does not start with PropertyDef:: -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("TrailingSpacesAreOkay")), QJsonValue(16)); // accept trailing spaces in group name -> integer QCOMPARE(md.rawData().value(QStringLiteral("MissingType")), QJsonValue(QStringLiteral("17"))); // Type= missing -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("InvalidType")), QJsonValue(QStringLiteral("18"))); // Type= is invalid -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid definition after invalid ones should still work -> integer } void testJSONMetadata() { const QString inputPath = QFINDTESTDATA("data/testmetadata.json"); KPluginMetaData md(inputPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Test")); QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml")); QJsonArray expected; expected.append(QStringLiteral("Export")); QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected); } }; QTEST_MAIN(KPluginMetaDataTest) #include "kpluginmetadatatest.moc" diff --git a/autotests/kprocesslisttest.cpp b/autotests/kprocesslisttest.cpp index c95b639..9bc8054 100644 --- a/autotests/kprocesslisttest.cpp +++ b/autotests/kprocesslisttest.cpp @@ -1,95 +1,83 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #include "kprocesslisttest.h" #include "kprocesslist.h" #include "kuser.h" #include #include #include namespace { QString getTestExeName() { static QString testExeName = QCoreApplication::instance()->applicationFilePath().section(QLatin1Char('/'), -1); return testExeName; } } QTEST_MAIN(KProcessListTest) void KProcessListTest::testKProcessInfoConstructionAssignment() { KProcessList::KProcessInfo processInfoDefaultConstructed; QVERIFY(processInfoDefaultConstructed.isValid() == false); const qint64 pid(42); const QString name(QStringLiteral("/bin/some_exe")); const QString user(QStringLiteral("some_user")); KProcessList::KProcessInfo processInfo(pid, name, user); QVERIFY(processInfo.isValid() == true); QCOMPARE(processInfo.pid(), pid); QCOMPARE(processInfo.name(), name); QCOMPARE(processInfo.user(), user); KProcessList::KProcessInfo processInfoCopy(processInfo); QVERIFY(processInfoCopy.isValid() == true); QCOMPARE(processInfoCopy.pid(), pid); QCOMPARE(processInfoCopy.name(), name); QCOMPARE(processInfoCopy.user(), user); KProcessList::KProcessInfo processInfoAssignment; processInfoAssignment = processInfo; QVERIFY(processInfoAssignment.isValid() == true); QCOMPARE(processInfoAssignment.pid(), pid); QCOMPARE(processInfoAssignment.name(), name); QCOMPARE(processInfoAssignment.user(), user); } void KProcessListTest::testProcessInfoList() { KProcessList::KProcessInfoList processInfoList = KProcessList::processInfoList(); QVERIFY(processInfoList.empty() == false); auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [](const KProcessList::KProcessInfo& info) { return info.command().endsWith(QLatin1String("/") + getTestExeName()); }); QVERIFY(testProcessIterator != processInfoList.end()); const auto& processInfo = *testProcessIterator; QVERIFY(processInfo.isValid() == true); QVERIFY(processInfo.command().endsWith(QLatin1String("/") + getTestExeName())); QCOMPARE(processInfo.name(), getTestExeName()); QCOMPARE(processInfo.pid(), QCoreApplication::applicationPid()); QCOMPARE(processInfo.user(), KUser().loginName()); } void KProcessListTest::testProcessInfo() { const qint64 testExePid = QCoreApplication::applicationPid(); KProcessList::KProcessInfo processInfo = KProcessList::processInfo(testExePid); QVERIFY(processInfo.isValid() == true); QVERIFY(processInfo.command().endsWith(QLatin1String("/") + getTestExeName())); QCOMPARE(processInfo.pid(), testExePid); QCOMPARE(processInfo.user(), KUser().loginName()); } void KProcessListTest::testProcessInfoNotFound() { KProcessList::KProcessInfo processInfo = KProcessList::processInfo(-1); QVERIFY(processInfo.isValid() == false); } diff --git a/autotests/kprocesslisttest.h b/autotests/kprocesslisttest.h index 326c0b0..55dae5b 100644 --- a/autotests/kprocesslisttest.h +++ b/autotests/kprocesslisttest.h @@ -1,36 +1,24 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KPROCESSLISTTEST_H #define KPROCESSLISTTEST_H #include class KProcessListTest : public QObject { Q_OBJECT private Q_SLOTS: void testKProcessInfoConstructionAssignment(); void testProcessInfoList(); void testProcessInfo(); void testProcessInfoNotFound(); }; #endif diff --git a/autotests/kprocesstest.cpp b/autotests/kprocesstest.cpp index e835bca..badcea9 100644 --- a/autotests/kprocesstest.cpp +++ b/autotests/kprocesstest.cpp @@ -1,102 +1,89 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kprocesstest_helper.h" #include #include #include #include #include #include #include #include class KProcessTest : public QObject { Q_OBJECT private Q_SLOTS: void test_channels(); void test_setShellCommand(); }; // IOCCC nomination pending static QString callHelper(KProcess::OutputChannelMode how) { QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); QString helper = QCoreApplication::applicationDirPath() + QStringLiteral("/kprocesstest_helper"); #ifdef Q_OS_WIN helper += QStringLiteral(".exe"); #endif Q_ASSERT(QFile::exists(helper)); p.start(helper, QStringList() << QString::number(how) << QStringLiteral("--nocrashhandler")); p.waitForFinished(); return QString::fromLatin1(p.readAllStandardOutput()); } #define EO EOUT "\n" #define EE EERR "\n" #define TESTCHAN(me,ms,pout,rout,rerr) \ e = QStringLiteral("mode: " ms "\n" POUT pout ROUT rout RERR rerr); \ a = QStringLiteral("mode: " ms "\n") + callHelper(KProcess::me); \ QCOMPARE(a, e) void KProcessTest::test_channels() { #ifdef Q_OS_UNIX QString e, a; TESTCHAN(SeparateChannels, "separate", "", EO, EE); TESTCHAN(ForwardedChannels, "forwarded", EO EE, "", ""); TESTCHAN(OnlyStderrChannel, "forwarded stdout", EO, "", EE); TESTCHAN(OnlyStdoutChannel, "forwarded stderr", EE, EO, ""); TESTCHAN(MergedChannels, "merged", "", EO EE, ""); #else Q_UNUSED(callHelper); QSKIP("This test needs a UNIX system"); #endif } void KProcessTest::test_setShellCommand() { // Condition copied from kprocess.cpp #if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) QSKIP("This test needs a free UNIX system"); #else KProcess p; p.setShellCommand(QStringLiteral("cat")); QCOMPARE(p.program().count(), 1); QCOMPARE(p.program().at(0), QStandardPaths::findExecutable(QStringLiteral("cat"))); QVERIFY(p.program().at(0).endsWith(QLatin1String("/bin/cat"))); p.setShellCommand(QStringLiteral("true || false")); QCOMPARE(p.program(), QStringList() << QStringLiteral("/bin/sh") << QStringLiteral("-c") << QString::fromLatin1("true || false")); #endif } QTEST_MAIN(KProcessTest) #include "kprocesstest.moc" diff --git a/autotests/kprocesstest_helper.cpp b/autotests/kprocesstest_helper.cpp index 7ff9683..f97097c 100644 --- a/autotests/kprocesstest_helper.cpp +++ b/autotests/kprocesstest_helper.cpp @@ -1,41 +1,28 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "kprocesstest_helper.h" #include #include int main(int argc, char **argv) { KProcess p; p.setShellCommand(QString::fromLatin1("echo " EOUT "; echo " EERR " >&2")); p.setOutputChannelMode(static_cast(atoi(argv[1]))); fputs(POUT, stdout); fflush(stdout); p.execute(); fputs(ROUT, stdout); fputs(p.readAllStandardOutput().constData(), stdout); fputs(RERR, stdout); fputs(p.readAllStandardError().constData(), stdout); return 0; } diff --git a/autotests/kprocesstest_helper.h b/autotests/kprocesstest_helper.h index 6560c39..044b130 100644 --- a/autotests/kprocesstest_helper.h +++ b/autotests/kprocesstest_helper.h @@ -1,26 +1,13 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #define EOUT "foo - stdout" #define EERR "bar - stderr" #define POUT "program output:\n" #define ROUT "received stdout:\n" #define RERR "received stderr:\n" diff --git a/autotests/krandomtest.cpp b/autotests/krandomtest.cpp index 31f09e3..ea1aabf 100644 --- a/autotests/krandomtest.cpp +++ b/autotests/krandomtest.cpp @@ -1,218 +1,207 @@ -/* This file is part of the KDE libraries - Copyright (c) 2016 Michael Pyne - Copyright (c) 2016 Arne Spiegelhauer - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2016 Michael Pyne + SPDX-FileCopyrightText: 2016 Arne Spiegelhauer + + SPDX-License-Identifier: LGPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #include typedef QVarLengthArray intSequenceType; static const char *binpath; static bool seqsAreEqual(const intSequenceType &l, const intSequenceType &r) { if(l.size() != r.size()) { return false; } const intSequenceType::const_iterator last(l.end()); intSequenceType::const_iterator l_first(l.begin()); intSequenceType::const_iterator r_first(r.begin()); while(l_first != last && *l_first == *r_first) { l_first++; r_first++; } return l_first == last; } // Fills seq with random bytes produced by a new process. Seq should already // be sized to the needed amount of random numbers. static bool getChildRandSeq(intSequenceType &seq) { QProcess subtestProcess; // Launch a separate process to generate random numbers to test first-time // seeding. subtestProcess.start(QLatin1String(binpath), QStringList() << QString::number(seq.count())); subtestProcess.waitForFinished(); QTextStream childStream(subtestProcess.readAllStandardOutput()); std::generate(seq.begin(), seq.end(), [&]() { int temp; childStream >> temp; return temp; }); char c; childStream >> c; return c == '\n' && childStream.status() == QTextStream::Ok; } class KRandomTest : public QObject { Q_OBJECT private Q_SLOTS: void test_random(); void test_randomString(); void test_randomStringThreaded(); void test_KRS(); }; void KRandomTest::test_random() { int testValue = KRandom::random(); QVERIFY(testValue >= 0); QVERIFY(testValue < RAND_MAX); // Verify seeding results in different numbers across different procs // See bug 362161 intSequenceType out1(10), out2(10); QVERIFY(getChildRandSeq(out1)); QVERIFY(getChildRandSeq(out2)); QVERIFY(!seqsAreEqual(out1, out2)); } void KRandomTest::test_randomString() { const int desiredLength = 12; const QString testString = KRandom::randomString(desiredLength); const QRegularExpression outputFormat(QRegularExpression::anchoredPattern(QStringLiteral("[A-Za-z0-9]+"))); const QRegularExpressionMatch match = outputFormat.match(testString); QCOMPARE(testString.length(), desiredLength); QVERIFY(match.hasMatch()); } void KRandomTest::test_KRS() { using std::generate; using std::all_of; const int maxInt = 50000; KRandomSequence krs1, krs2; intSequenceType out1(10), out2(10); generate(out1.begin(), out1.end(), [&]() { return krs1.getInt(maxInt); }); generate(out2.begin(), out2.end(), [&]() { return krs2.getInt(maxInt); }); QVERIFY(!seqsAreEqual(out1, out2)); QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) { return x < maxInt; })); QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) { return x < maxInt; })); // Compare same-seed krs1.setSeed(5000); krs2.setSeed(5000); generate(out1.begin(), out1.end(), [&]() { return krs1.getInt(maxInt); }); generate(out2.begin(), out2.end(), [&]() { return krs2.getInt(maxInt); }); QVERIFY(seqsAreEqual(out1, out2)); QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) { return x < maxInt; })); QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) { return x < maxInt; })); // Compare same-seed and assignment ctor krs1 = KRandomSequence(8000); krs2 = KRandomSequence(8000); generate(out1.begin(), out1.end(), [&]() { return krs1.getInt(maxInt); }); generate(out2.begin(), out2.end(), [&]() { return krs2.getInt(maxInt); }); QVERIFY(seqsAreEqual(out1, out2)); QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) { return x < maxInt; })); QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) { return x < maxInt; })); } class KRandomTestThread : public QThread { protected: void run() override { result = KRandom::randomString(32); }; public: QString result; }; void KRandomTest::test_randomStringThreaded() { static const int size = 5; KRandomTestThread* threads[size]; for (int i=0; i < size; ++i) { threads[i] = new KRandomTestThread(); threads[i]->start(); } QSet results; for (int i=0; i < size; ++i) { threads[i]->wait(2000); results.insert(threads[i]->result); } // each thread should have returned a unique result QCOMPARE(results.size(), size); for (int i=0; i < size; ++i) { delete threads[i]; } } // Used by getChildRandSeq... outputs random numbers to stdout and then // exits the process. static void childGenRandom(int count) { // No logic to 300, just wanted to avoid it accidentally being 2.4B... if (count <= 0 || count > 300) { exit(-1); } while (--count > 0) { std::cout << KRandom::random() << ' '; } std::cout << KRandom::random() << '\n'; exit(0); } // Manually implemented to dispatch to child process if needed to support // subtests int main(int argc, char *argv[]) { if (argc > 1) { childGenRandom(std::atoi(argv[1])); Q_UNREACHABLE(); } binpath = argv[0]; KRandomTest randomTest; return QTest::qExec(&randomTest); } #include "krandomtest.moc" diff --git a/autotests/kshareddatacachetest.cpp b/autotests/kshareddatacachetest.cpp index 0f83cd5..ea39305 100644 --- a/autotests/kshareddatacachetest.cpp +++ b/autotests/kshareddatacachetest.cpp @@ -1,77 +1,64 @@ -/* This file is part of the KDE libraries - * Copyright (c) 2012 David Faure - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2 of the License or ( at - * your option ) version 3 or, at the discretion of KDE e.V. ( which shall - * act as a proxy as in section 14 of the GPLv3 ), any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2012 David Faure + + SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include #include #include #include #include #include // strcpy class KSharedDataCacheTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void simpleInsert(); }; void KSharedDataCacheTest::initTestCase() { } void KSharedDataCacheTest::simpleInsert() { const QLatin1String cacheName("myTestCache"); const QLatin1String key("mypic"); // clear the cache QString cacheFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache"); QFile file(cacheFile); if (file.exists()) { QVERIFY(file.remove()); } // insert something into it KSharedDataCache cache(cacheName, 5 * 1024 * 1024); #ifndef Q_OS_WIN // the windows implementation is currently only memory based and not really shared QVERIFY(file.exists()); // make sure we got the cache filename right #endif QByteArray data; data.resize(9228); strcpy(data.data(), "Hello world"); QVERIFY(cache.insert(key, data)); // read it out again QByteArray result; QVERIFY(cache.find(key, &result)); QCOMPARE(result, data); // another insert strcpy(data.data(), "Hello KDE"); QVERIFY(cache.insert(key, data)); // and another read QVERIFY(cache.find(key, &result)); QCOMPARE(result, data); } QTEST_MAIN(KSharedDataCacheTest) #include "kshareddatacachetest.moc" diff --git a/autotests/kshelltest.cpp b/autotests/kshelltest.cpp index 88cf8db..70678fd 100644 --- a/autotests/kshelltest.cpp +++ b/autotests/kshelltest.cpp @@ -1,262 +1,252 @@ -/* This file is part of the KDE libraries - Copyright (c) 2003,2007-2008 Oswald Buddenhagen - Copyright (c) 2005 Thomas Braxton - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2003, 2007-2008 Oswald Buddenhagen + SPDX-FileCopyrightText: 2005 Thomas Braxton + + SPDX-License-Identifier: LGPL-2.0-only */ #include #include #include #include #include #include #include class KShellTest : public QObject { Q_OBJECT private Q_SLOTS: void tildeExpand(); void tildeCollapse(); void quoteArg(); void joinArgs(); void splitJoin(); void quoteSplit(); void quoteSplit_data(); void abortOnMeta(); }; // The expansion of ~me isn't exactly QDir::homePath(), in case $HOME has a trailing slash, it's kept. static QString myHomePath() { #ifdef Q_OS_WIN return QDir::homePath(); #else return QString::fromLocal8Bit(qgetenv("HOME")); #endif } void KShellTest::tildeExpand() { QString me(KUser().loginName()); QCOMPARE(KShell::tildeExpand(QStringLiteral("~")), QDir::homePath()); QCOMPARE(KShell::tildeExpand(QStringLiteral("~/dir")), QString(QDir::homePath() + QStringLiteral("/dir"))); QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me), myHomePath()); QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me + QStringLiteral("/dir")), QString(myHomePath() + QStringLiteral("/dir"))); #ifdef Q_OS_WIN QCOMPARE(KShell::tildeExpand(QStringLiteral("^~") + me), QString(QLatin1Char('~') + me)); #else QCOMPARE(KShell::tildeExpand(QStringLiteral("\\~") + me), QString(QStringLiteral("~") + me)); #endif } void KShellTest::tildeCollapse() { QCOMPARE(KShell::tildeCollapse(QDir::homePath()), QStringLiteral("~")); QCOMPARE(KShell::tildeCollapse(QDir::homePath() + QStringLiteral("/Documents")), QStringLiteral("~/Documents")); QCOMPARE(KShell::tildeCollapse(QStringLiteral("/test/") + QDir::homePath()), QStringLiteral("/test/") + QDir::homePath()); } void KShellTest::quoteArg() { #ifdef Q_OS_WIN QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("\"a space\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("fds\\\"")), QStringLiteral("fds\\\\\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\foo")), QStringLiteral("\\\\foo")); QCOMPARE(KShell::quoteArg(QStringLiteral("\"asdf\"")), QStringLiteral("\\^\"asdf\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("with\\")), QStringLiteral("\"with\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\")), QStringLiteral("\"\\\\\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\"a space\\\"")), QStringLiteral("\\^\"\"a space\"\\\\\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("as df\\")), QStringLiteral("\"as df\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("foo bar\"\\\"bla")), QStringLiteral("\"foo bar\"\\^\"\\\\\\^\"\"bla\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("a % space")), QStringLiteral("\"a %PERCENT_SIGN% space\"")); #else QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("'a space'")); #endif } void KShellTest::joinArgs() { QStringList list; list << QStringLiteral("this") << QStringLiteral("is") << QStringLiteral("a") << QStringLiteral("test"); QCOMPARE(KShell::joinArgs(list), QStringLiteral("this is a test")); } static QString sj(const QString &str, KShell::Options flags, KShell::Errors *ret) { return KShell::joinArgs(KShell::splitArgs(str, flags, ret)); } void KShellTest::splitJoin() { KShell::Errors err = KShell::NoError; #ifdef Q_OS_WIN QCOMPARE(sj(QStringLiteral("\"(sulli)\" text"), KShell::NoOptions, &err), QStringLiteral("\"(sulli)\" text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral(" ha\\ lo "), KShell::NoOptions, &err), QStringLiteral("\"ha\\\\\" lo")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err), QString()); QVERIFY(err == KShell::BadQuoting); QCOMPARE(sj(QStringLiteral("no \" error\""), KShell::NoOptions, &err), QStringLiteral("no \" error\"")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::NoOptions, &err), QString()); QVERIFY(err == KShell::BadQuoting); QCOMPARE(sj(QStringLiteral("BLA;asdf sdfess d"), KShell::NoOptions, &err), QStringLiteral("\"BLA;asdf\" sdfess d")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("B\"L\"A&sdf FOO|bar sdf wer "), KShell::NoOptions, &err), QStringLiteral("\"BLA&sdf\" \"FOO|bar\" sdf wer")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"\"\"just \"\" fine\"\"\""), KShell::NoOptions, &err), QStringLiteral("\\^\"\"just \"\\^\"\" fine\"\\^\"")); QVERIFY(err == KShell::NoError); #else QCOMPARE(sj(QString::fromUtf8("\"~qU4rK\" 'text' 'jo'\"jo\" $'crap' $'\\\\\\'\\e\\x21' ha\\ lo \\a"), KShell::NoOptions, &err), QString::fromUtf8("'~qU4rK' text jojo crap '\\'\\''\x1b!' 'ha lo' a")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"~qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~\"qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~/\"dir\" 'text'"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral("/dir text"))); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~ 'text' ~"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral(" text ") + QDir::homePath())); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\\~ blah"), KShell::TildeExpand, &err), QStringLiteral("'~' blah")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~qU4rK ~") + KUser().loginName(), KShell::TildeExpand, &err), QString(QStringLiteral("'~qU4rK' ") + myHomePath())); QVERIFY(err == KShell::NoError); const QString unicodeSpaceFileName = QStringLiteral("test テスト.txt"); // #345140 QCOMPARE(sj(unicodeSpaceFileName, KShell::AbortOnMeta | KShell::TildeExpand, &err), unicodeSpaceFileName); QVERIFY(err == KShell::NoError); #endif } void KShellTest::quoteSplit_data() { QTest::addColumn("string"); QTest::newRow("no space") << QStringLiteral("hiho"); QTest::newRow("regular space") << QStringLiteral("hi there"); QTest::newRow("special space") << QString::fromUtf8("如何定期清潔典型的電風扇 講義.pdf"); } void KShellTest::quoteSplit() { QFETCH(QString, string); // Splitting a quote arg should always just return one argument const QStringList args = KShell::splitArgs(KShell::quoteArg(string)); QCOMPARE(args.count(), 1); } void KShellTest::abortOnMeta() { KShell::Errors err1 = KShell::NoError, err2 = KShell::NoError; QCOMPARE(sj(QStringLiteral("text"), KShell::AbortOnMeta, &err1), QStringLiteral("text")); QVERIFY(err1 == KShell::NoError); #ifdef Q_OS_WIN QVERIFY(KShell::splitArgs(QStringLiteral("BLA & asdf sdfess d"), KShell::AbortOnMeta, &err1).isEmpty()); QVERIFY(err1 == KShell::FoundMeta); QVERIFY(KShell::splitArgs(QStringLiteral("foo %PATH% bar"), KShell::AbortOnMeta, &err1).isEmpty()); QVERIFY(err1 == KShell::FoundMeta); QCOMPARE(sj(QStringLiteral("foo %PERCENT_SIGN% bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo %PERCENT_SIGN% bar")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("@foo ^& bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo \"&\" bar")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"BLA|asdf\" sdfess d"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|asdf\" sdfess d")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("B\"L\"A\"|\"sdf \"FOO | bar\" sdf wer"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|sdf\" \"FOO | bar\" sdf wer")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("b-q me \\\\^|\\\\\\^\""), KShell::AbortOnMeta, &err1), QStringLiteral("b-q me \"\\\\|\"\\\\\\^\"")); QVERIFY(err1 == KShell::NoError); #else QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err1), QString()); QVERIFY(err1 != KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::AbortOnMeta, &err1), QString()); QVERIFY(err1 != KShell::NoError); QVERIFY(sj(QStringLiteral("say `echo no error`"), KShell::NoOptions, &err1) != sj(QStringLiteral("say `echo no error`"), KShell::AbortOnMeta, &err2)); QVERIFY(err1 != err2); QVERIFY(sj(QStringLiteral("BLA=say echo meta"), KShell::NoOptions, &err1) != sj(QStringLiteral("BLA=say echo meta"), KShell::AbortOnMeta, &err2)); QVERIFY(err1 != err2); QVERIFY(sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::NoOptions, &err1) == sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::AbortOnMeta, &err2)); #endif } QTEST_MAIN(KShellTest) #include "kshelltest.moc" diff --git a/autotests/ktexttohtmltest.cpp b/autotests/ktexttohtmltest.cpp index 1655c26..c07857b 100644 --- a/autotests/ktexttohtmltest.cpp +++ b/autotests/ktexttohtmltest.cpp @@ -1,571 +1,559 @@ /* - Copyright (C) 2005 Ingo Kloecker - Copyright (C) 2007 Allen Winter - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2005 Ingo Kloecker + SPDX-FileCopyrightText: 2007 Allen Winter + + SPDX-License-Identifier: LGPL-2.0-only */ #include "ktexttohtmltest.h" #include "../src/lib/text/ktexttohtml.h" #include "../src/lib/text/ktexttohtml_p.h" #include #include #include QTEST_MAIN(KTextToHTMLTest) Q_DECLARE_METATYPE(KTextToHTML::Options) #ifndef Q_OS_WIN void initLocale() { setenv("LC_ALL", "en_US.utf-8", 1); } Q_CONSTRUCTOR_FUNCTION(initLocale) #endif void KTextToHTMLTest::testGetEmailAddress() { // empty input const QString emptyQString; KTextToHTMLHelper ll1(emptyQString, 0); QVERIFY(ll1.getEmailAddress().isEmpty()); // no '@' at scan position KTextToHTMLHelper ll2(QStringLiteral("foo@bar.baz"), 0); QVERIFY(ll2.getEmailAddress().isEmpty()); // '@' in local part KTextToHTMLHelper ll3(QStringLiteral("foo@bar@bar.baz"), 7); QVERIFY(ll3.getEmailAddress().isEmpty()); // empty local part KTextToHTMLHelper ll4(QStringLiteral("@bar.baz"), 0); QVERIFY(ll4.getEmailAddress().isEmpty()); KTextToHTMLHelper ll5(QStringLiteral(".@bar.baz"), 1); QVERIFY(ll5.getEmailAddress().isEmpty()); KTextToHTMLHelper ll6(QStringLiteral(" @bar.baz"), 1); QVERIFY(ll6.getEmailAddress().isEmpty()); KTextToHTMLHelper ll7(QStringLiteral(".!#$%&'*+-/=?^_`{|}~@bar.baz"), qstrlen(".!#$%&'*+-/=?^_`{|}~")); QVERIFY(ll7.getEmailAddress().isEmpty()); // allowed special chars in local part of address KTextToHTMLHelper ll8(QStringLiteral("a.!#$%&'*+-/=?^_`{|}~@bar.baz"), qstrlen("a.!#$%&'*+-/=?^_`{|}~")); QCOMPARE(ll8.getEmailAddress(), QStringLiteral("a.!#$%&'*+-/=?^_`{|}~@bar.baz")); // '@' in domain part KTextToHTMLHelper ll9(QStringLiteral("foo@bar@bar.baz"), 3); QVERIFY(ll9.getEmailAddress().isEmpty()); // domain part without dot KTextToHTMLHelper lla(QStringLiteral("foo@bar"), 3); QVERIFY(lla.getEmailAddress().isEmpty()); KTextToHTMLHelper llb(QStringLiteral("foo@bar."), 3); QVERIFY(llb.getEmailAddress().isEmpty()); KTextToHTMLHelper llc(QStringLiteral(".foo@bar"), 4); QVERIFY(llc.getEmailAddress().isEmpty()); KTextToHTMLHelper lld(QStringLiteral("foo@bar "), 3); QVERIFY(lld.getEmailAddress().isEmpty()); KTextToHTMLHelper lle(QStringLiteral(" foo@bar"), 4); QVERIFY(lle.getEmailAddress().isEmpty()); KTextToHTMLHelper llf(QStringLiteral("foo@bar-bar"), 3); QVERIFY(llf.getEmailAddress().isEmpty()); // empty domain part KTextToHTMLHelper llg(QStringLiteral("foo@"), 3); QVERIFY(llg.getEmailAddress().isEmpty()); KTextToHTMLHelper llh(QStringLiteral("foo@."), 3); QVERIFY(llh.getEmailAddress().isEmpty()); KTextToHTMLHelper lli(QStringLiteral("foo@-"), 3); QVERIFY(lli.getEmailAddress().isEmpty()); // simple address KTextToHTMLHelper llj(QStringLiteral("foo@bar.baz"), 3); QCOMPARE(llj.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper llk(QStringLiteral("foo@bar.baz."), 3); QCOMPARE(llk.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper lll(QStringLiteral(".foo@bar.baz"), 4); QCOMPARE(lll.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper llm(QStringLiteral("foo@bar.baz-"), 3); QCOMPARE(llm.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper lln(QStringLiteral("-foo@bar.baz"), 4); QCOMPARE(lln.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper llo(QStringLiteral("foo@bar.baz "), 3); QCOMPARE(llo.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper llp(QStringLiteral(" foo@bar.baz"), 4); QCOMPARE(llp.getEmailAddress(), QStringLiteral("foo@bar.baz")); KTextToHTMLHelper llq(QStringLiteral("foo@bar-bar.baz"), 3); QCOMPARE(llq.getEmailAddress(), QStringLiteral("foo@bar-bar.baz")); } void KTextToHTMLTest::testGetUrl() { QStringList brackets; brackets << QStringLiteral("") << QStringLiteral(""); // no brackets brackets << QStringLiteral("<") << QStringLiteral(">"); brackets << QStringLiteral("[") << QStringLiteral("]"); brackets << QStringLiteral("\"") << QStringLiteral("\""); brackets << QStringLiteral("") << QStringLiteral(""); for (int i = 0; i < brackets.count(); i += 2) { testGetUrl2(brackets[ i ], brackets[ i + 1 ]); } } void KTextToHTMLTest::testGetUrl2(const QString &left, const QString &right) { QStringList schemas; schemas << QStringLiteral("http://"); schemas << QStringLiteral("https://"); schemas << QStringLiteral("vnc://"); schemas << QStringLiteral("fish://"); schemas << QStringLiteral("ftp://"); schemas << QStringLiteral("ftps://"); schemas << QStringLiteral("sftp://"); schemas << QStringLiteral("smb://"); schemas << QStringLiteral("file://"); QStringList urls; urls << QStringLiteral("www.kde.org"); urls << QStringLiteral("user@www.kde.org"); urls << QStringLiteral("user:pass@www.kde.org"); urls << QStringLiteral("user:pass@www.kde.org:1234"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path?a=1"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path?a=1#anchor"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/\npath \n /long/ path \t ?a=1#anchor"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path/special(123)?a=1#anchor"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla"); urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]"); urls << QStringLiteral("user:pass@www.kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]"); urls << QStringLiteral("user:pass@www.kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?") + QStringLiteral("\n\t \n\t a=1#anchor[bla]"); for (const QString &schema : qAsConst(schemas)) { for (QString url : qAsConst(urls)) { //krazy:exclude=foreach // by definition: if the URL is enclosed in brackets, the URL itself is not allowed // to contain the closing bracket, as this would be detected as the end of the URL if ((left.length() == 1) && (url.contains(right[ 0 ]))) { continue; } // if the url contains a whitespace, it must be enclosed with brackets if ((url.contains(QLatin1Char('\n')) || url.contains(QLatin1Char('\t')) || url.contains(QLatin1Char(' '))) && left.isEmpty()) { continue; } QString test(left + schema + url + right); KTextToHTMLHelper ll(test, left.length()); QString gotUrl = ll.getUrl(); // we want to have the url without whitespace url.remove(QLatin1Char(' ')); url.remove(QLatin1Char('\n')); url.remove(QLatin1Char('\t')); bool ok = (gotUrl == (schema + url)); //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (schema + url); if (!ok) { qDebug() << "got:" << gotUrl; } QVERIFY2(ok, qPrintable(test)); } } QStringList urlsWithoutSchema; urlsWithoutSchema << QStringLiteral(".kde.org"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path?a=1"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path?a=1#anchor"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path/special(123)?a=1#anchor"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]"); urlsWithoutSchema << QStringLiteral(".kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?") + QStringLiteral("\n\t \n\t a=1#anchor[bla]"); QStringList starts; starts << QStringLiteral("www") << QStringLiteral("ftp") << QStringLiteral("news:www"); for (const QString &start : qAsConst(starts)) { for (QString url : qAsConst(urlsWithoutSchema)) { // by definition: if the URL is enclosed in brackets, the URL itself is not allowed // to contain the closing bracket, as this would be detected as the end of the URL if ((left.length() == 1) && (url.contains(right[ 0 ]))) { continue; } // if the url contains a whitespace, it must be enclosed with brackets if ((url.contains(QLatin1Char('\n')) || url.contains(QLatin1Char('\t')) || url.contains(QLatin1Char(' '))) && left.isEmpty()) { continue; } QString test(left + start + url + right); KTextToHTMLHelper ll(test, left.length()); QString gotUrl = ll.getUrl(); // we want to have the url without whitespace url.remove(QLatin1Char(' ')); url.remove(QLatin1Char('\n')); url.remove(QLatin1Char('\t')); bool ok = (gotUrl == (start + url)); //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (start + url); if (!ok) { qDebug() << "got:" << gotUrl; } QVERIFY2(ok, qPrintable(gotUrl)); } } // test max url length QString url = QStringLiteral("https://www.kde.org/this/is/a_very_loooooong_url/test/test/test"); { KTextToHTMLHelper ll(url, 0, 10); QVERIFY(ll.getUrl().isEmpty()); // url too long } { KTextToHTMLHelper ll(url, 0, url.length() - 1); QVERIFY(ll.getUrl().isEmpty()); // url too long } { KTextToHTMLHelper ll(url, 0, url.length()); QCOMPARE(ll.getUrl(), url); } { KTextToHTMLHelper ll(url, 0, url.length() + 1); QCOMPARE(ll.getUrl(), url); } // mailto { QString addr = QStringLiteral("mailto:test@kde.org"); QString test(left + addr + right); KTextToHTMLHelper ll(test, left.length()); QString gotUrl = ll.getUrl(); bool ok = (gotUrl == addr); //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << addr; if (!ok) { qDebug() << "got:" << gotUrl; } QVERIFY2(ok, qPrintable(gotUrl)); } } void KTextToHTMLTest::testHtmlConvert_data() { QTest::addColumn("plainText"); QTest::addColumn("flags"); QTest::addColumn("htmlText"); // Linker error when using PreserveSpaces, therefore the hardcoded 0x01 or 0x09 // Test preserving whitespace correctly QTest::newRow("") << " foo" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << " foo"; QTest::newRow("") << " foo" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "  foo"; QTest::newRow("") << " foo " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "  foo  "; QTest::newRow("") << " foo " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "  foo "; QTest::newRow("") << "bla bla bla bla bla" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla bla bla bla bla"; QTest::newRow("") << "bla bla bla \n bla bla bla " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla bla bla 
\n  bla bla bla "; QTest::newRow("") << "bla bla bla" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla bla  bla"; QTest::newRow("") << " bla bla \n bla bla a\n bla bla " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << " bla bla 
\n bla bla a
\n" "  bla bla "; // Test highlighting with *, / and _ QTest::newRow("") << "Ce paragraphe _contient_ des mots ou des _groupes de mots_ à mettre en" " forme…" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "Ce paragraphe _contient_ des mots ou des" " _groupes de mots_ à mettre en forme…"; QTest::newRow("punctation-bug") << "Ce texte *a l'air* de _fonctionner_, à condition" " d’utiliser le guillemet ASCII." << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "Ce texte *a l'air* de _fonctionner_, à" " condition d’utiliser le guillemet ASCII."; QTest::newRow("punctation-bug") << "Un répertoire /est/ un *dossier* où on peut mettre des" " *fichiers*." << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "Un répertoire /est/ un" " *dossier* où on peut mettre des *fichiers*."; QTest::newRow("punctation-bug") << "*BLA BLA BLA BLA*." << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "BLA BLA BLA BLA."; QTest::newRow("") << "Je vais tenter de repérer des faux positif*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "Je vais tenter de repérer des faux positif*"; QTest::newRow("") << "*Ouais !* *Yes!*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "*Ouais !* *Yes!*"; QTest::newRow("multispace") << "*Ouais foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "*Ouais foo*"; QTest::newRow("multispace3") << "*Ouais: foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "*Ouais: foo*"; QTest::newRow("multi-") << "** Ouais: foo **" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "** Ouais:  foo **"; QTest::newRow("multi-") << "*** Ouais: foo ***" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "*** Ouais:  foo ***"; QTest::newRow("nohtmlversion") << "* Ouais: foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "* Ouais:     foo *"; QTest::newRow("nohtmlversion2") << "*Ouais: foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "*Ouais:     foo *"; QTest::newRow("nohtmlversion3") << "* Ouais: foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "* Ouais:     foo*"; QTest::newRow("nohtmlversion3") << "* Ouais: *ff sfsdf* foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "* Ouais: *ff sfsdf* foo *"; QTest::newRow("") << "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText) << "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file"; // This test has problems with the encoding, apparently. //QTest::newRow( "" ) << "*Ça fait plaisir de pouvoir utiliser des lettres accentuées dans du" // " texte mis en forme*." << 0x09 << "Ça fait plaisir de pouvoir" // " utiliser des lettres accentuées dans du texte mis en forme."; // Bug reported by dfaure, the would get lost QTest::newRow("") << "QUrl url(\"http://strange/\");" << KTextToHTML::Options(KTextToHTML::ReplaceSmileys | KTextToHTML::HighlightText) << "QUrl url("/\">" "http://strange<hostname>/");"; // Bug: 211128 - plain text emails should not replace ampersand & with & QTest::newRow("bug211128") << "https://green-site/?Ticket=85&Page=next" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "" "https://green-site/?Ticket=85&Page=next"; QTest::newRow("dotBeforeEnd") << "Look at this file: www.example.com/example.h" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "Look at this file: " "www.example.com/example.h"; QTest::newRow("dotInMiddle") << "Look at this file: www.example.com/.bashrc" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "Look at this file: " "www.example.com/.bashrc"; // A dot at the end of an URL is explicitly ignored QTest::newRow("dotAtEnd") << "Look at this file: www.example.com/test.cpp." << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "Look at this file: " "www.example.com/test.cpp."; // Bug 313719 - URL in parenthesis QTest::newRow("url-in-parenthesis-1") << "KDE (website https://www.kde.org)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "KDE (website https://www.kde.org)"; QTest::newRow("url-in-parenthesis-2") << "KDE website (https://www.kde.org)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "KDE website (https://www.kde.org)"; QTest::newRow("url-in-parenthesis-3") << "bla (https://www.kde.org - section 5.2)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla (https://www.kde.org - section 5.2)"; - + // Fix url as foo < > when we concatened them. QTest::newRow("url-with-url") << "foo >" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "foo <https://www.kde.org/ <https://www.kde.org/>>"; //Fix url exploit QTest::newRow("url-exec-html") << "https://\"> corrupt! throw KSDCCorrupted(); } void unlock() const { m_lock->unlock(); } class CacheLocker { mutable Private *d; bool cautiousLock() { int lockCount = 0; // Locking can fail due to a timeout. If it happens too often even though // we're taking corrective action assume there's some disastrous problem // and give up. while (!d->lock() && !isLockedCacheSafe()) { d->recoverCorruptedCache(); if (!d->shm) { qCWarning(KCOREADDONS_DEBUG) << "Lost the connection to shared memory for cache" << d->m_cacheName; return false; } if (lockCount++ > 4) { qCritical() << "There is a very serious problem with the KDE data cache" << d->m_cacheName << "giving up trying to access cache."; d->detachFromSharedMemory(); return false; } } return true; } // Runs a quick battery of tests on an already-locked cache and returns // false as soon as a sanity check fails. The cache remains locked in this // situation. bool isLockedCacheSafe() const { // Note that cachePageSize() itself runs a check that can throw. uint testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize()); if (Q_UNLIKELY(d->m_mapSize != testSize)) { return false; } if (Q_UNLIKELY(d->shm->version != SharedMemory::PIXMAP_CACHE_VERSION)) { return false; } #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) switch (d->shm->evictionPolicy.load()) { #else switch (d->shm->evictionPolicy.loadRelaxed()) { #endif case NoEvictionPreference: // fallthrough case EvictLeastRecentlyUsed: // fallthrough case EvictLeastOftenUsed: // fallthrough case EvictOldest: break; default: return false; } return true; } public: CacheLocker(const Private *_d) : d(const_cast(_d)) { if (Q_UNLIKELY(!d || !d->shm || !cautiousLock())) { d = nullptr; } } ~CacheLocker() { if (d && d->shm) { d->unlock(); } } CacheLocker(const CacheLocker &) = delete; CacheLocker &operator=(const CacheLocker &) = delete; bool failed() const { return !d || d->shm == nullptr; } }; QString m_cacheName; SharedMemory *shm; QSharedPointer m_lock; uint m_mapSize; uint m_defaultCacheSize; uint m_expectedItemSize; SharedLockId m_expectedType; }; // Must be called while the lock is already held! void SharedMemory::removeEntry(uint index) { if (index >= indexTableSize() || cacheAvail > pageTableSize()) { throw KSDCCorrupted(); } PageTableEntry *pageTableEntries = pageTable(); IndexTableEntry *entriesIndex = indexTable(); // Update page table first pageID firstPage = entriesIndex[index].firstPage; if (firstPage < 0 || static_cast(firstPage) >= pageTableSize()) { qCDebug(KCOREADDONS_DEBUG) << "Trying to remove an entry which is already invalid. This " << "cache is likely corrupt."; throw KSDCCorrupted(); } if (index != static_cast(pageTableEntries[firstPage].index)) { qCritical() << "Removing entry" << index << "but the matching data" << "doesn't link back -- cache is corrupt, clearing."; throw KSDCCorrupted(); } uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize()); uint savedCacheSize = cacheAvail; for (uint i = firstPage; i < pageTableSize() && static_cast(pageTableEntries[i].index) == index; ++i) { pageTableEntries[i].index = -1; cacheAvail++; } if ((cacheAvail - savedCacheSize) != entriesToRemove) { qCritical() << "We somehow did not remove" << entriesToRemove << "when removing entry" << index << ", instead we removed" << (cacheAvail - savedCacheSize); throw KSDCCorrupted(); } // For debugging #ifdef NDEBUG void *const startOfData = page(firstPage); if (startOfData) { QByteArray str((const char *) startOfData); str.prepend(" REMOVED: "); str.prepend(QByteArray::number(index)); str.prepend("ENTRY "); ::memcpy(startOfData, str.constData(), str.size() + 1); } #endif // Update the index entriesIndex[index].fileNameHash = 0; entriesIndex[index].totalItemSize = 0; entriesIndex[index].useCount = 0; entriesIndex[index].lastUsedTime = 0; entriesIndex[index].addTime = 0; entriesIndex[index].firstPage = -1; } KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize) : d(nullptr) { try { d = new Private(cacheName, defaultCacheSize, expectedItemSize); } catch (KSDCCorrupted) { KSharedDataCache::deleteCache(cacheName); // Try only once more try { d = new Private(cacheName, defaultCacheSize, expectedItemSize); } catch (KSDCCorrupted) { qCritical() << "Even a brand-new cache starts off corrupted, something is" << "seriously wrong. :-("; d = nullptr; // Just in case } } } KSharedDataCache::~KSharedDataCache() { // Note that there is no other actions required to separate from the // shared memory segment, simply unmapping is enough. This makes things // *much* easier so I'd recommend maintaining this ideal. if (!d) { return; } if (d->shm) { #ifdef KSDC_MSYNC_SUPPORTED ::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC); #endif ::munmap(d->shm, d->m_mapSize); } // Do not delete d->shm, it was never constructed, it's just an alias. d->shm = nullptr; delete d; } bool KSharedDataCache::insert(const QString &key, const QByteArray &data) { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } QByteArray encodedKey = key.toUtf8(); uint keyHash = generateHash(encodedKey); uint position = keyHash % d->shm->indexTableSize(); // See if we're overwriting an existing entry. IndexTableEntry *indices = d->shm->indexTable(); // In order to avoid the issue of a very long-lived cache having items // with a use count of 1 near-permanently, we attempt to artifically // reduce the use count of long-lived items when there is high load on // the cache. We do this randomly, with a weighting that makes the event // impossible if load < 0.5, and guaranteed if load >= 0.96. const static double startCullPoint = 0.5l; const static double mustCullPoint = 0.96l; // cacheAvail is in pages, cacheSize is in bytes. double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize() / d->shm->cacheSize); bool cullCollisions = false; if (Q_UNLIKELY(loadFactor >= mustCullPoint)) { cullCollisions = true; } else if (loadFactor > startCullPoint) { const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint); if (KRandom::random() >= tripWireValue) { cullCollisions = true; } } // In case of collisions in the index table (i.e. identical positions), use // quadratic chaining to attempt to find an empty slot. The equation we use // is: // position = (hash + (i + i*i) / 2) % size, where i is the probe number. uint probeNumber = 1; while (indices[position].useCount > 0 && probeNumber < MAX_PROBE_COUNT) { // If we actually stumbled upon an old version of the key we are // overwriting, then use that position, do not skip over it. if (Q_UNLIKELY(indices[position].fileNameHash == keyHash)) { break; } // If we are "culling" old entries, see if this one is old and if so // reduce its use count. If it reduces to zero then eliminate it and // use its old spot. if (cullCollisions && (::time(nullptr) - indices[position].lastUsedTime) > 60) { indices[position].useCount >>= 1; if (indices[position].useCount == 0) { qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing old cached entry due to collision."; d->shm->removeEntry(position); // Remove it first break; } } position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % d->shm->indexTableSize(); probeNumber++; } if (indices[position].useCount > 0 && indices[position].firstPage >= 0) { //qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing cached entry due to collision."; d->shm->removeEntry(position); // Remove it first } // Data will be stored as fileNamefoo\0PNGimagedata..... // So total size required is the length of the encoded file name + 1 // for the trailing null, and then the length of the image data. uint fileNameLength = 1 + encodedKey.length(); uint requiredSize = fileNameLength + data.size(); uint pagesNeeded = intCeil(requiredSize, d->shm->cachePageSize()); uint firstPage(-1); if (pagesNeeded >= d->shm->pageTableSize()) { qCWarning(KCOREADDONS_DEBUG) << key << "is too large to be cached."; return false; } // If the cache has no room, or the fragmentation is too great to find // the required number of consecutive free pages, take action. if (pagesNeeded > d->shm->cacheAvail || (firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize()) { // If we have enough free space just defragment uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2); if (d->shm->cacheAvail > freePagesDesired) { // TODO: How the hell long does this actually take on real // caches? d->shm->defragment(); firstPage = d->shm->findEmptyPages(pagesNeeded); } else { // If we already have free pages we don't want to remove a ton // extra. However we can't rely on the return value of // removeUsedPages giving us a good location since we're not // passing in the actual number of pages that we need. d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize()) - d->shm->cacheAvail); firstPage = d->shm->findEmptyPages(pagesNeeded); } if (firstPage >= d->shm->pageTableSize() || d->shm->cacheAvail < pagesNeeded) { qCritical() << "Unable to free up memory for" << key; return false; } } // Update page table PageTableEntry *table = d->shm->pageTable(); for (uint i = 0; i < pagesNeeded; ++i) { table[firstPage + i].index = position; } // Update index indices[position].fileNameHash = keyHash; indices[position].totalItemSize = requiredSize; indices[position].useCount = 1; indices[position].addTime = ::time(nullptr); indices[position].lastUsedTime = indices[position].addTime; indices[position].firstPage = firstPage; // Update cache d->shm->cacheAvail -= pagesNeeded; // Actually move the data in place void *dataPage = d->shm->page(firstPage); if (Q_UNLIKELY(!dataPage)) { throw KSDCCorrupted(); } // Verify it will all fit d->verifyProposedMemoryAccess(dataPage, requiredSize); // Cast for byte-sized pointer arithmetic uchar *startOfPageData = reinterpret_cast(dataPage); ::memcpy(startOfPageData, encodedKey.constData(), fileNameLength); ::memcpy(startOfPageData + fileNameLength, data.constData(), data.size()); return true; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return false; } } bool KSharedDataCache::find(const QString &key, QByteArray *destination) const { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } // Search in the index for our data, hashed by key; QByteArray encodedKey = key.toUtf8(); qint32 entry = d->shm->findNamedEntry(encodedKey); if (entry >= 0) { const IndexTableEntry *header = &d->shm->indexTable()[entry]; const void *resultPage = d->shm->page(header->firstPage); if (Q_UNLIKELY(!resultPage)) { throw KSDCCorrupted(); } d->verifyProposedMemoryAccess(resultPage, header->totalItemSize); header->useCount++; header->lastUsedTime = ::time(nullptr); // Our item is the key followed immediately by the data, so skip // past the key. const char *cacheData = reinterpret_cast(resultPage); cacheData += encodedKey.size(); cacheData++; // Skip trailing null -- now we're pointing to start of data if (destination) { *destination = QByteArray(cacheData, header->totalItemSize - encodedKey.size() - 1); } return true; } } catch (KSDCCorrupted) { d->recoverCorruptedCache(); } return false; } void KSharedDataCache::clear() { try { Private::CacheLocker lock(d); if (!lock.failed()) { d->shm->clear(); } } catch (KSDCCorrupted) { d->recoverCorruptedCache(); } } bool KSharedDataCache::contains(const QString &key) const { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } return d->shm->findNamedEntry(key.toUtf8()) >= 0; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return false; } } void KSharedDataCache::deleteCache(const QString &cacheName) { QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache"); // Note that it is important to simply unlink the file, and not truncate it // smaller first to avoid SIGBUS errors and similar with shared memory // attached to the underlying inode. qCDebug(KCOREADDONS_DEBUG) << "Removing cache at" << cachePath; QFile::remove(cachePath); } unsigned KSharedDataCache::totalSize() const { try { Private::CacheLocker lock(d); if (lock.failed()) { return 0u; } return d->shm->cacheSize; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return 0u; } } unsigned KSharedDataCache::freeSize() const { try { Private::CacheLocker lock(d); if (lock.failed()) { return 0u; } return d->shm->cacheAvail * d->shm->cachePageSize(); } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return 0u; } } KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const { if (d && d->shm) { return static_cast(d->shm->evictionPolicy.fetchAndAddAcquire(0)); } return NoEvictionPreference; } void KSharedDataCache::setEvictionPolicy(EvictionPolicy newPolicy) { if (d && d->shm) { d->shm->evictionPolicy.fetchAndStoreRelease(static_cast(newPolicy)); } } unsigned KSharedDataCache::timestamp() const { if (d && d->shm) { return static_cast(d->shm->cacheTimestamp.fetchAndAddAcquire(0)); } return 0; } void KSharedDataCache::setTimestamp(unsigned newTimestamp) { if (d && d->shm) { d->shm->cacheTimestamp.fetchAndStoreRelease(static_cast(newTimestamp)); } } diff --git a/src/lib/caching/kshareddatacache.h b/src/lib/caching/kshareddatacache.h index d4e29de..09419af 100644 --- a/src/lib/caching/kshareddatacache.h +++ b/src/lib/caching/kshareddatacache.h @@ -1,226 +1,215 @@ /* - * This file is part of the KDE project. - * Copyright © 2010 Michael Pyne - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2010 Michael Pyne + + SPDX-License-Identifier: LGPL-2.0-only +*/ #ifndef KSHAREDDATACACHE_H #define KSHAREDDATACACHE_H #include class QString; class QByteArray; /** * @class KSharedDataCache kshareddatacache.h KSharedDataCache * * @brief A simple data cache which uses shared memory to quickly access data * stored on disk. * * This class is meant to be used with KImageCache and similar classes but can * be used directly if used with care. * * Example usage: * * @code * QString loadTranslatedDocument(KSharedDataCache *cache) { * * // Find the data * QByteArray document; * * if (!cache->find("translated-doc-template", &document)) { * // Entry is not cached, manually generate and then add to cache. * document = translateDocument(globalTemplate()); * cache->insert(document); * } * * // Don't forget to encode/decode properly * return QString::fromUtf8(document); * } * @endcode * * @author Michael Pyne * @see KImageCache * @since 4.5 */ class KCOREADDONS_EXPORT KSharedDataCache { public: /** * Attaches to a shared cache, creating it if necessary. If supported, this * data cache will be shared across all processes using this cache (with * subsequent memory savings). If shared memory is unsupported or a * failure occurs, caching will still be supported, but only in the same * process, and only using the same KSharedDataCache object. * * @param cacheName Name of the cache to use/share. * @param defaultCacheSize Amount of data to be able to store, in bytes. The * actual size will be slightly larger on disk due to accounting * overhead. If the cache already existed then it will not be * resized. For this reason you should specify some reasonable size. * @param expectedItemSize The average size of an item that would be stored * in the cache, in bytes. Choosing an average size of zero bytes causes * KSharedDataCache to use whatever it feels is the best default for the * system. */ KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize = 0); ~KSharedDataCache(); KSharedDataCache(const KSharedDataCache &) = delete; KSharedDataCache &operator=(const KSharedDataCache &) = delete; enum EvictionPolicy { // The default value for data in our shared memory will be 0, so it is // important that whatever we want for the default value is also 0. NoEvictionPreference = 0, EvictLeastRecentlyUsed, EvictLeastOftenUsed, EvictOldest }; /** * @return The removal policy in use by the shared cache. * @see EvictionPolicy */ EvictionPolicy evictionPolicy() const; /** * Sets the entry removal policy for the shared cache to * @p newPolicy. The default is EvictionPolicy::NoEvictionPreference. * * @see EvictionPolicy */ void setEvictionPolicy(EvictionPolicy newPolicy); /** * Attempts to insert the entry @p data into the shared cache, named by * @p key, and returns true only if successful. * * Note that even if the insert was successful, that the newly added entry * may be evicted by other processes contending for the cache. */ bool insert(const QString &key, const QByteArray &data); /** * Returns the data in the cache named by @p key (even if it's some other * process's data named with the same key!), stored in @p destination. If there is * no entry named by @p key then @p destination is left unchanged. The return value * is used to tell what happened. * * If you simply want to verify whether an entry is present in the cache then * see contains(). * * @param key The key to find in the cache. * @param destination Is set to the value of @p key in the cache if @p key is * present, left unchanged otherwise. * @return true if @p key was present in the cache (@p destination will also be * updated), false if @p key was not present (@p destination will be * unchanged). */ bool find(const QString &key, QByteArray *destination) const; /** * Removes all entries from the cache. */ void clear(); /** * Removes the underlying file from the cache. Note that this is *all* that this * function does. The shared memory segment is still attached and will still contain * all the data until all processes currently attached remove the mapping. * * In order to remove the data see clear(). */ static void deleteCache(const QString &cacheName); /** * Returns true if the cache currently contains the image for the given * filename. * * NOTE: Calling this function is threadsafe, but it is in general not * possible to guarantee the image stays cached immediately afterwards, * so if you need the result use find(). */ bool contains(const QString &key) const; /** * Returns the usable cache size in bytes. The actual amount of memory * used will be slightly larger than this to account for required * accounting overhead. */ unsigned totalSize() const; /** * Returns the amount of free space in the cache, in bytes. Due to * implementation details it is possible to still not be able to fit an * entry in the cache at any given time even if it is smaller than the * amount of space remaining. */ unsigned freeSize() const; /** * @return The shared timestamp of the cache. The interpretation of the * timestamp returned is up to the application. KSharedDataCache * will initialize the timestamp to the time returned by @c time(2) * on cache creation, but KSharedDataCache will not touch the * timestamp again. * @see setTimestamp() * @since 4.6 */ unsigned timestamp() const; /** * Sets the shared timestamp of the cache. Timestamping is supported to * allow applications to more effectively "version" the data stored in the * cache. However, the timestamp is shared with all applications * using the cache so you should always be prepared for invalid * timestamps. * * When the cache is first created (note that this is different from * attaching to an existing shared cache on disk), the cache timestamp is * initialized to the time returned by @c time(2). KSharedDataCache will * not update the timestamp again, it is only updated through this method. * * Example: * @code * QImage loadCachedImage(const QString &key) * { * // Check timestamp * if (m_sharedCache->timestamp() < m_currentThemeTimestamp) { * // Cache is stale, clean it out. * m_sharedCache->clear(); * m_sharedCache->setTimestamp(m_currentThemeTimestamp); * } * * // Check cache and load image as usual... * } * @endcode * * @param newTimestamp The new timestamp to mark the shared cache with. * @see timestamp() * @since 4.6 */ void setTimestamp(unsigned newTimestamp); private: class Private; Private *d; }; #endif diff --git a/src/lib/caching/kshareddatacache_p.h b/src/lib/caching/kshareddatacache_p.h index 85a0684..4aecc9d 100644 --- a/src/lib/caching/kshareddatacache_p.h +++ b/src/lib/caching/kshareddatacache_p.h @@ -1,509 +1,498 @@ /* - * This file is part of the KDE project. - * Copyright © 2010 Michael Pyne - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2010 Michael Pyne + + SPDX-License-Identifier: LGPL-2.0-only +*/ #ifndef KSHAREDDATACACHE_P_H #define KSHAREDDATACACHE_P_H #include // HAVE_SYS_MMAN_H #include #include #include #include "kcoreaddons_debug.h" #include // Check for sched_yield #include // sched_yield #include #include #include // Mac OS X, for all its POSIX compliance, does not support timeouts on its // mutexes, which is kind of a disaster for cross-process support. However // synchronization primitives still work, they just might hang if the cache is // corrupted, so keep going. #if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L)) #define KSDC_TIMEOUTS_SUPPORTED 1 #endif #if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED) #warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt" #endif #if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__) #include #define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1 #endif #if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L)) #include #define KSDC_SEMAPHORES_SUPPORTED 1 #endif #if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) #warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless." #endif #if defined(_POSIX_MAPPED_FILES) && ((_POSIX_MAPPED_FILES == 0) || (_POSIX_MAPPED_FILES >= 200112L)) #define KSDC_MAPPED_FILES_SUPPORTED 1 #endif #if defined(_POSIX_SYNCHRONIZED_IO) && ((_POSIX_SYNCHRONIZED_IO == 0) || (_POSIX_SYNCHRONIZED_IO >= 200112L)) #define KSDC_SYNCHRONIZED_IO_SUPPORTED 1 #endif // msync(2) requires both MAPPED_FILES and SYNCHRONIZED_IO POSIX options #if defined(KSDC_MAPPED_FILES_SUPPORTED) && defined(KSDC_SYNCHRONIZED_IO_SUPPORTED) #define KSDC_MSYNC_SUPPORTED #endif // posix_fallocate is used to ensure that the file used for the cache is // actually fully committed to disk before attempting to use the file. #if defined(_POSIX_ADVISORY_INFO) && ((_POSIX_ADVISORY_INFO == 0) || (_POSIX_ADVISORY_INFO >= 200112L)) #define KSDC_POSIX_FALLOCATE_SUPPORTED 1 #endif #ifdef Q_OS_OSX #include "posix_fallocate_mac.h" #define KSDC_POSIX_FALLOCATE_SUPPORTED 1 #endif // BSD/Mac OS X compat #if HAVE_SYS_MMAN_H #include #endif #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif /** * This class defines an interface used by KSharedDataCache::Private to offload * proper locking and unlocking depending on what the platform supports at * runtime and compile-time. */ class KSDCLock { public: virtual ~KSDCLock() { } // Return value indicates if the mutex was properly initialized (including // threads-only as a fallback). virtual bool initialize(bool &processSharingSupported) { processSharingSupported = false; return false; } virtual bool lock() { return false; } virtual void unlock() { } }; /** * This is a very basic lock that should work on any system where GCC atomic * intrinsics are supported. It can waste CPU so better primitives should be * used if available on the system. */ class simpleSpinLock : public KSDCLock { public: simpleSpinLock(QBasicAtomicInt &spinlock) : m_spinlock(spinlock) { } bool initialize(bool &processSharingSupported) override { // Clear the spinlock #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) m_spinlock.store(0); #else m_spinlock.storeRelaxed(0); #endif processSharingSupported = true; return true; } bool lock() override { // Spin a few times attempting to gain the lock, as upper-level code won't // attempt again without assuming the cache is corrupt. for (unsigned i = 50; i > 0; --i) { if (m_spinlock.testAndSetAcquire(0, 1)) { return true; } // Don't steal the processor and starve the thread we're waiting // on. loopSpinPause(); } return false; } void unlock() override { m_spinlock.testAndSetRelease(1, 0); } private: #ifdef Q_CC_GNU __attribute__((always_inline, gnu_inline #if !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG) , artificial #endif )) #endif static inline void loopSpinPause() { // TODO: Spinning might be better in multi-core systems... but that means // figuring how to find numbers of CPUs in a cross-platform way. #ifdef _POSIX_PRIORITY_SCHEDULING sched_yield(); #else // Sleep for shortest possible time (nanosleep should round-up). struct timespec wait_time = { 0 /* sec */, 100 /* ns */ }; ::nanosleep(&wait_time, static_cast(0)); #endif } QBasicAtomicInt &m_spinlock; }; #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED class pthreadLock : public KSDCLock { public: pthreadLock(pthread_mutex_t &mutex) : m_mutex(mutex) { } bool initialize(bool &processSharingSupported) override { // Setup process-sharing. pthread_mutexattr_t mutexAttr; processSharingSupported = false; // Initialize attributes, enable process-shared primitives, and setup // the mutex. if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(&mutexAttr) == 0) { if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && pthread_mutex_init(&m_mutex, &mutexAttr) == 0) { processSharingSupported = true; } pthread_mutexattr_destroy(&mutexAttr); } // Attempt to setup for thread-only synchronization. if (!processSharingSupported && pthread_mutex_init(&m_mutex, nullptr) != 0) { return false; } return true; } bool lock() override { return pthread_mutex_lock(&m_mutex) == 0; } void unlock() override { pthread_mutex_unlock(&m_mutex); } protected: pthread_mutex_t &m_mutex; }; #endif #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) class pthreadTimedLock : public pthreadLock { public: pthreadTimedLock(pthread_mutex_t &mutex) : pthreadLock(mutex) { } bool lock() override { struct timespec timeout; // Long timeout, but if we fail to meet this timeout it's probably a cache // corruption (and if we take 8 seconds then it should be much much quicker // the next time anyways since we'd be paged back in from disk) timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now timeout.tv_nsec = 0; return pthread_mutex_timedlock(&m_mutex, &timeout) == 0; } }; #endif #ifdef KSDC_SEMAPHORES_SUPPORTED class semaphoreLock : public KSDCLock { public: semaphoreLock(sem_t &semaphore) : m_semaphore(semaphore) { } bool initialize(bool &processSharingSupported) override { processSharingSupported = false; if (::sysconf(_SC_SEMAPHORES) < 200112L) { return false; } // sem_init sets up process-sharing for us. if (sem_init(&m_semaphore, 1, 1) == 0) { processSharingSupported = true; } // If not successful try falling back to thread-shared. else if (sem_init(&m_semaphore, 0, 1) != 0) { return false; } return true; } bool lock() override { return sem_wait(&m_semaphore) == 0; } void unlock() override { sem_post(&m_semaphore); } protected: sem_t &m_semaphore; }; #endif #if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) class semaphoreTimedLock : public semaphoreLock { public: semaphoreTimedLock(sem_t &semaphore) : semaphoreLock(semaphore) { } bool lock() override { struct timespec timeout; // Long timeout, but if we fail to meet this timeout it's probably a cache // corruption (and if we take 8 seconds then it should be much much quicker // the next time anyways since we'd be paged back in from disk) timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now timeout.tv_nsec = 0; return sem_timedwait(&m_semaphore, &timeout) == 0; } }; #endif // This enum controls the type of the locking used for the cache to allow // for as much portability as possible. This value will be stored in the // cache and used by multiple processes, therefore you should consider this // a versioned field, do not re-arrange. enum SharedLockId { LOCKTYPE_INVALID = 0, LOCKTYPE_MUTEX = 1, // pthread_mutex LOCKTYPE_SEMAPHORE = 2, // sem_t LOCKTYPE_SPINLOCK = 3 // atomic int in shared memory }; // This type is a union of all possible lock types, with a SharedLockId used // to choose which one is actually in use. struct SharedLock { union { #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) pthread_mutex_t mutex; #endif #if defined(KSDC_SEMAPHORES_SUPPORTED) sem_t semaphore; #endif QBasicAtomicInt spinlock; // It would be highly unfortunate if a simple glibc upgrade or kernel // addition caused this structure to change size when an existing // lock was thought present, so reserve enough size to cover any // reasonable locking structure char unused[64]; }; SharedLockId type; }; /** * This is a method to determine the best lock type to use for a * shared cache, based on local support. An identifier to the appropriate * SharedLockId is returned, which can be passed to createLockFromId(). */ static SharedLockId findBestSharedLock() { // We would prefer a process-shared capability that also supports // timeouts. Failing that, process-shared is preferred over timeout // support. Failing that we'll go thread-local bool timeoutsSupported = false; bool pthreadsProcessShared = false; bool semaphoresProcessShared = false; #ifdef KSDC_TIMEOUTS_SUPPORTED timeoutsSupported = ::sysconf(_SC_TIMEOUTS) >= 200112L; #endif // Now that we've queried timeouts, try actually creating real locks and // seeing if there's issues with that. #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED { pthread_mutex_t tempMutex; QSharedPointer tempLock; if (timeoutsSupported) { #ifdef KSDC_TIMEOUTS_SUPPORTED tempLock = QSharedPointer(new pthreadTimedLock(tempMutex)); #endif } else { tempLock = QSharedPointer(new pthreadLock(tempMutex)); } tempLock->initialize(pthreadsProcessShared); } #endif // Our first choice is pthread_mutex_t for compatibility. if (timeoutsSupported && pthreadsProcessShared) { return LOCKTYPE_MUTEX; } #ifdef KSDC_SEMAPHORES_SUPPORTED { sem_t tempSemaphore; QSharedPointer tempLock; if (timeoutsSupported) { tempLock = QSharedPointer(new semaphoreTimedLock(tempSemaphore)); } else { tempLock = QSharedPointer(new semaphoreLock(tempSemaphore)); } tempLock->initialize(semaphoresProcessShared); } #endif if (timeoutsSupported && semaphoresProcessShared) { return LOCKTYPE_SEMAPHORE; } else if (pthreadsProcessShared) { return LOCKTYPE_MUTEX; } else if (semaphoresProcessShared) { return LOCKTYPE_SEMAPHORE; } // Fallback to a dumb-simple but possibly-CPU-wasteful solution. return LOCKTYPE_SPINLOCK; } static KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock) { switch (id) { #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED case LOCKTYPE_MUTEX: #ifdef KSDC_TIMEOUTS_SUPPORTED if (::sysconf(_SC_TIMEOUTS) >= 200112L) { return new pthreadTimedLock(lock.mutex); } #endif return new pthreadLock(lock.mutex); break; #endif #ifdef KSDC_SEMAPHORES_SUPPORTED case LOCKTYPE_SEMAPHORE: #ifdef KSDC_TIMEOUTS_SUPPORTED if (::sysconf(_SC_SEMAPHORES) >= 200112L) { return new semaphoreTimedLock(lock.semaphore); } #endif return new semaphoreLock(lock.semaphore); break; #endif case LOCKTYPE_SPINLOCK: return new simpleSpinLock(lock.spinlock); break; default: qCritical() << "Creating shell of a lock!"; return new KSDCLock; } } static bool ensureFileAllocated(int fd, size_t fileSize) { #ifdef KSDC_POSIX_FALLOCATE_SUPPORTED int result; while ((result = ::posix_fallocate(fd, 0, fileSize)) == EINTR) { ; } if (result != 0) { if (result == ENOSPC) { qCritical() << "No space left on device. Check filesystem free space at your XDG_CACHE_HOME!"; } qCritical() << "The operating system is unable to promise" << fileSize << "bytes for mapped cache, " "abandoning the cache for crash-safety."; return false; } return true; #else #ifdef __GNUC__ #warning "This system does not seem to support posix_fallocate, which is needed to ensure KSharedDataCache's underlying files are fully committed to disk to avoid crashes with low disk space." #endif qCWarning(KCOREADDONS_DEBUG) << "This system misses support for posix_fallocate()" " -- ensure this partition has room for at least" << fileSize << "bytes."; // TODO: It's possible to emulate the functionality, but doing so // overwrites the data in the file so we don't do this. If you were to add // this emulation, you must ensure it only happens on initial creation of a // new file and not just mapping an existing cache. return true; #endif } #endif /* KSHAREDDATACACHE_P_H */ diff --git a/src/lib/caching/kshareddatacache_win.cpp b/src/lib/caching/kshareddatacache_win.cpp index 2b173d3..e4761f6 100644 --- a/src/lib/caching/kshareddatacache_win.cpp +++ b/src/lib/caching/kshareddatacache_win.cpp @@ -1,119 +1,108 @@ /* - * This file is part of the KDE project. - * Copyright 2010 Michael Pyne - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2010 Michael Pyne + + SPDX-License-Identifier: LGPL-2.0-only +*/ /** * This is a horrifically simple implementation of KSharedDataCache that is * basically missing the "shared" part to it, for use on Windows or other platforms * that don't support POSIX. */ #include "kshareddatacache.h" #include #include #include class Q_DECL_HIDDEN KSharedDataCache::Private { public: KSharedDataCache::EvictionPolicy evictionPolicy; QCache cache; }; KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize) : d(new Private) { d->cache.setMaxCost(defaultCacheSize); Q_UNUSED(cacheName); Q_UNUSED(expectedItemSize); } KSharedDataCache::~KSharedDataCache() { delete d; } KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const { return d->evictionPolicy; } void KSharedDataCache::setEvictionPolicy(KSharedDataCache::EvictionPolicy newPolicy) { d->evictionPolicy = newPolicy; } bool KSharedDataCache::insert(const QString &key, const QByteArray &data) { return d->cache.insert(key, new QByteArray(data)); } bool KSharedDataCache::find(const QString &key, QByteArray *destination) const { QByteArray *value = d->cache.object(key); if (value) { if (destination) { *destination = *value; } return true; } else { return false; } } void KSharedDataCache::clear() { d->cache.clear(); } void KSharedDataCache::deleteCache(const QString &cacheName) { Q_UNUSED(cacheName); } bool KSharedDataCache::contains(const QString &key) const { return d->cache.contains(key); } unsigned KSharedDataCache::totalSize() const { return static_cast(d->cache.maxCost()); } unsigned KSharedDataCache::freeSize() const { if (d->cache.totalCost() < d->cache.maxCost()) { return static_cast(d->cache.maxCost() - d->cache.totalCost()); } else { return 0; } } unsigned KSharedDataCache::timestamp() const { return 0; } void KSharedDataCache::setTimestamp(unsigned newTimestamp) { } diff --git a/src/lib/caching/posix_fallocate_mac.h b/src/lib/caching/posix_fallocate_mac.h index b4dfe07..fa52488 100644 --- a/src/lib/caching/posix_fallocate_mac.h +++ b/src/lib/caching/posix_fallocate_mac.h @@ -1,118 +1,88 @@ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla code. - * - * The Initial Developer of the Original Code is - * Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Taras Glek - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ + + SPDX-FileCopyrightText: 2010 Mozilla Foundation + SPDX-FileContributor: Taras Glek + + SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later +*/ #ifndef POSIX_FALLOCATE_MAC_H #define POSIX_FALLOCATE_MAC_H #include #include #include #include // created from the OSX-specific code from Mozilla's mozilla::fallocation() function // of which the licensing information is copied above. // Adaptation (C) 2015,2016 R.J.V. Bertin // From Linux `man posix_fallocate`: // DESCRIPTION // The function posix_fallocate() ensures that disk space is allocated for // the file referred to by the descriptor fd for the bytes in the range // starting at offset and continuing for len bytes. After a successful // call to posix_fallocate(), subsequent writes to bytes in the specified // range are guaranteed not to fail because of lack of disk space. -// +// // If the size of the file is less than offset+len, then the file is // increased to this size; otherwise the file size is left unchanged. // From OS X man fcntl: // F_PREALLOCATE Preallocate file storage space. Note: upon success, the space // that is allocated can be the same size or larger than the space // requested. // The F_PREALLOCATE command operates on the following structure: // typedef struct fstore { // u_int32_t fst_flags; /* IN: flags word */ // int fst_posmode; /* IN: indicates offset field */ // off_t fst_offset; /* IN: start of the region */ // off_t fst_length; /* IN: size of the region */ // off_t fst_bytesalloc; /* OUT: number of bytes allocated */ // } fstore_t; // The flags (fst_flags) for the F_PREALLOCATE command are as follows: // F_ALLOCATECONTIG Allocate contiguous space. // F_ALLOCATEALL Allocate all requested space or no space at all. // The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use // the offset field. The modes are as follows: // F_PEOFPOSMODE Allocate from the physical end of file. // F_VOLPOSMODE Allocate from the volume offset. // From OS X man ftruncate: // DESCRIPTION // ftruncate() and truncate() cause the file named by path, or referenced by fildes, to // be truncated (or extended) to length bytes in size. If the file size exceeds length, -// any extra data is discarded. If the file size is smaller than length, the file +// any extra data is discarded. If the file size is smaller than length, the file // extended and filled with zeros to the indicated length. The ftruncate() form requires // the file to be open for writing. // Note: ftruncate() and truncate() do not modify the current file offset for any open // file descriptions associated with the file. static int posix_fallocate(int fd, off_t offset, off_t len) { off_t c_test; int ret; if (!__builtin_saddll_overflow(offset, len, &c_test)) { fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, offset + len}; // Try to get a continuous chunk of disk space fcntl(fd, F_PREALLOCATE, &store); if (ret < 0) { // OK, perhaps we are too fragmented, allocate non-continuous store.fst_flags = F_ALLOCATEALL; ret = fcntl(fd, F_PREALLOCATE, &store); if (ret < 0) { return ret; } } ret = ftruncate(fd, offset + len); } else { // offset+len would overflow. ret = -1; } return ret; } #endif diff --git a/src/lib/io/kautosavefile.cpp b/src/lib/io/kautosavefile.cpp index 20520ea..e6725e0 100644 --- a/src/lib/io/kautosavefile.cpp +++ b/src/lib/io/kautosavefile.cpp @@ -1,246 +1,235 @@ -/* This file is part of the KDE libraries - Copyright (c) 2006 Jacob R Rideout - Copyright (c) 2015 Nick Shaforostoff - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2006 Jacob R Rideout + SPDX-FileCopyrightText: 2015 Nick Shaforostoff + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kautosavefile.h" #include // for NAME_MAX #ifdef Q_OS_WIN #include // for _MAX_FNAME static const int maxNameLength = _MAX_FNAME; #else static const int maxNameLength = NAME_MAX; #endif #include #include #include #include #include #include #include "krandom.h" #include "kcoreaddons_debug.h" class KAutoSaveFilePrivate { public: enum {NamePadding=8}; KAutoSaveFilePrivate() : lock(nullptr), managedFileNameChanged(false) {} QString tempFileName(); QUrl managedFile; QLockFile *lock; bool managedFileNameChanged; }; static QStringList findAllStales(const QString &appName) { const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QStringList files; for (const QString &dir : dirs) { QDir appDir(dir + QLatin1String("/stalefiles/") + appName); //qCDebug(KCOREADDONS_DEBUG) << "Looking in" << appDir.absolutePath(); const auto listFiles = appDir.entryList(QDir::Files); for (const QString &file : listFiles) { files << (appDir.absolutePath() + QLatin1Char('/') + file); } } return files; } QString KAutoSaveFilePrivate::tempFileName() { // Note: we drop any query string and user/pass info const QString protocol(managedFile.scheme()); const QByteArray encodedDirectory = QUrl::toPercentEncoding(managedFile.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); const QString directory = QString::fromLatin1(encodedDirectory); const QByteArray encodedFileName = QUrl::toPercentEncoding(managedFile.fileName()); QString fileName = QString::fromLatin1(encodedFileName); // Remove any part of the path to the right if it is longer than the maximum file name length; // note that "file name" in this context means the file name component only (e.g. test.txt), and // not the whole path (e.g. /home/simba/text.txt). // Ensure that the max. file name length takes into account the other parts of the tempFileName // Subtract 1 for the _ char, 3 for the padding separator, 5 is for the .lock, // 7 for QLockFile's internal code (adding tmp .rmlock) = 16 const int pathLengthLimit = maxNameLength - NamePadding - fileName.size() - protocol.size() - 16; QString junk = KRandom::randomString(NamePadding); // This is done so that the separation between the filename and path can be determined fileName += junk.rightRef(3) + protocol + QLatin1Char('_') + directory.leftRef(pathLengthLimit) + junk; return fileName; } KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent) : QFile(parent), d(new KAutoSaveFilePrivate) { setManagedFile(filename); } KAutoSaveFile::KAutoSaveFile(QObject *parent) : QFile(parent), d(new KAutoSaveFilePrivate) { } KAutoSaveFile::~KAutoSaveFile() { releaseLock(); delete d->lock; delete d; } QUrl KAutoSaveFile::managedFile() const { return d->managedFile; } void KAutoSaveFile::setManagedFile(const QUrl &filename) { releaseLock(); d->managedFile = filename; d->managedFileNameChanged = true; } void KAutoSaveFile::releaseLock() { if (d->lock && d->lock->isLocked()) { delete d->lock; d->lock = nullptr; if (!fileName().isEmpty()) { remove(); } } } bool KAutoSaveFile::open(OpenMode openmode) { if (d->managedFile.isEmpty()) { return false; } QString tempFile; if (d->managedFileNameChanged) { QString staleFilesDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/stalefiles/") + QCoreApplication::instance()->applicationName(); if (!QDir().mkpath(staleFilesDir)) { return false; } tempFile = staleFilesDir + QChar::fromLatin1('/') + d->tempFileName(); } else { tempFile = fileName(); } d->managedFileNameChanged = false; setFileName(tempFile); if (QFile::open(openmode)) { if (!d->lock) { d->lock = new QLockFile(tempFile + QLatin1String(".lock")); d->lock->setStaleLockTime(60 * 1000); // HARDCODE, 1 minute } if (d->lock->isLocked() || d->lock->tryLock()) { return true; } else { qCWarning(KCOREADDONS_DEBUG)<<"Could not lock file:"< KAutoSaveFile::staleFiles(const QUrl &filename, const QString &applicationName) { QString appName(applicationName); if (appName.isEmpty()) { appName = QCoreApplication::instance()->applicationName(); } // get stale files const QStringList files = findAllStales(appName); QList list; // contruct a KAutoSaveFile for stale files corresponding given filename for (const QString &file : files) { if (file.endsWith(QLatin1String(".lock")) || (!filename.isEmpty() && !staleMatchesManaged(QFileInfo(file).fileName(), filename))) { continue; } // sets managedFile KAutoSaveFile *asFile = new KAutoSaveFile(filename.isEmpty() ? extractManagedFilePath(file) : filename); asFile->setFileName(file); asFile->d->managedFileNameChanged = false; // do not regenerate tempfile name list.append(asFile); } return list; } QList KAutoSaveFile::allStaleFiles(const QString &applicationName) { return staleFiles(QUrl(), applicationName); } #include "moc_kautosavefile.cpp" diff --git a/src/lib/io/kautosavefile.h b/src/lib/io/kautosavefile.h index 658fe05..577eb63 100644 --- a/src/lib/io/kautosavefile.h +++ b/src/lib/io/kautosavefile.h @@ -1,248 +1,237 @@ -/* This file is part of the KDE libraries - Copyright (c) 2006 Jacob R Rideout - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2006 Jacob R Rideout + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KAUTOSAVEFILE_H #define KAUTOSAVEFILE_H #include #include #include #include class KAutoSaveFilePrivate; /** * \class KAutoSaveFile kautosavefile.h * * @brief Creates and manages a temporary "auto-save" file. * Autosave files are temporary files that applications use to store * the unsaved data in a file they have open for * editing. KAutoSaveFile allows you to easily create and manage such * files, as well as to recover the unsaved data left over by a * crashed or otherwise gone process. * * Each KAutoSaveFile object is associated with one specific file that * the application holds open. KAutoSaveFile is also a QObject, so it * can be reparented to the actual opened file object, so as to manage * the lifetime of the temporary file. * * Typical use consists of: * - verifying whether stale autosave files exist for the opened file * - deciding whether to recover the old, autosaved data * - if not recovering, creating a KAutoSaveFile object for the opened file * - during normal execution of the program, periodically save unsaved * data into the KAutoSaveFile file. * * KAutoSaveFile holds a lock on the autosave file, so it's safe to * delete the file and recreate it later. Because of that, disposing * of stale autosave files should be done with releaseLock(). No lock is * held on the managed file. * * Examples: * Opening a new file: * @code * void Document::open(const QUrl &url) * { * // check whether autosave files exist: * const QList staleFiles = KAutoSaveFile::staleFiles(url); * if (!staleFiles.isEmpty()) { * if (KMessageBox::questionYesNo(parent, * "Auto-saved files exist. Do you want to recover them now?", * "File Recovery", * "Recover", "Don't recover") == KMessage::Yes) { * recoverFiles(staleFiles); * return; * } else { * // remove the stale files * for (KAutoSaveFile *stale : staleFiles) { * stale->open(QIODevice::ReadWrite); * delete stale; * } * } * } * * // create new autosave object * m_autosave = new KAutoSaveFile(url, this); * * // continue the process of opening file 'url' * ... * } * @endcode * * The function recoverFiles could loop over the list of files and do this: * @code * for (KAutoSaveFile *stale : staleFiles) { * if (!stale->open(QIODevice::ReadWrite)) { * // show an error message; we could not steal the lockfile * // maybe another application got to the file before us? * delete stale; * continue; * } * Document *doc = new Document; * doc->m_autosave = stale; * stale->setParent(doc); // reparent * * doc->setUrl(stale->managedFile()); * doc->setContents(stale->readAll()); * doc->setState(Document::Modified); // mark it as modified and unsaved * * documentManager->addDocument(doc); * } * @endcode * * If the file is unsaved, periodically write the contents to the save file: * @code * if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) { * // show error: could not open the autosave file * } * m_autosave->write(contents()); * @endcode * * When the user saves the file, the autosaved file is no longer * necessary and can be removed or emptied. * @code * m_autosave->resize(0); // leaves the file open * @endcode * * @code * m_autosave->remove(); // closes the file * @endcode * * @author Jacob R Rideout */ class KCOREADDONS_EXPORT KAutoSaveFile : public QFile { Q_OBJECT public: /** * Constructs a KAutoSaveFile for file @p filename. The temporary * file is not opened or created until actually needed. The file * @p filename does not have to exist for KAutoSaveFile to be * constructed (if it exists, it will not be touched). * * @param filename the filename that this KAutoSaveFile refers to * @param parent the parent object */ explicit KAutoSaveFile(const QUrl &filename, QObject *parent = nullptr); /** * @overload * Constructs a KAutoSaveFile object. Note that you need to call * setManagedFile() before calling open(). * * @param parent the parent object */ explicit KAutoSaveFile(QObject *parent = nullptr); /** * Destroys the KAutoSaveFile object, removes the autosave * file and drops the lock being held (if any). */ ~KAutoSaveFile() override; /** * Retrieves the URL of the file managed by KAutoSaveFile. This * is the same URL that was given to setManagedFile() or the * KAutoSaveFile constructor. * * This is the name of the real file being edited by the * application. To get the name of the temporary file where data * can be saved, use fileName() (after you have called open()). */ QUrl managedFile() const; /** * Sets the URL of the file managed by KAutoSaveFile. This should * be the name of the real file being edited by the application. * If the file was previously set, this function calls releaseLock(). * * @param filename the filename that this KAutoSaveFile refers to */ void setManagedFile(const QUrl &filename); /** * Closes the autosave file resource and removes the lock * file. The file name returned by fileName() will no longer be * protected and can be overwritten by another application at any * time. To obtain a new lock, call open() again. * * This function calls remove(), so the autosave temporary file * will be removed too. */ virtual void releaseLock(); /** * Opens the autosave file and locks it if it wasn't already * locked. The name of the temporary file where data can be saved * to will be set by this function and can be retrieved with * fileName(). It will not change unless releaseLock() is called. No * other application will attempt to edit such a file either while * the lock is held. * * @param openmode the mode that should be used to open the file, * probably QIODevice::ReadWrite * @returns true if the file could be opened (= locked and * created), false if the operation failed */ bool open(OpenMode openmode) override; /** * Checks for stale autosave files for the file @p url. Returns a list * of autosave files that contain autosaved data left behind by * other instances of the application, due to crashing or * otherwise uncleanly exiting. * * It is the application's job to determine what to do with such * unsaved data. Generally, this is done by asking the user if he * wants to see the recovered data, and then allowing the user to * save if he wants to. * * If not given, the application name is obtained from * QCoreApplication, so be sure to have set it correctly before * calling this function. * * This function returns a list of unopened KAutoSaveFile * objects. By calling open() on them, the application will steal * the lock. Subsequent releaseLock() or deleting of the object will * then erase the stale autosave file. */ static QList staleFiles(const QUrl &url, const QString &applicationName = QString()); /** * Returns all stale autosave files left behind by crashed or * otherwise gone instances of this application. * * If not given, the application name is obtained from * QCoreApplication, so be sure to have set it correctly before * calling this function. * * See staleFiles() for information on the returned objects. */ static QList allStaleFiles(const QString &applicationName = QString()); private: Q_DISABLE_COPY(KAutoSaveFile) friend class KAutoSaveFilePrivate; KAutoSaveFilePrivate *const d; }; #endif // KAUTOSAVEFILE_H diff --git a/src/lib/io/kbackup.cpp b/src/lib/io/kbackup.cpp index a1e7cdd..2d826ca 100644 --- a/src/lib/io/kbackup.cpp +++ b/src/lib/io/kbackup.cpp @@ -1,208 +1,197 @@ /* - This file is part of the KDE libraries - Copyright 1999 Waldo Bastian - Copyright 2006 Allen Winter - Copyright 2006 Gregory S. Hayes - Copyright 2006 Jaison Lee - Copyright 2011 Romain Perier - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Waldo Bastian + SPDX-FileCopyrightText: 2006 Allen Winter + SPDX-FileCopyrightText: 2006 Gregory S. Hayes + SPDX-FileCopyrightText: 2006 Jaison Lee + SPDX-FileCopyrightText: 2011 Romain Perier + + SPDX-License-Identifier: LGPL-2.0-only */ #include "kbackup.h" #include #include #include #include namespace KBackup { bool backupFile(const QString &qFilename, const QString &backupDir) { // get backup type from config, by default use "simple" // get extension from config, by default use "~" // get max number of backups from config, by default set to 10 #pragma message("KDE5 TODO: Remove KConfig correctly") #if 0 KConfigGroup g(KSharedConfig::openConfig(), "Backups"); // look in the Backups section QString type = g.readEntry("Type", "simple"); QString extension = g.readEntry("Extension", "~"); QString message = g.readEntry("Message", "Automated KDE Commit"); int maxnum = g.readEntry("MaxBackups", 10); if (type.toLower() == QLatin1String("numbered")) { return (numberedBackupFile(qFilename, backupDir, extension, maxnum)); } else if (type.toLower() == QLatin1String("rcs")) { return (rcsBackupFile(qFilename, backupDir, message)); } else { return (simpleBackupFile(qFilename, backupDir, extension)); } #endif return (simpleBackupFile(qFilename, backupDir, QStringLiteral("~"))); } bool simpleBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension) { QString backupFileName = qFilename + backupExtension; if (!backupDir.isEmpty()) { QFileInfo fileInfo(qFilename); backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension; } // qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << backupFileName; QFile::remove(backupFileName); return QFile::copy(qFilename, backupFileName); } bool rcsBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupMessage) { QFileInfo fileInfo(qFilename); QString qBackupFilename; if (backupDir.isEmpty()) { qBackupFilename = qFilename; } else { qBackupFilename = backupDir + fileInfo.fileName(); } qBackupFilename += QLatin1String(",v"); // If backupDir is specified, copy qFilename to the // backupDir and perform the commit there, unlinking // backupDir/qFilename when finished. if (!backupDir.isEmpty()) { if (!QFile::copy(qFilename, backupDir + fileInfo.fileName())) { return false; } fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName()); } const QString cipath = QStandardPaths::findExecutable(QStringLiteral("ci")); const QString copath = QStandardPaths::findExecutable(QStringLiteral("co")); const QString rcspath = QStandardPaths::findExecutable(QStringLiteral("rcs")); if (cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty()) { return false; } // Check in the file unlocked with 'ci' QProcess ci; if (!backupDir.isEmpty()) { ci.setWorkingDirectory(backupDir); } ci.start(cipath, QStringList { QStringLiteral("-u"), fileInfo.filePath() }); if (!ci.waitForStarted()) { return false; } ci.write(backupMessage.toLocal8Bit()); ci.write("."); ci.closeWriteChannel(); if (!ci.waitForFinished()) { return false; } // Use 'rcs' to unset strict locking QProcess rcs; if (!backupDir.isEmpty()) { rcs.setWorkingDirectory(backupDir); } rcs.start(rcspath, QStringList { QStringLiteral("-U"), qBackupFilename }); if (!rcs.waitForFinished()) { return false; } // Use 'co' to checkout the current revision and restore permissions QProcess co; if (!backupDir.isEmpty()) { co.setWorkingDirectory(backupDir); } co.start(copath, QStringList { qBackupFilename }); if (!co.waitForFinished()) { return false; } if (!backupDir.isEmpty()) { return QFile::remove(fileInfo.filePath()); } else { return true; } } bool numberedBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension, const uint maxBackups) { QFileInfo fileInfo(qFilename); // The backup file name template. QString sTemplate; if (backupDir.isEmpty()) { sTemplate = qFilename + QLatin1String(".%1") + backupExtension; } else { sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension; } // First, search backupDir for numbered backup files to remove. // Remove all with number 'maxBackups' and greater. QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir; d.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); const QStringList nameFilters = QStringList(fileInfo.fileName() + QLatin1String(".*") + backupExtension); d.setNameFilters(nameFilters); d.setSorting(QDir::Name); uint maxBackupFound = 0; const QFileInfoList infoList = d.entryInfoList(); for (const QFileInfo &fi : infoList) { if (fi.fileName().endsWith(backupExtension)) { // sTemp holds the file name, without the ending backupExtension QString sTemp = fi.fileName(); sTemp.truncate(fi.fileName().length() - backupExtension.length()); // compute the backup number int idex = sTemp.lastIndexOf(QLatin1Char('.')); if (idex > 0) { bool ok; uint num = sTemp.midRef(idex + 1).toUInt(&ok); if (ok) { if (num >= maxBackups) { QFile::remove(fi.filePath()); } else { maxBackupFound = qMax(maxBackupFound, num); } } } } } // Next, rename max-1 to max, max-2 to max-1, etc. QString to = sTemplate.arg(maxBackupFound + 1); for (int i = maxBackupFound; i > 0; i--) { QString from = sTemplate.arg(i); // qCDebug(KCOREADDONS_DEBUG) << "KBackup renaming " << from << " to " << to; QFile::rename(from, to); to = from; } // Finally create most recent backup by copying the file to backup number 1. // qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << sTemplate.arg(1); return QFile::copy(qFilename, sTemplate.arg(1)); } } diff --git a/src/lib/io/kbackup.h b/src/lib/io/kbackup.h index 84b85ac..09b5807 100644 --- a/src/lib/io/kbackup.h +++ b/src/lib/io/kbackup.h @@ -1,126 +1,115 @@ /* - This file is part of the KDE libraries - Copyright 1999 Waldo Bastian - Copyright 2006 Jaison Lee - Copyright 2011 Romain Perier + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 1999 Waldo Bastian + SPDX-FileCopyrightText: 2006 Jaison Lee + SPDX-FileCopyrightText: 2011 Romain Perier - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KBACKUP_H #define KBACKUP_H #include #include /** * @namespace KBackup * Provides utility functions for backup of files. */ namespace KBackup { /** * @brief Function to create a backup file before saving. * * If empty (the default), the backup will be in the same directory as @p filename. * The backup type (simple, rcs, or numbered), extension string, and maximum * number of backup files are read from the user's global configuration. * Use simpleBackupFile() or numberedBackupFile() to force one of these * specific backup styles. * You can use this method even if you don't use KSaveFile. * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool backupFile(const QString &filename, const QString &backupDir = QString()); /** * @brief Function to create a backup file for a given filename. * * This function creates a backup file from the given filename. * You can use this method even if you don't use KSaveFile. * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as @p filename. * @param backupExtension the extension to append to @p filename, "~" by default. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool simpleBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupExtension = QStringLiteral("~")); /** * @brief Function to create a backup file for a given filename. * * This function creates a series of numbered backup files from the * given filename. * * The backup file names will be of the form: * \.\\ * for instance * \verbatim chat.3.log \endverbatim * * The new backup file will be have the backup number 1. * Each existing backup file will have its number incremented by 1. * Any backup files with numbers greater than the maximum number * permitted (@p maxBackups) will be removed. * You can use this method even if you don't use KSaveFile. * * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as * @p filename. * @param backupExtension the extension to append to @p filename, * which is "~" by default. Do not use an extension containing digits. * @param maxBackups the maximum number of backup files permitted. * For best performance a small number (10) is recommended. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool numberedBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupExtension = QStringLiteral("~"), const uint maxBackups = 10 ); /** * @brief Function to create an rcs backup file for a given filename. * * This function creates a rcs-formatted backup file from the * given filename. * * The backup file names will be of the form: * \,v * for instance * \verbatim photo.jpg,v \endverbatim * * The new backup file will be in RCS format. * Each existing backup file will be committed as a new revision. * You can use this method even if you don't use KSaveFile. * * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as * @p filename. * @param backupMessage is the RCS commit message for this revision. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool rcsBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupMessage = QString() ); } #endif diff --git a/src/lib/io/kdirwatch.cpp b/src/lib/io/kdirwatch.cpp index c5b1eeb..6b0a53b 100644 --- a/src/lib/io/kdirwatch.cpp +++ b/src/lib/io/kdirwatch.cpp @@ -1,2063 +1,2051 @@ /* This file is part of the KDE libraries - Copyright (C) 1998 Sven Radej - Copyright (C) 2006 Dirk Mueller - Copyright (C) 2007 Flavio Castelli - Copyright (C) 2008 Rafal Rzepecki - Copyright (C) 2010 David Faure - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 1998 Sven Radej + SPDX-FileCopyrightText: 2006 Dirk Mueller + SPDX-FileCopyrightText: 2007 Flavio Castelli + SPDX-FileCopyrightText: 2008 Rafal Rzepecki + SPDX-FileCopyrightText: 2010 David Faure + + SPDX-License-Identifier: LGPL-2.0-only */ // CHANGES: // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal) // Aug 6, 2007 - KDirWatch::WatchModes support complete, flags work fine also // when using FAMD (Flavio Castelli) // Aug 3, 2007 - Handled KDirWatch::WatchModes flags when using inotify, now // recursive and file monitoring modes are implemented (Flavio Castelli) // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes // flag (Flavio Castelli) // Oct 4, 2005 - Inotify support (Dirk Mueller) // February 2002 - Add file watching and remote mount check for STAT // Mar 30, 2001 - Native support for Linux dir change notification. // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de) // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven) // May 23. 1998 - Removed static pointer - you can have more instances. // It was Needed for KRegistry. KDirWatch now emits signals and doesn't // call (or need) KFM. No more URL's - just plain paths. (sven) // Mar 29. 1998 - added docs, stop/restart for particular Dirs and // deep copies for list of dirs. (sven) // Mar 28. 1998 - Created. (sven) #include "kdirwatch.h" #include "kdirwatch_p.h" #include "kfilesystemtype.h" #include "kcoreaddons_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // QT_LSTAT, QT_STAT, QT_STATBUF #include #include #if HAVE_SYS_INOTIFY_H #include #include #include #ifndef IN_DONT_FOLLOW #define IN_DONT_FOLLOW 0x02000000 #endif #ifndef IN_ONLYDIR #define IN_ONLYDIR 0x01000000 #endif // debug #include #include #endif // HAVE_SYS_INOTIFY_H Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(KDIRWATCH, "kf5.kcoreaddons.kdirwatch", QtWarningMsg) // set this to true for much more verbose debug output static bool s_verboseDebug = false; static QThreadStorage dwp_self; static KDirWatchPrivate *createPrivate() { if (!dwp_self.hasLocalData()) { dwp_self.setLocalData(new KDirWatchPrivate); } return dwp_self.localData(); } // Convert a string into a watch Method static KDirWatch::Method methodFromString(const QByteArray &method) { if (method == "Fam") { return KDirWatch::FAM; } else if (method == "Stat") { return KDirWatch::Stat; } else if (method == "QFSWatch") { return KDirWatch::QFSWatch; } else { #if defined(HAVE_SYS_INOTIFY_H) // inotify supports delete+recreate+modify, which QFSWatch doesn't support return KDirWatch::INotify; #else return KDirWatch::QFSWatch; #endif } } static const char *methodToString(KDirWatch::Method method) { switch (method) { case KDirWatch::FAM: return "Fam"; case KDirWatch::INotify: return "INotify"; case KDirWatch::Stat: return "Stat"; case KDirWatch::QFSWatch: return "QFSWatch"; } // not reached return nullptr; } static const char s_envNfsPoll[] = "KDIRWATCH_NFSPOLLINTERVAL"; static const char s_envPoll[] = "KDIRWATCH_POLLINTERVAL"; static const char s_envMethod[] = "KDIRWATCH_METHOD"; static const char s_envNfsMethod[] = "KDIRWATCH_NFSMETHOD"; // // Class KDirWatchPrivate (singleton) // /* All entries (files/directories) to be watched in the * application (coming from multiple KDirWatch instances) * are registered in a single KDirWatchPrivate instance. * * At the moment, the following methods for file watching * are supported: * - Polling: All files to be watched are polled regularly * using stat (more precise: QFileInfo.lastModified()). * The polling frequency is determined from global kconfig * settings, defaulting to 500 ms for local directories * and 5000 ms for remote mounts * - FAM (File Alternation Monitor): first used on IRIX, SGI * has ported this method to LINUX. It uses a kernel part * (IMON, sending change events to /dev/imon) and a user * level daemon (fam), to which applications connect for * notification of file changes. For NFS, the fam daemon * on the NFS server machine is used; if IMON is not built * into the kernel, fam uses polling for local files. * - INOTIFY: In LINUX 2.6.13, inode change notification was * introduced. You're now able to watch arbitrary inode's * for changes, and even get notification when they're * unmounted. */ KDirWatchPrivate::KDirWatchPrivate() : timer(), freq(3600000), // 1 hour as upper bound statEntries(0), delayRemove(false), rescan_all(false), rescan_timer(), #if HAVE_SYS_INOTIFY_H mSn(nullptr), #endif _isStopped(false) { // Debug unittest on CI if (qAppName() == QLatin1String("kservicetest") || qAppName() == QLatin1String("filetypestest")) { s_verboseDebug = true; } timer.setObjectName(QStringLiteral("KDirWatchPrivate::timer")); connect(&timer, SIGNAL(timeout()), this, SLOT(slotRescan())); m_nfsPollInterval = qEnvironmentVariableIsSet(s_envNfsPoll) ? qEnvironmentVariableIntValue(s_envNfsPoll) : 5000; m_PollInterval = qEnvironmentVariableIsSet(s_envPoll) ? qEnvironmentVariableIntValue(s_envPoll) : 500; m_preferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envMethod) ? qgetenv(s_envMethod) : "inotify"); // The nfs method defaults to the normal (local) method m_nfsPreferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envNfsMethod) ? qgetenv(s_envNfsMethod) : "Fam"); QList availableMethods; availableMethods << "Stat"; // used for FAM and inotify rescan_timer.setObjectName(QStringLiteral("KDirWatchPrivate::rescan_timer")); rescan_timer.setSingleShot(true); connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan())); #if HAVE_FAM availableMethods << "FAM"; use_fam = true; sn = nullptr; #endif #if HAVE_SYS_INOTIFY_H supports_inotify = true; m_inotify_fd = inotify_init(); if (m_inotify_fd <= 0) { qCDebug(KDIRWATCH) << "Can't use Inotify, kernel doesn't support it:" << strerror(errno); supports_inotify = false; } //qCDebug(KDIRWATCH) << "INotify available: " << supports_inotify; if (supports_inotify) { availableMethods << "INotify"; (void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC); mSn = new QSocketNotifier(m_inotify_fd, QSocketNotifier::Read, this); connect(mSn, SIGNAL(activated(int)), this, SLOT(inotifyEventReceived())); } #endif #if HAVE_QFILESYSTEMWATCHER availableMethods << "QFileSystemWatcher"; fsWatcher = nullptr; #endif if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod); } } // This is called on app exit (deleted by QThreadStorage) KDirWatchPrivate::~KDirWatchPrivate() { timer.stop(); #if HAVE_FAM if (use_fam && sn) { FAMClose(&fc); } #endif #if HAVE_SYS_INOTIFY_H if (supports_inotify) { QT_CLOSE(m_inotify_fd); } #endif #if HAVE_QFILESYSTEMWATCHER delete fsWatcher; #endif } void KDirWatchPrivate::inotifyEventReceived() { //qCDebug(KDIRWATCH); #if HAVE_SYS_INOTIFY_H if (!supports_inotify) { return; } int pending = -1; int offsetStartRead = 0; // where we read into buffer char buf[8192]; assert(m_inotify_fd > -1); ioctl(m_inotify_fd, FIONREAD, &pending); while (pending > 0) { const int bytesToRead = qMin(pending, sizeof(buf) - offsetStartRead); int bytesAvailable = read(m_inotify_fd, &buf[offsetStartRead], bytesToRead); pending -= bytesAvailable; bytesAvailable += offsetStartRead; offsetStartRead = 0; int offsetCurrent = 0; while (bytesAvailable >= int(sizeof(struct inotify_event))) { const struct inotify_event *const event = reinterpret_cast(&buf[offsetCurrent]); const int eventSize = sizeof(struct inotify_event) + event->len; if (bytesAvailable < eventSize) { break; } bytesAvailable -= eventSize; offsetCurrent += eventSize; QString path; // strip trailing null chars, see inotify_event documentation // these must not end up in the final QString version of path int len = event->len; while (len > 1 && !event->name[len - 1]) { --len; } QByteArray cpath(event->name, len); if (len) { path = QFile::decodeName(cpath); } if (!path.isEmpty() && isNoisyFile(cpath.data())) { continue; } // Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir const bool isDir = (event->mask & (IN_ISDIR)); Entry *e = m_inotify_wd_to_entry.value(event->wd); if (!e) { continue; } const bool wasDirty = e->dirty; e->dirty = true; const QString tpath = e->path + QLatin1Char('/') + path; if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "got event 0x" << qPrintable(QString::number(event->mask, 16)) << " for " << e->path; } if (event->mask & IN_DELETE_SELF) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got deleteself signal for" << e->path; } e->m_status = NonExistent; m_inotify_wd_to_entry.remove(e->wd); e->wd = -1; e->m_ctime = invalid_ctime; emitEvent(e, Deleted, e->path); // If the parent dir was already watched, tell it something changed Entry *parentEntry = entry(e->parentDirectory()); if (parentEntry) { parentEntry->dirty = true; } // Add entry to parent dir to notice if the entry gets recreated addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); } if (event->mask & IN_IGNORED) { // Causes bug #207361 with kernels 2.6.31 and 2.6.32! //e->wd = -1; } if (event->mask & (IN_CREATE | IN_MOVED_TO)) { Entry *sub_entry = e->findSubEntry(tpath); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry; qCDebug(KDIRWATCH) << *e; } // The code below is very similar to the one in checkFAMEvent... if (sub_entry) { // We were waiting for this new file/dir to be created sub_entry->dirty = true; rescan_timer.start(0); // process this asap, to start watching that dir } else if (e->isDir && !e->m_clients.empty()) { const QList clients = e->inotifyClientsForFileOrDir(isDir); // See discussion in addEntry for why we don't addEntry for individual // files in WatchFiles mode with inotify. if (isDir) { for (const Client *client : clients) { addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); } } if (!clients.isEmpty()) { emitEvent(e, Created, tpath); qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath; } e->m_pendingFileChanges.append(e->path); if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } } } if (event->mask & (IN_DELETE | IN_MOVED_FROM)) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got DELETE signal for" << tpath; } if ((e->isDir) && (!e->m_clients.empty())) { // A file in this directory has been removed. It wasn't an explicitly // watched file as it would have its own watch descriptor, so // no addEntry/ removeEntry bookkeeping should be required. Emit // the event immediately if any clients are interested. KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; int counter = 0; for (const Client &client : e->m_clients) { if (client.m_watchModes & flag) { counter++; } } if (counter != 0) { emitEvent(e, Deleted, tpath); } } } if (event->mask & (IN_MODIFY | IN_ATTRIB)) { if ((e->isDir) && (!e->m_clients.empty())) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got MODIFY signal for" << (tpath); } // A file in this directory has been changed. No // addEntry/ removeEntry bookkeeping should be required. // Add the path to the list of pending file changes if // there are any interested clients. //QT_STATBUF stat_buf; //QByteArray tpath = QFile::encodeName(e->path+'/'+path); //QT_STAT(tpath, &stat_buf); //bool isDir = S_ISDIR(stat_buf.st_mode); // The API doc is somewhat vague as to whether we should emit // dirty() for implicitly watched files when WatchFiles has // not been specified - we'll assume they are always interested, // regardless. // Don't worry about duplicates for the time // being; this is handled in slotRescan. e->m_pendingFileChanges.append(tpath); // Avoid stat'ing the directory if only an entry inside it changed. e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB))); } } if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } } if (bytesAvailable > 0) { // copy partial event to beginning of buffer memmove(buf, &buf[offsetCurrent], bytesAvailable); offsetStartRead = bytesAvailable; } } #endif } KDirWatchPrivate::Entry::~Entry() { } /* In FAM mode, only entries which are marked dirty are scanned. * We first need to mark all yet nonexistent, but possible created * entries as dirty... */ void KDirWatchPrivate::Entry::propagate_dirty() { for (Entry *sub_entry : qAsConst(m_entries)) { if (!sub_entry->dirty) { sub_entry->dirty = true; sub_entry->propagate_dirty(); } } } /* A KDirWatch instance is interested in getting events for * this file/Dir entry. */ void KDirWatchPrivate::Entry::addClient(KDirWatch *instance, KDirWatch::WatchModes watchModes) { if (instance == nullptr) { return; } for (Client &client : m_clients) { if (client.instance == instance) { client.count++; client.m_watchModes = watchModes; return; } } m_clients.emplace_back(instance, watchModes); } void KDirWatchPrivate::Entry::removeClient(KDirWatch *instance) { auto it = m_clients.begin(); const auto end = m_clients.end(); for (; it != end; ++it) { Client &client = *it; if (client.instance == instance) { client.count--; if (client.count == 0) { m_clients.erase(it); } return; } } } /* get number of clients */ int KDirWatchPrivate::Entry::clientCount() const { int clients = 0; for (const Client &client : m_clients) { clients += client.count; } return clients; } QString KDirWatchPrivate::Entry::parentDirectory() const { return QDir::cleanPath(path + QLatin1String("/..")); } QList KDirWatchPrivate::Entry::clientsForFileOrDir(const QString &tpath, bool *isDir) const { QList ret; QFileInfo fi(tpath); if (fi.exists()) { *isDir = fi.isDir(); const KDirWatch::WatchModes flag = *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; for (const Client &client : m_clients) { if (client.m_watchModes & flag) { ret.append(&client); } } } else { // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp" //qCDebug(KDIRWATCH) << "ERROR: couldn't stat" << tpath; // In this case isDir is not set, but ret is empty anyway // so isDir won't be used. } return ret; } // inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder. // isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived QList KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const { QList ret; const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; for (const Client &client : m_clients) { if (client.m_watchModes & flag) { ret.append(&client); } } return ret; } QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry) { debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file"); if (entry.m_status == KDirWatchPrivate::NonExistent) { debug << ", non-existent"; } debug << ", using " << ((entry.m_mode == KDirWatchPrivate::FAMMode) ? "FAM" : (entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" : (entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" : "Unknown Method"); #if HAVE_SYS_INOTIFY_H if (entry.m_mode == KDirWatchPrivate::INotifyMode) { debug << " inotify_wd=" << entry.wd; } #endif debug << ", has " << entry.m_clients.size() << " clients"; debug.space(); if (!entry.m_entries.isEmpty()) { debug << ", nonexistent subentries:"; for (KDirWatchPrivate::Entry *subEntry : qAsConst(entry.m_entries)) { debug << subEntry << subEntry->path; } } debug << ']'; return debug; } KDirWatchPrivate::Entry *KDirWatchPrivate::entry(const QString &_path) { // we only support absolute paths if (_path.isEmpty() || QDir::isRelativePath(_path)) { return nullptr; } QString path(_path); if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { path.chop(1); } EntryMap::Iterator it = m_mapEntries.find(path); if (it == m_mapEntries.end()) { return nullptr; } else { return &(*it); } } // set polling frequency for a entry and adjust global freq if needed void KDirWatchPrivate::useFreq(Entry *e, int newFreq) { e->freq = newFreq; // a reasonable frequency for the global polling timer if (e->freq < freq) { freq = e->freq; if (timer.isActive()) { timer.start(freq); } qCDebug(KDIRWATCH) << "Global Poll Freq is now" << freq << "msec"; } } #if HAVE_FAM // setup FAM notification, returns false if not possible bool KDirWatchPrivate::useFAM(Entry *e) { if (!use_fam) { return false; } if (!sn) { if (FAMOpen(&fc) == 0) { sn = new QSocketNotifier(FAMCONNECTION_GETFD(&fc), QSocketNotifier::Read, this); connect(sn, SIGNAL(activated(int)), this, SLOT(famEventReceived())); } else { use_fam = false; return false; } } // handle FAM events to avoid deadlock // (FAM sends back all files in a directory when monitoring) famEventReceived(); e->m_mode = FAMMode; e->dirty = false; e->m_famReportedSeen = false; bool startedFAMMonitor = false; if (e->isDir) { if (e->m_status == NonExistent) { // If the directory does not exist we watch the parent directory addEntry(nullptr, e->parentDirectory(), e, true); } else { int res = FAMMonitorDirectory(&fc, QFile::encodeName(e->path).data(), &(e->fr), e); startedFAMMonitor = true; if (res < 0) { e->m_mode = UnknownMode; use_fam = false; delete sn; sn = nullptr; return false; } qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } } else { if (e->m_status == NonExistent) { // If the file does not exist we watch the directory addEntry(nullptr, QFileInfo(e->path).absolutePath(), e, true); } else { int res = FAMMonitorFile(&fc, QFile::encodeName(e->path).data(), &(e->fr), e); startedFAMMonitor = true; if (res < 0) { e->m_mode = UnknownMode; use_fam = false; delete sn; sn = nullptr; return false; } qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } } // handle FAM events to avoid deadlock // (FAM sends back all files in a directory when monitoring) do { famEventReceived(); if (startedFAMMonitor && !e->m_famReportedSeen) { // 50 is ~half the time it takes to setup a watch. If gamin's latency // gets better, this can be reduced. QThread::msleep(50); } } while (startedFAMMonitor &&!e->m_famReportedSeen); return true; } #endif #if HAVE_SYS_INOTIFY_H // setup INotify notification, returns false if not possible bool KDirWatchPrivate::useINotify(Entry *e) { //qCDebug(KDIRWATCH) << "trying to use inotify for monitoring"; e->wd = -1; e->dirty = false; if (!supports_inotify) { return false; } e->m_mode = INotifyMode; if (e->m_status == NonExistent) { addEntry(nullptr, e->parentDirectory(), e, true); return true; } // May as well register for almost everything - it's free! int mask = IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_MOVED_FROM | IN_MODIFY | IN_ATTRIB; if ((e->wd = inotify_add_watch(m_inotify_fd, QFile::encodeName(e->path).data(), mask)) != -1) { m_inotify_wd_to_entry.insert(e->wd, e); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd; } return true; } if (errno == ENOSPC) { // Inotify max_user_watches was reached (/proc/sys/fs/inotify/max_user_watches) // See man inotify_add_watch, https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers qCWarning(KDIRWATCH) << "inotify failed for monitoring" << e->path << "\n" << "Because it reached its max_user_watches,\n" << "you can increase the maximum number of file watches per user,\n"<< "by setting an appropriate fs.inotify.max_user_watches parameter in your /etc/sysctl.conf"; } else { qCDebug(KDIRWATCH) << "inotify failed for monitoring" << e->path << ":" << strerror(errno) << " (errno:" << errno << ")"; } return false; } #endif #if HAVE_QFILESYSTEMWATCHER bool KDirWatchPrivate::useQFSWatch(Entry *e) { e->m_mode = QFSWatchMode; e->dirty = false; if (e->m_status == NonExistent) { addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); return true; } //qCDebug(KDIRWATCH) << "fsWatcher->addPath" << e->path; if (!fsWatcher) { fsWatcher = new QFileSystemWatcher(); connect(fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(fswEventReceived(QString))); connect(fsWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fswEventReceived(QString))); } fsWatcher->addPath(e->path); return true; } #endif bool KDirWatchPrivate::useStat(Entry *e) { if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { // TODO: or Smbfs? useFreq(e, m_nfsPollInterval); } else { useFreq(e, m_PollInterval); } if (e->m_mode != StatMode) { e->m_mode = StatMode; statEntries++; if (statEntries == 1) { // if this was first STAT entry (=timer was stopped) timer.start(freq); // then start the timer qCDebug(KDIRWATCH) << " Started Polling Timer, freq " << freq; } } qCDebug(KDIRWATCH) << " Setup Stat (freq " << e->freq << ") for " << e->path; return true; } /* If !=0, this KDirWatch instance wants to watch at <_path>, * providing in the type of the entry to be watched. * Sometimes, entries are dependent on each other: if !=0, * this entry needs another entry to watch itself (when notExistent). */ void KDirWatchPrivate::addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes) { QString path(_path); if (path.startsWith(QLatin1String(":/"))) { qCWarning(KDIRWATCH) << "Cannot watch QRC-like path" << path; return; } if (path.isEmpty() #ifndef Q_OS_WIN || path == QLatin1String("/dev") || (path.startsWith(QLatin1String("/dev/")) && !path.startsWith(QLatin1String("/dev/.")) && !path.startsWith(QLatin1String("/dev/shm"))) #endif ) { return; // Don't even go there. } if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { path.chop(1); } EntryMap::Iterator it = m_mapEntries.find(path); if (it != m_mapEntries.end()) { if (sub_entry) { (*it).m_entries.append(sub_entry); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(for" << sub_entry->path << ")"; } } else { (*it).addClient(instance, watchModes); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(now" << (*it).clientCount() << "clients)" << QStringLiteral("[%1]").arg(instance->objectName()); } } return; } // we have a new path to watch QT_STATBUF stat_buf; bool exists = (QT_STAT(QFile::encodeName(path).constData(), &stat_buf) == 0); EntryMap::iterator newIt = m_mapEntries.insert(path, Entry()); // the insert does a copy, so we have to use now Entry *e = &(*newIt); if (exists) { e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR; #ifndef Q_OS_WIN if (e->isDir && !isDir) { if (QT_LSTAT(QFile::encodeName(path).constData(), &stat_buf) == 0) { if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { // if it's a symlink, don't follow it e->isDir = false; } } } #endif if (e->isDir && !isDir) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a directory. Use addDir!"; } else if (!e->isDir && isDir) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. Use addFile!"; } if (!e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. You can't use recursive or " "watchFiles options"; watchModes = KDirWatch::WatchDirOnly; } #ifdef Q_OS_WIN // ctime is the 'creation time' on windows - use mtime instead e->m_ctime = stat_buf.st_mtime; #else e->m_ctime = stat_buf.st_ctime; #endif e->m_status = Normal; e->m_nlink = stat_buf.st_nlink; e->m_ino = stat_buf.st_ino; } else { e->isDir = isDir; e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; e->m_ino = 0; } e->path = path; if (sub_entry) { e->m_entries.append(sub_entry); } else { e->addClient(instance, watchModes); } if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path << (e->m_status == NonExistent ? " NotExisting" : "") << " for " << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]"; } // now setup the notification method e->m_mode = UnknownMode; e->msecLeft = 0; if (isNoisyFile(QFile::encodeName(path).data())) { return; } if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { QFlags filters = QDir::NoDotAndDotDot; if ((watchModes & KDirWatch::WatchSubDirs) && (watchModes & KDirWatch::WatchFiles)) { filters |= (QDir::Dirs | QDir::Files); } else if (watchModes & KDirWatch::WatchSubDirs) { filters |= QDir::Dirs; } else if (watchModes & KDirWatch::WatchFiles) { filters |= QDir::Files; } #if HAVE_SYS_INOTIFY_H if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == KDirWatch::INotify)) { //qCDebug(KDIRWATCH) << "Ignoring WatchFiles directive - this is implicit with inotify"; // Placing a watch on individual files is redundant with inotify // (inotify gives us WatchFiles functionality "for free") and indeed // actively harmful, so prevent it. WatchSubDirs is necessary, though. filters &= ~QDir::Files; } #endif QDir basedir(e->path); const QFileInfoList contents = basedir.entryInfoList(filters); for (QFileInfoList::const_iterator iter = contents.constBegin(); iter != contents.constEnd(); ++iter) { const QFileInfo &fileInfo = *iter; // treat symlinks as files--don't follow them. bool isDir = fileInfo.isDir() && !fileInfo.isSymLink(); addEntry(instance, fileInfo.absoluteFilePath(), nullptr, isDir, isDir ? watchModes : KDirWatch::WatchDirOnly); } } addWatch(e); } void KDirWatchPrivate::addWatch(Entry *e) { // If the watch is on a network filesystem use the nfsPreferredMethod as the // default, otherwise use preferredMethod as the default, if the methods are // the same we can skip the mountpoint check // This allows to configure a different method for NFS mounts, since inotify // cannot detect changes made by other machines. However as a default inotify // is fine, since the most common case is a NFS-mounted home, where all changes // are made locally. #177892. KDirWatch::Method preferredMethod = m_preferredMethod; if (m_nfsPreferredMethod != m_preferredMethod) { if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { preferredMethod = m_nfsPreferredMethod; } } // Try the appropriate preferred method from the config first bool entryAdded = false; switch (preferredMethod) { #if HAVE_FAM case KDirWatch::FAM: entryAdded = useFAM(e); break; #endif #if HAVE_SYS_INOTIFY_H case KDirWatch::INotify: entryAdded = useINotify(e); break; #endif #if HAVE_QFILESYSTEMWATCHER case KDirWatch::QFSWatch: entryAdded = useQFSWatch(e); break; #endif case KDirWatch::Stat: entryAdded = useStat(e); break; } // Failing that try in order INotify, FAM, QFSWatch, Stat if (!entryAdded) { #if HAVE_SYS_INOTIFY_H if (useINotify(e)) { return; } #endif #if HAVE_FAM if (useFAM(e)) { return; } #endif #if HAVE_QFILESYSTEMWATCHER if (useQFSWatch(e)) { return; } #endif useStat(e); } } void KDirWatchPrivate::removeWatch(Entry *e) { #if HAVE_FAM if (e->m_mode == FAMMode) { FAMCancelMonitor(&fc, &(e->fr)); qCDebug(KDIRWATCH).nospace() << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } #endif #if HAVE_SYS_INOTIFY_H if (e->m_mode == INotifyMode) { m_inotify_wd_to_entry.remove(e->wd); (void) inotify_rm_watch(m_inotify_fd, e->wd); if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", " << e->wd << ") for " << e->path; } } #endif #if HAVE_QFILESYSTEMWATCHER if (e->m_mode == QFSWatchMode && fsWatcher) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "fsWatcher->removePath" << e->path; } fsWatcher->removePath(e->path); } #endif } void KDirWatchPrivate::removeEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "path=" << _path << "sub_entry:" << sub_entry; } Entry *e = entry(_path); if (e) { removeEntry(instance, e, sub_entry); } } void KDirWatchPrivate::removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry) { removeList.remove(e); if (sub_entry) { e->m_entries.removeAll(sub_entry); } else { e->removeClient(instance); } if (!e->m_clients.empty() || !e->m_entries.empty()) { return; } if (delayRemove) { removeList.insert(e); // now e->isValid() is false return; } if (e->m_status == Normal) { removeWatch(e); } else { // Removed a NonExistent entry - we just remove it from the parent if (e->isDir) { removeEntry(nullptr, e->parentDirectory(), e); } else { removeEntry(nullptr, QFileInfo(e->path).absolutePath(), e); } } if (e->m_mode == StatMode) { statEntries--; if (statEntries == 0) { timer.stop(); // stop timer if lists are empty qCDebug(KDIRWATCH) << " Stopped Polling Timer"; } } if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Removed " << (e->isDir ? "Dir " : "File ") << e->path << " for " << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]"; } QString p = e->path; // take a copy, QMap::remove takes a reference and deletes, since e points into the map #if HAVE_SYS_INOTIFY_H m_inotify_wd_to_entry.remove(e->wd); #endif m_mapEntries.remove(p); // not valid any more } /* Called from KDirWatch destructor: * remove as client from all entries */ void KDirWatchPrivate::removeEntries(KDirWatch *instance) { int minfreq = 3600000; QStringList pathList; // put all entries where instance is a client in list EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { Client *c = nullptr; for (Client &client : (*it).m_clients) { if (client.instance == instance) { c = &client; break; } } if (c) { c->count = 1; // forces deletion of instance as client pathList.append((*it).path); } else if ((*it).m_mode == StatMode && (*it).freq < minfreq) { minfreq = (*it).freq; } } for (const QString &path : qAsConst(pathList)) { removeEntry(instance, path, nullptr); } if (minfreq > freq) { // we can decrease the global polling frequency freq = minfreq; if (timer.isActive()) { timer.start(freq); } qCDebug(KDIRWATCH) << "Poll Freq now" << freq << "msec"; } } // instance ==0: stop scanning for all instances bool KDirWatchPrivate::stopEntryScan(KDirWatch *instance, Entry *e) { int stillWatching = 0; for (Client &client : e->m_clients) { if (!instance || instance == client.instance) { client.watchingStopped = true; } else if (!client.watchingStopped) { stillWatching += client.count; } } qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "stopped scanning" << e->path << "(now" << stillWatching << "watchers)"; if (stillWatching == 0) { // if nobody is interested, we don't watch, and we don't report // changes that happened while not watching e->m_ctime = invalid_ctime; // invalid // Changing m_status like this would create wrong "created" events in stat mode. // To really "stop watching" we would need to determine 'stillWatching==0' in scanEntry... //e->m_status = NonExistent; } return true; } // instance ==0: start scanning for all instances bool KDirWatchPrivate::restartEntryScan(KDirWatch *instance, Entry *e, bool notify) { int wasWatching = 0, newWatching = 0; for (Client &client : e->m_clients) { if (!client.watchingStopped) { wasWatching += client.count; } else if (!instance || instance == client.instance) { client.watchingStopped = false; newWatching += client.count; } } if (newWatching == 0) { return false; } qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "restarted scanning" << e->path << "(now" << wasWatching + newWatching << "watchers)"; // restart watching and emit pending events int ev = NoChange; if (wasWatching == 0) { if (!notify) { QT_STATBUF stat_buf; bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); if (exists) { // ctime is the 'creation time' on windows, but with qMax // we get the latest change of any kind, on any platform. e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_status = Normal; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to Normal for" << e << e->path; } e->m_nlink = stat_buf.st_nlink; e->m_ino = stat_buf.st_ino; // Same as in scanEntry: ensure no subentry in parent dir removeEntry(nullptr, e->parentDirectory(), e); } else { e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to NonExistent for" << e << e->path; } } } e->msecLeft = 0; ev = scanEntry(e); } emitEvent(e, ev); return true; } // instance ==0: stop scanning for all instances void KDirWatchPrivate::stopScan(KDirWatch *instance) { EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { stopEntryScan(instance, &(*it)); } } void KDirWatchPrivate::startScan(KDirWatch *instance, bool notify, bool skippedToo) { if (!notify) { resetList(instance, skippedToo); } EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { restartEntryScan(instance, &(*it), notify); } // timer should still be running when in polling mode } // clear all pending events, also from stopped void KDirWatchPrivate::resetList(KDirWatch *instance, bool skippedToo) { Q_UNUSED(instance); EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { for (Client &client : (*it).m_clients) { if (!client.watchingStopped || skippedToo) { client.pending = NoChange; } } } } // Return event happened on // int KDirWatchPrivate::scanEntry(Entry *e) { // Shouldn't happen: Ignore "unknown" notification method if (e->m_mode == UnknownMode) { return NoChange; } if (e->m_mode == FAMMode || e->m_mode == INotifyMode) { // we know nothing has changed, no need to stat if (!e->dirty) { return NoChange; } e->dirty = false; } if (e->m_mode == StatMode) { // only scan if timeout on entry timer happens; // e.g. when using 500msec global timer, a entry // with freq=5000 is only watched every 10th time e->msecLeft -= freq; if (e->msecLeft > 0) { return NoChange; } e->msecLeft += e->freq; } QT_STATBUF stat_buf; const bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); if (exists) { if (e->m_status == NonExistent) { // ctime is the 'creation time' on windows, but with qMax // we get the latest change of any kind, on any platform. e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_status = Normal; e->m_ino = stat_buf.st_ino; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to Normal for just created" << e << e->path; } // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo) removeEntry(nullptr, e->parentDirectory(), e); return Created; } #if 1 // for debugging the if() below if (s_verboseDebug) { struct tm *tmp = localtime(&e->m_ctime); char outstr[200]; strftime(outstr, sizeof(outstr), "%H:%M:%S", tmp); qCDebug(KDIRWATCH) << e->path << "e->m_ctime=" << e->m_ctime << outstr << "stat_buf.st_ctime=" << stat_buf.st_ctime << "stat_buf.st_mtime=" << stat_buf.st_mtime << "e->m_nlink=" << e->m_nlink << "stat_buf.st_nlink=" << stat_buf.st_nlink << "e->m_ino=" << e->m_ino << "stat_buf.st_ino=" << stat_buf.st_ino; } #endif if ((e->m_ctime != invalid_ctime) && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino || int(stat_buf.st_nlink) != int(e->m_nlink) #ifdef Q_OS_WIN // on Windows, we trust QFSW to get it right, the ctime comparisons above // fail for example when adding files to directories on Windows // which doesn't change the mtime of the directory || e->m_mode == QFSWatchMode #endif )) { e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_nlink = stat_buf.st_nlink; if (e->m_ino != stat_buf.st_ino) { // The file got deleted and recreated. We need to watch it again. removeWatch(e); addWatch(e); e->m_ino = stat_buf.st_ino; return (Deleted|Created); } else { return Changed; } } return NoChange; } // dir/file doesn't exist e->m_nlink = 0; e->m_ino = 0; e->m_status = NonExistent; if (e->m_ctime == invalid_ctime) { return NoChange; } e->m_ctime = invalid_ctime; return Deleted; } /* Notify all interested KDirWatch instances about a given event on an entry * and stored pending events. When watching is stopped, the event is * added to the pending events. */ void KDirWatchPrivate::emitEvent(Entry *e, int event, const QString &fileName) { QString path(e->path); if (!fileName.isEmpty()) { if (!QDir::isRelativePath(fileName)) { path = fileName; } else { #ifdef Q_OS_UNIX path += QLatin1Char('/') + fileName; #elif defined(Q_OS_WIN) //current drive is passed instead of / path += QDir::currentPath().leftRef(2) + QLatin1Char('/') + fileName; #endif } } if (s_verboseDebug) { qCDebug(KDIRWATCH) << event << path << e->m_clients.size() << "clients"; } for (Client &c : e->m_clients) { if (c.instance == nullptr || c.count == 0) { continue; } if (c.watchingStopped) { // Do not add event to a list of pending events, the docs say restartDirScan won't emit! #if 0 if (event == Changed) { c.pending |= event; } else if (event == Created || event == Deleted) { c.pending = event; } #endif continue; } // not stopped if (event == NoChange || event == Changed) { event |= c.pending; } c.pending = NoChange; if (event == NoChange) { continue; } // Emit the signals delayed, to avoid unexpected re-entrance from the slots (#220153) if (event & Deleted) { QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setDeleted(path); }, Qt::QueuedConnection); } if (event & Created) { QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setCreated(path); }, Qt::QueuedConnection); // possible emit Change event after creation } if (event & Changed) { QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setDirty(path); }, Qt::QueuedConnection); } } } // Remove entries which were marked to be removed void KDirWatchPrivate::slotRemoveDelayed() { delayRemove = false; // Removing an entry could also take care of removing its parent // (e.g. in FAM or inotify mode), which would remove other entries in removeList, // so don't use Q_FOREACH or iterators here... while (!removeList.isEmpty()) { Entry *entry = *removeList.begin(); removeEntry(nullptr, entry, nullptr); // this will remove entry from removeList } } /* Scan all entries to be watched for changes. This is done regularly * when polling. FAM and inotify use a single-shot timer to call this slot delayed. */ void KDirWatchPrivate::slotRescan() { if (s_verboseDebug) { qCDebug(KDIRWATCH); } EntryMap::Iterator it; // People can do very long things in the slot connected to dirty(), // like showing a message box. We don't want to keep polling during // that time, otherwise the value of 'delayRemove' will be reset. // ### TODO: now the emitEvent delays emission, this can be cleaned up bool timerRunning = timer.isActive(); if (timerRunning) { timer.stop(); } // We delay deletions of entries this way. // removeDir(), when called in slotDirty(), can cause a crash otherwise // ### TODO: now the emitEvent delays emission, this can be cleaned up delayRemove = true; if (rescan_all) { // mark all as dirty it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { (*it).dirty = true; } rescan_all = false; } else { // propagate dirty flag to dependent entries (e.g. file watches) it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) { (*it).propagate_dirty(); } } #if HAVE_SYS_INOTIFY_H QList cList; #endif it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { // we don't check invalid entries (i.e. remove delayed) Entry *entry = &(*it); if (!entry->isValid()) { continue; } const int ev = scanEntry(entry); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; } switch (entry->m_mode) { #if HAVE_SYS_INOTIFY_H case INotifyMode: if (ev == Deleted) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was deleted"; } addEntry(nullptr, entry->parentDirectory(), entry, true); } else if (ev == Created) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd; } if (entry->wd < 0) { cList.append(entry); addWatch(entry); } } break; #endif case FAMMode: case QFSWatchMode: if (ev == Created) { addWatch(entry); } break; default: // dunno about StatMode... break; } #if HAVE_SYS_INOTIFY_H if (entry->isDir) { // Report and clear the list of files that have changed in this directory. // Remove duplicates by changing to set and back again: // we don't really care about preserving the order of the // original changes. QStringList pendingFileChanges = entry->m_pendingFileChanges; pendingFileChanges.removeDuplicates(); for (const QString &changedFilename : qAsConst(pendingFileChanges)) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "processing pending file change for" << changedFilename; } emitEvent(entry, Changed, changedFilename); } entry->m_pendingFileChanges.clear(); } #endif if (ev != NoChange) { emitEvent(entry, ev); } } if (timerRunning) { timer.start(freq); } #if HAVE_SYS_INOTIFY_H // Remove watch of parent of new created directories for (Entry *e : qAsConst(cList)) { removeEntry(nullptr, e->parentDirectory(), e); } #endif QTimer::singleShot(0, this, SLOT(slotRemoveDelayed())); } bool KDirWatchPrivate::isNoisyFile(const char *filename) { // $HOME/.X.err grows with debug output, so don't notify change if (*filename == '.') { if (strncmp(filename, ".X.err", 6) == 0) { return true; } if (strncmp(filename, ".xsession-errors", 16) == 0) { return true; } // fontconfig updates the cache on every KDE app start // (inclusive kio_thumbnail slaves) if (strncmp(filename, ".fonts.cache", 12) == 0) { return true; } } return false; } #if HAVE_FAM void KDirWatchPrivate::famEventReceived() { static FAMEvent fe; delayRemove = true; //qCDebug(KDIRWATCH) << "Fam event received"; while (use_fam && FAMPending(&fc)) { if (FAMNextEvent(&fc, &fe) == -1) { qCWarning(KCOREADDONS_DEBUG) << "FAM connection problem, switching to polling."; use_fam = false; delete sn; sn = nullptr; // Replace all FAMMode entries with INotify/Stat EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if ((*it).m_mode == FAMMode && !(*it).m_clients.empty()) { Entry *e = &(*it); addWatch(e); } } else { checkFAMEvent(&fe); } } QTimer::singleShot(0, this, SLOT(slotRemoveDelayed())); } void KDirWatchPrivate::checkFAMEvent(FAMEvent *fe) { //qCDebug(KDIRWATCH); Entry *e = nullptr; EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if (FAMREQUEST_GETREQNUM(&((*it).fr)) == FAMREQUEST_GETREQNUM(&(fe->fr))) { e = &(*it); break; } // Don't be too verbose ;-) if ((fe->code == FAMExists) || (fe->code == FAMEndExist) || (fe->code == FAMAcknowledge)) { if (e) { e->m_famReportedSeen = true; } return; } if (isNoisyFile(fe->filename)) { return; } // Entry *e = static_cast(fe->userdata); if (s_verboseDebug) { // don't enable this except when debugging, see #88538 qCDebug(KDIRWATCH) << "Processing FAM event (" << ((fe->code == FAMChanged) ? "FAMChanged" : (fe->code == FAMDeleted) ? "FAMDeleted" : (fe->code == FAMStartExecuting) ? "FAMStartExecuting" : (fe->code == FAMStopExecuting) ? "FAMStopExecuting" : (fe->code == FAMCreated) ? "FAMCreated" : (fe->code == FAMMoved) ? "FAMMoved" : (fe->code == FAMAcknowledge) ? "FAMAcknowledge" : (fe->code == FAMExists) ? "FAMExists" : (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code") << ", " << fe->filename << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ") e=" << e; } if (!e) { // this happens e.g. for FAMAcknowledge after deleting a dir... // qCDebug(KDIRWATCH) << "No entry for FAM event ?!"; return; } if (e->m_status == NonExistent) { qCDebug(KDIRWATCH) << "FAM event for nonExistent entry " << e->path; return; } // Delayed handling. This rechecks changes with own stat calls. e->dirty = true; if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } // needed FAM control actions on FAM events switch (fe->code) { case FAMDeleted: // fe->filename is an absolute path when a watched file-or-dir is deleted if (!QDir::isRelativePath(QFile::decodeName(fe->filename))) { FAMCancelMonitor(&fc, &(e->fr)); // needed ? qCDebug(KDIRWATCH) << "Cancelled FAMReq" << FAMREQUEST_GETREQNUM(&(e->fr)) << "for" << e->path; e->m_status = NonExistent; e->m_ctime = invalid_ctime; emitEvent(e, Deleted, e->path); // If the parent dir was already watched, tell it something changed Entry *parentEntry = entry(e->parentDirectory()); if (parentEntry) { parentEntry->dirty = true; } // Add entry to parent dir to notice if the entry gets recreated addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); } else { // A file in this directory has been removed, and wasn't explicitly watched. // We could still inform clients, like inotify does? But stat can't. // For now we just marked e dirty and slotRescan will emit the dir as dirty. //qCDebug(KDIRWATCH) << "Got FAMDeleted for" << QFile::decodeName(fe->filename) << "in" << e->path << ". Absolute path -> NOOP!"; } break; case FAMCreated: { // check for creation of a directory we have to watch QString tpath(e->path + QLatin1Char('/') + QFile::decodeName(fe->filename)); // This code is very similar to the one in inotifyEventReceived... Entry *sub_entry = e->findSubEntry(tpath); if (sub_entry /*&& sub_entry->isDir*/) { // We were waiting for this new file/dir to be created. We don't actually // emit an event here, as the rescan_timer will re-detect the creation and // do the signal emission there. sub_entry->dirty = true; rescan_timer.start(0); // process this asap, to start watching that dir } else if (e->isDir && !e->m_clients.empty()) { bool isDir = false; const QList clients = e->clientsForFileOrDir(tpath, &isDir); for (const Client *client : clients) { addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); } if (!clients.isEmpty()) { emitEvent(e, Created, tpath); qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath; } } } break; default: break; } } #else void KDirWatchPrivate::famEventReceived() { qCWarning(KCOREADDONS_DEBUG) << "Fam event received but FAM is not supported"; } #endif void KDirWatchPrivate::statistics() { EntryMap::Iterator it; qCDebug(KDIRWATCH) << "Entries watched:"; if (m_mapEntries.count() == 0) { qCDebug(KDIRWATCH) << " None."; } else { it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { Entry *e = &(*it); qCDebug(KDIRWATCH) << " " << *e; for (const Client &c : e->m_clients) { QByteArray pending; if (c.watchingStopped) { if (c.pending & Deleted) { pending += "deleted "; } if (c.pending & Created) { pending += "created "; } if (c.pending & Changed) { pending += "changed "; } if (!pending.isEmpty()) { pending = " (pending: " + pending + ')'; } pending = ", stopped" + pending; } qCDebug(KDIRWATCH) << " by " << c.instance->objectName() << " (" << c.count << " times)" << pending; } if (!e->m_entries.isEmpty()) { qCDebug(KDIRWATCH) << " dependent entries:"; for (Entry *d : qAsConst(e->m_entries)) { qCDebug(KDIRWATCH) << " " << d << d->path << (d->m_status == NonExistent ? "NonExistent" : "EXISTS!!! ERROR!"); if (s_verboseDebug) { Q_ASSERT(d->m_status == NonExistent); // it doesn't belong here otherwise } } } } } } #if HAVE_QFILESYSTEMWATCHER // Slot for QFileSystemWatcher void KDirWatchPrivate::fswEventReceived(const QString &path) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << path; } EntryMap::Iterator it = m_mapEntries.find(path); if (it != m_mapEntries.end()) { Entry *e = &(*it); e->dirty = true; const int ev = scanEntry(e); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry for" << e->path << "says" << ev; } if (ev != NoChange) { emitEvent(e, ev); } if (ev == Deleted) { if (e->isDir) { addEntry(nullptr, e->parentDirectory(), e, true); } else { addEntry(nullptr, QFileInfo(e->path).absolutePath(), e, true); } } else if (ev == Created) { // We were waiting for it to appear; now watch it addWatch(e); } else if (e->isDir) { // Check if any file or dir was created under this directory, that we were waiting for for (Entry *sub_entry : qAsConst(e->m_entries)) { fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed } } else { /* Even though QFileSystemWatcher only reported the file as modified, it is possible that the file * was in fact just deleted and then immediately recreated. If the file was deleted, QFileSystemWatcher * will delete the watch, and will ignore the file, even after it is recreated. Since it is impossible * to reliably detect this case, always re-request the watch on a dirty signal, to avoid losing the * underlying OS monitor. */ fsWatcher->addPath(e->path); } } } #else void KDirWatchPrivate::fswEventReceived(const QString &path) { Q_UNUSED(path); qCWarning(KCOREADDONS_DEBUG) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported"; } #endif // HAVE_QFILESYSTEMWATCHER // // Class KDirWatch // Q_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf) KDirWatch *KDirWatch::self() { return s_pKDirWatchSelf(); } // is this used anywhere? // yes, see kio/src/core/kcoredirlister_p.h:328 bool KDirWatch::exists() { return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData(); } static void postRoutine_KDirWatch() { if (s_pKDirWatchSelf.exists()) { s_pKDirWatchSelf()->deleteQFSWatcher(); } } KDirWatch::KDirWatch(QObject *parent) : QObject(parent), d(createPrivate()) { static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1); const int counter = nameCounter.fetchAndAddRelaxed(1); // returns the old value setObjectName(QStringLiteral("KDirWatch-%1").arg(counter)); if (counter == 1) { // very first KDirWatch instance // Must delete QFileSystemWatcher before qApp is gone - bug 261541 qAddPostRoutine(postRoutine_KDirWatch); } } KDirWatch::~KDirWatch() { if (d && dwp_self.hasLocalData()) { // skip this after app destruction d->removeEntries(this); } } void KDirWatch::addDir(const QString &_path, WatchModes watchModes) { if (d) { d->addEntry(this, _path, nullptr, true, watchModes); } } void KDirWatch::addFile(const QString &_path) { if (!d) { return; } d->addEntry(this, _path, nullptr, false); } QDateTime KDirWatch::ctime(const QString &_path) const { KDirWatchPrivate::Entry *e = d->entry(_path); if (!e) { return QDateTime(); } return QDateTime::fromSecsSinceEpoch(e->m_ctime); } void KDirWatch::removeDir(const QString &_path) { if (d) { d->removeEntry(this, _path, nullptr); } } void KDirWatch::removeFile(const QString &_path) { if (d) { d->removeEntry(this, _path, nullptr); } } bool KDirWatch::stopDirScan(const QString &_path) { if (d) { KDirWatchPrivate::Entry *e = d->entry(_path); if (e && e->isDir) { return d->stopEntryScan(this, e); } } return false; } bool KDirWatch::restartDirScan(const QString &_path) { if (d) { KDirWatchPrivate::Entry *e = d->entry(_path); if (e && e->isDir) // restart without notifying pending events { return d->restartEntryScan(this, e, false); } } return false; } void KDirWatch::stopScan() { if (d) { d->stopScan(this); d->_isStopped = true; } } bool KDirWatch::isStopped() { return d->_isStopped; } void KDirWatch::startScan(bool notify, bool skippedToo) { if (d) { d->_isStopped = false; d->startScan(this, notify, skippedToo); } } bool KDirWatch::contains(const QString &_path) const { KDirWatchPrivate::Entry *e = d->entry(_path); if (!e) { return false; } for (const KDirWatchPrivate::Client &client : e->m_clients) { if (client.instance == this) { return true; } } return false; } void KDirWatch::deleteQFSWatcher() { delete d->fsWatcher; d->fsWatcher = nullptr; d = nullptr; } void KDirWatch::statistics() { if (!dwp_self.hasLocalData()) { qCDebug(KDIRWATCH) << "KDirWatch not used"; return; } dwp_self.localData()->statistics(); } void KDirWatch::setCreated(const QString &_file) { qCDebug(KDIRWATCH) << objectName() << "emitting created" << _file; emit created(_file); } void KDirWatch::setDirty(const QString &_file) { //qCDebug(KDIRWATCH) << objectName() << "emitting dirty" << _file; emit dirty(_file); } void KDirWatch::setDeleted(const QString &_file) { qCDebug(KDIRWATCH) << objectName() << "emitting deleted" << _file; emit deleted(_file); } KDirWatch::Method KDirWatch::internalMethod() const { // This reproduces the logic in KDirWatchPrivate::addWatch switch (d->m_preferredMethod) { #if HAVE_FAM case KDirWatch::FAM: if (d->use_fam) { return KDirWatch::FAM; } break; #endif #if HAVE_SYS_INOTIFY_H case KDirWatch::INotify: if (d->supports_inotify) { return KDirWatch::INotify; } break; #endif #if HAVE_QFILESYSTEMWATCHER case KDirWatch::QFSWatch: return KDirWatch::QFSWatch; #endif case KDirWatch::Stat: return KDirWatch::Stat; } #if HAVE_SYS_INOTIFY_H if (d->supports_inotify) { return KDirWatch::INotify; } #endif #if HAVE_FAM if (d->use_fam) { return KDirWatch::FAM; } #endif #if HAVE_QFILESYSTEMWATCHER return KDirWatch::QFSWatch; #else return KDirWatch::Stat; #endif } #include "moc_kdirwatch.cpp" #include "moc_kdirwatch_p.cpp" //sven diff --git a/src/lib/io/kdirwatch.h b/src/lib/io/kdirwatch.h index 8455208..55d89a7 100644 --- a/src/lib/io/kdirwatch.h +++ b/src/lib/io/kdirwatch.h @@ -1,311 +1,301 @@ -/* This file is part of the KDE libraries - Copyright (C) 1998 Sven Radej - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1998 Sven Radej + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef _KDIRWATCH_H #define _KDIRWATCH_H #include #include #include #include class KDirWatchPrivate; /** * @class KDirWatch kdirwatch.h KDirWatch * * @short Class for watching directory and file changes. * * Watch directories and files for changes. * The watched directories or files don't have to exist yet. * * When a watched directory is changed, i.e. when files therein are * created or deleted, KDirWatch will emit the signal dirty(). * * When a watched, but previously not existing directory gets created, * KDirWatch will emit the signal created(). * * When a watched directory gets deleted, KDirWatch will emit the * signal deleted(). The directory is still watched for new * creation. * * When a watched file is changed, i.e. attributes changed or written * to, KDirWatch will emit the signal dirty(). * * Scanning of particular directories or files can be stopped temporarily * and restarted. The whole class can be stopped and restarted. * Directories and files can be added/removed from the list in any state. * * The implementation uses the INOTIFY functionality on LINUX. * Otherwise the FAM service is used, when available. * As a last resort, a regular polling for change of modification times * is done; the polling interval is a global config option: * DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted * directories. * The choice of implementation can be adjusted by the user, with the key * [DirWatch] PreferredMethod={Fam|Stat|QFSWatch|inotify} * * @see self() * @author Sven Radej (in 1998) */ class KCOREADDONS_EXPORT KDirWatch : public QObject { Q_OBJECT public: /** * Available watch modes for directory monitoring **/ enum WatchMode { WatchDirOnly = 0, ///< Watch just the specified directory WatchFiles = 0x01, ///< Watch also all files contained by the directory WatchSubDirs = 0x02 ///< Watch also all the subdirs contained by the directory }; Q_DECLARE_FLAGS(WatchModes, WatchMode) /** * Constructor. * * Scanning begins immediately when a dir/file watch * is added. * @param parent the parent of the QObject (or @c nullptr for parent-less KDataTools) */ explicit KDirWatch(QObject *parent = nullptr); /** * Destructor. * * Stops scanning and cleans up. */ ~KDirWatch(); /** * Adds a directory to be watched. * * The directory does not have to exist. When @p watchModes is set to * WatchDirOnly (the default), the signals dirty(), created(), deleted() * can be emitted, all for the watched directory. * When @p watchModes is set to WatchFiles, all files in the watched * directory are watched for changes, too. Thus, the signals dirty(), * created(), deleted() can be emitted. * When @p watchModes is set to WatchSubDirs, all subdirs are watched using * the same flags specified in @p watchModes (symlinks aren't followed). * If the @p path points to a symlink to a directory, the target directory * is watched instead. If you want to watch the link, use @p addFile(). * * @param path the path to watch * @param watchModes watch modes * * @sa KDirWatch::WatchMode */ void addDir(const QString &path, WatchModes watchModes = WatchDirOnly); /** * Adds a file to be watched. * If it's a symlink to a directory, it watches the symlink itself. * @param file the file to watch */ void addFile(const QString &file); /** * Returns the time the directory/file was last changed. * @param path the file to check * @return the date of the last modification */ QDateTime ctime(const QString &path) const; /** * Removes a directory from the list of scanned directories. * * If specified path is not in the list this does nothing. * @param path the path of the dir to be removed from the list */ void removeDir(const QString &path); /** * Removes a file from the list of watched files. * * If specified path is not in the list this does nothing. * @param file the file to be removed from the list */ void removeFile(const QString &file); /** * Stops scanning the specified path. * * The @p path is not deleted from the internal list, it is just skipped. * Call this function when you perform an huge operation * on this directory (copy/move big files or many files). When finished, * call restartDirScan(path). * * @param path the path to skip * @return true if the @p path is being watched, otherwise false * @see restartDirScan() */ bool stopDirScan(const QString &path); /** * Restarts scanning for specified path. * * It doesn't notify about the changes (by emitting a signal). * The ctime value is reset. * * Call it when you are finished with big operations on that path, * @em and when @em you have refreshed that path. * * @param path the path to restart scanning * @return true if the @p path is being watched, otherwise false * @see stopDirScan() */ bool restartDirScan(const QString &path); /** * Starts scanning of all dirs in list. * * @param notify If true, all changed directories (since * stopScan() call) will be notified for refresh. If notify is * false, all ctimes will be reset (except those who are stopped, * but only if @p skippedToo is false) and changed dirs won't be * notified. You can start scanning even if the list is * empty. First call should be called with @p false or else all * directories * in list will be notified. * @param skippedToo if true, the skipped directories (scanning of which was * stopped with stopDirScan() ) will be reset and notified * for change. Otherwise, stopped directories will continue to be * unnotified. */ void startScan(bool notify = false, bool skippedToo = false); /** * Stops scanning of all directories in internal list. * * The timer is stopped, but the list is not cleared. */ void stopScan(); /** * Is scanning stopped? * After creation of a KDirWatch instance, this is false. * @return true when scanning stopped */ bool isStopped(); /** * Check if a directory is being watched by this KDirWatch instance * @param path the directory to check * @return true if the directory is being watched */ bool contains(const QString &path) const; void deleteQFSWatcher(); // KF6 TODO: remove from public API /** * Dump statistic information about the KDirWatch::self() instance. * This checks for consistency, too. */ static void statistics(); // TODO implement a QDebug operator for KDirWatch instead. enum Method { FAM, INotify, Stat, QFSWatch }; /** * Returns the preferred internal method to * watch for changes. */ Method internalMethod() const; /** * The KDirWatch instance usually globally used in an application. * It is automatically deleted when the application exits. * * However, you can create an arbitrary number of KDirWatch instances * aside from this one - for those you have to take care of memory management. * * This function returns an instance of KDirWatch. If there is none, it * will be created. * * @return a KDirWatch instance */ static KDirWatch *self(); /** * Returns true if there is an instance of KDirWatch. * @return true if there is an instance of KDirWatch. * @see KDirWatch::self() */ static bool exists(); public Q_SLOTS: /** * Emits created(). * @param path the path of the file or directory */ void setCreated(const QString &path); /** * Emits dirty(). * @param path the path of the file or directory */ void setDirty(const QString &path); /** * Emits deleted(). * @param path the path of the file or directory */ void setDeleted(const QString &path); Q_SIGNALS: /** * Emitted when a watched object is changed. * For a directory this signal is emitted when files * therein are created or deleted. * For a file this signal is emitted when its size or attributes change. * * When you watch a directory, changes in the size or attributes of * contained files may or may not trigger this signal to be emitted * depending on which backend is used by KDirWatch. * * The new ctime is set before the signal is emitted. * @param path the path of the file or directory */ void dirty(const QString &path); /** * Emitted when a file or directory (being watched explicitly) is created. * This is not emitted when creating a file is created in a watched directory. * @param path the path of the file or directory */ void created(const QString &path); /** * Emitted when a file or directory is deleted. * * The object is still watched for new creation. * @param path the path of the file or directory */ void deleted(const QString &path); private: KDirWatchPrivate *d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDirWatch::WatchModes) #endif diff --git a/src/lib/io/kdirwatch_p.h b/src/lib/io/kdirwatch_p.h index 5cf9983..5fb68c3 100644 --- a/src/lib/io/kdirwatch_p.h +++ b/src/lib/io/kdirwatch_p.h @@ -1,239 +1,228 @@ -/* Private Header for class of KDirWatchPrivate - * - * this separate header file is needed for MOC processing - * because KDirWatchPrivate has signals and slots - * - * This file is part of the KDE libraries - * Copyright (C) 1998 Sven Radej - * Copyright (C) 2006 Dirk Mueller - * Copyright (C) 2007 Flavio Castelli - * Copyright (C) 2008 Jarosław Staniek - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1998 Sven Radej + SPDX-FileCopyrightText: 2006 Dirk Mueller + SPDX-FileCopyrightText: 2007 Flavio Castelli + SPDX-FileCopyrightText: 2008 Jarosław Staniek + + SPDX-License-Identifier: LGPL-2.0-only + + Private Header for class of KDirWatchPrivate + this separate header file is needed for MOC processing + because KDirWatchPrivate has signals and slots +*/ #ifndef KDIRWATCH_P_H #define KDIRWATCH_P_H #include #include "kdirwatch.h" #ifndef QT_NO_FILESYSTEMWATCHER #define HAVE_QFILESYSTEMWATCHER 1 #else #define HAVE_QFILESYSTEMWATCHER 0 #endif #include #include #include #include #include #include class QSocketNotifier; #if HAVE_FAM #include #include #endif #include // time_t, ino_t #include #define invalid_ctime (static_cast(-1)) #if HAVE_QFILESYSTEMWATCHER #include #endif // HAVE_QFILESYSTEMWATCHER /* KDirWatchPrivate is a singleton and does the watching * for every KDirWatch instance in the application. */ class KDirWatchPrivate : public QObject { Q_OBJECT public: enum entryStatus { Normal = 0, NonExistent }; enum entryMode { UnknownMode = 0, StatMode, INotifyMode, FAMMode, QFSWatchMode }; enum { NoChange = 0, Changed = 1, Created = 2, Deleted = 4 }; struct Client { Client(KDirWatch *inst, KDirWatch::WatchModes watchModes) : instance(inst), count(1), watchingStopped(inst->isStopped()), pending(NoChange), m_watchModes(watchModes) {} // The compiler needs a copy ctor for Client when Entry is inserted into m_mapEntries // (even though the vector of clients is empty at that point, so no performance penalty there) //Client(const Client &) = delete; //Client &operator=(const Client &) = delete; //Client(Client &&) = default; //Client &operator=(Client &&) = default; KDirWatch *instance; int count; // did the instance stop watching bool watchingStopped; // events blocked when stopped int pending; KDirWatch::WatchModes m_watchModes; }; class Entry { public: ~Entry(); // instances interested in events std::vector m_clients; // nonexistent entries of this directory QList m_entries; QString path; // the last observed modification time time_t m_ctime; // last observed inode ino_t m_ino; // the last observed link count int m_nlink; entryStatus m_status; entryMode m_mode; int msecLeft, freq; bool isDir; QString parentDirectory() const; void addClient(KDirWatch *, KDirWatch::WatchModes); void removeClient(KDirWatch *); int clientCount() const; bool isValid() { return !m_clients.empty() || !m_entries.empty(); } Entry *findSubEntry(const QString &path) const { for (Entry *sub_entry : qAsConst(m_entries)) { if (sub_entry->path == path) { return sub_entry; } } return nullptr; } bool dirty; void propagate_dirty(); QList clientsForFileOrDir(const QString &tpath, bool *isDir) const; QList inotifyClientsForFileOrDir(bool isDir) const; #if HAVE_FAM FAMRequest fr; bool m_famReportedSeen; #endif #if HAVE_SYS_INOTIFY_H int wd; // Creation and Deletion of files happens infrequently, so // can safely be reported as they occur. File changes i.e. those that emit "dirty()" can // happen many times per second, though, so maintain a list of files in this directory // that can be emitted and flushed at the next slotRescan(...). // This will be unused if the Entry is not a directory. QList m_pendingFileChanges; #endif }; typedef QMap EntryMap; KDirWatchPrivate(); ~KDirWatchPrivate(); void resetList(KDirWatch *instance, bool skippedToo); void useFreq(Entry *e, int newFreq); void addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes = KDirWatch::WatchDirOnly); void removeEntry(KDirWatch *instance, const QString &path, Entry *sub_entry); void removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry); bool stopEntryScan(KDirWatch *instance, Entry *e); bool restartEntryScan(KDirWatch *instance, Entry *e, bool notify); void stopScan(KDirWatch *instance); void startScan(KDirWatch *instance, bool notify, bool skippedToo); void removeEntries(KDirWatch *instance); void statistics(); void addWatch(Entry *entry); void removeWatch(Entry *entry); Entry *entry(const QString &_path); int scanEntry(Entry *e); void emitEvent(Entry *e, int event, const QString &fileName = QString()); static bool isNoisyFile(const char *filename); public Q_SLOTS: void slotRescan(); void famEventReceived(); // for FAM void inotifyEventReceived(); // for inotify void slotRemoveDelayed(); void fswEventReceived(const QString &path); // for QFileSystemWatcher public: QTimer timer; EntryMap m_mapEntries; KDirWatch::Method m_preferredMethod, m_nfsPreferredMethod; int freq; int statEntries; int m_nfsPollInterval, m_PollInterval; bool useStat(Entry *e); // removeList is allowed to contain any entry at most once QSet removeList; bool delayRemove; bool rescan_all; QTimer rescan_timer; #if HAVE_FAM QSocketNotifier *sn; FAMConnection fc; bool use_fam; void checkFAMEvent(FAMEvent *fe); bool useFAM(Entry *e); #endif #if HAVE_SYS_INOTIFY_H QSocketNotifier *mSn; bool supports_inotify; int m_inotify_fd; QHash m_inotify_wd_to_entry; bool useINotify(Entry *e); #endif #if HAVE_QFILESYSTEMWATCHER QFileSystemWatcher *fsWatcher; bool useQFSWatch(Entry *e); #endif bool _isStopped; }; QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry); #endif // KDIRWATCH_P_H diff --git a/src/lib/io/kfilesystemtype.cpp b/src/lib/io/kfilesystemtype.cpp index f9545e6..b09d1f6 100644 --- a/src/lib/io/kfilesystemtype.cpp +++ b/src/lib/io/kfilesystemtype.cpp @@ -1,147 +1,136 @@ /* - This file is part of the KDE libraries - Copyright (c) 2011 David Faure + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2.1 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 2011 David Faure - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.1-only */ #include "kfilesystemtype.h" #include #include "kcoreaddons_debug.h" //#include #ifndef Q_OS_WIN inline KFileSystemType::Type kde_typeFromName(const char *name) { if (qstrncmp(name, "nfs", 3) == 0 || qstrncmp(name, "autofs", 6) == 0 || qstrncmp(name, "cachefs", 7) == 0 || qstrncmp(name, "fuse.sshfs", 10) == 0 || qstrncmp(name, "xtreemfs@", 9) == 0) { // #178678 return KFileSystemType::Nfs; } if (qstrncmp(name, "fat", 3) == 0 || qstrncmp(name, "vfat", 4) == 0 || qstrncmp(name, "msdos", 5) == 0) { return KFileSystemType::Fat; } if (qstrncmp(name, "cifs", 4) == 0 || qstrncmp(name, "smbfs", 5) == 0) { return KFileSystemType::Smb; } if (qstrncmp(name, "ramfs", 5) == 0) { return KFileSystemType::Ramfs; } return KFileSystemType::Other; } #if defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD) # include # include KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path) { struct statfs buf; if (statfs(path.constData(), &buf) != 0) { return KFileSystemType::Unknown; } return kde_typeFromName(buf.f_fstypename); } #elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) # include # ifdef QT_LINUXBASE // LSB 3.2 has statfs in sys/statfs.h, sys/vfs.h is just an empty dummy header # include # endif # ifndef NFS_SUPER_MAGIC # define NFS_SUPER_MAGIC 0x00006969 # endif # ifndef AUTOFS_SUPER_MAGIC # define AUTOFS_SUPER_MAGIC 0x00000187 # endif # ifndef AUTOFSNG_SUPER_MAGIC # define AUTOFSNG_SUPER_MAGIC 0x7d92b1a0 # endif # ifndef MSDOS_SUPER_MAGIC # define MSDOS_SUPER_MAGIC 0x00004d44 # endif # ifndef SMB_SUPER_MAGIC # define SMB_SUPER_MAGIC 0x0000517B #endif # ifndef FUSE_SUPER_MAGIC # define FUSE_SUPER_MAGIC 0x65735546 # endif # ifndef RAMFS_MAGIC # define RAMFS_MAGIC 0x858458F6 # endif // Reverse-engineering without C++ code: // strace stat -f /mnt 2>&1|grep statfs|grep mnt, and look for f_type static KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path) { struct statfs buf; if (statfs(path.constData(), &buf) != 0) { //qCDebug(KCOREADDONS_DEBUG) << path << errno << strerror(errno); return KFileSystemType::Unknown; } switch (static_cast(buf.f_type)) { case NFS_SUPER_MAGIC: case AUTOFS_SUPER_MAGIC: case AUTOFSNG_SUPER_MAGIC: case FUSE_SUPER_MAGIC: // TODO could be anything. Need to use statfs() to find out more. return KFileSystemType::Nfs; case SMB_SUPER_MAGIC: return KFileSystemType::Smb; case MSDOS_SUPER_MAGIC: return KFileSystemType::Fat; case RAMFS_MAGIC: return KFileSystemType::Ramfs; default: return KFileSystemType::Other; } } #elif defined(Q_OS_SOLARIS) || defined(Q_OS_IRIX) || defined(Q_OS_AIX) || defined(Q_OS_HPUX) \ || defined(Q_OS_OSF) || defined(Q_OS_QNX) || defined(Q_OS_SCO) \ || defined(Q_OS_UNIXWARE) || defined(Q_OS_RELIANT) || defined(Q_OS_NETBSD) # include KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path) { struct statvfs buf; if (statvfs(path.constData(), &buf) != 0) { return KFileSystemType::Unknown; } #if defined(Q_OS_NETBSD) return kde_typeFromName(buf.f_fstypename); #else return kde_typeFromName(buf.f_basetype); #endif } #endif #else KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path) { return KFileSystemType::Unknown; } #endif KFileSystemType::Type KFileSystemType::fileSystemType(const QString &path) { return determineFileSystemTypeImpl(QFile::encodeName(path)); } diff --git a/src/lib/io/kfilesystemtype.h b/src/lib/io/kfilesystemtype.h index f7e0e07..b7211ec 100644 --- a/src/lib/io/kfilesystemtype.h +++ b/src/lib/io/kfilesystemtype.h @@ -1,49 +1,38 @@ /* - This file is part of the KDE libraries - Copyright (c) 2011 David Faure - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2.1 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2011 David Faure + + SPDX-License-Identifier: LGPL-2.1-only */ #ifndef KFILESYSTEMTYPE_P_H #define KFILESYSTEMTYPE_P_H #include #include /** * @namespace KFileSystemType * Provides utility functions for the type of file systems. */ namespace KFileSystemType { enum Type { Unknown, Nfs, ///< NFS or other full-featured networked filesystems (autofs, subfs, cachefs, sshfs) Smb, ///< SMB/CIFS mount (networked but with some FAT-like behavior) Fat, ///< FAT or similar (msdos, fat, vfat) Ramfs, ///< RAMDISK mount Other ///< ext, reiser, and so on. "Normal" local filesystems. }; /** * Returns the file system type at a given path, as much as we are able to figure it out. * @since 5.0 */ KCOREADDONS_EXPORT Type fileSystemType(const QString &path); } #endif diff --git a/src/lib/io/kfileutils.cpp b/src/lib/io/kfileutils.cpp index 676d5e2..7f6c04c 100644 --- a/src/lib/io/kfileutils.cpp +++ b/src/lib/io/kfileutils.cpp @@ -1,75 +1,65 @@ -/* This file is part of the KDE libraries - Copyright (C) 2000-2005 David Faure +/* + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 2000-2005 David Faure - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #include "kfileutils.h" #include #include #include QString KFileUtils::suggestName(const QUrl &baseURL, const QString &oldName) { QString basename; // Extract the original file extension from the filename QMimeDatabase db; QString nameSuffix = db.suffixForFileName(oldName); if (oldName.lastIndexOf(QLatin1Char('.')) == 0) { basename = QStringLiteral("."); nameSuffix = oldName; } else if (nameSuffix.isEmpty()) { const int lastDot = oldName.lastIndexOf(QLatin1Char('.')); if (lastDot == -1) { basename = oldName; } else { basename = oldName.left(lastDot); nameSuffix = oldName.mid(lastDot); } } else { nameSuffix.prepend(QLatin1Char('.')); basename = oldName.left(oldName.length() - nameSuffix.length()); } // check if (number) exists at the end of the oldName and increment that number const QRegularExpression re(QStringLiteral("\\((\\d+)\\)")); QRegularExpressionMatch rmatch; oldName.lastIndexOf(re, -1, &rmatch); if (rmatch.hasMatch()) { const int currentNum = rmatch.captured(1).toInt(); const QString number = QString::number(currentNum + 1); basename.replace(rmatch.capturedStart(1), rmatch.capturedLength(1), number); } else { // number does not exist, so just append " (1)" to filename basename += QLatin1String(" (1)"); } const QString suggestedName = basename + nameSuffix; // Check if suggested name already exists bool exists = false; if (baseURL.isLocalFile()) { exists = QFileInfo::exists(baseURL.toLocalFile() + QLatin1Char('/') + suggestedName); } if (!exists) { return suggestedName; } else { // already exists -> recurse return suggestName(baseURL, suggestedName); } } diff --git a/src/lib/io/kfileutils.h b/src/lib/io/kfileutils.h index ccff785..113f37a 100644 --- a/src/lib/io/kfileutils.h +++ b/src/lib/io/kfileutils.h @@ -1,43 +1,33 @@ -/* This file is part of the KDE libraries - Copyright (C) 2000-2005 David Faure +/* + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 2000-2005 David Faure - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KFILEUTILS_H #define KFILEUTILS_H #include "kcoreaddons_export.h" #include #include /** * @short A namespace for KFileUtils globals * */ namespace KFileUtils { /** * Given a directory path and a filename (which usually exists already), * this function returns a suggested name for a file that doesn't exist * in that directory. The existence is only checked for local urls though. * The suggested file name is of the form "foo 1", "foo 2" etc. * @since 5.61 */ KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); } #endif diff --git a/src/lib/io/kmessage.cpp b/src/lib/io/kmessage.cpp index 55b9954..7e1d02b 100644 --- a/src/lib/io/kmessage.cpp +++ b/src/lib/io/kmessage.cpp @@ -1,101 +1,90 @@ -/* This file is part of the KDE libraries - Copyright (C) 2006 Michaël Larouche +/* + This file is part of the KDE libraries - This library 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; version 2 - of the License. + SPDX-FileCopyrightText: 2006 Michaël Larouche - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #include "kmessage.h" #include class StaticMessageHandler { public: StaticMessageHandler() {} ~StaticMessageHandler() { delete m_handler; } StaticMessageHandler(const StaticMessageHandler &) = delete; StaticMessageHandler &operator=(const StaticMessageHandler &) = delete; /* Sets the new message handler and deletes the old one */ void setHandler(KMessageHandler *handler) { delete m_handler; m_handler = handler; } KMessageHandler *handler() const { return m_handler; } protected: KMessageHandler *m_handler = nullptr; }; Q_GLOBAL_STATIC(StaticMessageHandler, s_messageHandler) static void internalMessageFallback(KMessage::MessageType messageType, const QString &text, const QString &caption) { QString prefix; switch (messageType) { case KMessage::Error: prefix = QStringLiteral("ERROR: "); break; case KMessage::Fatal: prefix = QStringLiteral("FATAL: "); break; case KMessage::Information: prefix = QStringLiteral("INFORMATION: "); break; case KMessage::Sorry: prefix = QStringLiteral("SORRY: "); break; case KMessage::Warning: prefix = QStringLiteral("WARNING: "); break; } QString message; if (!caption.isEmpty()) { message += QLatin1Char('(') + caption + QLatin1Char(')'); } message += prefix + text; // Show a message to the developer to setup a KMessageHandler std::cerr << "WARNING: Please setup an KMessageHandler with KMessage::setMessageHandler to display message propertly." << std::endl; // Show message to stdout std::cerr << qPrintable(message) << std::endl; } void KMessage::setMessageHandler(KMessageHandler *handler) { // Delete old message handler. s_messageHandler()->setHandler(handler); } void KMessage::message(KMessage::MessageType messageType, const QString &text, const QString &caption) { // Use current message handler if available, else use stdout if (s_messageHandler()->handler()) { s_messageHandler()->handler()->message(messageType, text, caption); } else { internalMessageFallback(messageType, text, caption); } } diff --git a/src/lib/io/kmessage.h b/src/lib/io/kmessage.h index 036e688..814765b 100644 --- a/src/lib/io/kmessage.h +++ b/src/lib/io/kmessage.h @@ -1,127 +1,116 @@ -/* This file is part of the KDE libraries - Copyright (C) 2006 Michaël Larouche +/* + This file is part of the KDE libraries - This library 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; version 2 - of the License. + SPDX-FileCopyrightText: 2006 Michaël Larouche - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KDECORE_KMESSAGE_H #define KDECORE_KMESSAGE_H #include #include class KMessageHandler; /** * @brief Display an informative message using a KMessageHandler. * * This class does not define how to display a message, it is just * a clean interface for developers to use. * The job is done by the current KMessageHandler set in the class. * * If no KMessageHandler is currently registered in KMessage, * the message will be outputed to stderr. * * Use KMessage::setMessageHandler() to use a KMessageHandler. * * @code * KMessage::setMessageHandler( new KMessageBoxHandler(this) ); * // some operation * * KMessage::message( KMessage::Error, i18n("Could not load service. Use kbuildsycoca to fix the service database."), i18n("KService") ); * @endcode * * Some KMessageHandler are already done such as KMessageBoxMessageHandler and KPassivePopupMessageHandler. * @author Michaël Larouche */ namespace KMessage { enum MessageType { /** * Error message. * Display critical information that affect the behavior of the application. */ Error, /** * Information message. * Display useful information to the user. */ Information, /** * Warning message. * Display a message that could affect the behavior of the application. */ Warning, /** * Sorry message. * Display a message explaining that a task couldn't be accomplished. */ Sorry, /** * Fatal message. * Display a message before the application fail and close itself. */ Fatal }; /** * @brief Display a long message of a certain type. * A long message span on multiple lines and can have a caption. * * @param messageType Currrent type of message. See MessageType enum. * @param text Long message to be displayed. * @param caption Caption to be used. This is optional. */ KCOREADDONS_EXPORT void message(KMessage::MessageType messageType, const QString &text, const QString &caption = QString()); /** * @brief Set the current KMessageHandler * Note that this method takes ownership of the KMessageHandler. * @param handler Instance of a real KMessageHandler. * * @warning This function isn't thread-safe. You don't want to * change the message handler during the program's * execution anyways. Do so only at start-up. */ KCOREADDONS_EXPORT void setMessageHandler(KMessageHandler *handler); } /** * \class KMessageHandler kmessage.h * * @brief Abstract class for KMessage handler. * This class define how KMessage display a message. * * Reimplement the virtual methods then set your custom * KMessageHandler using KMessage::setMessageHandler() * * @author Michaël Larouche */ class KCOREADDONS_EXPORT KMessageHandler { public: virtual ~KMessageHandler() {} // KF6 TODO: de-inline (-Wweak-vtables) /** * @brief Display a long message of a certain type. * A long message span on multiple lines and can have a caption. * * @param type Currrent type of message. See MessageType enum. * @param text Long message to be displayed. * @param caption Caption to be used. This is optional. */ virtual void message(KMessage::MessageType type, const QString &text, const QString &caption) = 0; }; #endif diff --git a/src/lib/io/kprocess.cpp b/src/lib/io/kprocess.cpp index 5aa6de4..1a9600c 100644 --- a/src/lib/io/kprocess.cpp +++ b/src/lib/io/kprocess.cpp @@ -1,330 +1,317 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kprocess_p.h" #include #include #include #ifdef Q_OS_WIN # include # include #endif #include ///////////////////////////// // public member functions // ///////////////////////////// KProcess::KProcess(QObject *parent) : QProcess(parent), d_ptr(new KProcessPrivate(this)) { setOutputChannelMode(ForwardedChannels); } KProcess::KProcess(KProcessPrivate *d, QObject *parent) : QProcess(parent), d_ptr(d) { d_ptr->q_ptr = this; setOutputChannelMode(ForwardedChannels); } KProcess::~KProcess() { delete d_ptr; } void KProcess::setOutputChannelMode(OutputChannelMode mode) { QProcess::setProcessChannelMode(static_cast(mode)); } KProcess::OutputChannelMode KProcess::outputChannelMode() const { return static_cast(QProcess::processChannelMode()); } void KProcess::setNextOpenMode(QIODevice::OpenMode mode) { Q_D(KProcess); d->openMode = mode; } #define DUMMYENV "_KPROCESS_DUMMY_=" void KProcess::clearEnvironment() { setEnvironment(QStringList { QStringLiteral(DUMMYENV) }); } void KProcess::setEnv(const QString &name, const QString &value, bool overwrite) { QStringList env = environment(); if (env.isEmpty()) { env = systemEnvironment(); env.removeAll(QStringLiteral(DUMMYENV)); } QString fname(name); fname.append(QLatin1Char('=')); for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) if ((*it).startsWith(fname)) { if (overwrite) { *it = fname.append(value); setEnvironment(env); } return; } env.append(fname.append(value)); setEnvironment(env); } void KProcess::unsetEnv(const QString &name) { QStringList env = environment(); if (env.isEmpty()) { env = systemEnvironment(); env.removeAll(QStringLiteral(DUMMYENV)); } QString fname(name); fname.append(QLatin1Char('=')); for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) if ((*it).startsWith(fname)) { env.erase(it); if (env.isEmpty()) { env.append(QStringLiteral(DUMMYENV)); } setEnvironment(env); return; } } void KProcess::setProgram(const QString &exe, const QStringList &args) { Q_D(KProcess); d->prog = exe; d->args = args; #ifdef Q_OS_WIN setNativeArguments(QString()); #endif } void KProcess::setProgram(const QStringList &argv) { Q_D(KProcess); Q_ASSERT(!argv.isEmpty()); d->args = argv; d->prog = d->args.takeFirst(); #ifdef Q_OS_WIN setNativeArguments(QString()); #endif } KProcess &KProcess::operator<<(const QString &arg) { Q_D(KProcess); if (d->prog.isEmpty()) { d->prog = arg; } else { d->args << arg; } return *this; } KProcess &KProcess::operator<<(const QStringList &args) { Q_D(KProcess); if (d->prog.isEmpty()) { setProgram(args); } else { d->args << args; } return *this; } void KProcess::clearProgram() { Q_D(KProcess); d->prog.clear(); d->args.clear(); #ifdef Q_OS_WIN setNativeArguments(QString()); #endif } void KProcess::setShellCommand(const QString &cmd) { Q_D(KProcess); KShell::Errors err; d->args = KShell::splitArgs( cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err); if (err == KShell::NoError && !d->args.isEmpty()) { d->prog = QStandardPaths::findExecutable(d->args[0]); if (!d->prog.isEmpty()) { d->args.removeFirst(); #ifdef Q_OS_WIN setNativeArguments(QString()); #endif return; } } d->args.clear(); #ifdef Q_OS_UNIX // #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh # if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) && !defined(__APPLE__) // If /bin/sh is a symlink, we can be pretty sure that it points to a // POSIX shell - the original bourne shell is about the only non-POSIX // shell still in use and it is always installed natively as /bin/sh. d->prog = QFile::symLinkTarget(QStringLiteral("/bin/sh")); if (d->prog.isEmpty()) { // Try some known POSIX shells. d->prog = QStandardPaths::findExecutable(QStringLiteral("ksh")); if (d->prog.isEmpty()) { d->prog = QStandardPaths::findExecutable(QStringLiteral("ash")); if (d->prog.isEmpty()) { d->prog = QStandardPaths::findExecutable(QStringLiteral("bash")); if (d->prog.isEmpty()) { d->prog = QStandardPaths::findExecutable(QStringLiteral("zsh")); if (d->prog.isEmpty()) // We're pretty much screwed, to be honest ... { d->prog = QStringLiteral("/bin/sh"); } } } } } # else d->prog = QStringLiteral("/bin/sh"); # endif d->args << QStringLiteral("-c") << cmd; #else // Q_OS_UNIX // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and // KShell::joinArgs() may generate these for security reasons. setEnv(PERCENT_VARIABLE, QStringLiteral("%")); #ifndef _WIN32_WCE WCHAR sysdir[MAX_PATH + 1]; UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1); d->prog = QString::fromUtf16((const ushort *) sysdir, size); d->prog += QLatin1String("\\cmd.exe"); setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"')); #else d->prog = QStringLiteral("\\windows\\cmd.exe"); setNativeArguments(QStringLiteral("/S /C \"") + cmd + QLatin1Char('"')); #endif #endif } QStringList KProcess::program() const { Q_D(const KProcess); QStringList argv = d->args; argv.prepend(d->prog); return argv; } void KProcess::start() { Q_D(KProcess); QProcess::start(d->prog, d->args, d->openMode); } int KProcess::execute(int msecs) { start(); if (!waitForFinished(msecs)) { kill(); waitForFinished(-1); return -2; } return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; } // static int KProcess::execute(const QString &exe, const QStringList &args, int msecs) { KProcess p; p.setProgram(exe, args); return p.execute(msecs); } // static int KProcess::execute(const QStringList &argv, int msecs) { KProcess p; p.setProgram(argv); return p.execute(msecs); } int KProcess::startDetached() { Q_D(KProcess); qint64 pid; if (!QProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) { return 0; } return static_cast(pid); } // static int KProcess::startDetached(const QString &exe, const QStringList &args) { qint64 pid; if (!QProcess::startDetached(exe, args, QString(), &pid)) { return 0; } return static_cast(pid); } // static int KProcess::startDetached(const QStringList &argv) { QStringList args = argv; QString prog = args.takeFirst(); return startDetached(prog, args); } int KProcess::pid() const { #ifdef Q_OS_UNIX return static_cast(QProcess::pid()); #else return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; #endif } #include "moc_kprocess.cpp" diff --git a/src/lib/io/kprocess.h b/src/lib/io/kprocess.h index 21b430e..86c2a31 100644 --- a/src/lib/io/kprocess.h +++ b/src/lib/io/kprocess.h @@ -1,341 +1,328 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KPROCESS_H #define KPROCESS_H #include #include class KProcessPrivate; /** * \class KProcess kprocess.h * * Child process invocation, monitoring and control. * * This class extends QProcess by some useful functionality, overrides * some defaults with saner values and wraps parts of the API into a more * accessible one. * Only use KProcess if you need the extra features, otherwise QProcess * is the preferred way of spawning child processes. * * @author Oswald Buddenhagen **/ class KCOREADDONS_EXPORT KProcess : public QProcess { Q_OBJECT Q_DECLARE_PRIVATE(KProcess) public: /** * Modes in which the output channels can be opened. */ enum OutputChannelMode { SeparateChannels = QProcess::SeparateChannels, /**< Standard output and standard error are handled by KProcess as separate channels */ MergedChannels = QProcess::MergedChannels, /**< Standard output and standard error are handled by KProcess as one channel */ ForwardedChannels = QProcess::ForwardedChannels, /**< Both standard output and standard error are forwarded to the parent process' respective channel */ OnlyStdoutChannel = QProcess::ForwardedErrorChannel, /**< Only standard output is handled; standard error is forwarded */ OnlyStderrChannel = QProcess::ForwardedOutputChannel /**< Only standard error is handled; standard output is forwarded */ }; /** * Constructor */ explicit KProcess(QObject *parent = nullptr); /** * Destructor */ ~KProcess() override; /** * Set how to handle the output channels of the child process. * * The default is ForwardedChannels, which is unlike in QProcess. * Do not request more than you actually handle, as this output is * simply lost otherwise. * * This function must be called before starting the process. * * @param mode the output channel handling mode */ void setOutputChannelMode(OutputChannelMode mode); /** * Query how the output channels of the child process are handled. * * @return the output channel handling mode */ OutputChannelMode outputChannelMode() const; /** * Set the QIODevice open mode the process will be opened in. * * This function must be called before starting the process, obviously. * * @param mode the open mode. Note that this mode is automatically * "reduced" according to the channel modes and redirections. * The default is QIODevice::ReadWrite. */ void setNextOpenMode(QIODevice::OpenMode mode); /** * Adds the variable @p name to the process' environment. * * This function must be called before starting the process. * * @param name the name of the environment variable * @param value the new value for the environment variable * @param overwrite if @c false and the environment variable is already * set, the old value will be preserved */ void setEnv(const QString &name, const QString &value, bool overwrite = true); /** * Removes the variable @p name from the process' environment. * * This function must be called before starting the process. * * @param name the name of the environment variable */ void unsetEnv(const QString &name); /** * Empties the process' environment. * * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added * on *NIX. * * This function must be called before starting the process. */ void clearEnvironment(); /** * Set the program and the command line arguments. * * This function must be called before starting the process, obviously. * * @param exe the program to execute * @param args the command line arguments for the program, * one per list element */ void setProgram(const QString &exe, const QStringList &args = QStringList()); /** * @overload * * @param argv the program to execute and the command line arguments * for the program, one per list element */ void setProgram(const QStringList &argv); /** * Append an element to the command line argument list for this process. * * If no executable is set yet, it will be set instead. * * For example, doing an "ls -l /usr/local/bin" can be achieved by: * \code * KProcess p; * p << "ls" << "-l" << "/usr/local/bin"; * ... * \endcode * * This function must be called before starting the process, obviously. * * @param arg the argument to add * @return a reference to this KProcess */ KProcess &operator<<(const QString &arg); /** * @overload * * @param args the arguments to add * @return a reference to this KProcess */ KProcess &operator<<(const QStringList &args); /** * Clear the program and command line argument list. */ void clearProgram(); /** * Set a command to execute through a shell (a POSIX sh on *NIX * and cmd.exe on Windows). * * Using this for anything but user-supplied commands is usually a bad * idea, as the command's syntax depends on the platform. * Redirections including pipes, etc. are better handled by the * respective functions provided by QProcess. * * If KProcess determines that the command does not really need a * shell, it will transparently execute it without one for performance * reasons. * * This function must be called before starting the process, obviously. * * @param cmd the command to execute through a shell. * The caller must make sure that all filenames etc. are properly * quoted when passed as argument. Failure to do so often results in * serious security holes. See KShell::quoteArg(). */ void setShellCommand(const QString &cmd); /** * Obtain the currently set program and arguments. * * @return a list, the first element being the program, the remaining ones * being command line arguments to the program. */ QStringList program() const; /** * Start the process. * * @see QProcess::start(const QString &, const QStringList &, OpenMode) */ void start(); /** * Start the process, wait for it to finish, and return the exit code. * * This method is roughly equivalent to the sequence: * @code * start(); * waitForFinished(msecs); * return exitCode(); * @endcode * * Unlike the other execute() variants this method is not static, * so the process can be parametrized properly and talked to. * * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ int execute(int msecs = -1); /** * @overload * * @param exe the program to execute * @param args the command line arguments for the program, * one per list element * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); /** * @overload * * @param argv the program to execute and the command line arguments * for the program, one per list element * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ static int execute(const QStringList &argv, int msecs = -1); /** * Start the process and detach from it. See QProcess::startDetached() * for details. * * Unlike the other startDetached() variants this method is not static, * so the process can be parametrized properly. * @note Currently, only the setProgram()/setShellCommand() and * setWorkingDirectory() parametrizations are supported. * * The KProcess object may be re-used immediately after calling this * function. * * @return the PID of the started process or 0 on error */ int startDetached(); /** * @overload * * @param exe the program to start * @param args the command line arguments for the program, * one per list element * @return the PID of the started process or 0 on error */ static int startDetached(const QString &exe, const QStringList &args = QStringList()); /** * @overload * * @param argv the program to start and the command line arguments * for the program, one per list element * @return the PID of the started process or 0 on error */ static int startDetached(const QStringList &argv); /** * Obtain the process' ID as known to the system. * * Unlike with QProcess::pid(), this is a real PID also on Windows. * * This function can be called only while the process is running. * It cannot be applied to detached processes. * * @return the process ID */ int pid() const; protected: /** * @internal */ KProcess(KProcessPrivate *d, QObject *parent); /** * @internal */ KProcessPrivate *const d_ptr; private: // hide those #if QT_DEPRECATED_SINCE(5, 13) using QProcess::setReadChannelMode; using QProcess::readChannelMode; #endif using QProcess::setProcessChannelMode; using QProcess::processChannelMode; }; #endif diff --git a/src/lib/io/kprocess_p.h b/src/lib/io/kprocess_p.h index 611fa72..edce10f 100644 --- a/src/lib/io/kprocess_p.h +++ b/src/lib/io/kprocess_p.h @@ -1,44 +1,31 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KPROCESS_P_H #define KPROCESS_P_H #include "kprocess.h" class KProcessPrivate { Q_DECLARE_PUBLIC(KProcess) protected: KProcessPrivate(KProcess* q) : openMode(QIODevice::ReadWrite), q_ptr(q) { } QString prog; QStringList args; QIODevice::OpenMode openMode; KProcess *q_ptr; }; #endif diff --git a/src/lib/io/kurlmimedata.cpp b/src/lib/io/kurlmimedata.cpp index fd48a8b..30815e6 100644 --- a/src/lib/io/kurlmimedata.cpp +++ b/src/lib/io/kurlmimedata.cpp @@ -1,120 +1,109 @@ -/* This file is part of the KDE libraries - * Copyright (C) 2005-2012 David Faure - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2005-2012 David Faure + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "kurlmimedata.h" #include #include static QString kdeUriListMime() { return QStringLiteral("application/x-kde4-urilist"); } // keep this name "kde4" for compat. static QByteArray uriListData(const QList &urls) { // compatible with qmimedata.cpp encoding of QUrls QByteArray result; for (int i = 0; i < urls.size(); ++i) { result += urls.at(i).toEncoded(); result += "\r\n"; } return result; } void KUrlMimeData::setUrls(const QList &urls, const QList &mostLocalUrls, QMimeData *mimeData) { // Export the most local urls as text/uri-list and plain text, for non KDE apps. mimeData->setUrls(mostLocalUrls); // set text/uri-list and text/plain // Export the real KIO urls as a kde-specific mimetype mimeData->setData(kdeUriListMime(), uriListData(urls)); } void KUrlMimeData::setMetaData(const MetaDataMap &metaData, QMimeData *mimeData) { QByteArray metaDataData; // :) for (MetaDataMap::const_iterator it = metaData.begin(); it != metaData.end(); ++it) { metaDataData += it.key().toUtf8(); metaDataData += "$@@$"; metaDataData += it.value().toUtf8(); metaDataData += "$@@$"; } mimeData->setData(QStringLiteral("application/x-kio-metadata"), metaDataData); } QStringList KUrlMimeData::mimeDataTypes() { return QStringList { kdeUriListMime(), QStringLiteral("text/uri-list") }; } static QList extractKdeUriList(const QMimeData *mimeData) { QList uris; const QByteArray ba = mimeData->data(kdeUriListMime()); // Code from qmimedata.cpp QList urls = ba.split('\n'); for (int i = 0; i < urls.size(); ++i) { QByteArray data = urls.at(i).trimmed(); if (!data.isEmpty()) { uris.append(QUrl::fromEncoded(data)); } } return uris; } QList KUrlMimeData::urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions, MetaDataMap *metaData) { QList uris; if (decodeOptions == PreferLocalUrls) { // Extracting uris from text/uri-list, use the much faster QMimeData method urls() uris = mimeData->urls(); if (uris.isEmpty()) { uris = extractKdeUriList(mimeData); } } else { uris = extractKdeUriList(mimeData); if (uris.isEmpty()) { uris = mimeData->urls(); } } if (metaData) { const QByteArray metaDataPayload = mimeData->data(QStringLiteral("application/x-kio-metadata")); if (!metaDataPayload.isEmpty()) { QString str = QString::fromUtf8(metaDataPayload.constData()); Q_ASSERT(str.endsWith(QLatin1String("$@@$"))); str.chop(4); const QStringList lst = str.split(QStringLiteral("$@@$")); bool readingKey = true; // true, then false, then true, etc. QString key; for (QStringList::const_iterator it = lst.begin(); it != lst.end(); ++it) { if (readingKey) { key = *it; } else { metaData->insert(key, *it); } readingKey = !readingKey; } Q_ASSERT(readingKey); // an odd number of items would be, well, odd ;-) } } return uris; } diff --git a/src/lib/io/kurlmimedata.h b/src/lib/io/kurlmimedata.h index 9592f2f..85d6e07 100644 --- a/src/lib/io/kurlmimedata.h +++ b/src/lib/io/kurlmimedata.h @@ -1,107 +1,96 @@ -/* This file is part of the KDE libraries - * Copyright (C) 2005-2012 David Faure - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2005-2012 David Faure + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef KURLMIMEDATA_H #define KURLMIMEDATA_H #include #include #include "kcoreaddons_export.h" QT_BEGIN_NAMESPACE class QMimeData; QT_END_NAMESPACE /** * Utility functions for using URLs in QMimeData. * In addition to QMimeData::setUrls() and QMimeData::urls(), these functions allow to: * * - Store two sets of URLs, the KDE-specific URLs and the equivalent local-file URLs * for compatibility with non-KDE applications * - Store KIO metadata, such as the HTTP referrer for a given URL (some websites * require it for downloading e.g. an image) * * @since 5.0 */ namespace KUrlMimeData { typedef QMap MetaDataMap; /** * Adds URLs and KIO metadata into the given QMimeData. * * WARNING: do not call this method multiple times on the same mimedata object, * you can add urls only once. But you can add other things, e.g. images, XML... * * @param mimeData the QMimeData instance used to drag or copy this URL */ KCOREADDONS_EXPORT void setUrls(const QList &urls, const QList &mostLocalUrls, QMimeData *mimeData); /** * @param metaData KIO metadata shipped in the mime data, which is used for instance to * set a correct HTTP referrer (some websites require it for downloading e.g. an image) */ KCOREADDONS_EXPORT void setMetaData(const MetaDataMap &metaData, QMimeData *mimeData); /** * Return the list of mimeTypes that can be decoded by urlsFromMimeData */ KCOREADDONS_EXPORT QStringList mimeDataTypes(); /** * Flags to be used in urlsFromMimeData. */ enum DecodeOptions { /** * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), * decode it as local urls. Useful in paste/drop operations that end up calling KIO, * so that urls from other users work as well. */ PreferLocalUrls, /** * When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and * the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo), * decode it as the KDE-style URL. Useful in DnD code e.g. when moving icons, * and the kde-style url is used as identifier for the icons. */ PreferKdeUrls }; /** * Extract a list of urls from the contents of @p mimeData. * * Compared to QMimeData::urls(), this method has support for retrieving KDE-specific URLs * when urls() would retrieve "most local URLs" instead. * * Decoding will fail if @p mimeData does not contain any URLs, or if at * least one extracted URL is not valid. * * @param mimeData the mime data to extract from; cannot be 0 * @param decodeOptions options for decoding * @param metaData optional pointer to a map which will hold the metadata after this call * @return the list of urls */ KCOREADDONS_EXPORT QList urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions = PreferKdeUrls, MetaDataMap *metaData = nullptr); } #endif /* KURLMIMEDATA_H */ diff --git a/src/lib/jobs/kcompositejob.cpp b/src/lib/jobs/kcompositejob.cpp index fc35f34..0c444ab 100644 --- a/src/lib/jobs/kcompositejob.cpp +++ b/src/lib/jobs/kcompositejob.cpp @@ -1,116 +1,105 @@ -/* This file is part of the KDE project - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #include "kcompositejob.h" #include "kcompositejob_p.h" KCompositeJobPrivate::KCompositeJobPrivate() { } KCompositeJobPrivate::~KCompositeJobPrivate() { } KCompositeJob::KCompositeJob(QObject *parent) : KJob(*new KCompositeJobPrivate, parent) { } KCompositeJob::KCompositeJob(KCompositeJobPrivate &dd, QObject *parent) : KJob(dd, parent) { } KCompositeJob::~KCompositeJob() { } bool KCompositeJob::addSubjob(KJob *job) { Q_D(KCompositeJob); if (job == nullptr || d->subjobs.contains(job)) { return false; } job->setParent(this); d->subjobs.append(job); connect(job, &KJob::result, this, &KCompositeJob::slotResult); // Forward information from that subjob. connect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage); return true; } bool KCompositeJob::removeSubjob(KJob *job) { Q_D(KCompositeJob); // remove only Subjobs that are on the list if (d->subjobs.removeAll(job) > 0) { job->setParent(nullptr); disconnect(job, &KJob::result, this, &KCompositeJob::slotResult); disconnect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage); return true; } return false; } bool KCompositeJob::hasSubjobs() const { return !d_func()->subjobs.isEmpty(); } const QList &KCompositeJob::subjobs() const { return d_func()->subjobs; } void KCompositeJob::clearSubjobs() { Q_D(KCompositeJob); for (KJob *job : qAsConst(d->subjobs)) { job->setParent(nullptr); disconnect(job, &KJob::result, this, &KCompositeJob::slotResult); disconnect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage); } d->subjobs.clear(); } void KCompositeJob::slotResult(KJob *job) { // Did job have an error ? if (job->error() && !error()) { // Store it in the parent only if first error setError(job->error()); setErrorText(job->errorText()); // Finish this job emitResult(); } // After a subjob is done, we might want to start another one. // Therefore do not emitResult removeSubjob(job); } void KCompositeJob::slotInfoMessage(KJob *job, const QString &plain, const QString &rich) { emit infoMessage(job, plain, rich); } #include "moc_kcompositejob.cpp" diff --git a/src/lib/jobs/kcompositejob.h b/src/lib/jobs/kcompositejob.h index 1944496..44550a0 100644 --- a/src/lib/jobs/kcompositejob.h +++ b/src/lib/jobs/kcompositejob.h @@ -1,123 +1,112 @@ -/* This file is part of the KDE project - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KCOMPOSITEJOB_H #define KCOMPOSITEJOB_H #include #include #include class KCompositeJobPrivate; /** * @class KCompositeJob kcompositejob.h KCompositeJob * * The base class for all jobs able to be composed of one * or more subjobs. */ class KCOREADDONS_EXPORT KCompositeJob : public KJob { Q_OBJECT public: /** * Creates a new KCompositeJob object. * * @param parent the parent QObject */ explicit KCompositeJob(QObject *parent = nullptr); /** * Destroys a KCompositeJob object. */ ~KCompositeJob() override; protected: /** * Add a job that has to be finished before a result * is emitted. This has obviously to be called before * the result has been emitted by the job. * * Note that the composite job takes ownership of @p job * * @param job the subjob to add * @return true if the job has been added correctly, false otherwise */ virtual bool addSubjob(KJob *job); /** * Mark a sub job as being done. * * The ownership of @p job is passed on to the caller. * * @param job the subjob to remove * @return true if the job has been removed correctly, false otherwise */ virtual bool removeSubjob(KJob *job); /** * Checks if this job has subjobs running. * * @return true if we still have subjobs running, false otherwise */ bool hasSubjobs() const; /** * Retrieves the list of the subjobs. * * @return the full list of sub jobs */ const QList &subjobs() const; /** * Clears the list of subjobs. * * Note that this will *not* delete the subjobs. * Ownership of the subjobs is passed on to the caller. */ void clearSubjobs(); protected Q_SLOTS: /** * Called whenever a subjob finishes. * Default implementation checks for errors and propagates * to parent job, and in all cases it calls removeSubjob. * * @param job the subjob */ virtual void slotResult(KJob *job); /** * Forward signal from subjob. * * @param job the subjob * @param plain the info message in plain text version * @param rich the info message in rich text version * @see infoMessage() */ virtual void slotInfoMessage(KJob *job, const QString &plain, const QString &rich); protected: KCompositeJob(KCompositeJobPrivate &dd, QObject *parent); private: Q_DECLARE_PRIVATE(KCompositeJob) }; #endif diff --git a/src/lib/jobs/kcompositejob_p.h b/src/lib/jobs/kcompositejob_p.h index cbc8050..e722be9 100644 --- a/src/lib/jobs/kcompositejob_p.h +++ b/src/lib/jobs/kcompositejob_p.h @@ -1,41 +1,30 @@ -/* This file is part of the KDE project - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KCOMPOSITEJOB_P_H #define KCOMPOSITEJOB_P_H #include "kcompositejob.h" #include "kjob_p.h" // This is a private class, but it's exported for // KIO::Job's usage. Other Job classes in kdelibs may // use it too. class KCOREADDONS_EXPORT KCompositeJobPrivate: public KJobPrivate { public: KCompositeJobPrivate(); ~KCompositeJobPrivate(); QList subjobs; Q_DECLARE_PUBLIC(KCompositeJob) }; #endif diff --git a/src/lib/jobs/kjob.cpp b/src/lib/jobs/kjob.cpp index bff1627..99d0081 100644 --- a/src/lib/jobs/kjob.cpp +++ b/src/lib/jobs/kjob.cpp @@ -1,340 +1,329 @@ -/* This file is part of the KDE project - Copyright (C) 2000 Stephan Kulow - David Faure - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2000 Stephan Kulow + SPDX-FileCopyrightText: 2000 David Faure + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #include "kjob.h" #include "kjob_p.h" #include "kjobuidelegate.h" #include #include KJobPrivate::KJobPrivate() : q_ptr(nullptr), uiDelegate(nullptr), error(KJob::NoError), progressUnit(KJob::Bytes), percentage(0), speedTimer(nullptr), eventLoop(nullptr), capabilities(KJob::NoCapabilities), suspended(false), isAutoDelete(true), isFinished(false) { } KJobPrivate::~KJobPrivate() { } KJob::KJob(QObject *parent) : QObject(parent), d_ptr(new KJobPrivate) { d_ptr->q_ptr = this; } KJob::KJob(KJobPrivate &dd, QObject *parent) : QObject(parent), d_ptr(&dd) { d_ptr->q_ptr = this; } KJob::~KJob() { if (!d_ptr->isFinished) { emit finished(this, QPrivateSignal()); } delete d_ptr->speedTimer; delete d_ptr->uiDelegate; delete d_ptr; } void KJob::setUiDelegate(KJobUiDelegate *delegate) { Q_D(KJob); if (delegate == nullptr || delegate->setJob(this)) { delete d->uiDelegate; d->uiDelegate = delegate; if (d->uiDelegate) { d->uiDelegate->connectJob(this); } } } KJobUiDelegate *KJob::uiDelegate() const { return d_func()->uiDelegate; } KJob::Capabilities KJob::capabilities() const { return d_func()->capabilities; } bool KJob::isSuspended() const { return d_func()->suspended; } void KJob::finishJob(bool emitResult) { Q_D(KJob); d->isFinished = true; if (d->eventLoop) { d->eventLoop->quit(); } // If we are displaying a progress dialog, remove it first. emit finished(this, QPrivateSignal()); if (emitResult) { emit result(this, QPrivateSignal()); } if (isAutoDelete()) { deleteLater(); } } bool KJob::kill(KillVerbosity verbosity) { if (doKill()) { setError(KilledJobError); finishJob(verbosity != Quietly); return true; } else { return false; } } bool KJob::suspend() { Q_D(KJob); if (!d->suspended) { if (doSuspend()) { d->suspended = true; emit suspended(this, QPrivateSignal()); return true; } } return false; } bool KJob::resume() { Q_D(KJob); if (d->suspended) { if (doResume()) { d->suspended = false; emit resumed(this, QPrivateSignal()); return true; } } return false; } bool KJob::doKill() { return false; } bool KJob::doSuspend() { return false; } bool KJob::doResume() { return false; } void KJob::setCapabilities(KJob::Capabilities capabilities) { Q_D(KJob); d->capabilities = capabilities; } bool KJob::exec() { Q_D(KJob); // Usually this job would delete itself, via deleteLater() just after // emitting result() (unless configured otherwise). Since we use an event // loop below, that event loop will process the deletion event and we'll // have been deleted when exec() returns. This crashes, so temporarily // suspend autodeletion and manually do it afterwards. const bool wasAutoDelete = isAutoDelete(); setAutoDelete(false); Q_ASSERT(! d->eventLoop); QEventLoop loop(this); d->eventLoop = &loop; start(); if (!d->isFinished) { d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents); } d->eventLoop = nullptr; if (wasAutoDelete) { deleteLater(); } return (d->error == NoError); } int KJob::error() const { return d_func()->error; } QString KJob::errorText() const { return d_func()->errorText; } QString KJob::errorString() const { return d_func()->errorText; } qulonglong KJob::processedAmount(Unit unit) const { return d_func()->processedAmount[unit]; } qulonglong KJob::totalAmount(Unit unit) const { return d_func()->totalAmount[unit]; } unsigned long KJob::percent() const { return d_func()->percentage; } void KJob::setError(int errorCode) { Q_D(KJob); d->error = errorCode; } void KJob::setErrorText(const QString &errorText) { Q_D(KJob); d->errorText = errorText; } void KJob::setProcessedAmount(Unit unit, qulonglong amount) { Q_D(KJob); bool should_emit = (d->processedAmount[unit] != amount); d->processedAmount[unit] = amount; if (should_emit) { emit processedAmount(this, unit, amount); if (unit == d->progressUnit) { emit processedSize(this, amount); emitPercent(d->processedAmount[unit], d->totalAmount[unit]); } } } void KJob::setTotalAmount(Unit unit, qulonglong amount) { Q_D(KJob); bool should_emit = (d->totalAmount[unit] != amount); d->totalAmount[unit] = amount; if (should_emit) { emit totalAmount(this, unit, amount); if (unit == d->progressUnit) { emit totalSize(this, amount); emitPercent(d->processedAmount[unit], d->totalAmount[unit]); } } } void KJob::setPercent(unsigned long percentage) { Q_D(KJob); if (d->percentage != percentage) { d->percentage = percentage; emit percent(this, percentage); } } void KJob::emitResult() { finishJob(true); } void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount) { Q_D(KJob); // calculate percents if (totalAmount) { unsigned long oldPercentage = d->percentage; d->percentage = 100.0 * processedAmount / totalAmount; if (d->percentage != oldPercentage) { emit percent(this, d->percentage); } } } void KJob::emitSpeed(unsigned long value) { Q_D(KJob); if (!d->speedTimer) { d->speedTimer = new QTimer(this); connect(d->speedTimer, SIGNAL(timeout()), SLOT(_k_speedTimeout())); } emit speed(this, value); d->speedTimer->start(5000); // 5 seconds interval should be enough } void KJobPrivate::_k_speedTimeout() { Q_Q(KJob); // send 0 and stop the timer // timer will be restarted only when we receive another speed event emit q->speed(q, 0); speedTimer->stop(); } bool KJob::isAutoDelete() const { Q_D(const KJob); return d->isAutoDelete; } void KJob::setAutoDelete(bool autodelete) { Q_D(KJob); d->isAutoDelete = autodelete; } #include "moc_kjob.cpp" diff --git a/src/lib/jobs/kjob.h b/src/lib/jobs/kjob.h index 15e215a..6f78c8f 100644 --- a/src/lib/jobs/kjob.h +++ b/src/lib/jobs/kjob.h @@ -1,655 +1,644 @@ -/* This file is part of the KDE project - Copyright (C) 2000 Stephan Kulow - David Faure - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2000 Stephan Kulow + SPDX-FileCopyrightText: 2000 David Faure + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KJOB_H #define KJOB_H #include #include #include class KJobUiDelegate; class KJobPrivate; /** * @class KJob kjob.h KJob * * The base class for all jobs. * For all jobs created in an application, the code looks like * * \code * void SomeClass::methodWithAsynchronousJobCall() * { * KJob* job = someoperation(some parameters); * connect(job, &KJob::result, * this, &SomeClass::handleResult); * job->start(); * } * \endcode * (other connects, specific to the job) * * And handleResult is usually at least: * * \code * void SomeClass::handleResult(KJob *job) * { * if (job->error()) { * doSomething(); * } * } * \endcode * * With the synchronous interface the code looks like * * \code * void SomeClass::methodWithSynchronousJobCall() * { * KJob *job = someoperation( some parameters ); * if (!job->exec()) { * // An error occurred * } else { * // Do something * } * } * \endcode * * Subclasses must implement start(), which should trigger * the execution of the job (although the work should be * done asynchronously). errorString() should also be * reimplemented by any subclasses that introduce new * error codes. * * @note KJob and its subclasses are meant to be used * in a fire-and-forget way. Jobs will delete themselves * when they finish using deleteLater() (although this * behaviour can be changed), so a job instance will * disappear after the next event loop run. */ class KCOREADDONS_EXPORT KJob : public QObject { Q_OBJECT Q_PROPERTY(int error READ error NOTIFY result) Q_PROPERTY(QString errorText READ errorText NOTIFY result) Q_PROPERTY(QString errorString READ errorString NOTIFY result) Q_PROPERTY(unsigned long percent READ percent NOTIFY percent) Q_PROPERTY(Capabilities capabilities READ capabilities CONSTANT) public: enum Unit { Bytes, Files, Directories }; Q_ENUM(Unit) enum Capability { NoCapabilities = 0x0000, Killable = 0x0001, Suspendable = 0x0002 }; Q_ENUM(Capability) Q_DECLARE_FLAGS(Capabilities, Capability) Q_FLAG(Capabilities) /** * Creates a new KJob object. * * @param parent the parent QObject */ explicit KJob(QObject *parent = nullptr); /** * Destroys a KJob object. */ ~KJob() override; /** * Attach a UI delegate to this job. * * If the job had another UI delegate, it's automatically deleted. Once * attached to the job, the UI delegate will be deleted with the job. * * @param delegate the new UI delegate to use * @see KJobUiDelegate */ void setUiDelegate(KJobUiDelegate *delegate); /** * Retrieves the delegate attached to this job. * * @return the delegate attached to this job, or @c nullptr if there's no such delegate */ KJobUiDelegate *uiDelegate() const; /** * Returns the capabilities of this job. * * @return the capabilities that this job supports * @see setCapabilities() */ Capabilities capabilities() const; /** * Returns if the job was suspended with the suspend() call. * * @return if the job was suspended * @see suspend() resume() */ bool isSuspended() const; /** * Starts the job asynchronously. * * When the job is finished, result() is emitted. * * Warning: Never implement any synchronous workload in this method. This method * should just trigger the job startup, not do any work itself. It is expected to * be non-blocking. * * This is the method all subclasses need to implement. * It should setup and trigger the workload of the job. It should not do any * work itself. This includes all signals and terminating the job, e.g. by * emitResult(). The workload, which could be another method of the * subclass, is to be triggered using the event loop, e.g. by code like: * \code * void ExampleJob::start() * { * QTimer::singleShot(0, this, &ExampleJob::doWork); * } * \endcode */ Q_SCRIPTABLE virtual void start() = 0; enum KillVerbosity { Quietly, EmitResult }; Q_ENUM(KillVerbosity) public Q_SLOTS: /** * Aborts this job. * * This kills and deletes the job. * * @param verbosity if equals to EmitResult, Job will emit signal result * and ask uiserver to close the progress window. * @p verbosity is set to EmitResult for subjobs. Whether applications * should call with Quietly or EmitResult depends on whether they rely * on result being emitted or not. Please notice that if @p verbosity is * set to Quietly, signal result will NOT be emitted. * @return true if the operation is supported and succeeded, false otherwise */ bool kill(KillVerbosity verbosity = Quietly); /** * Suspends this job. * The job should be kept in a state in which it is possible to resume it. * * @return true if the operation is supported and succeeded, false otherwise */ bool suspend(); /** * Resumes this job. * * @return true if the operation is supported and succeeded, false otherwise */ bool resume(); protected: /** * Aborts this job quietly. * * This simply kills the job, no error reporting or job deletion should be involved. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doKill(); /** * Suspends this job. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doSuspend(); /** * Resumes this job. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doResume(); /** * Sets the capabilities for this job. * * @param capabilities are the capabilities supported by this job * @see capabilities() */ void setCapabilities(Capabilities capabilities); public: /** * Executes the job synchronously. * * This will start a nested QEventLoop internally. Nested event loop can be dangerous and * can have unintended side effects, you should avoid calling exec() whenever you can and use the * asynchronous interface of KJob instead. * * Should you indeed call this method, you need to make sure that all callers are reentrant, * so that events delivered by the inner event loop don't cause non-reentrant functions to be * called, which usually wreaks havoc. * * Note that the event loop started by this method does not process user input events, which means * your user interface will effectively be blocked. Other events like paint or network events are * still being processed. The advantage of not processing user input events is that the chance of * accidental reentrance is greatly reduced. Still you should avoid calling this function. * * @return true if the job has been executed without error, false otherwise */ bool exec(); enum { /*** Indicates there is no error */ NoError = 0, /*** Indicates the job was killed */ KilledJobError = 1, /*** Subclasses should define error codes starting at this value */ UserDefinedError = 100 }; /** * Returns the error code, if there has been an error. * * Only call this method from the slot connected to result(). * * @return the error code for this job, 0 if no error. */ int error() const; /** * Returns the error text if there has been an error. * * Only call if error is not 0. * * This is usually some extra data associated with the error, * such as a URL. Use errorString() to get a human-readable, * translated message. * * @return a string to help understand the error */ QString errorText() const; /** * A human-readable error message. * * This provides a translated, human-readable description of the * error. Only call if error is not 0. * * Subclasses should implement this to create a translated * error message from the error code and error text. * For example: * \code * if (error() == ReadFailed) { * i18n("Could not read \"%1\"", errorText()); * } * \endcode * * @return a translated error message, providing error() is 0 */ virtual QString errorString() const; /** * Returns the processed amount of a given unit for this job. * * @param unit the unit of the requested amount * @return the processed size */ Q_SCRIPTABLE qulonglong processedAmount(Unit unit) const; /** * Returns the total amount of a given unit for this job. * * @param unit the unit of the requested amount * @return the total size */ Q_SCRIPTABLE qulonglong totalAmount(Unit unit) const; /** * Returns the overall progress of this job. * * @return the overall progress of this job */ unsigned long percent() const; /** * set the auto-delete property of the job. If @p autodelete is * set to false the job will not delete itself once it is finished. * * The default for any KJob is to automatically delete itself. * * @param autodelete set to false to disable automatic deletion * of the job. */ void setAutoDelete(bool autodelete); /** * Returns whether this job automatically deletes itself once * the job is finished. * * @return whether the job is deleted automatically after * finishing. */ bool isAutoDelete() const; Q_SIGNALS: /** * Emitted when the job is finished, in any case. It is used to notify * observers that the job is terminated and that progress can be hidden. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitResult() instead. * * In general, to be notified of a job's completion, client code should connect to result() * rather than finished(), so that kill(Quietly) is indeed quiet. * However if you store a list of jobs and they might get killed silently, * then you must connect to this instead of result(), to avoid dangling pointers in your list. * * @param job the job that emitted this signal * @internal * * @see result */ void finished(KJob *job #if !defined(K_DOXYGEN) , QPrivateSignal #endif ); /** * Emitted when the job is suspended. * * This is a private signal, it can't be emitted directly by subclasses of * KJob. * * @param job the job that emitted this signal */ void suspended(KJob *job #if !defined(K_DOXYGEN) , QPrivateSignal #endif ); /** * Emitted when the job is resumed. * * This is a private signal, it can't be emitted directly by subclasses of * KJob. * * @param job the job that emitted this signal */ void resumed(KJob *job #if !defined(K_DOXYGEN) , QPrivateSignal #endif ); /** * Emitted when the job is finished (except when killed with KJob::Quietly). * * Use error to know if the job was finished with error. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitResult() instead. * * Please connect to this signal instead of finished. * * @param job the job that emitted this signal * * @see kill */ void result(KJob *job #if !defined(K_DOXYGEN) , QPrivateSignal #endif ); /** * Emitted to display general description of this job. A description has * a title and two optional fields which can be used to complete the * description. * * Examples of titles are "Copying", "Creating resource", etc. * The fields of the description can be "Source" with an URL, and, * "Destination" with an URL for a "Copying" description. * @param job the job that emitted this signal * @param title the general description of the job * @param field1 first field (localized name and value) * @param field2 second field (localized name and value) */ void description(KJob *job, const QString &title, const QPair &field1 = QPair(), const QPair &field2 = QPair()); /** * Emitted to display state information about this job. * Examples of message are "Resolving host", "Connecting to host...", etc. * * @param job the job that emitted this signal * @param plain the info message * @param rich the rich text version of the message, or QString() is none is available */ void infoMessage(KJob *job, const QString &plain, const QString &rich = QString()); /** * Emitted to display a warning about this job. * * @param job the job that emitted this signal * @param plain the warning message * @param rich the rich text version of the message, or QString() is none is available */ void warning(KJob *job, const QString &plain, const QString &rich = QString()); Q_SIGNALS: // These signals must be connected from KIO::KCoreDirLister (among others), // therefore they must be public. /** * Emitted when we know the amount the job will have to process. The unit of this * amount is sent too. It can be emitted several times if the job manages several * different units. * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use setTotalAmount() instead. * * @param job the job that emitted this signal * @param unit the unit of the total amount * @param amount the total amount */ void totalAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Regularly emitted to show the progress of this job by giving the current amount. * The unit of this amount is sent too. It can be emitted several times if the job * manages several different units. * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use setProcessedAmount() instead. * * @param job the job that emitted this signal * @param unit the unit of the processed amount * @param amount the processed amount */ void processedAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Emitted when we know the size of this job (data size in bytes for transfers, * number of entries for listings, etc). * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use setTotalAmount() instead. * * @param job the job that emitted this signal * @param size the total size */ void totalSize(KJob *job, qulonglong size); /** * Regularly emitted to show the progress of this job * (current data size in bytes for transfers, entries listed, etc.). * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use setProcessedAmount() instead. * * @param job the job that emitted this signal * @param size the processed size */ void processedSize(KJob *job, qulonglong size); /** * Progress signal showing the overall progress of the job * This is valid for any kind of job, and allows using a * a progress bar very easily. (see KProgressBar). * Note that this signal is not emitted for finished jobs. * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use emitPercent(), setPercent() setTotalAmount() or * setProcessedAmount() instead. * * @param job the job that emitted this signal * @param percent the percentage */ void percent(KJob *job, unsigned long percent); /** * Emitted to display information about the speed of this job. * * @note This is a private signal, it shouldn't be emitted directly by subclasses of * KJob, use emitSpeed() instead. * * @param job the job that emitted this signal * @param speed the speed in bytes/s */ void speed(KJob *job, unsigned long speed); protected: /** * Sets the error code. * * It should be called when an error * is encountered in the job, just before calling emitResult(). * * You should define an (anonymous) enum of error codes, * with values starting at KJob::UserDefinedError, and use * those. For example, * @code * enum { * InvalidFoo = UserDefinedError, * BarNotFound * }; * @endcode * * @param errorCode the error code * @see emitResult() */ void setError(int errorCode); /** * Sets the error text. * * It should be called when an error * is encountered in the job, just before calling emitResult(). * * Provides extra information about the error that cannot be * determined directly from the error code. For example, a * URL or filename. This string is not normally translatable. * * @param errorText the error text * @see emitResult(), errorString(), setError() */ void setErrorText(const QString &errorText); /** * Sets the processed size. The processedAmount() and percent() signals * are emitted if the values changed. The percent() signal is emitted * only for the progress unit. * * @param unit the unit of the new processed amount * @param amount the new processed amount */ void setProcessedAmount(Unit unit, qulonglong amount); /** * Sets the total size. The totalSize() and percent() signals * are emitted if the values changed. The percent() signal is emitted * only for the progress unit. * * @param unit the unit of the new total amount * @param amount the new total amount */ void setTotalAmount(Unit unit, qulonglong amount); /** * Sets the overall progress of the job. The percent() signal * is emitted if the value changed. * * @param percentage the new overall progress */ void setPercent(unsigned long percentage); /** * Utility function to emit the result signal, and suicide this job. * It first notifies the observers to hide the progress for this job using * the finished() signal. * * @note Deletes this job using deleteLater(). * * @see result() * @see finished() */ void emitResult(); /** * Utility function for inherited jobs. * Emits the percent signal if bigger than previous value, * after calculating it from the parameters. * * @param processedAmount the processed amount * @param totalAmount the total amount * @see percent() */ void emitPercent(qulonglong processedAmount, qulonglong totalAmount); /** * Utility function for inherited jobs. * Emits the speed signal and starts the timer for removing that info * * @param speed the speed in bytes/s */ void emitSpeed(unsigned long speed); protected: KJobPrivate *const d_ptr; KJob(KJobPrivate &dd, QObject *parent); private: void finishJob(bool emitResult); Q_PRIVATE_SLOT(d_func(), void _k_speedTimeout()) Q_DECLARE_PRIVATE(KJob) }; Q_DECLARE_METATYPE(KJob::Unit) Q_DECLARE_OPERATORS_FOR_FLAGS(KJob::Capabilities) #endif diff --git a/src/lib/jobs/kjob_p.h b/src/lib/jobs/kjob_p.h index b9edea7..4e59f79 100644 --- a/src/lib/jobs/kjob_p.h +++ b/src/lib/jobs/kjob_p.h @@ -1,67 +1,56 @@ -/* This file is part of the KDE project - Copyright (C) 2000 Stephan Kulow - David Faure - Copyright (C) 2006 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2000 Stephan Kulow + SPDX-FileCopyrightText: 2000 David Faure + SPDX-FileCopyrightText: 2006 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KJOB_P_H #define KJOB_P_H #include "kjob.h" #include #include class KJobUiDelegate; class QTimer; class QEventLoop; // This is a private class, but it's exported for // KIO::Job's usage. Other Job classes in kdelibs may // use it too. class KCOREADDONS_EXPORT KJobPrivate { public: KJobPrivate(); virtual ~KJobPrivate(); KJob *q_ptr; KJobUiDelegate *uiDelegate; QString errorText; int error; KJob::Unit progressUnit; QMap processedAmount; QMap totalAmount; unsigned long percentage; QTimer *speedTimer; QEventLoop *eventLoop; // eventLoopLocker prevents QCoreApplication from exiting when the last // window is closed until the job has finished running QEventLoopLocker eventLoopLocker; KJob::Capabilities capabilities; bool suspended; bool isAutoDelete; void _k_speedTimeout(); bool isFinished; Q_DECLARE_PUBLIC(KJob) }; #endif diff --git a/src/lib/jobs/kjobtrackerinterface.cpp b/src/lib/jobs/kjobtrackerinterface.cpp index f25ee65..42f9297 100644 --- a/src/lib/jobs/kjobtrackerinterface.cpp +++ b/src/lib/jobs/kjobtrackerinterface.cpp @@ -1,150 +1,139 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #include "kjobtrackerinterface.h" #include "kjob.h" class Q_DECL_HIDDEN KJobTrackerInterface::Private { public: Private(KJobTrackerInterface *interface) : q(interface) { } KJobTrackerInterface *const q; }; KJobTrackerInterface::KJobTrackerInterface(QObject *parent) : QObject(parent), d(new Private(this)) { qRegisterMetaType>(); } KJobTrackerInterface::~KJobTrackerInterface() { delete d; } void KJobTrackerInterface::registerJob(KJob *job) { QObject::connect(job, SIGNAL(finished(KJob*)), this, SLOT(unregisterJob(KJob*))); QObject::connect(job, SIGNAL(finished(KJob*)), this, SLOT(finished(KJob*))); QObject::connect(job, SIGNAL(suspended(KJob*)), this, SLOT(suspended(KJob*))); QObject::connect(job, SIGNAL(resumed(KJob*)), this, SLOT(resumed(KJob*))); QObject::connect(job, SIGNAL(description(KJob *, const QString &, const QPair &, const QPair &)), this, SLOT(description(KJob *, const QString &, const QPair &, const QPair &))); QObject::connect(job, SIGNAL(infoMessage(KJob*,QString,QString)), this, SLOT(infoMessage(KJob*,QString,QString))); QObject::connect(job, SIGNAL(warning(KJob*,QString,QString)), this, SLOT(warning(KJob*,QString,QString))); QObject::connect(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(totalAmount(KJob*,KJob::Unit,qulonglong))); QObject::connect(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(processedAmount(KJob*,KJob::Unit,qulonglong))); QObject::connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(percent(KJob*,ulong))); QObject::connect(job, SIGNAL(speed(KJob*,ulong)), this, SLOT(speed(KJob*,ulong))); } void KJobTrackerInterface::unregisterJob(KJob *job) { job->disconnect(this); } void KJobTrackerInterface::finished(KJob *job) { Q_UNUSED(job) } void KJobTrackerInterface::suspended(KJob *job) { Q_UNUSED(job) } void KJobTrackerInterface::resumed(KJob *job) { Q_UNUSED(job) } void KJobTrackerInterface::description(KJob *job, const QString &title, const QPair &field1, const QPair &field2) { Q_UNUSED(job) Q_UNUSED(title) Q_UNUSED(field1) Q_UNUSED(field2) } void KJobTrackerInterface::infoMessage(KJob *job, const QString &plain, const QString &rich) { Q_UNUSED(job) Q_UNUSED(plain) Q_UNUSED(rich) } void KJobTrackerInterface::warning(KJob *job, const QString &plain, const QString &rich) { Q_UNUSED(job) Q_UNUSED(plain) Q_UNUSED(rich) } void KJobTrackerInterface::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount) { Q_UNUSED(job) Q_UNUSED(unit) Q_UNUSED(amount) } void KJobTrackerInterface::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount) { Q_UNUSED(job) Q_UNUSED(unit) Q_UNUSED(amount) } void KJobTrackerInterface::percent(KJob *job, unsigned long percent) { Q_UNUSED(job) Q_UNUSED(percent) } void KJobTrackerInterface::speed(KJob *job, unsigned long value) { Q_UNUSED(job) Q_UNUSED(value) } #include "moc_kjobtrackerinterface.cpp" diff --git a/src/lib/jobs/kjobtrackerinterface.h b/src/lib/jobs/kjobtrackerinterface.h index 4c76ca9..05551c0 100644 --- a/src/lib/jobs/kjobtrackerinterface.h +++ b/src/lib/jobs/kjobtrackerinterface.h @@ -1,193 +1,182 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Kevin Ottens +/* + This file is part of the KDE project - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Kevin Ottens + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KJOBTRACKERINTERFACE_H #define KJOBTRACKERINTERFACE_H #include #include #include #include /** * @class KJobTrackerInterface kjobtrackerinterface.h KJobTrackerInterface * * The interface to implement to track the progresses of a job. */ class KCOREADDONS_EXPORT KJobTrackerInterface : public QObject { Q_OBJECT public: /** * Creates a new KJobTrackerInterface * * @param parent the parent object */ explicit KJobTrackerInterface(QObject *parent = nullptr); /** * Destroys a KJobTrackerInterface */ ~KJobTrackerInterface() override; public Q_SLOTS: /** * Register a new job in this tracker. * The default implementation connects the following KJob signals * to the respective protected slots of this class: * - finished() (also connected to the unregisterJob() slot) * - suspended() * - resumed() * - description() * - infoMessage() * - totalAmount() * - processedAmount() * - percent() * - speed() * * If you re-implement this method, you may want to call the default * implementation or add at least: * * @code * connect(job, &KJob::finished, this, &MyJobTracker::unregisterJob); * @endcode * * so that you won't have to manually call unregisterJob(). * * @param job the job to register * @see unregisterJob() */ virtual void registerJob(KJob *job); /** * Unregister a job from this tracker. * @note You need to manually call this method only if you re-implemented * registerJob() without connecting KJob::finished to this slot. * * @param job the job to unregister * @see registerJob() */ virtual void unregisterJob(KJob *job); protected Q_SLOTS: /** * Called when a job is finished, in any case. It is used to notify * that the job is terminated and that progress UI (if any) can be hidden. * * @param job the job that emitted this signal */ virtual void finished(KJob *job); /** * Called when a job is suspended. * * @param job the job that emitted this signal */ virtual void suspended(KJob *job); /** * Called when a job is resumed. * * @param job the job that emitted this signal */ virtual void resumed(KJob *job); /** * Called to display general description of a job. A description has * a title and two optional fields which can be used to complete the * description. * * Examples of titles are "Copying", "Creating resource", etc. * The fields of the description can be "Source" with an URL, and, * "Destination" with an URL for a "Copying" description. * @param job the job that emitted this signal * @param title the general description of the job * @param field1 first field (localized name and value) * @param field2 second field (localized name and value) */ virtual void description(KJob *job, const QString &title, const QPair &field1, const QPair &field2); /** * Called to display state information about a job. * Examples of message are "Resolving host", "Connecting to host...", etc. * * @param job the job that emitted this signal * @param plain the info message * @param rich the rich text version of the message, or QString() is none is available */ virtual void infoMessage(KJob *job, const QString &plain, const QString &rich); /** * Emitted to display a warning about a job. * * @param job the job that emitted this signal * @param plain the warning message * @param rich the rich text version of the message, or QString() is none is available */ virtual void warning(KJob *job, const QString &plain, const QString &rich); /** * Called when we know the amount a job will have to process. The unit of this * amount is provided too. It can be called several times for a given job if the job * manages several different units. * * @param job the job that emitted this signal * @param unit the unit of the total amount * @param amount the total amount */ virtual void totalAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Regularly called to show the progress of a job by giving the current amount. * The unit of this amount is provided too. It can be called several times for a given * job if the job manages several different units. * * @param job the job that emitted this signal * @param unit the unit of the processed amount * @param amount the processed amount */ virtual void processedAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Called to show the overall progress of the job. * Note that this is not called for finished jobs. * * @param job the job that emitted this signal * @param percent the percentage */ virtual void percent(KJob *job, unsigned long percent); /** * Called to show the speed of the job. * * @param job the job that emitted this signal * @param value the current speed of the job */ virtual void speed(KJob *job, unsigned long value); private: class Private; Private *const d; }; #endif diff --git a/src/lib/jobs/kjobuidelegate.cpp b/src/lib/jobs/kjobuidelegate.cpp index 97c4cd7..828492d 100644 --- a/src/lib/jobs/kjobuidelegate.cpp +++ b/src/lib/jobs/kjobuidelegate.cpp @@ -1,116 +1,105 @@ -/* This file is part of the KDE libraries - Copyright (C) 2000 Stephan Kulow - David Faure - Copyright (C) 2006 Kevin Ottens - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2000 Stephan Kulow + SPDX-FileCopyrightText: 2000 David Faure + SPDX-FileCopyrightText: 2006 Kevin Ottens + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kjobuidelegate.h" #include "kjob.h" class Q_DECL_HIDDEN KJobUiDelegate::Private { public: Private(KJobUiDelegate *delegate) : q(delegate), job(nullptr), autoErrorHandling(false), autoWarningHandling(true) { } KJobUiDelegate *const q; KJob *job; bool autoErrorHandling : 1; bool autoWarningHandling : 1; void connectJob(KJob *job); void _k_result(); }; KJobUiDelegate::KJobUiDelegate() : QObject(), d(new Private(this)) { } KJobUiDelegate::~KJobUiDelegate() { delete d; } bool KJobUiDelegate::setJob(KJob *job) { if (d->job != nullptr) { return false; } d->job = job; setParent(job); return true; } KJob *KJobUiDelegate::job() const { return d->job; } void KJobUiDelegate::showErrorMessage() { } void KJobUiDelegate::setAutoErrorHandlingEnabled(bool enable) { d->autoErrorHandling = enable; } bool KJobUiDelegate::isAutoErrorHandlingEnabled() const { return d->autoErrorHandling; } void KJobUiDelegate::setAutoWarningHandlingEnabled(bool enable) { d->autoWarningHandling = enable; } bool KJobUiDelegate::isAutoWarningHandlingEnabled() const { return d->autoWarningHandling; } void KJobUiDelegate::slotWarning(KJob *job, const QString &plain, const QString &rich) { Q_UNUSED(job) Q_UNUSED(plain) Q_UNUSED(rich) } void KJobUiDelegate::connectJob(KJob *job) { connect(job, &KJob::result, this, [this](){ d->_k_result();} ); connect(job, &KJob::warning, this, &KJobUiDelegate::slotWarning); } void KJobUiDelegate::Private::_k_result() { if (job->error() && autoErrorHandling) { q->showErrorMessage(); } } #include "moc_kjobuidelegate.cpp" diff --git a/src/lib/jobs/kjobuidelegate.h b/src/lib/jobs/kjobuidelegate.h index bb4179a..42e8dab 100644 --- a/src/lib/jobs/kjobuidelegate.h +++ b/src/lib/jobs/kjobuidelegate.h @@ -1,138 +1,127 @@ -/* This file is part of the KDE libraries - Copyright (C) 2000 Stephan Kulow - David Faure - Copyright (C) 2006 Kevin Ottens - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2000 Stephan Kulow + SPDX-FileCopyrightText: 2000 David Faure + SPDX-FileCopyrightText: 2006 Kevin Ottens + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KJOBUIDELEGATE_H #define KJOBUIDELEGATE_H #include #include class KJob; /** * @class KJobUiDelegate kjobuidelegate.h KJobUiDelegate * * The base class for all KJob UI delegate. * * A UI delegate is responsible for the events of a * job and provides a UI for them (an error message * box or warning etc.). * * @see KJob */ class KCOREADDONS_EXPORT KJobUiDelegate : public QObject { Q_OBJECT public: /** * Constructs a new KJobUiDelegate. */ KJobUiDelegate(); /** * Destroys a KJobUiDelegate. */ ~KJobUiDelegate() override; protected: /** * Attach this UI delegate to a job. Once attached it'll track the job events. * * @return true if the job we're correctly attached to the job, false otherwise. */ virtual bool setJob(KJob *job); protected: /** * Retrieves the current job this UI delegate is attached to. * * @return current job this UI delegate is attached to, or @c nullptr if * this UI delegate is not tracking any job */ KJob *job() const; friend class KJob; public: /** * Display a dialog box to inform the user of the error given by * this job. * Only call if error is not 0, and only in the slot connected * to result. */ virtual void showErrorMessage(); /** * Enable or disable the automatic error handling. When automatic * error handling is enabled and an error occurs, then showErrorDialog() * is called, right before the emission of the result signal. * * The default is false. * * See also isAutoErrorHandlingEnabled , showErrorDialog * * @param enable enable or disable automatic error handling * @see isAutoErrorHandlingEnabled() */ void setAutoErrorHandlingEnabled(bool enable); /** * Returns whether automatic error handling is enabled or disabled. * See also setAutoErrorHandlingEnabled . * @return true if automatic error handling is enabled * @see setAutoErrorHandlingEnabled() */ bool isAutoErrorHandlingEnabled() const; /** * Enable or disable the automatic warning handling. When automatic * warning handling is enabled and an error occurs, then a message box * is displayed with the warning message * * The default is true. * * See also isAutoWarningHandlingEnabled , showErrorDialog * * @param enable enable or disable automatic warning handling * @see isAutoWarningHandlingEnabled() */ void setAutoWarningHandlingEnabled(bool enable); /** * Returns whether automatic warning handling is enabled or disabled. * See also setAutoWarningHandlingEnabled . * @return true if automatic warning handling is enabled * @see setAutoWarningHandlingEnabled() */ bool isAutoWarningHandlingEnabled() const; protected Q_SLOTS: virtual void slotWarning(KJob *job, const QString &plain, const QString &rich); private: void connectJob(KJob *job); class Private; Private *const d; }; #endif // KJOBUIDELEGATE_H diff --git a/src/lib/kaboutdata.cpp b/src/lib/kaboutdata.cpp index 1fcdfa3..2fd135a 100644 --- a/src/lib/kaboutdata.cpp +++ b/src/lib/kaboutdata.cpp @@ -1,1255 +1,1242 @@ /* - * This file is part of the KDE Libraries - * Copyright (C) 2000 Espen Sand (espen@kde.org) - * Copyright (C) 2006 Nicolas GOUTTE - * Copyright (C) 2008 Friedrich W. H. Kossebau - * Copyright (C) 2010 Teo Mrnjavac - * Copyright (C) 2017 Harald Sitter - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ + This file is part of the KDE Libraries + + SPDX-FileCopyrightText: 2000 Espen Sand + SPDX-FileCopyrightText: 2006 Nicolas GOUTTE + SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2010 Teo Mrnjavac + SPDX-FileCopyrightText: 2017 Harald Sitter + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "kaboutdata.h" #include "kpluginmetadata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KABOUTDATA) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(KABOUTDATA, "kf5.kcoreaddons.kaboutdata", QtWarningMsg) class Q_DECL_HIDDEN KAboutPerson::Private { public: QString _name; QString _task; QString _emailAddress; QString _webAddress; QString _ocsUsername; }; KAboutPerson::KAboutPerson(const QString &_name, const QString &_task, const QString &_emailAddress, const QString &_webAddress, const QString &_ocsUsername) : d(new Private) { d->_name = _name; d->_task = _task; d->_emailAddress = _emailAddress; d->_webAddress = _webAddress; d->_ocsUsername = _ocsUsername; } KAboutPerson::KAboutPerson(const QString &_name, const QString &_email, bool) : d(new Private) { d->_name = _name; d->_emailAddress = _email; } KAboutPerson::KAboutPerson(const KAboutPerson &other): d(new Private) { *d = *other.d; } KAboutPerson::~KAboutPerson() { delete d; } QString KAboutPerson::name() const { return d->_name; } QString KAboutPerson::task() const { return d->_task; } QString KAboutPerson::emailAddress() const { return d->_emailAddress; } QString KAboutPerson::webAddress() const { return d->_webAddress; } QString KAboutPerson::ocsUsername() const { return d->_ocsUsername; } KAboutPerson &KAboutPerson::operator=(const KAboutPerson &other) { *d = *other.d; return *this; } KAboutPerson KAboutPerson::fromJSON(const QJsonObject &obj) { const QString name = KPluginMetaData::readTranslatedString(obj, QStringLiteral("Name")); const QString task = KPluginMetaData::readTranslatedString(obj, QStringLiteral("Task")); const QString email = obj[QStringLiteral("Email")].toString(); const QString website = obj[QStringLiteral("Website")].toString(); const QString userName = obj[QStringLiteral("UserName")].toString(); return KAboutPerson(name, task, email, website, userName); } class Q_DECL_HIDDEN KAboutLicense::Private : public QSharedData { public: Private(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData); Private(const Private &other); QString spdxID() const; LicenseKey _licenseKey; QString _licenseText; QString _pathToLicenseTextFile; VersionRestriction _versionRestriction; // needed for access to the possibly changing copyrightStatement() const KAboutData *_aboutData; }; KAboutLicense::Private::Private(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData) : QSharedData(), _licenseKey(licenseType), _versionRestriction(versionRestriction), _aboutData(aboutData) { } KAboutLicense::Private::Private(const KAboutLicense::Private &other) : QSharedData(other), _licenseKey(other._licenseKey), _licenseText(other._licenseText), _pathToLicenseTextFile(other._pathToLicenseTextFile), _versionRestriction(other._versionRestriction), _aboutData(other._aboutData) {} QString KAboutLicense::Private::spdxID() const { switch (_licenseKey) { case KAboutLicense::GPL_V2: return QStringLiteral("GPL-2.0"); case KAboutLicense::LGPL_V2: return QStringLiteral("LGPL-2.0"); case KAboutLicense::BSDL: return QStringLiteral("BSD-2-Clause"); case KAboutLicense::Artistic: return QStringLiteral("Artistic-1.0"); case KAboutLicense::QPL_V1_0: return QStringLiteral("QPL-1.0"); case KAboutLicense::GPL_V3: return QStringLiteral("GPL-3.0"); case KAboutLicense::LGPL_V3: return QStringLiteral("LGPL-3.0"); case KAboutLicense::LGPL_V2_1: return QStringLiteral("LGPL-2.1"); case KAboutLicense::Custom: case KAboutLicense::File: case KAboutLicense::Unknown: return QString(); } return QString(); } KAboutLicense::KAboutLicense() : d(new Private(Unknown, {}, nullptr)) {} KAboutLicense::KAboutLicense(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData) : d(new Private(licenseType, versionRestriction, aboutData)) { } KAboutLicense::KAboutLicense(LicenseKey licenseType, const KAboutData *aboutData) : d(new Private(licenseType, OnlyThisVersion, aboutData)) { } KAboutLicense::KAboutLicense(const KAboutData *aboutData) : d(new Private(Unknown, OnlyThisVersion, aboutData)) { } KAboutLicense::KAboutLicense(const KAboutLicense &other) : d(other.d) { } KAboutLicense::~KAboutLicense() {} void KAboutLicense::setLicenseFromPath(const QString &pathToFile) { d->_licenseKey = KAboutLicense::File; d->_pathToLicenseTextFile = pathToFile; } void KAboutLicense::setLicenseFromText(const QString &licenseText) { d->_licenseKey = KAboutLicense::Custom; d->_licenseText = licenseText; } QString KAboutLicense::text() const { QString result; const QString lineFeed = QStringLiteral("\n\n"); if (d->_aboutData && !d->_aboutData->copyrightStatement().isEmpty()) { result = d->_aboutData->copyrightStatement() + lineFeed; } bool knownLicense = false; QString pathToFile; // rel path if known license switch (d->_licenseKey) { case KAboutLicense::File: pathToFile = d->_pathToLicenseTextFile; break; case KAboutLicense::GPL_V2: knownLicense = true; pathToFile = QStringLiteral("GPL_V2"); break; case KAboutLicense::LGPL_V2: knownLicense = true; pathToFile = QStringLiteral("LGPL_V2"); break; case KAboutLicense::BSDL: knownLicense = true; pathToFile = QStringLiteral("BSD"); break; case KAboutLicense::Artistic: knownLicense = true; pathToFile = QStringLiteral("ARTISTIC"); break; case KAboutLicense::QPL_V1_0: knownLicense = true; pathToFile = QStringLiteral("QPL_V1.0"); break; case KAboutLicense::GPL_V3: knownLicense = true; pathToFile = QStringLiteral("GPL_V3"); break; case KAboutLicense::LGPL_V3: knownLicense = true; pathToFile = QStringLiteral("LGPL_V3"); break; case KAboutLicense::LGPL_V2_1: knownLicense = true; pathToFile = QStringLiteral("LGPL_V21"); break; case KAboutLicense::Custom: if (!d->_licenseText.isEmpty()) { result = d->_licenseText; break; } Q_FALLTHROUGH(); // fall through default: result += QCoreApplication::translate( "KAboutLicense", "No licensing terms for this program have been specified.\n" "Please check the documentation or the source for any\n" "licensing terms.\n"); } if (knownLicense) { pathToFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kf5/licenses/") + pathToFile); result += QCoreApplication::translate( "KAboutLicense", "This program is distributed under the terms of the %1.").arg(name(KAboutLicense::ShortName)); if (!pathToFile.isEmpty()) { result += lineFeed; } } if (!pathToFile.isEmpty()) { QFile file(pathToFile); if (file.open(QIODevice::ReadOnly)) { QTextStream str(&file); result += str.readAll(); } } return result; } QString KAboutLicense::spdx() const { // SPDX licenses are comprised of an identifier (e.g. GPL-2.0), an optional + to denote 'or // later versions' and optional ' WITH $exception' to denote standardized exceptions from the // core license. As we do not offer exceptions we effectively only return GPL-2.0 or GPL-2.0+, // this may change in the future. To that end the documentation makes no assertations about the // actual content of the SPDX license expression we return. // Expressions can in theory also contain AND, OR and () to build constructs involving more than // one license. As this is outside the scope of a single license object we'll ignore this here // for now. // The expecation is that the return value is only run through spec-compliant parsers, so this // can potentially be changed. auto id = d->spdxID(); if (id.isNull()) { // Guard against potential future changes which would allow 'Foo+' as input. return id; } return d->_versionRestriction == OrLaterVersions ? id.append(QLatin1Char('+')) : id; } QString KAboutLicense::name(KAboutLicense::NameFormat formatName) const { QString licenseShort; QString licenseFull; switch (d->_licenseKey) { case KAboutLicense::GPL_V2: licenseShort = QCoreApplication::translate("KAboutLicense", "GPL v2", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU General Public License Version 2", "@item license"); break; case KAboutLicense::LGPL_V2: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v2", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 2", "@item license"); break; case KAboutLicense::BSDL: licenseShort = QCoreApplication::translate("KAboutLicense", "BSD License", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "BSD License", "@item license"); break; case KAboutLicense::Artistic: licenseShort = QCoreApplication::translate("KAboutLicense", "Artistic License", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "Artistic License", "@item license"); break; case KAboutLicense::QPL_V1_0: licenseShort = QCoreApplication::translate("KAboutLicense", "QPL v1.0", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "Q Public License", "@item license"); break; case KAboutLicense::GPL_V3: licenseShort = QCoreApplication::translate("KAboutLicense", "GPL v3", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU General Public License Version 3", "@item license"); break; case KAboutLicense::LGPL_V3: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v3", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 3", "@item license"); break; case KAboutLicense::LGPL_V2_1: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v2.1", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 2.1", "@item license"); break; case KAboutLicense::Custom: case KAboutLicense::File: licenseShort = licenseFull = QCoreApplication::translate("KAboutLicense", "Custom", "@item license"); break; default: licenseShort = licenseFull = QCoreApplication::translate("KAboutLicense", "Not specified", "@item license"); } const QString result = (formatName == KAboutLicense::ShortName) ? licenseShort : (formatName == KAboutLicense::FullName) ? licenseFull : QString(); return result; } KAboutLicense &KAboutLicense::operator=(const KAboutLicense &other) { d = other.d; return *this; } KAboutLicense::LicenseKey KAboutLicense::key() const { return d->_licenseKey; } KAboutLicense KAboutLicense::byKeyword(const QString &rawKeyword) { // Setup keyword->enum dictionary on first call. // Use normalized keywords, by the algorithm below. static const QHash licenseDict { { "gpl", KAboutLicense::GPL }, { "gplv2", KAboutLicense::GPL_V2 }, { "gplv2+", KAboutLicense::GPL_V2 }, { "gpl20", KAboutLicense::GPL_V2 }, { "gpl20+", KAboutLicense::GPL_V2 }, { "lgpl", KAboutLicense::LGPL }, { "lgplv2", KAboutLicense::LGPL_V2 }, { "lgplv2+", KAboutLicense::LGPL_V2 }, { "lgpl20", KAboutLicense::LGPL_V2 }, { "lgpl20+", KAboutLicense::LGPL_V2 }, { "bsd", KAboutLicense::BSDL }, { "bsd2clause", KAboutLicense::BSDL }, { "artistic", KAboutLicense::Artistic }, { "artistic10", KAboutLicense::Artistic }, { "qpl", KAboutLicense::QPL }, { "qplv1", KAboutLicense::QPL_V1_0 }, { "qplv10", KAboutLicense::QPL_V1_0 }, { "qpl10", KAboutLicense::QPL_V1_0 }, { "gplv3", KAboutLicense::GPL_V3 }, { "gplv3+", KAboutLicense::GPL_V3 }, { "gpl30", KAboutLicense::GPL_V3 }, { "gpl30+", KAboutLicense::GPL_V3 }, { "lgplv3", KAboutLicense::LGPL_V3 }, { "lgplv3+", KAboutLicense::LGPL_V3 }, { "lgpl30", KAboutLicense::LGPL_V3 }, { "lgpl30+", KAboutLicense::LGPL_V3 }, { "lgplv21", KAboutLicense::LGPL_V2_1 }, { "lgplv21+", KAboutLicense::LGPL_V2_1 }, { "lgpl21", KAboutLicense::LGPL_V2_1 }, { "lgpl21+", KAboutLicense::LGPL_V2_1 }, }; // Normalize keyword. QString keyword = rawKeyword; keyword = keyword.toLower(); keyword.remove(QLatin1Char(' ')); keyword.remove(QLatin1Char('.')); keyword.remove(QLatin1Char('-')); LicenseKey license = licenseDict.value(keyword.toLatin1(), KAboutLicense::Custom); auto restriction = keyword.endsWith(QLatin1Char('+')) ? OrLaterVersions : OnlyThisVersion; return KAboutLicense(license, restriction, nullptr); } class Q_DECL_HIDDEN KAboutData::Private { public: Private() : customAuthorTextEnabled(false) {} QString _componentName; QString _displayName; QString _shortDescription; QString _copyrightStatement; QString _otherText; QString _homepageAddress; QList _authorList; QList _creditList; QList _translatorList; QList _licenseList; QString productName; #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 2) QString programIconName; #endif QVariant programLogo; QString customAuthorPlainText, customAuthorRichText; bool customAuthorTextEnabled; QString organizationDomain; QString _ocsProviderUrl; QString desktopFileName; // Everything dr.konqi needs, we store as utf-8, so we // can just give it a pointer, w/o any allocations. QByteArray _internalProgramName; QByteArray _version; QByteArray _bugAddress; static QList parseTranslators(const QString &translatorName, const QString &translatorEmail); }; KAboutData::KAboutData(const QString &_componentName, const QString &_displayName, const QString &_version, const QString &_shortDescription, enum KAboutLicense::LicenseKey licenseType, const QString &_copyrightStatement, const QString &text, const QString &homePageAddress, const QString &bugAddress ) : d(new Private) { d->_componentName = _componentName; int p = d->_componentName.indexOf(QLatin1Char('/')); if (p >= 0) { d->_componentName = d->_componentName.mid(p + 1); } d->_displayName = _displayName; if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name d->_internalProgramName = _displayName.toUtf8(); } d->_version = _version.toUtf8(); d->_shortDescription = _shortDescription; d->_licenseList.append(KAboutLicense(licenseType, this)); d->_copyrightStatement = _copyrightStatement; d->_otherText = text; d->_homepageAddress = homePageAddress; d->_bugAddress = bugAddress.toUtf8(); QUrl homePageUrl(homePageAddress); if (!homePageUrl.isValid() || homePageUrl.scheme().isEmpty()) { // Default domain if nothing else is better homePageUrl.setUrl(QStringLiteral("https://kde.org/")); } const QChar dotChar(QLatin1Char('.')); QStringList hostComponents = homePageUrl.host().split(dotChar); // Remove leading component unless 2 (or less) components are present if (hostComponents.size() > 2) { hostComponents.removeFirst(); } d->organizationDomain = hostComponents.join(dotChar); // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty // see KAboutData::desktopFileName() for details // desktop file name is reverse domain name std::reverse(hostComponents.begin(), hostComponents.end()); hostComponents.append(_componentName); d->desktopFileName = hostComponents.join(dotChar); } KAboutData::KAboutData(const QString &_componentName, const QString &_displayName, const QString &_version ) : d(new Private) { d->_componentName = _componentName; int p = d->_componentName.indexOf(QLatin1Char('/')); if (p >= 0) { d->_componentName = d->_componentName.mid(p + 1); } d->_displayName = _displayName; if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name d->_internalProgramName = _displayName.toUtf8(); } d->_version = _version.toUtf8(); // match behaviour of other constructors d->_licenseList.append(KAboutLicense(KAboutLicense::Unknown, this)); d->_bugAddress = "submit@bugs.kde.org"; d->organizationDomain = QStringLiteral("kde.org"); // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty // see KAboutData::desktopFileName() for details d->desktopFileName = QLatin1String("org.kde.") + d->_componentName; } KAboutData::~KAboutData() { delete d; } KAboutData::KAboutData(const KAboutData &other): d(new Private) { *d = *other.d; QList::iterator it = d->_licenseList.begin(), itEnd = d->_licenseList.end(); for (; it != itEnd; ++it) { KAboutLicense &al = *it; al.d.detach(); al.d->_aboutData = this; } } KAboutData &KAboutData::operator=(const KAboutData &other) { if (this != &other) { *d = *other.d; QList::iterator it = d->_licenseList.begin(), itEnd = d->_licenseList.end(); for (; it != itEnd; ++it) { KAboutLicense &al = *it; al.d.detach(); al.d->_aboutData = this; } } return *this; } #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 65) KAboutData KAboutData::fromPluginMetaData(const KPluginMetaData &plugin) { KAboutData ret(plugin.pluginId(), plugin.name(), plugin.version(), plugin.description(), KAboutLicense::byKeyword(plugin.license()).key(), plugin.copyrightText(), plugin.extraInformation(), plugin.website()); #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 2) ret.d->programIconName = plugin.iconName(); #endif ret.d->_authorList = plugin.authors(); ret.d->_translatorList = plugin.translators(); ret.d->_creditList = plugin.otherContributors(); return ret; } #endif KAboutData &KAboutData::addAuthor(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QString &ocsUsername) { d->_authorList.append(KAboutPerson(name, task, emailAddress, webAddress, ocsUsername)); return *this; } KAboutData &KAboutData::addCredit(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QString &ocsUsername) { d->_creditList.append(KAboutPerson(name, task, emailAddress, webAddress, ocsUsername)); return *this; } KAboutData &KAboutData::setTranslator(const QString &name, const QString &emailAddress) { d->_translatorList = Private::parseTranslators(name, emailAddress); return *this; } KAboutData &KAboutData::setLicenseText(const QString &licenseText) { d->_licenseList[0] = KAboutLicense(this); d->_licenseList[0].setLicenseFromText(licenseText); return *this; } KAboutData &KAboutData::addLicenseText(const QString &licenseText) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; KAboutLicense newLicense(this); newLicense.setLicenseFromText(licenseText); if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = newLicense; } else { d->_licenseList.append(newLicense); } return *this; } KAboutData &KAboutData::setLicenseTextFile(const QString &pathToFile) { d->_licenseList[0] = KAboutLicense(this); d->_licenseList[0].setLicenseFromPath(pathToFile); return *this; } KAboutData &KAboutData::addLicenseTextFile(const QString &pathToFile) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; KAboutLicense newLicense(this); newLicense.setLicenseFromPath(pathToFile); if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = newLicense; } else { d->_licenseList.append(newLicense); } return *this; } KAboutData &KAboutData::setComponentName(const QString &componentName) { d->_componentName = componentName; return *this; } KAboutData &KAboutData::setDisplayName(const QString &_displayName) { d->_displayName = _displayName; d->_internalProgramName = _displayName.toUtf8(); return *this; } KAboutData &KAboutData::setOcsProvider(const QString &_ocsProviderUrl) { d->_ocsProviderUrl = _ocsProviderUrl; return *this; } KAboutData &KAboutData::setVersion(const QByteArray &_version) { d->_version = _version; return *this; } KAboutData &KAboutData::setShortDescription(const QString &_shortDescription) { d->_shortDescription = _shortDescription; return *this; } KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey) { return setLicense(licenseKey, KAboutLicense::OnlyThisVersion); } KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction) { d->_licenseList[0] = KAboutLicense(licenseKey, versionRestriction, this); return *this; } KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey) { return addLicense(licenseKey, KAboutLicense::OnlyThisVersion); } KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = KAboutLicense(licenseKey, versionRestriction, this); } else { d->_licenseList.append(KAboutLicense(licenseKey, versionRestriction, this)); } return *this; } KAboutData &KAboutData::setCopyrightStatement(const QString &_copyrightStatement) { d->_copyrightStatement = _copyrightStatement; return *this; } KAboutData &KAboutData::setOtherText(const QString &_otherText) { d->_otherText = _otherText; return *this; } KAboutData &KAboutData::setHomepage(const QString &homepage) { d->_homepageAddress = homepage; return *this; } KAboutData &KAboutData::setBugAddress(const QByteArray &_bugAddress) { d->_bugAddress = _bugAddress; return *this; } KAboutData &KAboutData::setOrganizationDomain(const QByteArray &domain) { d->organizationDomain = QString::fromLatin1(domain.data()); return *this; } KAboutData &KAboutData::setProductName(const QByteArray &_productName) { d->productName = QString::fromUtf8(_productName.data()); return *this; } QString KAboutData::componentName() const { return d->_componentName; } QString KAboutData::productName() const { if (!d->productName.isEmpty()) { return d->productName; } return componentName(); } QString KAboutData::displayName() const { if (!d->_displayName.isEmpty()) { return d->_displayName; } return componentName(); } /// @internal /// Return the program name. It is always pre-allocated. /// Needed for KCrash in particular. const char *KAboutData::internalProgramName() const { return d->_internalProgramName.constData(); } #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 2) QString KAboutData::programIconName() const { return d->programIconName.isEmpty() ? componentName() : d->programIconName; } KAboutData &KAboutData::setProgramIconName(const QString &iconName) { d->programIconName = iconName; return *this; } #endif QVariant KAboutData::programLogo() const { return d->programLogo; } KAboutData &KAboutData::setProgramLogo(const QVariant &image) { d->programLogo = image; return *this; } QString KAboutData::ocsProviderUrl() const { return d->_ocsProviderUrl; } QString KAboutData::version() const { return QString::fromUtf8(d->_version.data()); } /// @internal /// Return the untranslated and uninterpreted (to UTF8) string /// for the version information. Used in particular for KCrash. const char *KAboutData::internalVersion() const { return d->_version.constData(); } QString KAboutData::shortDescription() const { return d->_shortDescription; } QString KAboutData::homepage() const { return d->_homepageAddress; } QString KAboutData::bugAddress() const { return QString::fromUtf8(d->_bugAddress.constData()); } QString KAboutData::organizationDomain() const { return d->organizationDomain; } /// @internal /// Return the untranslated and uninterpreted (to UTF8) string /// for the bug mail address. Used in particular for KCrash. const char *KAboutData::internalBugAddress() const { if (d->_bugAddress.isEmpty()) { return nullptr; } return d->_bugAddress.constData(); } QList KAboutData::authors() const { return d->_authorList; } QList KAboutData::credits() const { return d->_creditList; } QList KAboutData::Private::parseTranslators(const QString &translatorName, const QString &translatorEmail) { QList personList; if (translatorName.isEmpty() || translatorName == QLatin1String("Your names")) { return personList; } const QStringList nameList(translatorName.split(QLatin1Char(','))); QStringList emailList; if (!translatorEmail.isEmpty() && translatorEmail != QLatin1String("Your emails")) { emailList = translatorEmail.split(QLatin1Char(','), QString::KeepEmptyParts); } QStringList::const_iterator nit; QStringList::const_iterator eit = emailList.constBegin(); for (nit = nameList.constBegin(); nit != nameList.constEnd(); ++nit) { QString email; if (eit != emailList.constEnd()) { email = *eit; ++eit; } personList.append(KAboutPerson((*nit).trimmed(), email.trimmed(), true)); } return personList; } QList KAboutData::translators() const { return d->_translatorList; } QString KAboutData::aboutTranslationTeam() { return QCoreApplication::translate( "KAboutData", "

KDE is translated into many languages thanks to the work " "of the translation teams all over the world.

" "

For more information on KDE internationalization " "visit https://l10n.kde.org

", "replace this with information about your translation team" ); } QString KAboutData::otherText() const { return d->_otherText; } QList KAboutData::licenses() const { return d->_licenseList; } QString KAboutData::copyrightStatement() const { return d->_copyrightStatement; } QString KAboutData::customAuthorPlainText() const { return d->customAuthorPlainText; } QString KAboutData::customAuthorRichText() const { return d->customAuthorRichText; } bool KAboutData::customAuthorTextEnabled() const { return d->customAuthorTextEnabled; } KAboutData &KAboutData::setCustomAuthorText(const QString &plainText, const QString &richText) { d->customAuthorPlainText = plainText; d->customAuthorRichText = richText; d->customAuthorTextEnabled = true; return *this; } KAboutData &KAboutData::unsetCustomAuthorText() { d->customAuthorPlainText = QString(); d->customAuthorRichText = QString(); d->customAuthorTextEnabled = false; return *this; } KAboutData &KAboutData::setDesktopFileName(const QString &desktopFileName) { d->desktopFileName = desktopFileName; return *this; } QString KAboutData::desktopFileName() const { return d->desktopFileName; // KF6: switch to this code and adapt API dox #if 0 // if desktopFileName has been explicitly set, use that value if (!d->desktopFileName.isEmpty()) { return d->desktopFileName; } // return a string calculated on-the-fly from the current org domain & component name const QChar dotChar(QLatin1Char('.')); QStringList hostComponents = d->organizationDomain.split(dotChar); // desktop file name is reverse domain name std::reverse(hostComponents.begin(), hostComponents.end()); hostComponents.append(componentName()); return hostComponents.join(dotChar); #endif } class KAboutDataRegistry { public: KAboutDataRegistry() : m_appData(nullptr) {} ~KAboutDataRegistry() { delete m_appData; qDeleteAll(m_pluginData); } KAboutDataRegistry(const KAboutDataRegistry &) = delete; KAboutDataRegistry &operator=(const KAboutDataRegistry &) = delete; KAboutData *m_appData; QHash m_pluginData; }; Q_GLOBAL_STATIC(KAboutDataRegistry, s_registry) namespace { void warnIfOutOfSync(const char *aboutDataString, const QString &aboutDataValue, const char *appDataString, const QString &appDataValue) { if (aboutDataValue != appDataValue) { qCWarning(KABOUTDATA) << appDataString <m_appData; // not yet existing if (!aboutData) { // init from current Q*Application data aboutData = new KAboutData(QCoreApplication::applicationName(), QString(), QString()); // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication, // we have to try to get them via the property system, as the static getter methods are // part of QtGui only. Disadvantage: requires an app instance. // Either get all or none of the properties & warn about it if (app) { aboutData->setOrganizationDomain(QCoreApplication::organizationDomain().toUtf8()); aboutData->setVersion(QCoreApplication::applicationVersion().toUtf8()); aboutData->setDisplayName(app->property("applicationDisplayName").toString()); aboutData->setDesktopFileName(app->property("desktopFileName").toString()); } else { qCWarning(KABOUTDATA) << "Could not initialize the properties of KAboutData::applicationData by the equivalent properties from Q*Application: no app instance (yet) existing."; } s_registry->m_appData = aboutData; } else { // check if in-sync with Q*Application metadata, as their setters could have been called // after the last KAboutData::setApplicationData, with different values warnIfOutOfSync("KAboutData::applicationData().componentName", aboutData->componentName(), "QCoreApplication::applicationName", QCoreApplication::applicationName()); warnIfOutOfSync("KAboutData::applicationData().version", aboutData->version(), "QCoreApplication::applicationVersion", QCoreApplication::applicationVersion()); warnIfOutOfSync("KAboutData::applicationData().organizationDomain", aboutData->organizationDomain(), "QCoreApplication::organizationDomain", QCoreApplication::organizationDomain()); if (app) { warnIfOutOfSync("KAboutData::applicationData().displayName", aboutData->displayName(), "QGuiApplication::applicationDisplayName", app->property("applicationDisplayName").toString()); warnIfOutOfSync("KAboutData::applicationData().desktopFileName", aboutData->desktopFileName(), "QGuiApplication::desktopFileName", app->property("desktopFileName").toString()); } } return *aboutData; } void KAboutData::setApplicationData(const KAboutData &aboutData) { if (s_registry->m_appData) { *s_registry->m_appData = aboutData; } else { s_registry->m_appData = new KAboutData(aboutData); } // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication, // we have to try to set them via the property system, as the static getter methods are // part of QtGui only. Disadvantage: requires an app instance. // So set either all or none of the properties & warn about it QCoreApplication *app = QCoreApplication::instance(); if (app) { app->setApplicationVersion(aboutData.version()); app->setApplicationName(aboutData.componentName()); app->setOrganizationDomain(aboutData.organizationDomain()); app->setProperty("applicationDisplayName", aboutData.displayName()); app->setProperty("desktopFileName", aboutData.desktopFileName()); } else { qCWarning(KABOUTDATA) << "Could not initialize the equivalent properties of Q*Application: no instance (yet) existing."; } // KF6: Rethink the current relation between KAboutData::applicationData and the Q*Application metadata // Always overwriting the Q*Application metadata here, but not updating back the KAboutData // in applicationData() is unbalanced and can result in out-of-sync data if the Q*Application // setters have been called meanwhile // Options are to remove the overlapping properties of KAboutData for cleancode, or making the // overlapping properties official shadow properties of their Q*Application countparts, though // that increases behavioural complexity a little. } void KAboutData::registerPluginData(const KAboutData &aboutData) { s_registry->m_pluginData.insert(aboutData.componentName(), new KAboutData(aboutData)); } KAboutData *KAboutData::pluginData(const QString &componentName) { KAboutData *ad = s_registry->m_pluginData.value(componentName); return ad; } // only for KCrash (no memory allocation allowed) const KAboutData *KAboutData::applicationDataPointer() { if (s_registry.exists()) { return s_registry->m_appData; } return nullptr; } bool KAboutData::setupCommandLine(QCommandLineParser *parser) { if (!d->_shortDescription.isEmpty()) { parser->setApplicationDescription(d->_shortDescription); } parser->addHelpOption(); QCoreApplication *app = QCoreApplication::instance(); if (app && !app->applicationVersion().isEmpty()) { parser->addVersionOption(); } return parser->addOption(QCommandLineOption(QStringLiteral("author"), QCoreApplication::translate("KAboutData CLI", "Show author information."))) && parser->addOption(QCommandLineOption(QStringLiteral("license"), QCoreApplication::translate("KAboutData CLI", "Show license information."))) && parser->addOption(QCommandLineOption(QStringLiteral("desktopfile"), QCoreApplication::translate("KAboutData CLI", "The base file name of the desktop entry for this application."), QCoreApplication::translate("KAboutData CLI", "file name"))); } void KAboutData::processCommandLine(QCommandLineParser *parser) { bool foundArgument = false; if (parser->isSet(QStringLiteral("author"))) { foundArgument = true; if (d->_authorList.isEmpty()) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "This application was written by somebody who wants to remain anonymous."))); } else { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "%1 was written by:").arg(qAppName()))); for (const KAboutPerson &person : qAsConst(d->_authorList)) { QString authorData = QLatin1String(" ") + person.name(); if (!person.emailAddress().isEmpty()) { authorData.append(QLatin1String(" <") + person.emailAddress() + QLatin1Char('>')); } printf("%s\n", qPrintable(authorData)); } } if (!customAuthorTextEnabled()) { if (bugAddress() == QLatin1String("submit@bugs.kde.org") ) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please use https://bugs.kde.org to report bugs."))); } else if (!bugAddress().isEmpty()) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please report bugs to %1.").arg(bugAddress()))); } } else { printf("%s\n", qPrintable(customAuthorPlainText())); } } else if (parser->isSet(QStringLiteral("license"))) { foundArgument = true; for (const KAboutLicense &license : qAsConst(d->_licenseList)) { printf("%s\n", qPrintable(license.text())); } } const QString desktopFileName = parser->value(QStringLiteral("desktopfile")); if (!desktopFileName.isEmpty()) { d->desktopFileName = desktopFileName; } if (foundArgument) { ::exit(EXIT_SUCCESS); } } template QVariantList listToVariant(const QList& values) { QVariantList ret; ret.reserve(values.count()); for(const auto &license: values) { ret << QVariant::fromValue(license); } return ret; } QVariantList KAboutData::licensesVariant() const { return listToVariant(d->_licenseList); } QVariantList KAboutData::authorsVariant() const { return listToVariant(d->_authorList); } QVariantList KAboutData::creditsVariant() const { return listToVariant(d->_creditList); } QVariantList KAboutData::translatorsVariant() const { return listToVariant(d->_translatorList); } diff --git a/src/lib/kaboutdata.h b/src/lib/kaboutdata.h index bab7220..621f7bf 100644 --- a/src/lib/kaboutdata.h +++ b/src/lib/kaboutdata.h @@ -1,1192 +1,1179 @@ /* - * This file is part of the KDE Libraries - * Copyright (C) 2000 Espen Sand (espen@kde.org) - * Copyright (C) 2008 Friedrich W. H. Kossebau - * Copyright (C) 2010 Teo Mrnjavac - * Copyright (C) 2013 David Faure - * Copyright (C) 2017 Harald Sitter - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ + This file is part of the KDE Libraries + + SPDX-FileCopyrightText: 2000 Espen Sand + SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2010 Teo Mrnjavac + SPDX-FileCopyrightText: 2013 David Faure + SPDX-FileCopyrightText: 2017 Harald Sitter + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef KABOUTDATA_H #define KABOUTDATA_H #include #include #include #include #include class QCommandLineParser; class QJsonObject; class KAboutData; class KPluginMetaData; namespace KCrash { Q_DECL_IMPORT void defaultCrashHandler(int sig); } /** * This class is used to store information about a person or developer. * It can store the person's name, a task, an email address and a * link to a home page. This class is intended for use in the * KAboutData class, but it can be used elsewhere as well. * Normally you should at least define the person's name. * Creating a KAboutPerson object by yourself is relatively useless, * but the KAboutData methods KAboutData::authors() and KAboutData::credits() * return lists of KAboutPerson data objects which you can examine. * * Example usage within a main(), retrieving the list of people involved * with a program and re-using data from one of them: * * @code * KAboutData about("khello", i18n("KHello"), "0.1", * i18n("A KDE version of Hello, world!"), * KAboutLicense::LGPL, * i18n("Copyright (C) 2014 Developer")); * * about.addAuthor(i18n("Joe Developer"), i18n("developer"), "joe@host.com", 0); * QList people = about.authors(); * about.addCredit(people[0].name(), people[0].task()); * @endcode */ class KCOREADDONS_EXPORT KAboutPerson { Q_GADGET Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString task READ task CONSTANT) Q_PROPERTY(QString emailAddress READ emailAddress CONSTANT) Q_PROPERTY(QString webAddress READ webAddress CONSTANT) Q_PROPERTY(QString ocsUsername READ ocsUsername CONSTANT) friend class KAboutData; public: /** * Convenience constructor * * @param name The name of the person. * * @param task The task of this person. * * @param emailAddress The email address of the person. * * @param webAddress Home page of the person. * * @param ocsUsername Open Collaboration Services username of the person. * * @p name default argument @since 5.53 */ explicit KAboutPerson(const QString &name = QString(), const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutPerson(const KAboutPerson &other); ~KAboutPerson(); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutPerson &operator=(const KAboutPerson &other); /** * The person's name * @return the person's name (can be QString(), if it has been * constructed with an empty name) */ QString name() const; /** * The person's task * @return the person's task (can be QString(), if it has been * constructed with an empty task) */ QString task() const; /** * The person's email address * @return the person's email address (can be QString(), if it has been * constructed with an empty email) */ QString emailAddress() const; /** * The home page or a relevant link * @return the persons home page (can be QString(), if it has been * constructed with an empty home page) */ QString webAddress() const; /** * The person's Open Collaboration Services username * @return the persons OCS username (can be QString(), if it has been * constructed with an empty username) */ QString ocsUsername() const; /** * Creates a @c KAboutPerson from a JSON object with the following structure: * * Key | Accessor * -----------| ---------------------------- * Name | name() * Email | emailAddress() * Task | task() * Website | webAddress() * UserName | ocsUsername() * * The @c Name and @c Task key are translatable (by using e.g. a "Task[de_DE]" key) * * @since 5.18 */ static KAboutPerson fromJSON(const QJsonObject &obj); private: /** * @internal Used by KAboutData to construct translator data. */ explicit KAboutPerson(const QString &name, const QString &email, bool disambiguation); class Private; Private *const d; }; /** * This class is used to store information about a license. * The license can be one of some predefined, one given as text or one * that can be loaded from a file. This class is used in the KAboutData class. * Explicitly creating a KAboutLicense object is not possible. * If the license is wanted for a KDE component having KAboutData object, * use KAboutData::licenses() to get the licenses for that component. * If the license is for a non-code resource and given by a keyword * (e.g. in .desktop files), try using KAboutLicense::byKeyword(). */ class KCOREADDONS_EXPORT KAboutLicense { Q_GADGET Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(KAboutLicense::LicenseKey key READ key CONSTANT) Q_PROPERTY(QString spdx READ spdx CONSTANT) friend class KAboutData; public: /** * Describes the license of the software. */ enum LicenseKey { Custom = -2, File = -1, Unknown = 0, GPL = 1, GPL_V2 = 1, LGPL = 2, LGPL_V2 = 2, BSDL = 3, Artistic = 4, QPL = 5, QPL_V1_0 = 5, GPL_V3 = 6, LGPL_V3 = 7, LGPL_V2_1 = 8 ///< @since 5.25 }; Q_ENUM(LicenseKey) /** * Format of the license name. */ enum NameFormat { ShortName, FullName }; Q_ENUM(NameFormat) /** * Whether later versions of the license are allowed. */ enum VersionRestriction { OnlyThisVersion, OrLaterVersions }; Q_ENUM(VersionRestriction) /** * @since 5.53 */ explicit KAboutLicense(); /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutLicense(const KAboutLicense &other); ~KAboutLicense(); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutLicense &operator=(const KAboutLicense &other); /** * Returns the full license text. If the licenseType argument of the * constructor has been used, any text defined by setLicenseText is ignored, * and the standard text for the chosen license will be returned. * * @return The license text. */ QString text() const; /** * Returns the license name. * * Default argument @since 5.53 * * @return The license name as a string. */ QString name(KAboutLicense::NameFormat formatName = ShortName) const; /** * Returns the license key. * * @return The license key as element of KAboutLicense::LicenseKey enum. */ KAboutLicense::LicenseKey key() const; /** * Returns the SPDX license expression of this license. * If the underlying license cannot be expressed as a SPDX expression a null string is returned. * * @note SPDX expression are expansive constructs. If you parse the return value, do it in a * SPDX specification compliant manner by splitting on whitespaces to discard unwanted * information or by using a complete SPDX license expression parser. * @note SPDX identifiers are case-insensitive. Do not use case-sensitive checks on the return * value. * @see https://spdx.org/licenses * @return SPDX license expression or QString() if the license has no identifier. Compliant * with SPDX 2.1. * * @since 5.37 */ QString spdx() const; /** * Fetch a known license by a keyword/spdx ID * * Frequently the license data is provided by a terse keyword-like string, * e.g. by a field in a .desktop file. Using this method, an application * can get hold of a proper KAboutLicense object, providing that the * license is one of the several known to KDE, and use it to present * more human-readable information to the user. * * Keywords are matched by stripping all whitespace and lowercasing. * The known keywords correspond to the KAboutLicense::LicenseKey enumeration, * e.g. any of "LGPLV3", "LGPLv3", "LGPL v3" would match KAboutLicense::LGPL_V3. * If there is no match for the keyword, a valid license object is still * returned, with its name and text informing about a custom license, * and its key equal to KAboutLicense::Custom. * * @param keyword The license keyword. * @return The license object. * * @see KAboutLicense::LicenseKey */ static KAboutLicense byKeyword(const QString &keyword); private: /** * @internal Used by KAboutData to construct a predefined license. */ explicit KAboutLicense(enum KAboutLicense::LicenseKey licenseType, enum KAboutLicense::VersionRestriction versionRestriction, const KAboutData *aboutData); /** * @internal Used by KAboutData to construct a predefined license. */ explicit KAboutLicense(enum KAboutLicense::LicenseKey licenseType, const KAboutData *aboutData); /** * @internal Used by KAboutData to construct a KAboutLicense */ explicit KAboutLicense(const KAboutData *aboutData); /** * @internal Used by KAboutData to construct license by given text */ void setLicenseFromPath(const QString &pathToFile); /** * @internal Used by KAboutData to construct license by given text */ void setLicenseFromText(const QString &licenseText); class Private; QSharedDataPointer d; }; /** * @class KAboutData kaboutdata.h KAboutData * * This class is used to store information about a program or plugin. * It can store such values as version number, program name, home page, address * for bug reporting, multiple authors and contributors * (using KAboutPerson), license and copyright information. * * Currently, the values set here are shown by the "About" box * (see KAboutDialog), used by the bug report dialog (see KBugReport), * and by the help shown on command line (see KAboutData::setupCommandLine()). * * Porting Notes: Since KDE Frameworks 5.0, the translation catalog mechanism * must be provided by your translation framework to load the correct catalog * instead (eg: KLocalizedString::setApplicationDomain() for KI18n, or * QCoreApplication::installTranslator() for Qt's translation system). This * applies to the old setCatalogName() and catalogName() members. But see also * K4AboutData in kde4support as a compatibility class. * * Example: * Setting the metadata of an application using KAboutData in code also relying * on the KDE Framework modules KI18n and KDBusAddons: * @code * // create QApplication instance * QApplication app(argc, argv); * // setup translation string domain for the i18n calls * KLocalizedString::setApplicationDomain("foo"); * // create a KAboutData object to use for setting the application metadata * KAboutData aboutData("foo", i18n("Foo"), "0.1", * i18n("To Foo or not To Foo"), * KAboutLicense::LGPL, * i18n("Copyright 2017 Bar Foundation"), QString(), * "https://www.foo-the-app.net"); * // overwrite default-generated values of organizationDomain & desktopFileName * aboutData.setOrganizationDomain("barfoundation.org"); * aboutData.setDesktopFileName("org.barfoundation.foo"); * * // set the application metadata * KAboutData::setApplicationData(aboutData); * // in GUI apps set the window icon manually, not covered by KAboutData * // needed for environments where the icon name is not extracted from * // the information in the application's desktop file * QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("foo"))); * * // integrate with commandline argument handling * QCommandLineParser parser; * aboutData.setupCommandLine(&parser); * // setup of app specific commandline args * [...] * parser.process(app); * aboutData.processCommandLine(&parser); * * // with the application metadata set, register to the D-Bus session * KDBusService programDBusService(KDBusService::Multiple | KDBusService::NoExitOnFailure); * @endcode * * @short Holds information needed by the "About" box and other * classes. * @author Espen Sand (espen@kde.org), David Faure (faure@kde.org) * */ class KCOREADDONS_EXPORT KAboutData { Q_GADGET Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(QString productName READ productName CONSTANT) Q_PROPERTY(QString componentName READ componentName CONSTANT) Q_PROPERTY(QVariant programLogo READ programLogo CONSTANT) Q_PROPERTY(QString shortDescription READ shortDescription CONSTANT) Q_PROPERTY(QString homepage READ homepage CONSTANT) Q_PROPERTY(QString bugAddress READ bugAddress CONSTANT) Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString otherText READ otherText CONSTANT) Q_PROPERTY(QVariantList authors READ authorsVariant CONSTANT) //constant in practice as addAuthor is not exposed to Q_GADGET Q_PROPERTY(QVariantList credits READ creditsVariant CONSTANT) Q_PROPERTY(QVariantList translators READ translatorsVariant CONSTANT) Q_PROPERTY(QVariantList licenses READ licensesVariant CONSTANT) Q_PROPERTY(QString copyrightStatement READ copyrightStatement CONSTANT) Q_PROPERTY(QString desktopFileName READ desktopFileName CONSTANT) public: /** * Returns the KAboutData for the application. * * This contains information such as authors, license, etc., * provided that setApplicationData has been called before. * If not called before, the returned KAboutData will be initialized from the * equivalent properties of QCoreApplication (and its subclasses), * if an instance of that already exists. * For the list of such properties see setApplicationData * (before 5.22: limited to QCoreApplication::applicationName). * @see setApplicationData */ static KAboutData applicationData(); /** * Sets the application data for this application. * * In addition to changing the result of applicationData(), this initializes * the equivalent properties of QCoreApplication (and its subclasses) with * information from @p aboutData, if an instance of that already exists. * Those properties are:
  • QCoreApplication::applicationName
  • QCoreApplication::applicationVersion
  • QCoreApplication::organizationDomain
  • QGuiApplication::applicationDisplayName
  • QGuiApplication::desktopFileName (since 5.16)
* @see applicationData */ static void setApplicationData(const KAboutData &aboutData); /** * Register the KAboutData information for a plugin. * Call this from the constructor of the plugin. * This will register the plugin's @p aboutData under the component name * that was set in @p aboutData. */ static void registerPluginData(const KAboutData &aboutData); /** * Return the KAboutData for the given plugin identified by @p componentName. */ static KAboutData *pluginData(const QString &componentName); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 65) /** * Creates a @c KAboutData from the given @p plugin metadata * * @since 5.18 * @deprecated Since 5.65, use KAboutPluginDialog to show info about a plugin * instead of KAboutApplicationDialog, with the latter having had been the * only known need for this conversion. */ KCOREADDONS_DEPRECATED_VERSION(5, 65, "See API docs") static KAboutData fromPluginMetaData(const KPluginMetaData &plugin); #endif public: /** * Constructor. * * Porting Note: The @p catalogName parameter present in KDE4 was * deprecated and removed. See also K4AboutData * in kde4support if this feature is needed for compatibility purposes, or * consider using componentName() instead. * * @param componentName The program name or plugin name used internally. * Example: QStringLiteral("kwrite"). This should never be translated. * * @param displayName A displayable name for the program or plugin. This string * should be translated. Example: i18n("KWrite") * * @param version The component version string. Example: QStringLiteral("1.0"). * * @param shortDescription A short description of what the component does. * This string should be translated. * Example: i18n("A simple text editor.") * * @param licenseType The license identifier. Use setLicenseText or setLicenseTextFile if you use a license not predefined here. * * @param copyrightStatement A copyright statement, that can look like this: * i18n("Copyright (C) 1999-2000 Name"). The string specified here is * taken verbatim; the author information from addAuthor is not used. * * @param otherText Some free form text, that can contain any kind of * information. The text can contain newlines. This string * should be translated. * * @param homePageAddress The URL to the component's homepage, including * URL scheme. "http://some.domain" is correct, "some.domain" is * not. Since KDE Frameworks 5.17, https and other valid URL schemes * are also valid. See also the note below. * * @param bugAddress The bug report address string, an email address or a URL. * This defaults to the kde.org bug system. * * @note The @p homePageAddress argument is used to derive a default organization * domain for the application (which is used to register on the session D-Bus, * locate the appropriate desktop file, etc.), by taking the host name and dropping * the first component, unless there are less than three (e.g. "www.kde.org" -> "kde.org"). * Use both setOrganizationDomain(const QByteArray&) and setDesktopFileName() if their default values * do not have proper values. * * @see setOrganizationDomain(const QByteArray&), setDesktopFileName(const QString&) */ // KF6: remove constructor that includes catalogName, and put default // values back in for shortDescription and licenseType KAboutData(const QString &componentName, const QString &displayName, const QString &version, const QString &shortDescription, enum KAboutLicense::LicenseKey licenseType, const QString ©rightStatement = QString(), const QString &otherText = QString(), const QString &homePageAddress = QString(), const QString &bugAddress = QStringLiteral("submit@bugs.kde.org") ); /** * Constructor. * * @param componentName The program name or plugin name used internally. * Example: "kwrite". * * @param displayName A displayable name for the program or plugin. This string * should be translated. Example: i18n("KWrite") * * @param version The component version string. * * Sets the property desktopFileName to "org.kde."+componentName and * the property organizationDomain to "kde.org". * * Default arguments @since 5.53 * * @see setOrganizationDomain(const QByteArray&), setDesktopFileName(const QString&) */ explicit KAboutData(const QString &componentName = {}, const QString &displayName = {}, const QString &version = {} ); /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutData(const KAboutData &other); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutData &operator=(const KAboutData &other); ~KAboutData(); /** * Defines an author. * * You can call this function as many times as you need. Each entry is * appended to a list. The person in the first entry is assumed to be * the leader of the project. * * @param name The developer's name. It should be translated. * * @param task What the person is responsible for. This text can contain * newlines. It should be translated. * Can be left empty. * * @param emailAddress An Email address where the person can be reached. * Can be left empty. * * @param webAddress The person's homepage or a relevant link. * Start the address with "http://". "http://some.domain" is * correct, "some.domain" is not. Can be left empty. * * @param ocsUsername The person's Open Collaboration Services username. * The provider can be optionally specified with @see setOcsProvider. * */ KAboutData &addAuthor(const QString &name, const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * Defines a person that deserves credit. * * You can call this function as many times as you need. Each entry * is appended to a list. * * @param name The person's name. It should be translated. * * @param task What the person has done to deserve the honor. The * text can contain newlines. It should be translated. * Can be left empty. * * @param emailAddress An email address when the person can be reached. * Can be left empty. * * @param webAddress The person's homepage or a relevant link. * Start the address with "http://". "http://some.domain" is * is correct, "some.domain" is not. Can be left empty. * * @param ocsUsername The person's Open Collaboration Services username. * The provider can be optionally specified with @see setOcsProvider. * */ KAboutData &addCredit(const QString &name, const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * @brief Sets the name(s) of the translator(s) of the GUI. * * The canonical use with the ki18n framework is: * * \code * setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), * i18nc("EMAIL OF TRANSLATORS", "Your emails")); * \endcode * * If you are using a KMainWindow this is done for you automatically. * * The name and emailAddress are treated as lists separated with ",". * * If the strings are empty or "Your names"/"Your emails" * respectively they will be ignored. * * @param name the name(s) of the translator(s) * @param emailAddress the email address(es) of the translator(s) * @see KAboutTranslator */ KAboutData &setTranslator(const QString &name, const QString &emailAddress); /** * Defines a license text, which is translated. * * Example: * \code * setLicenseText( i18n("This is my license") ); * \endcode * * @param license The license text. */ KAboutData &setLicenseText(const QString &license); /** * Adds a license text, which is translated. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * Example: * \code * addLicenseText( i18n("This is my license") ); * \endcode * * @param license The license text. * @see setLicenseText, addLicense, addLicenseTextFile */ KAboutData &addLicenseText(const QString &license); /** * Defines a license text by pointing to a file where it resides. * The file format has to be plain text in an encoding compatible to the locale. * * @param file Path to the file in the local filesystem containing the license text. */ KAboutData &setLicenseTextFile(const QString &file); /** * Adds a license text by pointing to a file where it resides. * The file format has to be plain text in an encoding compatible to the locale. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param file Path to the file in the local filesystem containing the license text. * @see addLicenseText, addLicense, setLicenseTextFile */ KAboutData &addLicenseTextFile(const QString &file); /** * Defines the component name used internally. * * @param componentName The application or plugin name. Example: "kate". */ KAboutData &setComponentName(const QString &componentName); /** * Defines the displayable component name string. * * @param displayName The display name. This string should be * translated. * Example: i18n("Advanced Text Editor"). */ KAboutData &setDisplayName(const QString &displayName); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2) /** * Obsolete method * * This method used to set the icon name but this is no longer * possible in KDE Frameworks 5 because KCoreAddons does not * depend on QtGui. * * @param iconName name of the icon. Example: "accessories-text-editor" * @see programIconName() * * @deprecated since 5.2, use QApplication::setWindowIcon(QIcon::fromTheme()) instead. */ KCOREADDONS_DEPRECATED_VERSION(5, 2, "Use QApplication::setWindowIcon") KAboutData &setProgramIconName(const QString &iconName); #endif /** * Defines the program logo. * * Use this if you need to have an application logo * in AboutData other than the application icon. * * Because KAboutData is a core class it cannot use QImage/QPixmap/QIcon directly, * so this is a QVariant that should contain a QImage/QPixmap/QIcon. * * QIcon should be preferred, to be able to properly handle HiDPI scaling. * If a QIcon is provided, it will be used at a typical size of 48x48. * * @param image logo image. * @see programLogo() */ KAboutData &setProgramLogo(const QVariant &image); /** * Specifies an Open Collaboration Services provider by URL. * A provider file must be available for the chosen provider. * * Use this if you need to override the default provider. * * If this method is not used, all the KAboutPerson OCS usernames * will be used with the openDesktop.org entry from the default * provider file. * * @param providerUrl The provider URL as defined in the provider file. */ KAboutData &setOcsProvider(const QString &providerUrl); /** * Defines the program version string. * * @param version The program version. */ KAboutData &setVersion(const QByteArray &version); /** * Defines a short description of what the program does. * * @param shortDescription The program description. This string should * be translated. Example: i18n("An advanced text * editor with syntax highlighting support."). */ KAboutData &setShortDescription(const QString &shortDescription); /** * Defines the license identifier. * * @param licenseKey The license identifier. * @see addLicenseText, setLicenseText, setLicenseTextFile */ KAboutData &setLicense(KAboutLicense::LicenseKey licenseKey); /** * Defines the license identifier. * * @param licenseKey The license identifier. * @param versionRestriction Whether later versions of the license are also allowed. * e.g. licensed under "GPL 2.0 or at your option later versions" would be OrLaterVersions. * @see addLicenseText, setLicenseText, setLicenseTextFile * * @since 5.37 */ KAboutData &setLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction); /** * Adds a license identifier. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param licenseKey The license identifier. * @see setLicenseText, addLicenseText, addLicenseTextFile */ KAboutData &addLicense(KAboutLicense::LicenseKey licenseKey); /** * Adds a license identifier. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param licenseKey The license identifier. * @param versionRestriction Whether later versions of the license are also allowed. * e.g. licensed under "GPL 2.0 or at your option later versions" would be OrLaterVersions. * @see setLicenseText, addLicenseText, addLicenseTextFile * * @since 5.37 */ KAboutData &addLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction); /** * Defines the copyright statement to show when displaying the license. * * @param copyrightStatement A copyright statement, that can look like * this: i18n("Copyright (C) 1999-2000 Name"). The string specified here is * taken verbatim; the author information from addAuthor is not used. */ KAboutData &setCopyrightStatement(const QString ©rightStatement); /** * Defines the additional text to show in the about dialog. * * @param otherText Some free form text, that can contain any kind of * information. The text can contain newlines. This string * should be translated. */ KAboutData &setOtherText(const QString &otherText); /** * Defines the program homepage. * * @param homepage The program homepage string. * Start the address with "http://". "http://kate.kde.org" * is correct but "kate.kde.org" is not. */ KAboutData &setHomepage(const QString &homepage); /** * Defines the address where bug reports should be sent. * * @param bugAddress The bug report email address or URL. * This defaults to the kde.org bug system. */ KAboutData &setBugAddress(const QByteArray &bugAddress); /** * Defines the domain of the organization that wrote this application. * The domain is set to kde.org by default, or the domain of the homePageAddress constructor argument, * if set. * * Make sure to call setOrganizationDomain(const QByteArray&) if your product * is not developed inside the KDE community. * * Used e.g. for the registration to D-Bus done by KDBusService * from the KDE Frameworks KDBusAddons module. * * Calling this method has no effect on the value of the desktopFileName property. * * @note If your program should work as a D-Bus activatable service, the base name * of the D-Bus service description file or of the desktop file you install must match * the D-Bus "well-known name" for which the program will register. * For example, KDBusService will use a name created from the reversed organization domain * with the component name attached, so for an organization domain "bar.org" and a * component name "foo" the name of an installed D-Bus service file needs to be * "org.bar.foo.service" or the name of the installed desktop file "org.bar.foo.desktop" * (and the desktopFileName property accordingly set to "org.bar.foo"). * For still supporting the deprecated start of services via KToolInvocation, * the desktop file needs to have an entry with the key "X-DBUS-ServiceName" * and a value which matches the used D-Bus "well-known name" as just described, * so with the above used values it needs a line "X-DBUS-ServiceName=org.bar.foo" * * @param domain the domain name, for instance kde.org, koffice.org, etc. * * @see setDesktopFileName(const QString&) */ KAboutData &setOrganizationDomain(const QByteArray &domain); /** * Defines the product name which will be used in the KBugReport dialog. * By default it's the componentName, but you can overwrite it here to provide * support for special components e.g. in the form 'product/component', * such as 'kontact/summary'. * * @param name The name of product */ KAboutData &setProductName(const QByteArray &name); /** * Returns the application's internal name. * @return the internal program name. */ QString componentName() const; /** * Returns the application's product name, which will be used in KBugReport * dialog. By default it returns componentName(), otherwise the one which is set * with setProductName() * * @return the product name. */ QString productName() const; /** * Returns the translated program name. * @return the program name (translated). */ QString displayName() const; /** * Returns the domain name of the organization that wrote this application. * * @see setOrganizationDomain(const QByteArray&) */ QString organizationDomain() const; /** * @internal * Provided for use by KCrash */ const char *internalProgramName() const; // Not using KCOREADDONS_ENABLE_DEPRECATED_SINCE because KXmlGui and KConfigWidgets need this, for compat #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 2) /** * Returns the program's icon name. * * The default value is componentName(). * @return the program's icon name. * * This is mostly for compatibility, given that setProgramIconName is deprecated. */ KCOREADDONS_DEPRECATED_VERSION(5, 2, "Use QApplication::windowIcon") QString programIconName() const; #endif /** * Returns the program logo image. * * Because KAboutData is a core class it cannot use QImage/QPixmap/QIcon directly, * so this is a QVariant containing a QImage/QPixmap/QIcon. * * @return the program logo data, or a null image if there is * no custom application logo defined. */ QVariant programLogo() const; /** * Returns the chosen Open Collaboration Services provider URL. * @return the provider URL. */ QString ocsProviderUrl() const; /** * Returns the program's version. * @return the version string. */ QString version() const; /** * @internal * Provided for use by KCrash */ const char *internalVersion() const; /** * Returns a short, translated description. * @return the short description (translated). Can be * QString() if not set. */ QString shortDescription() const; /** * Returns the application homepage. * @return the application homepage URL. Can be QString() if * not set. */ QString homepage() const; /** * Returns the email address or URL for bugs. * @return the address where to report bugs. */ QString bugAddress() const; /** * @internal * Provided for use by KCrash */ const char *internalBugAddress() const; /** * Returns a list of authors. * @return author information (list of persons). */ QList authors() const; /** * Returns a list of persons who contributed. * @return credit information (list of persons). */ QList credits() const; /** * Returns a list of translators. * @return translators information (list of persons) */ QList translators() const; /** * Returns a message about the translation team. * @return a message about the translation team */ static QString aboutTranslationTeam(); /** * Returns a translated, free form text. * @return the free form text (translated). Can be QString() if not set. */ QString otherText() const; /** * Returns a list of licenses. * * @return licenses information (list of licenses) */ QList licenses() const; /** * Returns the copyright statement. * @return the copyright statement. Can be QString() if not set. */ QString copyrightStatement() const; /** * Returns the plain text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @return the plain text displayed around the list of authors instead * of the default message. Can be QString(). */ QString customAuthorPlainText() const; /** * Returns the rich text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @return the rich text displayed around the list of authors instead * of the default message. Can be QString(). */ QString customAuthorRichText() const; /** * Returns whether custom text should be displayed around the list of * authors. * * @return whether custom text should be displayed around the list of * authors. */ bool customAuthorTextEnabled() const; /** * Sets the custom text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @param plainText The plain text. * @param richText The rich text. * * Setting both to parameters to QString() will cause no message to be * displayed at all. Call unsetCustomAuthorText() to revert to the default * message. */ KAboutData &setCustomAuthorText(const QString &plainText, const QString &richText); /** * Clears any custom text displayed around the list of authors and falls * back to the default message telling users to send bug reports to * bugAddress(). */ KAboutData &unsetCustomAuthorText(); /** * Configures the @p parser command line parser to provide an authors entry with * information about the developers of the application and an entry specifying the license. * * Additionally, it will set the description to the command line parser, will add the help * option and if the QApplication has a version set (e.g. via KAboutData::setApplicationData) * it will also add the version option. * * Since 5.16 it also adds an option to set the desktop file name. * * @returns true if adding the options was successful; otherwise returns false. * * @sa processCommandLine() */ bool setupCommandLine(QCommandLineParser *parser); /** * Reads the processed @p parser and sees if any of the arguments are the ones set * up from setupCommandLine(). * * @sa setupCommandLine() */ void processCommandLine(QCommandLineParser *parser); /** * Sets the base name of the desktop entry for this application. * * This is the file name, without the full path and without extension, * of the desktop entry that represents this application according to * the freedesktop desktop entry specification (e.g. "org.kde.foo"). * * A default desktop file name is constructed when the KAboutData * object is created, using the reverse domain name of the * organizationDomain() and the componentName() as they are at the time * of the KAboutData object creation. * Call this method to override that default name. Typically this is * done when also setOrganizationDomain(const QByteArray&) or setComponentName(const QString&) * need to be called to override the initial values. * * The desktop file name can also be passed to the application at runtime through * the @c desktopfile command line option which is added by setupCommandLine(QCommandLineParser*). * This is useful if an application supports multiple desktop files with different runtime * settings. * * @param desktopFileName The desktop file name of this application * * @sa desktopFileName() * @sa organizationDomain() * @sa componentName() * @sa setupCommandLine(QCommandLineParser*) * @since 5.16 **/ KAboutData &setDesktopFileName(const QString &desktopFileName); /** * @returns The desktop file name of this application (e.g. "org.kde.foo") * @sa setDesktopFileName(const QString&) * @since 5.16 **/ QString desktopFileName() const; private: QVariantList licensesVariant() const; QVariantList authorsVariant() const; QVariantList creditsVariant() const; QVariantList translatorsVariant() const; friend void KCrash::defaultCrashHandler(int sig); static const KAboutData *applicationDataPointer(); class Private; Private *const d; }; Q_DECLARE_METATYPE(KAboutData) Q_DECLARE_METATYPE(KAboutLicense) Q_DECLARE_METATYPE(KAboutPerson) #endif diff --git a/src/lib/kcoreaddons.cpp b/src/lib/kcoreaddons.cpp index 4b9ffc9..78c7518 100644 --- a/src/lib/kcoreaddons.cpp +++ b/src/lib/kcoreaddons.cpp @@ -1,36 +1,23 @@ /* - * This file is part of the KDE Libraries - * Copyright (C) 2016 David Edmundson - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ + This file is part of the KDE Libraries + + SPDX-FileCopyrightText: 2016 David Edmundson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "kcoreaddons.h" #include #include "kcoreaddons_version.h" QString KCoreAddons::versionString() { return QStringLiteral(KCOREADDONS_VERSION_STRING); } uint KCoreAddons::version() { return KCOREADDONS_VERSION; } diff --git a/src/lib/kcoreaddons.h b/src/lib/kcoreaddons.h index aaa33ea..3bfa82f 100644 --- a/src/lib/kcoreaddons.h +++ b/src/lib/kcoreaddons.h @@ -1,57 +1,44 @@ /* - * This file is part of the KDE Libraries - * Copyright (C) 2016 David Edmundson - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ + This file is part of the KDE Libraries + + SPDX-FileCopyrightText: 2016 David Edmundson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef KCOREADDONS_H #define KCOREADDONS_H #include #include /** * @namespace KCoreAddons * Provides utility functions for metadata about the KCoreAddons library. */ namespace KCoreAddons { /** * Returns the version number of KCoreAddons at run-time as a string (for example, "5.19.0"). * This may be a different version than the version the application was compiled against. * @since 5.20 */ KCOREADDONS_EXPORT QString versionString(); /** * Returns a numerical version number of KCoreAddons at run-time in the form 0xMMNNPP * (MM = major, NN = minor, PP = patch) * This can be compared using the macro QT_VERSION_CHECK. * * For example: * \code * if (KCoreAddons::version() < QT_VERSION_CHECK(5,19,0)) * \endcode * * This may be a different version than the version the application was compiled against. * @since 5.20 */ KCOREADDONS_EXPORT unsigned int version(); } #endif diff --git a/src/lib/plugin/desktopfileparser.cpp b/src/lib/plugin/desktopfileparser.cpp index 52ae261..b8982b0 100644 --- a/src/lib/plugin/desktopfileparser.cpp +++ b/src/lib/plugin/desktopfileparser.cpp @@ -1,611 +1,594 @@ -/****************************************************************************** - * Copyright 2013-2014 Sebastian Kügler * - * Copyright 2014 Alex Richardson * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) version 3, or any * - * later version accepted by the membership of KDE e.V. (or its * - * successor approved by the membership of KDE e.V.), which shall * - * act as a proxy defined in Section 6 of version 3 of the license. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this library. If not, see * - * . * - * * - ******************************************************************************/ +/* + SPDX-FileCopyrightText: 2013-2014 Sebastian Kügler + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ #include "desktopfileparser_p.h" #include #include #include #include #include #include #include #include #include // in the desktoptojson binary enable debug messages by default, in the library only warning messages #ifdef BUILDING_DESKTOPTOJSON_TOOL Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtDebugMsg) #else Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtWarningMsg) #endif #ifdef BUILDING_DESKTOPTOJSON_TOOL // use if not else to prevent wrong scoping #define DESKTOPTOJSON_VERBOSE_DEBUG if (!DesktopFileParser::s_verbose) {} else qCDebug(DESKTOPPARSER) #define DESKTOPTOJSON_VERBOSE_WARNING if (!DesktopFileParser::s_verbose) {} else qCWarning(DESKTOPPARSER) #else #define DESKTOPTOJSON_VERBOSE_DEBUG QT_NO_QDEBUG_MACRO() #define DESKTOPTOJSON_VERBOSE_WARNING QT_NO_QDEBUG_MACRO() #endif using namespace DesktopFileParser; // This code was taken from KConfigGroupPrivate::deserializeList QStringList DesktopFileParser::deserializeList(const QString &data, char separator) { if (data.isEmpty()) { return QStringList(); } if (data == QLatin1String("\\0")) { return QStringList(QString()); } QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == '\\') { quoted = true; } else if (data[p].unicode() == separator) { value.append(val); if (p == data.length() - 1) { // don't add an empty entry to the end if the last character is a separator return value; } val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } QByteArray DesktopFileParser::escapeValue(const QByteArray &input) { const int start = input.indexOf('\\'); if (start < 0) { return input; } // we could do this in place, but this code is simpler // this tool is probably only transitional, so no need to optimize QByteArray result; result.reserve(input.size()); result.append(input.data(), start); for (int i = start; i < input.length(); ++i) { if (input[i] != '\\') { result.append(input[i]); } else { if (i + 1 >= input.length()) { // just append the backslash if we are at end of line result.append(input[i]); break; } i++; // consume next character char nextChar = input[i]; switch (nextChar) { case 's': result.append(' '); break; case 'n': result.append('\n'); break; case 't': result.append('\t'); break; case 'r': result.append('\r'); break; case '\\': result.append('\\'); break; default: result.append('\\'); result.append(nextChar); // just ignore the escape sequence } } } return result; } struct CustomPropertyDefinition { // default ctor needed for QVector CustomPropertyDefinition() : type(QVariant::String) {} CustomPropertyDefinition(const QByteArray &key, QVariant::Type type) : key(key) , type(type) {} QJsonValue fromString(const QString &str) const { switch (type) { case QVariant::String: return str; case QVariant::StringList: return QJsonArray::fromStringList(deserializeList(str)); case QVariant::Int: { bool ok = false; int result = str.toInt(&ok); if (!ok) { qCWarning(DESKTOPPARSER) << "Invalid integer value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } case QVariant::Double: { bool ok = false; double result = str.toDouble(&ok); if (!ok) { qCWarning(DESKTOPPARSER) << "Invalid double value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } case QVariant::Bool: { bool result = str.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0; if (!result && str.compare(QLatin1String("false"), Qt::CaseInsensitive) != 0) { qCWarning(DESKTOPPARSER) << "Invalid boolean value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } default: // This was checked when parsing the file, no other QVariant::Type values are possible Q_UNREACHABLE(); } } QByteArray key; QVariant::Type type; }; namespace { bool readUntilDesktopEntryGroup(QFile &file, const QString &path, int &lineNr) { if (!file.open(QFile::ReadOnly)) { qCWarning(DESKTOPPARSER) << "Error: Failed to open " << path; return false; } // we only convert data inside the [Desktop Entry] group while (!file.atEnd()) { const QByteArray line = file.readLine().trimmed(); lineNr++; if (line == "[Desktop Entry]") { return true; } } qCWarning(DESKTOPPARSER) << "Error: Could not find [Desktop Entry] group in " << path; return false; } QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup, QByteArray *pName) { QByteArray group = *nextGroup; QByteArray type; if (group.isEmpty()) { qCWarning(DESKTOPPARSER, "Read empty .desktop file group name! Invalid file?"); } while (!df.atEnd()) { QByteArray line = df.readLine().trimmed(); // skip empty lines and comments if (line.isEmpty() || line.startsWith('#')) { continue; } if (line.startsWith('[')) { if (!line.endsWith(']')) { qCWarning(DESKTOPPARSER) << "Illegal .desktop group definition (does not end with ']'):" << line; } QByteArray name = line.mid(1, line.lastIndexOf(']') - 1).trimmed(); // we have reached the next group -> return current group and Type= value *nextGroup = name; break; } const static QRegularExpression typeEntryRegex( QStringLiteral("^Type\\s*=\\s*(.*)$")); const auto match = typeEntryRegex.match(QString::fromUtf8(line)); if (match.hasMatch()) { type = match.captured(1).toUtf8(); } else if (pName) { const static QRegularExpression nameEntryRegex( QStringLiteral("^X-KDE-ServiceType\\s*=\\s*(.*)$")); const auto nameMatch = nameEntryRegex.match(QString::fromUtf8(line)); if (nameMatch.hasMatch()) { *pName = nameMatch.captured(1).toUtf8(); } } } return type; } bool tokenizeKeyValue(QFile &df, const QString &src, QByteArray &key, QString &value, int &lineNr) { const QByteArray line = df.readLine().trimmed(); lineNr++; if (line.isEmpty()) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": empty"; return true; } if (line.startsWith('#')) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": comment"; return true; // skip comments } if (line.startsWith('[')) { // start of new group -> doesn't interest us anymore DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": start of new group " << line; return false; } // must have form key=value now const int equalsIndex = line.indexOf('='); if (equalsIndex == -1) { qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Line is neither comment nor group " "and doesn't contain an '=' character: \"" << line.constData() << '\"'; return true; } // trim key and value to remove spaces around the '=' char key = line.mid(0, equalsIndex).trimmed(); if (key.isEmpty()) { qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Key name is missing: \"" << line.constData() << '\"'; return true; } const QByteArray valueRaw = line.mid(equalsIndex + 1).trimmed(); const QByteArray valueEscaped = escapeValue(valueRaw); value = QString::fromUtf8(valueEscaped); #ifdef BUILDING_DESKTOPTOJSON_TOOL DESKTOPTOJSON_VERBOSE_DEBUG.nospace() << "Line " << lineNr << ": key=" << key << ", value=" << value; if (valueEscaped != valueRaw) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << " contained escape sequences"; } #endif return true; } static QString locateRelativeServiceType(const QString &relPath) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservicetypes5/") + relPath); } static ServiceTypeDefinition* parseServiceTypesFile(const QString &inputPath) { int lineNr = 0; QString path = inputPath; if (QDir::isRelativePath(path)) { path = locateRelativeServiceType(path); QString rcPath; if (path.isEmpty()) { rcPath = QLatin1String(":/kservicetypes5/") + inputPath; if (QFileInfo::exists(rcPath)) { path = rcPath; } } if (path.isEmpty()) { qCWarning(DESKTOPPARSER).nospace() << "Could not locate service type file kservicetypes5/" << qPrintable(inputPath) << ", tried " << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation) << " and " << rcPath; return nullptr; } } QFile df(path); if (!df.exists()) { qCCritical(DESKTOPPARSER) << "Service type file" << path << "does not exist"; return nullptr; } if (!readUntilDesktopEntryGroup(df, path, lineNr)) { return nullptr; } ServiceTypeDefinition result; // TODO: passing nextGroup by pointer is inefficient as it will make deep copies every time // Not exactly performance critical code though so low priority QByteArray nextGroup = "Desktop Entry"; // Type must be ServiceType now QByteArray typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, &result.m_serviceTypeName); if (typeStr != QByteArrayLiteral("ServiceType")) { qCWarning(DESKTOPPARSER) << path << "is not a valid service type: Type entry should be 'ServiceType', got" << typeStr << "instead."; return nullptr; } while (!df.atEnd()) { QByteArray currentGroup = nextGroup; typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, nullptr); if (!currentGroup.startsWith(QByteArrayLiteral("PropertyDef::"))) { qCWarning(DESKTOPPARSER) << "Skipping invalid group" << currentGroup << "in service type" << path; continue; } if (typeStr.isEmpty()) { qCWarning(DESKTOPPARSER) << "Could not find Type= key in group" << currentGroup; continue; } QByteArray propertyName = currentGroup.mid(qstrlen("PropertyDef::")); QVariant::Type type = QVariant::nameToType(typeStr.constData()); switch (type) { case QVariant::String: case QVariant::StringList: case QVariant::Int: case QVariant::Double: case QVariant::Bool: qCDebug(DESKTOPPARSER) << "Found property definition" << propertyName << "with type" << typeStr; result.m_propertyDefs.push_back(CustomPropertyDefinition(propertyName, type)); break; case QVariant::Invalid: qCWarning(DESKTOPPARSER) << "Property type" << typeStr << "is not a known QVariant type." " Found while parsing property definition for" << propertyName << "in" << path; break; default: qCWarning(DESKTOPPARSER) << "Unsupported property type" << typeStr << "for property" << propertyName << "found in" << path << "\nOnly QString, QStringList, int, double and bool are supported."; } } return new ServiceTypeDefinition(result); } // a lazy map of service type definitions typedef QCache ServiceTypesHash; Q_GLOBAL_STATIC(ServiceTypesHash, s_serviceTypes) // access must be guarded by serviceTypesMutex as this code could be executed by multiple threads QBasicMutex s_serviceTypesMutex; } // end of anonymous namespace ServiceTypeDefinitions ServiceTypeDefinitions::fromFiles(const QStringList &paths) { ServiceTypeDefinitions ret; ret.m_definitions.reserve(paths.size()); // as we might modify the cache we need to acquire a mutex here for (const QString &serviceTypePath : paths) { bool added = ret.addFile(serviceTypePath); if (!added) { #ifdef BUILDING_DESKTOPTOJSON_TOOL exit(1); // this is a fatal error when using kcoreaddons_desktop_to_json() #endif } } return ret; } bool ServiceTypeDefinitions::addFile(const QString& path) { QMutexLocker lock(&s_serviceTypesMutex); ServiceTypeDefinition* def = s_serviceTypes->object(path); if (def) { // in cache but we still must make our own copy m_definitions << *def; } else { // not found in cache -> we need to parse the file qCDebug(DESKTOPPARSER) << "About to parse service type file" << path; def = parseServiceTypesFile(path); if (!def) { return false; } m_definitions << *def; // This must *precede* insert call, insert might delete s_serviceTypes->insert(path, def); } return true; } QJsonValue ServiceTypeDefinitions::parseValue(const QByteArray &key, const QString &value) const { // check whether the key has a special type associated with it for (const auto &def : m_definitions) { for (const CustomPropertyDefinition &propertyDef : def.m_propertyDefs) { if (propertyDef.key == key) { return propertyDef.fromString(value); } } } qCDebug(DESKTOPPARSER) << "Unknown property type for key" << key << "-> falling back to string"; return QJsonValue(value); } bool ServiceTypeDefinitions::hasServiceType(const QByteArray &serviceTypeName) const { const auto it = std::find_if(m_definitions.begin(), m_definitions.end(), [&serviceTypeName](const ServiceTypeDefinition &def) { return def.m_serviceTypeName == serviceTypeName; }); return it != m_definitions.end(); } void DesktopFileParser::convertToJson(const QByteArray &key, ServiceTypeDefinitions &serviceTypes, const QString &value, QJsonObject &json, QJsonObject &kplugin, int lineNr) { /* The following keys are recognized (and added to a "KPlugin" object): Icon=mypluginicon Type=Service ServiceTypes=KPluginInfo MimeType=text/plain;image/png Name=User Visible Name (translatable) Comment=Description of what the plugin does (translatable) X-KDE-PluginInfo-Author=Author's Name X-KDE-PluginInfo-Email=author@foo.bar X-KDE-PluginInfo-Name=internalname X-KDE-PluginInfo-Version=1.1 X-KDE-PluginInfo-Website=http://www.plugin.org/ X-KDE-PluginInfo-Category=playlist X-KDE-PluginInfo-Depends=plugin1,plugin3 X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true X-KDE-FormFactors=desktop */ if (key == QByteArrayLiteral("Icon")) { kplugin[QStringLiteral("Icon")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Name")) { kplugin[QStringLiteral("Id")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Category")) { kplugin[QStringLiteral("Category")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-License")) { kplugin[QStringLiteral("License")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Version")) { kplugin[QStringLiteral("Version")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Website")) { kplugin[QStringLiteral("Website")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Depends")) { kplugin[QStringLiteral("Dependencies")] = QJsonArray::fromStringList(deserializeList(value)); } else if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) { //NOTE: "X-KDE-ServiceTypes" and "ServiceTypes" were already managed in the first parse step, so this second one is almost a noop const auto services = deserializeList(value); kplugin[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(services); } else if (key == QByteArrayLiteral("MimeType")) { // MimeType is a XDG string list and not a KConfig list so we need to use ';' as the separator kplugin[QStringLiteral("MimeTypes")] = QJsonArray::fromStringList(deserializeList(value, ';')); // make sure that applications using kcoreaddons_desktop_to_json() that depend on reading // the MimeType property still work (see https://git.reviewboard.kde.org/r/125527/) json[QStringLiteral("MimeType")] = value; // TODO KF6 remove this compatibility code } else if (key == QByteArrayLiteral("X-KDE-FormFactors")) { kplugin[QStringLiteral("FormFactors")] = QJsonArray::fromStringList(deserializeList(value)); } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-EnabledByDefault")) { bool boolValue = false; // should only be lower case, but be tolerant here if (value.toLower() == QLatin1String("true")) { boolValue = true; } else { if (value.toLower() != QLatin1String("false")) { qCWarning(DESKTOPPARSER).nospace() << "Expected boolean value for key \"" << key << "\" at line " << lineNr << "but got \"" << value << "\" instead."; } } kplugin[QStringLiteral("EnabledByDefault")] = boolValue; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Author")) { QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject(); // if the authors object doesn't exist yet this will create it authorsObject[QStringLiteral("Name")] = value; QJsonArray array; array.append(authorsObject); kplugin[QStringLiteral("Authors")] = array; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Email")) { QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject(); // if the authors object doesn't exist yet this will create it authorsObject[QStringLiteral("Email")] = value; QJsonArray array; array.append(authorsObject); kplugin[QStringLiteral("Authors")] = array; } else if (key == QByteArrayLiteral("Name") || key.startsWith(QByteArrayLiteral("Name["))) { // TODO: also handle GenericName? does that make any sense, or is X-KDE-PluginInfo-Category enough? kplugin[QString::fromUtf8(key)] = value; } else if (key == QByteArrayLiteral("Comment")) { kplugin[QStringLiteral("Description")] = value; } else if (key.startsWith(QByteArrayLiteral("Comment["))) { kplugin[QStringLiteral("Description") + QString::fromUtf8(key.mid(qstrlen("Comment")))] = value; } else if (key == QByteArrayLiteral("InitialPreference")) { kplugin[QStringLiteral("InitialPreference")] = value.toInt(); } else if (key == QByteArrayLiteral("Hidden")) { DESKTOPTOJSON_VERBOSE_WARNING << "Hidden= key found in desktop file, this makes no sense" " with metadata inside the plugin."; kplugin[QString::fromUtf8(key)] = (value.toLower() == QLatin1String("true")); } else if (key == QByteArrayLiteral("Exec") || key == QByteArrayLiteral("Type") || key == QByteArrayLiteral("X-KDE-Library") || key == QByteArrayLiteral("Encoding")) { // Exec= doesn't make sense here, however some .desktop files (like e.g. in kdevelop) have a dummy value here // also the Type=Service entry is no longer needed // X-KDE-Library is also not needed since we already have the library to read this metadata // Encoding= is also not converted as we always use utf-8 for reading DESKTOPTOJSON_VERBOSE_DEBUG << "Not converting key " << key << "=" << value; } else { // check service type definitions or fall back to QString json[QString::fromUtf8(key)] = serviceTypes.parseValue(key, value); } } bool DesktopFileParser::convert(const QString &src, const QStringList &serviceTypes, QJsonObject &json, QString *libraryPath) { QFile df(src); int lineNr = 0; ServiceTypeDefinitions serviceTypeDef = ServiceTypeDefinitions::fromFiles(serviceTypes); readUntilDesktopEntryGroup(df, src, lineNr); DESKTOPTOJSON_VERBOSE_DEBUG << "Found [Desktop Entry] group in line" << lineNr; auto startPos = df.pos(); //parse it a first time to know servicetype while (!df.atEnd()) { QByteArray key; QString value; if (!tokenizeKeyValue(df, src, key, value, lineNr)) { break; } // some .desktop files still use the legacy ServiceTypes= key if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) { const QString dotDesktop = QStringLiteral(".desktop"); const QChar slashChar(QLatin1Char('/')); const auto serviceList = deserializeList(value); for (const auto &service : serviceList) { if (!serviceTypeDef.hasServiceType(service.toLatin1())) { // Make up the filename from the service type name. This assumes consistent naming... QString absFileName = locateRelativeServiceType( service.toLower().replace(slashChar, QLatin1Char('-')) + dotDesktop); if (absFileName.isEmpty()) { absFileName = locateRelativeServiceType( service.toLower().remove(slashChar) + dotDesktop); } if (absFileName.isEmpty()) { qCWarning(DESKTOPPARSER) << "Unable to find service type for service" << service << "listed in" << src; } else { serviceTypeDef.addFile(absFileName); } } } break; } } lineNr=0; df.seek(startPos); QJsonObject kplugin; // the "KPlugin" key of the metadata //QJsonObject json; while (!df.atEnd()) { QByteArray key; QString value; if (!tokenizeKeyValue(df, src, key, value, lineNr)) { break; } else if (key.isEmpty()) { continue; } #ifdef BUILDING_DESKTOPTOJSON_TOOL if (s_compatibilityMode) { convertToCompatibilityJson(QString::fromUtf8(key), value, json, lineNr); } else { convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr); } #else convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr); #endif if (libraryPath && key == QByteArrayLiteral("X-KDE-Library")) { *libraryPath = value; } } json[QStringLiteral("KPlugin")] = kplugin; return true; } diff --git a/src/lib/plugin/desktopfileparser_p.h b/src/lib/plugin/desktopfileparser_p.h index e3112a2..47ff49a 100644 --- a/src/lib/plugin/desktopfileparser_p.h +++ b/src/lib/plugin/desktopfileparser_p.h @@ -1,80 +1,63 @@ -/****************************************************************************** - * Copyright 2013-2014 Sebastian Kügler * - * Copyright 2014 Alex Richardson * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) version 3, or any * - * later version accepted by the membership of KDE e.V. (or its * - * successor approved by the membership of KDE e.V.), which shall * - * act as a proxy defined in Section 6 of version 3 of the license. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this library. If not, see * - * . * - * * - ******************************************************************************/ +/* + SPDX-FileCopyrightText: 2013-2014 Sebastian Kügler + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ #ifndef DESKTOPFILEPARSER_H #define DESKTOPFILEPARSER_H #include #include #include class QJsonObject; class QJsonValue; Q_DECLARE_LOGGING_CATEGORY(DESKTOPPARSER) struct CustomPropertyDefinition; struct ServiceTypeDefinition { QVector m_propertyDefs; QByteArray m_serviceTypeName; }; struct ServiceTypeDefinitions { static ServiceTypeDefinitions fromFiles(const QStringList &paths); /** * @return @p value converted to the correct JSON type. * If there is no custom property definition for @p key this will simply return the string value */ QJsonValue parseValue(const QByteArray &key, const QString &value) const; /** * Parses the service file in @p path and extracts its definitions * * @returns whether the action could be performed */ bool addFile(const QString &path); bool hasServiceType(const QByteArray &serviceTypeName) const; private: QVector m_definitions; }; namespace DesktopFileParser { QByteArray escapeValue(const QByteArray &input); QStringList deserializeList(const QString &data, char separator = ','); bool convert(const QString &src, const QStringList &serviceTypes, QJsonObject &json, QString *libraryPath); void convertToJson(const QByteArray &key, ServiceTypeDefinitions &serviceTypes, const QString &value, QJsonObject &json, QJsonObject &kplugin, int lineNr); #ifdef BUILDING_DESKTOPTOJSON_TOOL void convertToCompatibilityJson(const QString &key, const QString &value, QJsonObject &json, int lineNr); extern bool s_verbose; extern bool s_compatibilityMode; #endif } #endif // DESKTOPFILEPARSER_H diff --git a/src/lib/plugin/kexportplugin.h b/src/lib/plugin/kexportplugin.h index f503769..d059fbe 100644 --- a/src/lib/plugin/kexportplugin.h +++ b/src/lib/plugin/kexportplugin.h @@ -1,57 +1,48 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Bernhard Loos - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + + SPDX-FileCopyrightText: 2007 Bernhard Loos + + SPDX-License-Identifier: LGPL-2.0-only */ + #ifndef KEXPORTPLUGIN_H #define KEXPORTPLUGIN_H #include #include #include /** * \relates KPluginLoader * Use this macro if you want to give your plugin a version number. * You can later access the version number with KPluginLoader::pluginVersion() */ #define K_EXPORT_PLUGIN_VERSION(version) \ Q_EXTERN_C Q_DECL_EXPORT const quint32 kde_plugin_version = version; #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 0) /** * \relates KPluginLoader * This macro exports the main object of the plugin. Most times, this will be a KPluginFactory * or derived class, but any QObject derived class can be used. * Take a look at the documentation of Q_EXPORT_PLUGIN2 for some details. */ #if defined (Q_OS_WIN32) && defined(Q_CC_BOR) #define Q_STANDARD_CALL __stdcall #else #define Q_STANDARD_CALL class KCOREADDONS_DEPRECATED_EXPORT K_EXPORT_PLUGIN_is_deprecated_see_KDE5PORTING { }; #define K_EXPORT_PLUGIN(factory) \ K_EXPORT_PLUGIN_is_deprecated_see_KDE5PORTING dummy; #endif #endif #endif // KEXPORTPLUGIN_H diff --git a/src/lib/plugin/kpluginfactory.cpp b/src/lib/plugin/kpluginfactory.cpp index 5c24a36..bae4f12 100644 --- a/src/lib/plugin/kpluginfactory.cpp +++ b/src/lib/plugin/kpluginfactory.cpp @@ -1,174 +1,162 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Matthias Kretz - Copyright (C) 2007 Bernhard Loos +/* + This file is part of the KDE project - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Matthias Kretz + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kpluginfactory.h" #include "kpluginfactory_p.h" #include #include "kcoreaddons_debug.h" Q_GLOBAL_STATIC(QObjectCleanupHandler, factorycleanup) extern int kLibraryDebugArea(); KPluginFactory::KPluginFactory() : d_ptr(new KPluginFactoryPrivate) { Q_D(KPluginFactory); d->q_ptr = this; factorycleanup()->add(this); } KPluginFactory::KPluginFactory(KPluginFactoryPrivate &d) : d_ptr(&d) { factorycleanup()->add(this); } KPluginFactory::~KPluginFactory() { delete d_ptr; } void KPluginFactory::registerPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction) { Q_D(KPluginFactory); Q_ASSERT(metaObject); // we allow different interfaces to be registered without keyword if (!keyword.isEmpty()) { if (d->createInstanceHash.contains(keyword)) { qCWarning(KCOREADDONS_DEBUG) << "A plugin with the keyword" << keyword << "was already registered. A keyword must be unique!"; } d->createInstanceHash.insert(keyword, KPluginFactoryPrivate::Plugin(metaObject, instanceFunction)); } else { const QList clashes(d->createInstanceHash.values(keyword)); const QMetaObject *superClass = metaObject->superClass(); if (superClass) { for (const KPluginFactoryPrivate::Plugin &plugin : clashes) { for (const QMetaObject *otherSuper = plugin.first->superClass(); otherSuper; otherSuper = otherSuper->superClass()) { if (superClass == otherSuper) { qCWarning(KCOREADDONS_DEBUG) << "Two plugins with the same interface(" << superClass->className() << ") were registered. Use keywords to identify the plugins."; } } } } for (const KPluginFactoryPrivate::Plugin &plugin : clashes) { superClass = plugin.first->superClass(); if (superClass) { for (const QMetaObject *otherSuper = metaObject->superClass(); otherSuper; otherSuper = otherSuper->superClass()) { if (superClass == otherSuper) { qCWarning(KCOREADDONS_DEBUG) << "Two plugins with the same interface(" << superClass->className() << ") were registered. Use keywords to identify the plugins."; } } } } d->createInstanceHash.insert(keyword, KPluginFactoryPrivate::Plugin(metaObject, instanceFunction)); } } #if KCOREADDONS_BUILD_DEPRECATED_SINCE(4, 0) QObject *KPluginFactory::createObject(QObject *parent, const char *className, const QStringList &args) { Q_UNUSED(parent); Q_UNUSED(className); Q_UNUSED(args); return nullptr; } #endif #if KCOREADDONS_BUILD_DEPRECATED_SINCE(4, 0) KParts::Part *KPluginFactory::createPartObject(QWidget *parentWidget, QObject *parent, const char *classname, const QStringList &args) { Q_UNUSED(parent); Q_UNUSED(parentWidget); Q_UNUSED(classname); Q_UNUSED(args); return nullptr; } #endif QObject *KPluginFactory::create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) { Q_D(KPluginFactory); QObject *obj = nullptr; #if KCOREADDONS_BUILD_DEPRECATED_SINCE(4, 0) if (keyword.isEmpty()) { const QStringList argsStringList = variantListToStringList(args); if ((obj = reinterpret_cast(createPartObject(parentWidget, parent, iface, argsStringList)))) { Q_EMIT objectCreated(obj); return obj; } if ((obj = createObject(parent, iface, argsStringList))) { Q_EMIT objectCreated(obj); return obj; } } #endif const QList candidates(d->createInstanceHash.values(keyword)); // for !keyword.isEmpty() candidates.count() is 0 or 1 for (const KPluginFactoryPrivate::Plugin &plugin : candidates) { for (const QMetaObject *current = plugin.first; current; current = current->superClass()) { if (0 == qstrcmp(iface, current->className())) { if (obj) { qCWarning(KCOREADDONS_DEBUG) << "ambiguous interface requested from a DSO containing more than one plugin"; } obj = plugin.second(parentWidget, parent, args); break; } } } if (obj) { emit objectCreated(obj); } return obj; } QStringList KPluginFactory::variantListToStringList(const QVariantList &list) { QStringList stringlist; for (const QVariant &var : list) { stringlist << var.toString(); } return stringlist; } QVariantList KPluginFactory::stringListToVariantList(const QStringList &list) { QVariantList variantlist; for (const QString &str : list) { variantlist << QVariant(str); } return variantlist; } diff --git a/src/lib/plugin/kpluginfactory.h b/src/lib/plugin/kpluginfactory.h index 09b14f1..13c5c61 100644 --- a/src/lib/plugin/kpluginfactory.h +++ b/src/lib/plugin/kpluginfactory.h @@ -1,585 +1,573 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Matthias Kretz - Copyright (C) 2007 Bernhard Loos +/* + This file is part of the KDE project - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Matthias Kretz + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KPLUGINFACTORY_H #define KPLUGINFACTORY_H #include "kcoreaddons_export.h" #include #include #include #include // for source compat class QWidget; class KPluginFactoryPrivate; namespace KParts { class Part; } #define KPluginFactory_iid "org.kde.KPluginFactory" #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, ...) \ class name : public KPluginFactory \ { \ Q_OBJECT \ Q_INTERFACES(KPluginFactory) \ __VA_ARGS__ \ public: \ explicit name(); \ ~name(); \ }; #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_JSON(name, baseFactory, json) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE json)) #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, Q_PLUGIN_METADATA(IID KPluginFactory_iid)) #define K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ name::name() \ { \ pluginRegistrations \ } \ name::~name() {} #define K_PLUGIN_FACTORY_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) #define K_PLUGIN_FACTORY_WITH_BASEFACTORY_JSON(name, baseFactory, jsonFile, pluginRegistrations) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_JSON(name, baseFactory, jsonFile) \ K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * @note K_PLUGIN_FACTORY declares the subclass including a Q_OBJECT macro. * So you need to make sure to have Qt's moc run also for the source file * where you use the macro. E.g. in projects using CMake and it's automoc feature, * as usual you need to have a line * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY(MyPluginFactory, * registerPlugin(); * ) * * #include * \endcode * * If you want to compile a .json file into the plugin, use K_PLUGIN_FACTORY_WITH_JSON. * * \see K_PLUGIN_FACTORY_WITH_JSON * \see K_PLUGIN_FACTORY_DECLARATION * \see K_PLUGIN_FACTORY_DEFINITION */ #define K_PLUGIN_FACTORY(name, pluginRegistrations) K_PLUGIN_FACTORY_WITH_BASEFACTORY(name, KPluginFactory, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object with * JSON metadata. * * This macro does the same as K_PLUGIN_FACTORY, but adds a JSON file as plugin * metadata. See Q_PLUGIN_METADATA() for more information. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * \param jsonFile Name of the json file to be compiled into the plugin as metadata * * @note K_PLUGIN_FACTORY_WITH_JSON declares the subclass including a Q_OBJECT macro. * So you need to make sure to have Qt's moc run also for the source file * where you use the macro. E.g. in projects using CMake and it's automoc feature, * as usual you need to have a line * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY_WITH_JSON(MyPluginFactory, * "metadata.json", * registerPlugin(); * ) * * #include * \endcode * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DECLARATION * \see K_PLUGIN_FACTORY_DEFINITION * * @since 5.0 */ #define K_PLUGIN_FACTORY_WITH_JSON(name, jsonFile, pluginRegistrations) K_PLUGIN_FACTORY_WITH_BASEFACTORY_JSON(name, KPluginFactory, jsonFile, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object with * JSON metadata. * * This macro does the same as K_PLUGIN_FACTORY_WITH_JSON, but you only have to pass the class name and the json file. * The factory name and registerPlugin call are deduced from the class name. * * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_CLASS_WITH_JSON(MyPlugin, "metadata.json") * * #include * \endcode * * \see K_PLUGIN_FACTORY_WITH_JSON * * @since 5.44 */ #define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile) K_PLUGIN_FACTORY_WITH_JSON(classname ## Factory, jsonFile, registerPlugin();) /** * \relates KPluginFactory * * K_PLUGIN_FACTORY_DECLARATION declares the KPluginFactory subclass. This macro * can be used in a header file. * * \param name The name of the KPluginFactory derived class. * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DEFINITION */ #define K_PLUGIN_FACTORY_DECLARATION(name) K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, KPluginFactory) /** * \relates KPluginFactory * K_PLUGIN_FACTORY_DEFINITION defines the KPluginFactory subclass. This macro * can not be used in a header file. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DECLARATION */ #define K_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations) K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, KPluginFactory, pluginRegistrations) /** * \class KPluginFactory kpluginfactory.h * * KPluginFactory provides a convenient way to provide factory-style plugins. * Qt plugins provide a singleton object, but a common pattern is for plugins * to generate as many objects of a particular type as the application requires. * By using KPluginFactory, you can avoid implementing the factory pattern * yourself. * * KPluginFactory also allows plugins to provide multiple different object * types, indexed by keywords. * * The objects created by KPluginFactory must inherit QObject, and must have a * standard constructor pattern: * \li if the object is a KPart::Part, it must be of the form * \code * T(QWidget *parentWidget, QObject *parent, const QVariantList &args) * \endcode * \li if it is a QWidget, it must be of the form * \code * T(QWidget *parent, const QVariantList &args) * \endcode * \li otherwise it must be of the form * \code * T(QObject *parent, const QVariantList &args) * \endcode * * You should typically use either K_PLUGIN_FACTORY() or * K_PLUGIN_FACTORY_WITH_JSON() in your plugin code to create the factory. The * typical pattern is * * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY(MyPluginFactory, * registerPlugin(); * ) * #include * \endcode * * If you want to write a custom KPluginFactory not using the standard macro(s) * you can reimplement the * create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) * method. * * Example: * \code * class SomeScriptLanguageFactory : public KPluginFactory * { * Q_OBJECT * public: * SomeScriptLanguageFactory() * {} * * protected: * virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) * { * const QString identifier = QLatin1String(iface) + QLatin1Char('_') + keyword; * // load scripting language module from the information in identifier * // and return it: * return object; * } * }; * \endcode * * If you want to load a library use KPluginLoader. * The application that wants to instantiate plugin classes can do the following: * \code * KPluginFactory *factory = KPluginLoader("libraryname").factory(); * if (factory) { * PluginInterface *p1 = factory->create(parent); * OtherInterface *p2 = factory->create(parent); * NextInterface *p3 = factory->create("keyword1", parent); * NextInterface *p3 = factory->create("keyword2", parent); * } * \endcode * * \author Matthias Kretz * \author Bernhard Loos */ class KCOREADDONS_EXPORT KPluginFactory : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(KPluginFactory) public: /** * This constructor creates a factory for a plugin. */ explicit KPluginFactory(); /** * This destroys the PluginFactory. */ ~KPluginFactory() override; /** * Use this method to create an object. It will try to create an object which inherits * \p T. If it has multiple choices it's not defined which object will be returned, so be careful * to request a unique interface or use keywords. * * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or @c nullptr if an error occurred. */ template T *create(QObject *parent = nullptr, const QVariantList &args = QVariantList()); /** * Use this method to create an object. It will try to create an object which inherits * \p T and was registered with \p keyword. * * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param keyword The keyword of the object. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or @c nullptr if an error occurred. */ template T *create(const QString &keyword, QObject *parent = nullptr, const QVariantList &args = QVariantList()); /** * Use this method to create an object. It will try to create an object which inherits * \p T and was registered with \p keyword. * This overload has an additional \p parentWidget argument, which is used by some plugins (e.g. Parts). * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param parentWidget An additional parent widget. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param keyword The keyword of the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or @c nullptr if an error occurred. */ template T *create(QWidget *parentWidget, QObject *parent, const QString &keyword = QString(), const QVariantList &args = QVariantList()); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(4, 0) /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ template KCOREADDONS_DEPRECATED_VERSION(4, 0, "Use KPluginFactory::create(QObject *parent, const QVariantList &args)") T *create(QObject *parent, const QStringList &args) { return create(parent, stringListToVariantList(args)); } /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ KCOREADDONS_DEPRECATED_VERSION(4, 0, "Use KPluginFactory::create(QObject *parent, const QVariantList &args)") QObject *create(QObject *parent = nullptr, const char *classname = "QObject", const QStringList &args = QStringList()) { return create(classname, nullptr, parent, stringListToVariantList(args), QString()); } #endif /** * \internal * Converts a QStringList to a QVariantList */ static QVariantList stringListToVariantList(const QStringList &list); /** * \internal * Converts a QVariantList of strings to a QStringList */ static QStringList variantListToStringList(const QVariantList &list); Q_SIGNALS: void objectCreated(QObject *object); protected: /** * Function pointer type to a function that instantiates a plugin. */ typedef QObject *(*CreateInstanceFunction)(QWidget *, QObject *, const QVariantList &); /** * This is used to detect the arguments need for the constructor of plugin classes. * You can inherit it, if you want to add new classes and still keep support for the old ones. */ template struct InheritanceChecker { CreateInstanceFunction createInstanceFunction(KParts::Part *) { return &createPartInstance; } CreateInstanceFunction createInstanceFunction(QWidget *) { return &createInstance; } CreateInstanceFunction createInstanceFunction(...) { return &createInstance; } }; explicit KPluginFactory(KPluginFactoryPrivate &dd); /** * Registers a plugin with the factory. Call this function from the constructor of the * KPluginFactory subclass to make the create function able to instantiate the plugin when asked * for an interface the plugin implements. * * You can register as many plugin classes as you want as long as either the plugin interface or * the \p keyword makes it unique. E.g. it is possible to register a KCModule and a * KParts::Part without having to specify keywords since their interfaces differ. * * \tparam T the name of the plugin class * * \param keyword An optional keyword as unique identifier for the plugin. This allows you to * put more than one plugin with the same interface into the same library using the same * factory. X-KDE-PluginKeyword is a convenient way to specify the keyword in a desktop file. * * \param instanceFunction A function pointer to a function that creates an instance of the * plugin. The default function that will be used depends on the type of interface. If the * interface inherits from * \li \c KParts::Part the function will call * \code * new T(QWidget *parentWidget, QObject *parent, const QVariantList &args) * \endcode * \li \c QWidget the function will call * \code * new T(QWidget *parent, const QVariantList &args) * \endcode * \li else the function will call * \code * new T(QObject *parent, const QVariantList &args) * \endcode */ template void registerPlugin(const QString &keyword = QString(), CreateInstanceFunction instanceFunction = InheritanceChecker().createInstanceFunction(static_cast(nullptr))) { registerPlugin(keyword, &T::staticMetaObject, instanceFunction); } KPluginFactoryPrivate *const d_ptr; #if KCOREADDONS_BUILD_DEPRECATED_SINCE(4, 0) /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ KCOREADDONS_DEPRECATED_VERSION(4, 0, "Use KPluginFactory::create(QObject *parent, const QVariantList &args)") virtual QObject *createObject(QObject *parent, const char *className, const QStringList &args); /** * @deprecated since 4.0 use create(QWidget *parentWidget, QObject *parent, const QString &keyword, const QVariantList &args) */ KCOREADDONS_DEPRECATED_VERSION(4, 0, "Use KPluginFactory::create(QWidget *parentWidget, QObject *parent, const QString &keyword, const QVariantList &args)") virtual KParts::Part *createPartObject(QWidget *parentWidget, QObject *parent, const char *classname, const QStringList &args); #endif /** * This function is called when the factory asked to create an Object. * * You may reimplement it to provide a very flexible factory. This is especially useful to * provide generic factories for plugins implemented using a scripting language. * * \param iface The staticMetaObject::className() string identifying the plugin interface that * was requested. E.g. for KCModule plugins this string will be "KCModule". * \param parentWidget Only used if the requested plugin is a KPart. * \param parent The parent object for the plugin object. * \param args A plugin specific list of arbitrary arguments. * \param keyword A string that uniquely identifies the plugin. If a KService is used this * keyword is read from the X-KDE-PluginKeyword entry in the .desktop file. */ virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword); template static QObject *createInstance(QWidget *parentWidget, QObject *parent, const QVariantList &args) { Q_UNUSED(parentWidget) ParentType *p = nullptr; if (parent) { p = qobject_cast(parent); Q_ASSERT(p); } return new impl(p, args); } template static QObject *createPartInstance(QWidget *parentWidget, QObject *parent, const QVariantList &args) { return new impl(parentWidget, parent, args); } private: void registerPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction); }; typedef KPluginFactory KLibFactory; template inline T *KPluginFactory::create(QObject *parent, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parent && parent->isWidgetType() ? reinterpret_cast(parent) : nullptr, parent, args, QString()); T *t = qobject_cast(o); if (!t) { delete o; } return t; } template inline T *KPluginFactory::create(const QString &keyword, QObject *parent, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parent && parent->isWidgetType() ? reinterpret_cast(parent) : nullptr, parent, args, keyword); T *t = qobject_cast(o); if (!t) { delete o; } return t; } template inline T *KPluginFactory::create(QWidget *parentWidget, QObject *parent, const QString &keyword, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parentWidget, parent, args, keyword); T *t = qobject_cast(o); if (!t) { delete o; } return t; } Q_DECLARE_INTERFACE(KPluginFactory, KPluginFactory_iid) #endif // KPLUGINFACTORY_H diff --git a/src/lib/plugin/kpluginfactory_p.h b/src/lib/plugin/kpluginfactory_p.h index 0f195e5..95e67d2 100644 --- a/src/lib/plugin/kpluginfactory_p.h +++ b/src/lib/plugin/kpluginfactory_p.h @@ -1,47 +1,35 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Matthias Kretz - Copyright (C) 2007 Bernhard Loos +/* + This file is part of the KDE project - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Matthias Kretz + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KPLUGINFACTORY_P_H #define KPLUGINFACTORY_P_H #include "kpluginfactory.h" #include class KPluginFactoryPrivate { Q_DECLARE_PUBLIC(KPluginFactory) protected: typedef QPair Plugin; KPluginFactoryPrivate() : catalogInitialized(false) {} ~KPluginFactoryPrivate() { } QMultiHash createInstanceHash; QString catalogName; bool catalogInitialized; KPluginFactory *q_ptr; }; #endif // KPLUGINFACTORY_P_H diff --git a/src/lib/plugin/kpluginloader.cpp b/src/lib/plugin/kpluginloader.cpp index 6959c97..4a13da0 100644 --- a/src/lib/plugin/kpluginloader.cpp +++ b/src/lib/plugin/kpluginloader.cpp @@ -1,308 +1,298 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Bernhard Loos - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + * This file is part of the KDE project + + SPDX-FileCopyrightText: 2007 Bernhard Loos + + SPDX-License-Identifier: LGPL-2.0-only */ #include "kpluginloader.h" #include "kpluginfactory.h" #include "kpluginmetadata.h" #include #include #include #include "kcoreaddons_debug.h" #include #include // TODO: Upstream the versioning stuff to Qt // TODO: Patch for Qt to expose plugin-finding code directly // TODO: Add a convenience method to KFactory to replace KPluginLoader::factory() // TODO: (after the above) deprecate this class class KPluginLoaderPrivate { Q_DECLARE_PUBLIC(KPluginLoader) protected: KPluginLoaderPrivate(const QString &libname) : name(libname), loader(nullptr), pluginVersion(~0U), pluginVersionResolved(false) {} ~KPluginLoaderPrivate() {} KPluginLoader *q_ptr; const QString name; QString errorString; QPluginLoader *loader; quint32 pluginVersion; bool pluginVersionResolved; }; QString KPluginLoader::findPlugin(const QString &name) { // We just defer to Qt; unfortunately, QPluginLoader's searching code is not // accessible without creating a QPluginLoader object. // Workaround for QTBUG-39642 static QMutex s_qtWorkaroundMutex; QMutexLocker lock(&s_qtWorkaroundMutex); QPluginLoader loader(name); return loader.fileName(); } KPluginLoader::KPluginLoader(const QString &plugin, QObject *parent) : QObject(parent), d_ptr(new KPluginLoaderPrivate(plugin)) { d_ptr->q_ptr = this; Q_D(KPluginLoader); d->loader = new QPluginLoader(plugin, this); } KPluginLoader::KPluginLoader(const KPluginName &pluginName, QObject *parent) : QObject(parent), d_ptr(new KPluginLoaderPrivate(pluginName.name())) { d_ptr->q_ptr = this; Q_D(KPluginLoader); d->loader = new QPluginLoader(this); if (pluginName.isValid()) { d->loader->setFileName(pluginName.name()); if (d->loader->fileName().isEmpty()) { qCDebug(KCOREADDONS_DEBUG) << "Failed to load plugin" << pluginName.name() << d->loader->errorString() << "\nPlugin search paths are" << QCoreApplication::libraryPaths() << "\nThe environment variable QT_PLUGIN_PATH might be not correctly set"; } } else { d->errorString = pluginName.errorString(); } } KPluginLoader::~KPluginLoader() { delete d_ptr; } KPluginFactory *KPluginLoader::factory() { Q_D(KPluginLoader); QObject *obj = instance(); if (!obj) { return nullptr; } KPluginFactory *factory = qobject_cast(obj); if (factory == nullptr) { qCDebug(KCOREADDONS_DEBUG) << "Expected a KPluginFactory, got a" << obj->metaObject()->className(); delete obj; d->errorString = tr("The library %1 does not offer a KPluginFactory.").arg(d->name); } return factory; } quint32 KPluginLoader::pluginVersion() { Q_D(const KPluginLoader); if (!load()) { return qint32(-1); } return d->pluginVersion; } QString KPluginLoader::pluginName() const { Q_D(const KPluginLoader); return d->name; } QString KPluginLoader::errorString() const { Q_D(const KPluginLoader); if (!d->errorString.isEmpty()) { return d->errorString; } return d->loader->errorString(); } QString KPluginLoader::fileName() const { Q_D(const KPluginLoader); return d->loader->fileName(); } QObject *KPluginLoader::instance() { Q_D(const KPluginLoader); if (!load()) { return nullptr; } return d->loader->instance(); } bool KPluginLoader::isLoaded() const { Q_D(const KPluginLoader); return d->loader->isLoaded() && d->pluginVersionResolved; } bool KPluginLoader::load() { Q_D(KPluginLoader); if (!d->loader->load()) { return false; } if (d->pluginVersionResolved) { return true; } Q_ASSERT(!fileName().isEmpty()); QLibrary lib(fileName()); Q_ASSERT(lib.isLoaded()); // already loaded by QPluginLoader::load() // TODO: this messes up KPluginLoader::errorString(): it will change from unknown error to could not resolve kde_plugin_version quint32 *version = reinterpret_cast(lib.resolve("kde_plugin_version")); if (version) { d->pluginVersion = *version; } else { d->pluginVersion = ~0U; } d->pluginVersionResolved = true; return true; } QLibrary::LoadHints KPluginLoader::loadHints() const { Q_D(const KPluginLoader); return d->loader->loadHints(); } QJsonObject KPluginLoader::metaData() const { Q_D(const KPluginLoader); return d->loader->metaData(); } void KPluginLoader::setLoadHints(QLibrary::LoadHints loadHints) { Q_D(KPluginLoader); d->loader->setLoadHints(loadHints); } bool KPluginLoader::unload() { Q_D(KPluginLoader); // Even if *this* call does not unload it, another might, // so we err on the side of re-resolving the version. d->pluginVersionResolved = false; return d->loader->unload(); } void KPluginLoader::forEachPlugin(const QString &directory, std::function callback) { QStringList dirsToCheck; if (QDir::isAbsolutePath(directory)) { dirsToCheck << directory; } else { const QStringList listPaths = QCoreApplication::libraryPaths(); for (const QString &libDir : listPaths) { dirsToCheck << libDir + QLatin1Char('/') + directory; } } qCDebug(KCOREADDONS_DEBUG) << "Checking for plugins in" << dirsToCheck; for (const QString &dir : qAsConst(dirsToCheck)) { QDirIterator it(dir, QDir::Files); while (it.hasNext()) { it.next(); if (QLibrary::isLibrary(it.fileName())) { callback(it.fileInfo().absoluteFilePath()); } } } } QVector KPluginLoader::findPlugins(const QString &directory, std::function filter) { QVector ret; forEachPlugin(directory, [&](const QString &pluginPath) { KPluginMetaData metadata(pluginPath); if (!metadata.isValid()) { return; } if (filter && !filter(metadata)) { return; } ret.append(metadata); }); return ret; } QVector< KPluginMetaData > KPluginLoader::findPluginsById(const QString& directory, const QString& pluginId) { auto filter = [&pluginId](const KPluginMetaData &md) -> bool { return md.pluginId() == pluginId; }; return KPluginLoader::findPlugins(directory, filter); } QList KPluginLoader::instantiatePlugins(const QString &directory, std::function filter, QObject* parent) { QList ret; QPluginLoader loader; const QVector listMetaData = findPlugins(directory, filter); for (const KPluginMetaData &metadata : listMetaData) { loader.setFileName(metadata.fileName()); QObject* obj = loader.instance(); if (!obj) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not instantiate plugin \"" << metadata.fileName() << "\": " << loader.errorString(); continue; } obj->setParent(parent); ret.append(obj); } return ret; } diff --git a/src/lib/plugin/kpluginloader.h b/src/lib/plugin/kpluginloader.h index 3b11d49..7b0f6c3 100644 --- a/src/lib/plugin/kpluginloader.h +++ b/src/lib/plugin/kpluginloader.h @@ -1,475 +1,465 @@ -/* This file is part of the KDE project - Copyright (C) 2007 Bernhard Loos - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + + SPDX-FileCopyrightText: 2007 Bernhard Loos + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KPLUGINLOADER_H #define KPLUGINLOADER_H #include #include #include class KPluginFactory; class KPluginMetaData; class KPluginLoaderPrivate; class KPluginName; /** * \class KPluginLoader kpluginloader.h * * This class behaves largely like QPluginLoader (and, indeed, uses it * internally), but additionally reads the plugin version, as provided by the * K_EXPORT_PLUGIN_VERSION macro (see pluginVersion()) and provides access to a * KPluginFactory instance if the plugin provides one (see factory()) * * Note that the factory() is a typesafe convenience method that just wraps a * qobject_cast on the result of QPluginLoader::instance(). Therefore, if you do * not need the plugin version feature, you can (and should) just use * QPluginLoader instead. * * Unlike QPluginLoader, it is not possible to re-use KPluginLoader for more * than one plugin (it provides no setFileName method). * * The same notes and caveats that apply to QPluginLoader also apply to * KPluginLoader. * * Sample code: * \code * KPluginLoader loader( ...library or kservice... ); * KPluginFactory* factory = loader.factory(); * if (!factory) { * qWarning() << "Error loading plugin:" << loader.errorString(); * } else { * MyInterface* obj = factory->create(); * if (!obj) { * qWarning() << "Error creating object"; * } * } * \endcode * * \see KPluginFactory * * \author Bernhard Loos */ class KCOREADDONS_EXPORT KPluginLoader : public QObject { Q_OBJECT Q_PROPERTY(QString fileName READ fileName) Q_PROPERTY(QLibrary::LoadHints loadHints READ loadHints WRITE setLoadHints) Q_PROPERTY(QString pluginName READ pluginName) Q_PROPERTY(quint32 pluginVersion READ pluginVersion) public: /** * Load a plugin by name. * * This should be the name of the plugin object file, without any suffix * (like .so or .dll). Plugin object files should not have a 'lib' prefix. * * fileName() will be empty if the plugin could not be found. * * \param plugin The name of the plugin. * \param parent A parent object. */ explicit KPluginLoader(const QString &plugin, QObject *parent = nullptr); /** * Load a plugin by name. * * This constructor behaves exactly the same as * KPluginLoader(const QString&,QObject*). It allows any class that can be * cast to KPluginName (such as KService) to be passed to KPluginLoader. * * \param name The name of the plugin. * \param parent A parent object. */ explicit KPluginLoader(const KPluginName &name, QObject *parent = nullptr); /** * Destroys the plugin loader. * * Unless unload() was called explicitly, the plugin stays in memory until * the application terminates. */ ~KPluginLoader(); /** * Returns the factory object of the plugin. * * This is typically created by one of the KPluginFactory macros. * Internally, this uses QPluginLoader::instance(), and the same * behaviours apply. * * \returns The factory of the plugin or @c nullptr on error. */ KPluginFactory *factory(); /** * Returns the name of this plugin as given to the constructor. * * If the KService constructor was used, this is the name of the library * provided by the service. * * \returns The plugin name. * * \see fileName() */ QString pluginName() const; /** * Returns the plugin version. * * This will load the plugin if it is not already loaded. * * \returns The version given to K_EXPORT_PLUGIN_VERSION, or (quint32) -1 if * the macro was not used or the plugin could not be loaded. */ quint32 pluginVersion(); /** * Locates a plugin. * * Searches for a dynamic object file in the locations KPluginLoader and * QPluginLoader would search (ie: the current directory and * QCoreApplication::libraryPaths()). * * This can be useful if you wish to use a plugin that does not conform to * the Qt plugin scheme of providing a QObject that declares * Q_PLUGIN_METADATA. In this case, you can find the plugin with this * method, and load it with QLibrary. * * Note that the path is not necessarily absolute. In particular, if the * plugin is found in the current directory, it will be a relative path. * * \param name The name of the plugin (can be a relative path; see above). * This should not include a file extension (like .so or .dll). * \returns The path to the plugin if it was found, or QString() if it * could not be found. * * @since 5.0 */ static QString findPlugin(const QString &name); /** * Returns the last error. * * \returns The description of the last error. * * \see QPluginLoader::errorString() */ QString errorString() const; /** * Returns the path of the plugin. * * This will be the full path of the plugin if it was found, and empty if * it could not be found. * * \returns The full path of the plugin, or the null string if it could * not be found. * * \see QPluginLoader::fileName(), pluginName() */ QString fileName() const; /** * Returns the root object of the plugin. * * The plugin will be loaded if necessary. If the plugin used one of the * KPluginFactory macros, you should use factory() instead. * * \returns The plugin's root object. * * \see QPluginLoader::instance() */ QObject *instance(); /** * Determines whether the plugin is loaded. * * \returns @c True if the plugin is loaded, @c false otherwise. * * \see QPluginLoader::isLoaded() */ bool isLoaded() const; /** * Loads the plugin. * * It is safe to call this multiple times; if the plugin was already loaded, * it will just return @c true. * * Methods that require the plugin to be loaded will load it as necessary * anyway, so you do not normally need to call this method. * * \returns @c True if the plugin was loaded successfully, @c false * otherwise. * * \see QPluginLoader::load() */ bool load(); /** * Returns the load hints for the plugin. * * Determines how load() should work. See QLibrary::loadHints for more * information. * * \returns The load hints for the plugin. * * \see QPluginLoader::loadHints(), setLoadHints() */ QLibrary::LoadHints loadHints() const; /** * Returns the meta data for the plugin. * * \returns A JSON object containing the plugin's metadata, if found. * * \see QPluginLoader::metaData() */ QJsonObject metaData() const; /** * Set the load hints for the plugin. * * Determines how load() should work. See QLibrary::loadHints for more * information. * * \param loadHints The load hints for the plugin. * * \see QPluginLoader::setLoadHints(), loadHints() */ void setLoadHints(QLibrary::LoadHints loadHints); /** * Attempts to unload the plugin. * * If other instances of KPluginLoader or QPluginLoader are using the same * plugin, this will fail; unloading will only happen when every instance * has called unload(). * * \returns @c True if the plugin was unloaded, @c false otherwise. * * \see QPluginLoader::unload(), load(), instance(), factory() */ bool unload(); /** * Finds and instantiates (by calling QPluginLoader::instance()) all plugins from a given * directory. Only plugins which have JSON metadata will be considered. A filter can be passed * which determines which of the found plugins should actually be loaded. * * If you use KConfig you could have a group "Plugins" in your configuration file with the * plugin name as the key and true/false as the value to indicate whether the plugin should * be loaded. In order to easily load all the enable plugins you could use the following code: * @code * KConfigGroup pluginGroup = KSharedConfig::openConfig()->group("Plugins"); * auto filter = [&](const KPluginMetaData &md) { * if (!pluginGroup.hasKey(md.pluginName())) { * return md.isEnabledByDefault(); * } else { * return pluginGroup.readEntry(md.pluginName(), false); * } * }; * QList plugins = KPluginLoader::instantiatePlugins("myapp", filter); * @endcode * * @param directory the directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param filter a callback function that returns @c true if the found plugin should be loaded * and @c false if it should be skipped. If this argument is omitted all plugins will be loaded. * * @param parent the parent to set for the instantiated plugins, if the * plugins were not already loaded. * * @note If the plugins have been previously loaded (via QPluginLoader, * directly or due to this class) without being deleted in the meantime * then they are not re-created or re-parented and will be returned using * the parent they were originally created with. @sa * QPluginLoader::instance(). * * @return a list containing an instantiation of each plugin that met the filter criteria * * @see KPluginLoader::findPlugins() * * @since 5.1 */ static QList instantiatePlugins(const QString &directory, std::function filter = std::function(), QObject* parent = nullptr); /** * Find all plugins inside @p directory. Only plugins which have JSON metadata will be considered. * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param filter a callback function that returns @c true if the found plugin should be loaded * and @c false if it should be skipped. If this argument is omitted all plugins will be loaded. * * @return all plugins found in @p directory that fulfil the constraints of @p filter * * @see KPluginLoader::instantiatePlugins() * * @since 5.1 */ static QVector findPlugins(const QString &directory, std::function filter = std::function()); /** * Find all plugins inside @p directory with a given pluginId. Only plugins which have JSON metadata will be considered. * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param pluginId The Id of the plugin, for example KPluginMetaData.pluginId(). * * @return all plugins found in @p directory with the given pluginId. * * @see KPluginLoader::instantiatePlugins() * * @since 5.11 */ static QVector findPluginsById(const QString &directory, const QString &pluginId); /** * Invokes @p callback for each valid plugin found inside @p directory. This is useful if * your application needs to customize the behaviour of KPluginLoader::findPlugins() or * KPluginLoader::instantiatePlugins(). * * @note The files found do not necessarily contain JSON metadata and may not be loadable using K/QPluginLoader. * The only guarantee made is that they are valid library file names as determined by QLibrary::isLibrary(). * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param callback This function will be invoked for each valid plugin that is found. It will receive * the absolute path to the plugin as an argument * * @see KPluginLoader::findPlugins(), KPluginLoader::instantiatePlugins() * * @since 5.1 */ static void forEachPlugin(const QString &directory, std::function callback = std::function()); private: Q_DECLARE_PRIVATE(KPluginLoader) Q_DISABLE_COPY(KPluginLoader) KPluginLoaderPrivate *const d_ptr; }; /** * Represents the name of a plugin intended for KPluginLoader. * * This exists only so that classes such as KService can provide a cast * operator to allow them to be used as arguments to KPluginLoader. * Unless you are implementing such a cast operator, you should never * need to use this class directly. */ // NOTE: this class is all inline, as it mainly exists for typing reasons // (ie: to prevent the issues that would be caused by adding an // operator QString() method to KService) class KCOREADDONS_EXPORT KPluginName { public: /** * Construct a (valid) plugin name from a string. * * If there was an error and the name could not be determined, * fromErrorString() should be used instead to construct an * appropriate error message. * * @param name The name of the plugin; this should not be empty. */ inline explicit KPluginName(const QString &name); /** * The name of the plugin. * * @returns The string passed to the constructor if isValid() is * @c true, QString() otherwise. */ inline QString name() const; /** * Whether the name is valid. * * Note that this only determines how the KPluginName was * constructed, not anything about the value of the string. * * @returns @c true if the KPluginName(const QString&) constructor * was used, @c false if fromErrorString() was used. */ inline bool isValid() const; /** * The error string if no name could be determined. * * @returns The string passed to fromErrorString() if isValid() is * @c false, QString() otherwise. */ inline QString errorString() const; /** * Construct an invalid plugin name with an error message. * * When this object is passed to KPluginLoader, @p errorString * will be used for KPluginLoader::errorString(). * * @param errorString The (translated) error string. */ static inline KPluginName fromErrorString(const QString &errorString); private: inline KPluginName(const QString &name, bool isError); QString m_name; bool m_isError; }; inline KPluginName::KPluginName(const QString &name) : m_name(name), m_isError(false) {} inline KPluginName::KPluginName(const QString &name, bool isError) : m_name(name), m_isError(isError) {} inline QString KPluginName::name() const { return m_isError ? QString() : m_name; } inline bool KPluginName::isValid() const { return !m_isError; } inline QString KPluginName::errorString() const { return m_isError ? m_name : QString(); } inline KPluginName KPluginName::fromErrorString(const QString &errorString) { return KPluginName(errorString, true); } #endif diff --git a/src/lib/plugin/kpluginmetadata.cpp b/src/lib/plugin/kpluginmetadata.cpp index 50291e6..612d6de 100644 --- a/src/lib/plugin/kpluginmetadata.cpp +++ b/src/lib/plugin/kpluginmetadata.cpp @@ -1,401 +1,391 @@ -/* This file is part of the KDE project - Copyright (C) 2014 Alex Richardson - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.0-only */ #include "kpluginmetadata.h" #include "desktopfileparser_p.h" #include #include #include #include #include #include #include #include "kcoreaddons_debug.h" #include "kpluginloader.h" #include "kaboutdata.h" class KPluginMetaDataPrivate : public QSharedData { public: QString metaDataFileName; }; KPluginMetaData::KPluginMetaData() { } KPluginMetaData::KPluginMetaData(const KPluginMetaData &other) : m_metaData(other.m_metaData), m_fileName(other.fileName()), d(other.d) { } KPluginMetaData &KPluginMetaData::operator=(const KPluginMetaData &other) { m_metaData = other.m_metaData; m_fileName = other.m_fileName; d = other.d; return *this; } KPluginMetaData::~KPluginMetaData() { } KPluginMetaData::KPluginMetaData(const QString &file) { if (file.endsWith(QLatin1String(".desktop"))) { loadFromDesktopFile(file, QStringList()); } else if (file.endsWith(QLatin1String(".json"))) { d = new KPluginMetaDataPrivate; QFile f(file); bool b = f.open(QIODevice::ReadOnly); if (!b) { qCWarning(KCOREADDONS_DEBUG) << "Couldn't open" << file; return; } QJsonParseError error; m_metaData = QJsonDocument::fromJson(f.readAll(), &error).object(); if (error.error) { qCWarning(KCOREADDONS_DEBUG) << "error parsing" << file << error.errorString(); } m_fileName = file; d->metaDataFileName = file; } else { QPluginLoader loader(file); m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } } KPluginMetaData::KPluginMetaData(const QPluginLoader &loader) { m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } KPluginMetaData::KPluginMetaData(const KPluginLoader &loader) { m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } KPluginMetaData::KPluginMetaData(const QJsonObject &metaData, const QString &file) { m_fileName = file; m_metaData = metaData; } KPluginMetaData::KPluginMetaData(const QJsonObject &metaData, const QString &pluginFile, const QString &metaDataFile) { m_fileName = pluginFile; m_metaData = metaData; if (!metaDataFile.isEmpty()) { d = new KPluginMetaDataPrivate; d->metaDataFileName = metaDataFile; } } KPluginMetaData KPluginMetaData::fromDesktopFile(const QString &file, const QStringList &serviceTypes) { KPluginMetaData result; result.loadFromDesktopFile(file, serviceTypes); return result; } void KPluginMetaData::loadFromDesktopFile(const QString &file, const QStringList &serviceTypes) { QString libraryPath; if (!DesktopFileParser::convert(file, serviceTypes, m_metaData, &libraryPath)) { Q_ASSERT(!isValid()); return; // file could not be parsed for some reason, leave this object invalid } d = new KPluginMetaDataPrivate; d->metaDataFileName = QFileInfo(file).absoluteFilePath(); if (!libraryPath.isEmpty()) { // this was a plugin with a shared library m_fileName = libraryPath; } else { // no library, make filename point to the .desktop file m_fileName = d->metaDataFileName; } } QJsonObject KPluginMetaData::rawData() const { return m_metaData; } QString KPluginMetaData::fileName() const { return m_fileName; } QString KPluginMetaData::metaDataFileName() const { return d ? d->metaDataFileName : m_fileName; } bool KPluginMetaData::isValid() const { // it can be valid even if m_fileName is empty (as long as the plugin id is set in the metadata) return !pluginId().isEmpty() && !m_metaData.isEmpty(); } bool KPluginMetaData::isHidden() const { return rootObject()[QStringLiteral("Hidden")].toBool(); } QJsonObject KPluginMetaData::rootObject() const { return m_metaData[QStringLiteral("KPlugin")].toObject(); } QStringList KPluginMetaData::readStringList(const QJsonObject &obj, const QString &key) { const QJsonValue value = obj.value(key); if (value.isUndefined() || value.isObject() || value.isNull()) { return QStringList(); } else if (value.isArray()) { return value.toVariant().toStringList(); } else { QString asString = value.isString() ? value.toString() : value.toVariant().toString(); if (asString.isEmpty()) { return QStringList(); } const QString id = obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Id")).toString(); qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a string list." " Treating it as a list with a single entry:" << asString << id.toLatin1().constData(); return QStringList(asString); } } QJsonValue KPluginMetaData::readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue) { QString languageWithCountry = QLocale().name(); auto it = jo.constFind(key + QLatin1Char('[') + languageWithCountry + QLatin1Char(']')); if (it != jo.constEnd()) { return it.value(); } const QStringRef language = languageWithCountry.midRef(0, languageWithCountry.indexOf(QLatin1Char('_'))); it = jo.constFind(key + QLatin1Char('[') + language + QLatin1Char(']')); if (it != jo.constEnd()) { return it.value(); } // no translated value found -> check key it = jo.constFind(key); if (it != jo.constEnd()) { return jo.value(key); } return defaultValue; } QString KPluginMetaData::readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue) { return readTranslatedValue(jo, key, defaultValue).toString(defaultValue); } static inline void addPersonFromJson(const QJsonObject &obj, QList* out) { KAboutPerson person = KAboutPerson::fromJSON(obj); if (person.name().isEmpty()) { qCWarning(KCOREADDONS_DEBUG) << "Invalid plugin metadata: Attempting to create a KAboutPerson from json without 'Name' property:" << obj; return; } out->append(person); } static QList aboutPersonFromJSON(const QJsonValue &people) { QList ret; if (people.isObject()) { // single author addPersonFromJson(people.toObject(), &ret); } else if (people.isArray()) { const QJsonArray peopleArray = people.toArray(); for (const QJsonValue &val : peopleArray) { if (val.isObject()) { addPersonFromJson(val.toObject(), &ret); } } } return ret; } QList KPluginMetaData::authors() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("Authors")]); } QList KPluginMetaData::translators() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("Translators")]); } QList KPluginMetaData::otherContributors() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("OtherContributors")]); } QString KPluginMetaData::category() const { return rootObject()[QStringLiteral("Category")].toString(); } QString KPluginMetaData::description() const { return readTranslatedString(rootObject(), QStringLiteral("Description")); } QString KPluginMetaData::iconName() const { return rootObject()[QStringLiteral("Icon")].toString(); } QString KPluginMetaData::license() const { return rootObject()[QStringLiteral("License")].toString(); } QString KPluginMetaData::name() const { return readTranslatedString(rootObject(), QStringLiteral("Name")); } QString KPluginMetaData::copyrightText() const { return readTranslatedString(rootObject(), QStringLiteral("Copyright")); } QString KPluginMetaData::extraInformation() const { return readTranslatedString(rootObject(), QStringLiteral("ExtraInformation")); } QString KPluginMetaData::pluginId() const { QJsonObject root = rootObject(); auto nameFromMetaData = root.constFind(QStringLiteral("Id")); if (nameFromMetaData != root.constEnd()) { const QString id = nameFromMetaData.value().toString(); if (!id.isEmpty()) { return id; } } // passing QFileInfo an empty string gives the CWD, which is not what we want if (m_fileName.isEmpty()) { return QString(); } return QFileInfo(m_fileName).baseName(); } QString KPluginMetaData::version() const { return rootObject()[QStringLiteral("Version")].toString(); } QString KPluginMetaData::website() const { return rootObject()[QStringLiteral("Website")].toString(); } QStringList KPluginMetaData::dependencies() const { return readStringList(rootObject(), QStringLiteral("Dependencies")); } QStringList KPluginMetaData::serviceTypes() const { return readStringList(rootObject(), QStringLiteral("ServiceTypes")); } QStringList KPluginMetaData::mimeTypes() const { return readStringList(rootObject(), QStringLiteral("MimeTypes")); } bool KPluginMetaData::supportsMimeType(const QString &mimeType) const { QMimeDatabase db; const QMimeType mime = db.mimeTypeForName(mimeType); if (!mime.isValid()) { return false; } const QStringList mimes = mimeTypes(); auto inherits = [&](const QString &supportedMimeName) { return mime.inherits(supportedMimeName); }; return std::find_if(mimes.begin(), mimes.end(), inherits) != mimes.end(); } QStringList KPluginMetaData::formFactors() const { return readStringList(rootObject(), QStringLiteral("FormFactors")); } bool KPluginMetaData::isEnabledByDefault() const { QJsonValue val = rootObject()[QStringLiteral("EnabledByDefault")]; if (val.isBool()) { return val.toBool(); } else if (val.isString()) { return val.toString() == QLatin1String("true"); } return false; } int KPluginMetaData::initialPreference() const { return rootObject()[QStringLiteral("InitialPreference")].toInt(); } QString KPluginMetaData::value(const QString &key, const QString &defaultValue) const { const QJsonValue value = m_metaData.value(key); if (value.isString()) { return value.toString(defaultValue); } else if (value.isArray()) { qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a single string." " but it is a stringlist"; const QStringList list = value.toVariant().toStringList(); if (list.isEmpty()) { return defaultValue; } return list.join(QChar::fromLatin1(',')); } else if (value.isBool()) { qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a single string." " but it is a bool"; return value.toBool() ? QStringLiteral("true") : QStringLiteral("false"); } return defaultValue; } bool KPluginMetaData::operator==(const KPluginMetaData &other) const { return m_fileName == other.m_fileName && m_metaData == other.m_metaData; } QObject* KPluginMetaData::instantiate() const { return QPluginLoader(m_fileName).instance(); } diff --git a/src/lib/plugin/kpluginmetadata.h b/src/lib/plugin/kpluginmetadata.h index dc0506d..f4fbc7f 100644 --- a/src/lib/plugin/kpluginmetadata.h +++ b/src/lib/plugin/kpluginmetadata.h @@ -1,430 +1,420 @@ -/* This file is part of the KDE project - Copyright (C) 2014 Alex Richardson - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE project + + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KPLUGINMETADATA_H #define KPLUGINMETADATA_H #include "kcoreaddons_export.h" #include #include #include #include #include #include class KPluginLoader; class QPluginLoader; class QStringList; class KPluginMetaDataPrivate; class KAboutPerson; class QObject; /** * @class KPluginMetaData kpluginmetadata.h KPluginMetaData * * This class allows easily accessing some standardized values from the JSON metadata that * can be embedded into Qt plugins. Additional plugin-specific metadata can be retrieved by * directly reading from the QJsonObject returned by KPluginMetaData::rawData(). * * This class can be used instead of KPluginInfo from KService for applications that only load * Qt C++ plugins. * * The following keys will be read from an object "KPlugin" inside the metadata JSON: * * Key | Accessor function | JSON Type * -------------------| -------------------- | --------------------- * Name | name() | string * Description | description() | string * ExtraInformation | extraInformation() | string * Icon | iconName() | string * Authors | authors() | object array (KAboutPerson) * Category | category() | string * License | license() | string * Copyright | copyrightText() | string * Id | pluginId() | string * Version | version() | string * Website | website() | string * EnabledByDefault | isEnabledByDefault() | bool * ServiceTypes | serviceTypes() | string array * MimeTypes | mimeTypes() | string array * FormFactors | formFactors() | string array * Translators | translators() | object array (KAboutPerson) * OtherContributors | otherContributors() | object array (KAboutPerson) * * The Authors, Translators and OtherContributors keys are expected to be * list of objects that match the structure expected by KAboutPerson::fromJSON(). * * An example metadata json file could look like this: * @verbatim { "KPlugin": { "Name": "Date and Time", "Description": "Date and time by timezone", "Icon": "preferences-system-time", "Authors": { "Name": "Aaron Seigo", "Email": "aseigo@kde.org" }, "Category": "Date and Time", "Dependencies": [], "EnabledByDefault": "true", "License": "LGPL", "Id": "time", "Version": "1.0", "Website": "https://plasma.kde.org/", "ServiceTypes": ["Plasma/DataEngine"] } } @endverbatim * * @sa KAboutPerson::fromJSON() * @since 5.1 */ class KCOREADDONS_EXPORT KPluginMetaData { public: /** Creates an invalid KPluginMetaData instance */ KPluginMetaData(); /** * Reads the plugin metadata from a KPluginLoader instance. You must call KPluginLoader::setFileName() * or use the appropriate constructor on @p loader before calling this. */ KPluginMetaData(const KPluginLoader &loader); /** * Reads the plugin metadata from a QPluginLoader instance. You must call QPluginLoader::setFileName() * or use the appropriate constructor on @p loader before calling this. */ KPluginMetaData(const QPluginLoader &loader); /** * Reads the plugin metadata from a plugin or .desktop which can be loaded from @p file. * * For plugins, platform-specific library suffixes may be omitted since @p file will be resolved * using the same logic as QPluginLoader. * * If the file name ends with ".desktop", the .desktop file will be parsed instead of * reading the metadata from the QPluginLoader. This is the same as calling * KPluginMetaData::fromDesktopFile() without the serviceTypes parameter. * * If @p file ends with .json, the file will be loaded as the QJsonObject metadata. * * @see QPluginLoader::setFileName() * @see KPluginMetaData::fromDesktopFile() */ KPluginMetaData(const QString &file); /** * Creates a KPluginMetaData from a QJsonObject holding the metadata and a file name * This can be used if the data is not retrieved from a Qt C++ plugin library but from some * other source. * @see KPluginMetaData(const QJsonObject &, const QString &, const QString &) */ KPluginMetaData(const QJsonObject &metaData, const QString &file); // TODO: KF6: merge with the above and make metaDataFile default to QString() /** * Creates a KPluginMetaData * @param metaData the JSON metadata to use for this object * @param pluginFile the file that the plugin can be loaded from * @param metaDataFile the file that the JSON metadata was read from * * This can be used if the data is not retrieved from a Qt C++ plugin library but from some * other source. * * @since 5.5 */ KPluginMetaData(const QJsonObject &metaData, const QString &pluginFile, const QString &metaDataFile); /** * Copy contructor */ KPluginMetaData(const KPluginMetaData &); /** * Copy assignment */ KPluginMetaData &operator=(const KPluginMetaData &); /** * Destructor */ ~KPluginMetaData(); /** * Load a KPluginMetaData instace from a .desktop file. Unlike the constructor which takes * a single file parameter this method allows you to specify which service type files should * be parsed to determine the correct type for a given .desktop property. * This ensures that a e.g. comma-separated string list field in the .desktop file will correctly * be converted to a JSON string array. * * @note This function mostly exists for backwards-compatibility. It is recommended * that new applications load JSON files directly instead of using .desktop files for plugin metadata. * * @param file the .desktop file to load * @param serviceTypes a list of files to parse If one of these paths is a relative path it * will be resolved relative to the "kservicetypes5" subdirectory in QStandardPaths::GenericDataLocation. * If the list is empty only the default set of properties will be treated specially and all other entries * will be read as the JSON string type. * * @since 5.16 */ static KPluginMetaData fromDesktopFile(const QString &file, const QStringList &serviceTypes = QStringList()); /** * @return whether this object holds valid information about a plugin. * If this is @c true pluginId() will return a non-empty string. */ bool isValid() const; /** * @return whether this object should be hidden, this is usually not used for binary * plugins, when loading a KPluginMetaData from a .desktop file, this will reflect * the value of the "Hidden" key. * * @since 5.8 */ bool isHidden() const; /** * @return the path to the plugin. This string can be passed to the KPluginLoader * or QPluginLoader constructors in order to attempt to load this plugin. * @note It is not guaranteed that this is a valid path to a shared library (i.e. loadable * by QPluginLoader) since the metadata could also refer to a non-C++ plugin. */ QString fileName() const; /** * @return the file that the metadata was read from. This is not necessarily the same as * fileName(), since not all plugins have the metadata embedded. The metadata could also be * stored in a separate .desktop file. * * @since 5.5 */ QString metaDataFileName() const; /** * @return the full metadata stored inside the plugin file. */ QJsonObject rawData() const; /** * Tries to instantiate this plugin using KPluginMetaData::fileName(). * @note The value of KPluginMetaData::dependencies() is not used here, dependencies must be * resolved manually. * * @return The plugin root object or @c nullptr if it could not be loaded * @see QPluginLoader::instance(), KPluginLoader::instance() */ QObject *instantiate() const; /** * @return the user visible name of the plugin. */ QString name() const; /** * @return a short description of the plugin. */ QString description() const; /** * @return additional information about this plugin (e.g. for use in an "about plugin" dialog) * * @since 5.18 */ QString extraInformation() const; /** * @return the author(s) of this plugin. */ QList authors() const; /** * @return the translator(s) of this plugin. * * @since 5.18 */ QList translators() const; /** * @return a list of people that contributed to this plugin (other than the authors and translators). * * @since 5.18 */ QList otherContributors() const; /** * @return the categories of this plugin (e.g. "playlist/skin"). */ QString category() const; /** * @return the icon name for this plugin * @see QIcon::fromTheme() */ QString iconName() const; /** * @return the short license identifier (e.g. LGPL). * @see KAboutLicense::byKeyword() for retrieving the full license information */ QString license() const; /** * @return a short copyright statement * * @since 5.18 */ QString copyrightText() const; /** * @return the internal name of the plugin * If the Id property is not set in the metadata, this will return the * plugin file name without the file extension. */ QString pluginId() const; /** * @return the version of the plugin. */ QString version() const; /** * @return the website of the plugin. */ QString website() const; /** * @return a list of plugins that this plugin depends on so that it can function properly * @see KJsonPluginInfo::pluginId() */ QStringList dependencies() const; /** * Returns the service types that this plugin implements. * * This is mostly for historical / compatibility purposes. * As a general rule, instead of opening many plugins to then filter by servicetype, * put all plugins of the same type in a subdirectory, that you can pass to findPlugins directly. * No point in opening 20 plugins to pick out only 3, when the filesystem can do that filtering for you. * * @note Unlike KService this does not contain the MIME types. To get the handled MIME types * use the KPluginMetaData::mimeTypes() function. * @return a list of service types this plugin implements (e.g. "Plasma/DataEngine") */ QStringList serviceTypes() const; /** * @return a list of MIME types this plugin can handle (e.g. "application/pdf", "image/png", etc.) * @since 5.16 */ QStringList mimeTypes() const; /** * @return true if this plugin can handle the given mimetype * This is more accurate than mimeTypes().contains(mimeType) because it also * takes MIME type inheritance into account. * @since 5.66 */ bool supportsMimeType(const QString &mimeType) const; /** * @return A string list of formfactors this plugin is useful for, e.g. desktop, tablet, * handset, mediacenter, etc. * The keys for this are not formally defined. * * @since 5.12 */ QStringList formFactors() const; /** * @return whether the plugin should be enabled by default. * This is only a recommendation, applications can ignore this value if they want to. */ bool isEnabledByDefault() const; /** * @return the initial preference of the plugin. * This is the preference to associate with this plugin initially (before * the user has had any chance to define preferences for it). * Higher values indicate stronger preference. * @since 5.67 */ int initialPreference() const; /** * @return the value for @p key from the metadata or @p defaultValue if the key does not exist * or the value for @p key is not of type string * * @see KPluginMetaData::rawData() if QString is not the correct type for @p key */ QString value(const QString &key, const QString &defaultValue = QString()) const; /** @return the value for @p key inside @p jo as a string list. If the type of @p key is string, a list with containing * just that string will be returned, if it is an array the list will contain one entry for each array member. * If the key cannot be found an empty list will be returned. */ static QStringList readStringList(const QJsonObject &jo, const QString &key); /** * Reads a value from @p jo but unlike QJsonObject::value() it allows different entries for each locale * This is done by appending the locale identifier in brackets to the key (e.g. "[de_DE]" or "[es]") * When looking for a key "foo" with German (Germany) locale we will first attempt to read "foo[de_DE]", * if that does not exist "foo[de]", finally falling back to "foo" if that also doesn't exist. * @return the translated value for @p key from @p jo or @p defaultValue if @p key was not found */ static QJsonValue readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue = QJsonValue()); /** * @return the translated value of @p key from @p jo as a string or @p defaultValue if @p key was not found * or the value for @p key is not of type string * @see KPluginMetaData::readTranslatedValue(const QJsonObject &jo, const QString &key) */ static QString readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue = QString()); /** * @return @c true if this object is equal to @p other, otherwise @c false */ bool operator==(const KPluginMetaData &other) const; /** * @return @c true if this object is not equal to @p other, otherwise @c false. */ inline bool operator!=(const KPluginMetaData &other) const { return !(*this == other); } private: QJsonObject rootObject() const; void loadFromDesktopFile(const QString &file, const QStringList &serviceTypes); private: QJsonObject m_metaData; QString m_fileName; QExplicitlySharedDataPointer d; // for future binary compatible extensions }; inline uint qHash(const KPluginMetaData &md, uint seed) { return qHash(md.pluginId(), seed); } Q_DECLARE_METATYPE(KPluginMetaData) #endif // KPLUGINMETADATA_H diff --git a/src/lib/randomness/krandom.cpp b/src/lib/randomness/krandom.cpp index 85cb5a3..231f25b 100644 --- a/src/lib/randomness/krandom.cpp +++ b/src/lib/randomness/krandom.cpp @@ -1,80 +1,69 @@ -/* This file is part of the KDE libraries - Copyright (c) 1999 Matthias Kalle Dalheimer - Copyright (c) 2000 Charles Samuels - Copyright (c) 2005 Joseph Wenninger +/* + This file is part of the KDE libraries - This library 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 2 of the License, or (at your option) any later version. + SPDX-FileCopyrightText: 1999 Matthias Kalle Dalheimer + SPDX-FileCopyrightText: 2000 Charles Samuels + SPDX-FileCopyrightText: 2005 Joseph Wenninger - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. - */ + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "krandom.h" #include #ifdef Q_OS_WIN #include #else // Q_OS_WIN #include #endif // Q_OS_WIN #include #include #ifndef Q_OS_WIN #include #endif // Q_OS_WIN #include #include #include #include int KRandom::random() { static QThreadStorage initialized_threads; if (!initialized_threads.localData()) { unsigned int seed; initialized_threads.setLocalData(true); QFile urandom(QStringLiteral("/dev/urandom")); bool opened = urandom.open(QIODevice::ReadOnly | QIODevice::Unbuffered); if (!opened || urandom.read(reinterpret_cast(&seed), sizeof(seed)) != sizeof(seed)) { // No /dev/urandom... try something else. qsrand(getpid()); seed = qrand() ^ time(nullptr) ^ reinterpret_cast(QThread::currentThread()); } qsrand(seed); } return qrand(); } QString KRandom::randomString(int length) { if (length <= 0) { return QString(); } QString str; str.resize(length); int i = 0; while (length--) { int r = random() % 62; r += 48; if (r > 57) { r += 7; } if (r > 90) { r += 6; } str[i++] = char(r); // so what if I work backwards? } return str; } diff --git a/src/lib/randomness/krandom.h b/src/lib/randomness/krandom.h index 656495d..65f5166 100644 --- a/src/lib/randomness/krandom.h +++ b/src/lib/randomness/krandom.h @@ -1,56 +1,45 @@ -/* This file is part of the KDE libraries - Copyright (c) 1999 Matthias Kalle Dalheimer - Copyright (c) 2000 Charles Samuels - Copyright (c) 2005 Joseph Wenninger - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Matthias Kalle Dalheimer + SPDX-FileCopyrightText: 2000 Charles Samuels + SPDX-FileCopyrightText: 2005 Joseph Wenninger + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KRANDOM_H #define KRANDOM_H #include #include /** * \headerfile krandom.h * * @short Helper class to create random data * * This namespace provides methods which generate random data. * KRandom is not recommended for serious random-number generation needs, * like cryptography. */ namespace KRandom { /** * Generates a uniform random number. * @return A random number in the range [0, RAND_MAX). The RNG is seeded * on first use. */ KCOREADDONS_EXPORT int random(); /** * Generates a random string. It operates in the range [A-Za-z0-9] * @param length Generate a string of this length. * @return the random string */ KCOREADDONS_EXPORT QString randomString(int length); } #endif diff --git a/src/lib/randomness/krandomsequence.cpp b/src/lib/randomness/krandomsequence.cpp index de8293e..3d82414 100644 --- a/src/lib/randomness/krandomsequence.cpp +++ b/src/lib/randomness/krandomsequence.cpp @@ -1,217 +1,205 @@ /* - This file is part of the KDE libraries - Copyright (c) 1999 Sean Harmer - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Sean Harmer + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "krandomsequence.h" #include "krandom.h" class Q_DECL_HIDDEN KRandomSequence::Private { public: enum {SHUFFLE_TABLE_SIZE = 32}; void draw(); // Generate the random number int lngSeed1; int lngSeed2; int lngShufflePos; int shuffleArray[SHUFFLE_TABLE_SIZE]; }; ////////////////////////////////////////////////////////////////////////////// // Construction / Destruction ////////////////////////////////////////////////////////////////////////////// KRandomSequence::KRandomSequence(long lngSeed1) : d(new Private) { // Seed the generator setSeed(lngSeed1); } KRandomSequence::KRandomSequence(int lngSeed1) : d(new Private) { // Seed the generator setSeed(lngSeed1); } KRandomSequence::~KRandomSequence() { delete d; } KRandomSequence::KRandomSequence(const KRandomSequence &a) : d(new Private) { *d = *a.d; } KRandomSequence &KRandomSequence::operator=(const KRandomSequence &a) { if (this != &a) { *d = *a.d; } return *this; } ////////////////////////////////////////////////////////////////////////////// // Member Functions ////////////////////////////////////////////////////////////////////////////// void KRandomSequence::setSeed(long lngSeed1) { setSeed(static_cast(lngSeed1)); } void KRandomSequence::setSeed(int lngSeed1) { // Convert the positive seed number to a negative one so that the draw() // function can initialise itself the first time it is called. We just have // to make sure that the seed used != 0 as zero perpetuates itself in a // sequence of random numbers. if (lngSeed1 < 0) { d->lngSeed1 = -1; } else if (lngSeed1 == 0) { d->lngSeed1 = -((KRandom::random() & ~1) + 1); } else { d->lngSeed1 = -lngSeed1; } } static const int sMod1 = 2147483563; static const int sMod2 = 2147483399; void KRandomSequence::Private::draw() { static const int sMM1 = sMod1 - 1; static const int sA1 = 40014; static const int sA2 = 40692; static const int sQ1 = 53668; static const int sQ2 = 52774; static const int sR1 = 12211; static const int sR2 = 3791; static const int sDiv = 1 + sMM1 / SHUFFLE_TABLE_SIZE; // Long period (>2 * 10^18) random number generator of L'Ecuyer with // Bayes-Durham shuffle and added safeguards. Returns a uniform random // deviate between 0.0 and 1.0 (exclusive of the endpoint values). Call // with a negative number to initialize; thereafter, do not alter idum // between successive deviates in a sequence. RNMX should approximate // the largest floating point value that is less than 1. int j; // Index for the shuffle table int k; // Initialise if (lngSeed1 <= 0) { lngSeed2 = lngSeed1; // Load the shuffle table after 8 warm-ups for (j = SHUFFLE_TABLE_SIZE + 7; j >= 0; --j) { k = lngSeed1 / sQ1; lngSeed1 = sA1 * (lngSeed1 - k * sQ1) - k * sR1; if (lngSeed1 < 0) { lngSeed1 += sMod1; } if (j < SHUFFLE_TABLE_SIZE) { shuffleArray[j] = lngSeed1; } } lngShufflePos = shuffleArray[0]; } // Start here when not initializing // Compute lngSeed1 = ( lngIA1*lngSeed1 ) % lngIM1 without overflows // by Schrage's method k = lngSeed1 / sQ1; lngSeed1 = sA1 * (lngSeed1 - k * sQ1) - k * sR1; if (lngSeed1 < 0) { lngSeed1 += sMod1; } // Compute lngSeed2 = ( lngIA2*lngSeed2 ) % lngIM2 without overflows // by Schrage's method k = lngSeed2 / sQ2; lngSeed2 = sA2 * (lngSeed2 - k * sQ2) - k * sR2; if (lngSeed2 < 0) { lngSeed2 += sMod2; } j = lngShufflePos / sDiv; lngShufflePos = shuffleArray[j] - lngSeed2; shuffleArray[j] = lngSeed1; if (lngShufflePos < 1) { lngShufflePos += sMM1; } } void KRandomSequence::modulate(int i) { d->lngSeed2 -= i; if (d->lngSeed2 < 0) { d->lngShufflePos += sMod2; } d->draw(); d->lngSeed1 -= i; if (d->lngSeed1 < 0) { d->lngSeed1 += sMod1; } d->draw(); } double KRandomSequence::getDouble() { static const double finalAmp = 1.0 / double(sMod1); static const double epsilon = 1.2E-7; static const double maxRand = 1.0 - epsilon; double temp; d->draw(); // Return a value that is not one of the endpoints if ((temp = finalAmp * d->lngShufflePos) > maxRand) { // We don't want to return 1.0 return maxRand; } else { return temp; } } unsigned long KRandomSequence::getLong(unsigned long max) { return getInt(static_cast(max)); } unsigned int KRandomSequence::getInt(unsigned int max) { d->draw(); return max ? ((static_cast(d->lngShufflePos)) % max) : 0; } bool KRandomSequence::getBool() { d->draw(); return ((static_cast(d->lngShufflePos)) & 1); } diff --git a/src/lib/randomness/krandomsequence.h b/src/lib/randomness/krandomsequence.h index 3d374fe..36333f5 100644 --- a/src/lib/randomness/krandomsequence.h +++ b/src/lib/randomness/krandomsequence.h @@ -1,158 +1,149 @@ -/* This file is part of the KDE libraries - Copyright (c) 1999 Sean Harmer - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Sean Harmer + + SPDX-License-Identifier: LGPL-2.0-only */ + #ifndef K_RANDOM_SEQUENCE_H #define K_RANDOM_SEQUENCE_H #include #include /** * \class KRandomSequence krandomsequence.h * * A class to create a pseudo-random sequence * * Given a seed number, this class will produce a sequence of * pseudo-random numbers. This would typically be used in * applications like games. * * In general, you should instantiate a KRandomSequence object and * pass along your seed number in the constructor. From then on, * simply call getDouble or getLong to obtain the next * number in the sequence. * * @author Sean Harmer */ class KCOREADDONS_EXPORT KRandomSequence { public: /** * Creates a pseudo-random sequence based on the seed lngSeed. * * A Pseudo-random sequence is different for each seed but can be * reproduced by starting the sequence with the same seed. * * If you need a single value which needs to be unpredictable, * you need to use KRandom::random() instead. * * @param intSeed Seed to initialize the sequence with. * If lngSeed is 0, the sequence is initialized with a value from * KRandom::random(). * * Do not use methods working with long type because on 64-bit * their size is different. */ explicit KRandomSequence(int intSeed = 0); explicit KRandomSequence(long lngSeed); /** * Standard destructor */ virtual ~KRandomSequence(); /** * Copy constructor */ KRandomSequence(const KRandomSequence &a); /** * Assignment */ KRandomSequence &operator=(const KRandomSequence &a); /** * Restart the sequence based on lngSeed. * @param intSeed Seed to initialize the sequence with. * If lngSeed is 0, the sequence is initialized with a value from * KRandom::random(). */ void setSeed(int intSeed = 0); void setSeed(long lngSeed = 0); /** * Get the next number from the pseudo-random sequence. * * @return a pseudo-random double value between [0,1) */ double getDouble(); /** * Get the next number from the pseudo-random sequence. * * @return a pseudo-random integer value between [0, max) * with 0 <= max < 1.000.000 */ unsigned int getInt(unsigned int max); unsigned long getLong(unsigned long max); /** * Get a boolean from the pseudo-random sequence. * * @return a boolean which is either true or false */ bool getBool(); /** * Put a list in random order. * * Since kdelibs 4.11, this function uses a more efficient * algorithm (Fisher-Yates). Therefore, the order of the items in * the randomized list is different from the one in earlier * versions if the same seed value is used for the random * sequence. * * @param list the list whose order will be modified * @note modifies the list in place */ template void randomize(QList &list) { // Fisher-Yates algorithm for (int index = list.count() - 1; index > 0; --index) { const int swapIndex = getInt(index + 1); qSwap(list[index], list[swapIndex]); } } /** * Modulate the random sequence. * * If S(i) is the sequence of numbers that will follow * given the current state after calling modulate(i), * then S(i) != S(j) for i != j and * S(i) == S(j) for i == j. * * This can be useful in game situation where "undo" restores * the state of the random sequence. If the game modulates the * random sequence with the move chosen by the player, the * random sequence will be identical whenever the player "redo"-s * his or hers original move, but different when the player * chooses another move. * * With this scenario "undo" can no longer be used to repeat a * certain move over and over again until the computer reacts * with a favorable response or to predict the response for a * certain move based on the response to another move. * @param i the sequence identified */ void modulate(int i); private: class Private; Private *const d; }; #endif diff --git a/src/lib/text/kmacroexpander.cpp b/src/lib/text/kmacroexpander.cpp index 5c5be92..81356e0 100644 --- a/src/lib/text/kmacroexpander.cpp +++ b/src/lib/text/kmacroexpander.cpp @@ -1,405 +1,392 @@ /* This file is part of the KDE libraries - Copyright (c) 2002-2003 Oswald Buddenhagen - Copyright (c) 2003 Waldo Bastian - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003 Waldo Bastian + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kmacroexpander_p.h" #include #include KMacroExpanderBase::KMacroExpanderBase(QChar c) : d(new KMacroExpanderBasePrivate(c)) { } KMacroExpanderBase::~KMacroExpanderBase() { delete d; } void KMacroExpanderBase::setEscapeChar(QChar c) { d->escapechar = c; } QChar KMacroExpanderBase::escapeChar() const { return d->escapechar; } void KMacroExpanderBase::expandMacros(QString &str) { int pos; int len; ushort ec = d->escapechar.unicode(); QStringList rst; QString rsts; for (pos = 0; pos < str.length();) { if (ec != 0) { if (str.unicode()[pos].unicode() != ec) { goto nohit; } if (!(len = expandEscapedMacro(str, pos, rst))) { goto nohit; } } else { if (!(len = expandPlainMacro(str, pos, rst))) { goto nohit; } } if (len < 0) { pos -= len; continue; } rsts = rst.join(QLatin1Char(' ')); rst.clear(); str.replace(pos, len, rsts); pos += rsts.length(); continue; nohit: pos++; } } bool KMacroExpanderBase::expandMacrosShellQuote(QString &str) { int pos = 0; return expandMacrosShellQuote(str, pos) && pos == str.length(); } int KMacroExpanderBase::expandPlainMacro(const QString &, int, QStringList &) { qFatal("KMacroExpanderBase::expandPlainMacro called!"); return 0; } int KMacroExpanderBase::expandEscapedMacro(const QString &, int, QStringList &) { qFatal("KMacroExpanderBase::expandEscapedMacro called!"); return 0; } ////////////////////////////////////////////////// template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; private: QHash macromap; }; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) static QStringList &operator+=(QStringList &s, const QString &n) { s << n; return s; } #endif //////// static bool isIdentifier(ushort c) { return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } //////// template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; private: QHash macromap; }; template int KMacroMapExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { typename QHash::const_iterator it = macromap.constFind(str.unicode()[pos]); if (it != macromap.constEnd()) { ret += it.value(); return 1; } return 0; } template int KMacroMapExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } typename QHash::const_iterator it = macromap.constFind(str.unicode()[pos + 1]); if (it != macromap.constEnd()) { ret += it.value(); return 2; } return 0; } template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; private: QHash macromap; }; template int KMacroMapExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) { return 0; } int sl; for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) ; if (!sl) { return 0; } typename QHash::const_iterator it = macromap.constFind(str.mid(pos, sl)); if (it != macromap.constEnd()) { ret += it.value(); return sl; } return 0; } template int KMacroMapExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } int sl, rsl, rpos; if (str.unicode()[pos + 1].unicode() == '{') { rpos = pos + 2; if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) { return 0; } sl -= rpos; rsl = sl + 3; } else { rpos = pos + 1; for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) ; rsl = sl + 1; } if (!sl) { return 0; } typename QHash::const_iterator it = macromap.constFind(str.mid(rpos, sl)); if (it != macromap.constEnd()) { ret += it.value(); return rsl; } return 0; } //////////// int KCharMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (expandMacro(str.unicode()[pos], ret)) { return 1; } return 0; } int KCharMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } if (expandMacro(str.unicode()[pos + 1], ret)) { return 2; } return 0; } int KWordMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) { return 0; } int sl; for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) ; if (!sl) { return 0; } if (expandMacro(str.mid(pos, sl), ret)) { return sl; } return 0; } int KWordMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } int sl, rsl, rpos; if (str.unicode()[pos + 1].unicode() == '{') { rpos = pos + 2; if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) { return 0; } sl -= rpos; rsl = sl + 3; } else { rpos = pos + 1; for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) ; rsl = sl + 1; } if (!sl) { return 0; } if (expandMacro(str.mid(rpos, sl), ret)) { return rsl; } return 0; } //////////// template inline QString TexpandMacros(const QString &ostr, const QHash &map, QChar c) { QString str(ostr); KMacroMapExpander kmx(map, c); kmx.expandMacros(str); return str; } template inline QString TexpandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { QString str(ostr); KMacroMapExpander kmx(map, c); if (!kmx.expandMacrosShellQuote(str)) { return QString(); } return str; } // public API namespace KMacroExpander { QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } } // namespace diff --git a/src/lib/text/kmacroexpander.h b/src/lib/text/kmacroexpander.h index 7801c12..cf1c7b6 100644 --- a/src/lib/text/kmacroexpander.h +++ b/src/lib/text/kmacroexpander.h @@ -1,412 +1,399 @@ /* This file is part of the KDE libraries - Copyright (c) 2002-2003 Oswald Buddenhagen - Copyright (c) 2003 Waldo Bastian + SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003 Waldo Bastian - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KMACROEXPANDER_H #define KMACROEXPANDER_H #include #include class QString; class QStringList; template class QHash; class KMacroExpanderBasePrivate; /** * \class KMacroExpanderBase kmacroexpander.h * * Abstract base class for the worker classes behind the KMacroExpander namespace * and the KCharMacroExpander and KWordMacroExpander classes. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KMacroExpanderBase(QChar c = QLatin1Char('%')); /** * Destructor. */ virtual ~KMacroExpanderBase(); /** * Perform safe macro expansion (substitution) on a string. * * @param str the string in which macros are expanded in-place */ void expandMacros(QString &str); // TODO: This documentation is relevant for end-users. Where to put it? /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. * *

*NIX notes

* * Explicitly supported shell constructs: * \ '' "" $'' $"" {} () $(()) ${} $() `` * * Implicitly supported shell constructs: * (()) * * Unsupported shell constructs that will cause problems: * Shortened "case $v in pat)" syntax. Use * "case $v in (pat)" instead. * * The rest of the shell (incl. bash) syntax is simply ignored, * as it is not expected to cause problems. * * Note that bash contains a bug which makes macro expansion within * double quoted substitutions ("${VAR:-%macro}") inherently * insecure. * * For security reasons, @em never put expandos in command line arguments * that are shell commands by themselves - * "sh -c 'foo \%f'" is taboo. * "file=\%f sh -c 'foo "$file"'" is OK. * *

Windows notes

* * All quoting syntax supported by KShell is supported here as well. * Additionally, command grouping via parentheses is recognized - note * however, that the parser is much stricter about unquoted parentheses * than cmd itself. * The rest of the cmd syntax is simply ignored, as it is not expected * to cause problems - do not use commands that embed other commands, * though - "for /f ..." is taboo. * * @param str the string in which macros are expanded in-place * @param pos the position inside the string at which parsing/substitution * should start, and upon exit where processing stopped * @return false if the string could not be parsed and therefore no safe * substitution was possible. Note that macros will have been processed * up to the point where the error occurred. An unmatched closing paren * or brace outside any shell construct is @em not an error (unlike in * the function below), but still prematurely terminates processing. */ bool expandMacrosShellQuote(QString &str, int &pos); /** * Same as above, but always starts at position 0, and unmatched closing * parens and braces are treated as errors. */ bool expandMacrosShellQuote(QString &str); /** * Set the macro escape character. * @param c escape char indicating start of macros, or QChar::null if none */ void setEscapeChar(QChar c); /** * Obtain the macro escape character. * @return escape char indicating start of macros, or QChar::null if none */ QChar escapeChar() const; protected: /** * This function is called for every single char within the string if * the escape char is QChar::null. It should determine whether the * string starting at @p pos within @p str is a valid macro and return * the substitution value for it if so. * @param str the input string * @param pos the offset within @p str * @param ret return value: the string to substitute for the macro * @return If greater than zero, the number of chars at @p pos in @p str * to substitute with @p ret (i.e., a valid macro was found). If less * than zero, subtract this value from @p pos (to skip a macro, i.e., * substitute it with itself). If zero, no macro starts at @p pos. */ virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret); /** * This function is called every time the escape char is found if it is * not QChar::null. It should determine whether the * string starting at @p pos witin @p str is a valid macro and return * the substitution value for it if so. * @param str the input string * @param pos the offset within @p str. Note that this is the position of * the occurrence of the escape char * @param ret return value: the string to substitute for the macro * @return If greater than zero, the number of chars at @p pos in @p str * to substitute with @p ret (i.e., a valid macro was found). If less * than zero, subtract this value from @p pos (to skip a macro, i.e., * substitute it with itself). If zero, scanning continues as if no * escape char was encountered at all. */ virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret); private: KMacroExpanderBasePrivate *const d; }; /** * \class KWordMacroExpander kmacroexpander.h * * Abstract base class for simple word macro substitutors. Use this instead of * the functions in the KMacroExpander namespace if speculatively pre-filling * the substitution map would be too expensive. * * A typical application: * * \code * class MyClass { * ... * private: * QString m_str; * ... * friend class MyExpander; * }; * * class MyExpander : public KWordMacroExpander { * public: * MyExpander( MyClass *_that ) : KWordMacroExpander(), that( _that ) {} * protected: * virtual bool expandMacro( const QString &str, QStringList &ret ); * private: * MyClass *that; * }; * * bool MyExpander::expandMacro( const QString &str, QStringList &ret ) * { * if (str == "macro") { * ret += complexOperation( that->m_str ); * return true; * } * return false; * } * * ... MyClass::...(...) * { * QString str; * ... * MyExpander mx( this ); * mx.expandMacrosShellQuote( str ); * ... * } * \endcode * * Alternatively MyClass could inherit from KWordMacroExpander directly. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KWordMacroExpander(QChar c = QLatin1Char('%')) : KMacroExpanderBase(c) {} protected: /** \internal Not to be called or reimplemented. */ int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; /** \internal Not to be called or reimplemented. */ int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; /** * Return substitution list @p ret for string macro @p str. * @param str the macro to expand * @param ret return variable reference. It is guaranteed to be empty * when expandMacro is entered. * @return @c true iff @p chr was a recognized macro name */ virtual bool expandMacro(const QString &str, QStringList &ret) = 0; }; /** * \class KCharMacroExpander kmacroexpander.h * * Abstract base class for single char macro substitutors. Use this instead of * the functions in the KMacroExpander namespace if speculatively pre-filling * the substitution map would be too expensive. * * See KWordMacroExpander for a sample application. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KCharMacroExpander(QChar c = QLatin1Char('%')) : KMacroExpanderBase(c) {} protected: /** \internal Not to be called or reimplemented. */ int expandPlainMacro(const QString &str, int pos, QStringList &ret) override; /** \internal Not to be called or reimplemented. */ int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override; /** * Return substitution list @p ret for single-character macro @p chr. * @param chr the macro to expand * @param ret return variable reference. It is guaranteed to be empty * when expandMacro is entered. * @return @c true iff @p chr was a recognized macro name */ virtual bool expandMacro(QChar chr, QStringList &ret) = 0; }; /** * A group of functions providing macro expansion (substitution) in strings, * optionally with quoting appropriate for shell execution. */ namespace KMacroExpander { /** * Perform safe macro expansion (substitution) on a string. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded * * \code * // Code example * QHash map; * map.insert('u', "/tmp/myfile.txt"); * map.insert('n', "My File"); * QString s = "%% Title: %u:%n"; * s = KMacroExpander::expandMacros(s, map); * // s is now "% Title: /tmp/myfile.txt:My File"; * \endcode */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded, or a null string * if a shell syntax error was detected in the command * * \code * // Code example * QHash map; * map.insert('u', "/tmp/myfile.txt"); * map.insert('n', "My File"); * QString s = "kedit --caption %n %u"; * s = KMacroExpander::expandMacrosShellQuote(s, map); * // s is now "kedit --caption 'My File' '/tmp/myfile.txt'"; * system(QFile::encodeName(s)); * \endcode */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * Macro names can consist of chars in the range [A-Za-z0-9_]; * use braces to delimit macros from following words starting * with these chars, or to use other chars for macro names. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded * * \code * // Code example * QHash map; * map.insert("url", "/tmp/myfile.txt"); * map.insert("name", "My File"); * QString s = "Title: %{url}-%name"; * s = KMacroExpander::expandMacros(s, map); * // s is now "Title: /tmp/myfile.txt-My File"; * \endcode */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. See KMacroExpanderBase::expandMacrosShellQuote() * for the exact semantics. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * Macro names can consist of chars in the range [A-Za-z0-9_]; * use braces to delimit macros from following words starting * with these chars, or to use other chars for macro names. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded, or a null string * if a shell syntax error was detected in the command * * \code * // Code example * QHash map; * map.insert("url", "/tmp/myfile.txt"); * map.insert("name", "My File"); * QString s = "kedit --caption %name %{url}"; * s = KMacroExpander::expandMacrosShellQuote(s, map); * // s is now "kedit --caption 'My File' '/tmp/myfile.txt'"; * system(QFile::encodeName(s)); * \endcode */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Same as above, except that the macros expand to string lists that * are simply join(" ")ed together. */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Same as above, except that the macros expand to string lists. * If the macro appears inside a quoted string, the list is simply * join(" ")ed together; otherwise every element expands to a separate * quoted string. */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); } #endif /* KMACROEXPANDER_H */ diff --git a/src/lib/text/kmacroexpander_p.h b/src/lib/text/kmacroexpander_p.h index 1ecc5eb..18a5016 100644 --- a/src/lib/text/kmacroexpander_p.h +++ b/src/lib/text/kmacroexpander_p.h @@ -1,34 +1,21 @@ /* This file is part of the KDE libraries - Copyright (c) 2002-2003,2007 Oswald Buddenhagen + SPDX-FileCopyrightText: 2002-2003, 2007 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KMACROEXPANDER_P_H #define KMACROEXPANDER_P_H #include "kmacroexpander.h" class KMacroExpanderBasePrivate { public: KMacroExpanderBasePrivate(QChar c) : escapechar(c) {} QChar escapechar; }; #endif diff --git a/src/lib/text/kmacroexpander_unix.cpp b/src/lib/text/kmacroexpander_unix.cpp index fe7c28d..5e424fc 100644 --- a/src/lib/text/kmacroexpander_unix.cpp +++ b/src/lib/text/kmacroexpander_unix.cpp @@ -1,260 +1,247 @@ /* This file is part of the KDE libraries - Copyright (c) 2002-2003 Oswald Buddenhagen - Copyright (c) 2003 Waldo Bastian + SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003 Waldo Bastian - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kmacroexpander_p.h" #include #include #include namespace KMacroExpander { enum Quoting { noquote, singlequote, doublequote, dollarquote, paren, subst, group, math }; typedef struct { Quoting current; bool dquote; } State; typedef struct { QString str; int pos; } Save; } using namespace KMacroExpander; #pragma message("TODO: Import these methods into Qt") inline static bool isSpecial(QChar cUnicode) { static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 }; // 0-32 \'"$`<>|;&(){}*?#!~[] uint c = cUnicode.unicode(); return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } static QString quoteArg(const QString &arg) { if (!arg.length()) { return QStringLiteral("''"); } for (int i = 0; i < arg.length(); i++) if (isSpecial(arg.unicode()[i])) { QChar q(QLatin1Char('\'')); return q + QString(arg).replace(q, QLatin1String("'\\''")) + q; } return arg; } static QString joinArgs(const QStringList &args) { QString ret; for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { if (!ret.isEmpty()) { ret.append(QLatin1Char(' ')); } ret.append(quoteArg(*it)); } return ret; } bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos) { int len; int pos2; ushort ec = d->escapechar.unicode(); State state = { noquote, false }; QStack sstack; QStack ostack; QStringList rst; QString rsts; while (pos < str.length()) { ushort cc = str.unicode()[pos].unicode(); if (ec != 0) { if (cc != ec) { goto nohit; } if (!(len = expandEscapedMacro(str, pos, rst))) { goto nohit; } } else { if (!(len = expandPlainMacro(str, pos, rst))) { goto nohit; } } if (len < 0) { pos -= len; continue; } if (state.dquote) { rsts = rst.join(QLatin1Char(' ')); rsts.replace(QRegularExpression(QStringLiteral("([$`\"\\\\])")), QStringLiteral("\\\\1")); } else if (state.current == dollarquote) { rsts = rst.join(QLatin1Char(' ')); rsts.replace(QRegularExpression(QStringLiteral("(['\\\\])")), QStringLiteral("\\\\1")); } else if (state.current == singlequote) { rsts = rst.join(QLatin1Char(' ')); rsts.replace(QLatin1Char('\''), QLatin1String("'\\''")); } else { if (rst.isEmpty()) { str.remove(pos, len); continue; } else { rsts = joinArgs(rst); } } rst.clear(); str.replace(pos, len, rsts); pos += rsts.length(); continue; nohit: if (state.current == singlequote) { if (cc == '\'') { state = sstack.pop(); } } else if (cc == '\\') { // always swallow the char -> prevent anomalies due to expansion pos += 2; continue; } else if (state.current == dollarquote) { if (cc == '\'') { state = sstack.pop(); } } else if (cc == '$') { cc = str.unicode()[++pos].unicode(); if (cc == '(') { sstack.push(state); if (str.unicode()[pos + 1].unicode() == '(') { Save sav = { str, pos + 2 }; ostack.push(sav); state.current = math; pos += 2; continue; } else { state.current = paren; state.dquote = false; } } else if (cc == '{') { sstack.push(state); state.current = subst; } else if (!state.dquote) { if (cc == '\'') { sstack.push(state); state.current = dollarquote; } else if (cc == '"') { sstack.push(state); state.current = doublequote; state.dquote = true; } } // always swallow the char -> prevent anomalies due to expansion } else if (cc == '`') { str.replace(pos, 1, QStringLiteral("$( ")); // add space -> avoid creating $(( pos2 = pos += 3; for (;;) { if (pos2 >= str.length()) { pos = pos2; return false; } cc = str.unicode()[pos2].unicode(); if (cc == '`') { break; } if (cc == '\\') { cc = str.unicode()[++pos2].unicode(); if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) { str.remove(pos2 - 1, 1); continue; } } pos2++; } str[pos2] = QLatin1Char(')'); sstack.push(state); state.current = paren; state.dquote = false; continue; } else if (state.current == doublequote) { if (cc == '"') { state = sstack.pop(); } } else if (cc == '\'') { if (!state.dquote) { sstack.push(state); state.current = singlequote; } } else if (cc == '"') { if (!state.dquote) { sstack.push(state); state.current = doublequote; state.dquote = true; } } else if (state.current == subst) { if (cc == '}') { state = sstack.pop(); } } else if (cc == ')') { if (state.current == math) { if (str.unicode()[pos + 1].unicode() == ')') { state = sstack.pop(); pos += 2; } else { // false hit: the $(( was a $( ( in fact // ash does not care, but bash does pos = ostack.top().pos; str = ostack.top().str; ostack.pop(); state.current = paren; state.dquote = false; sstack.push(state); } continue; } else if (state.current == paren) { state = sstack.pop(); } else { break; } } else if (cc == '}') { if (state.current == KMacroExpander::group) { state = sstack.pop(); } else { break; } } else if (cc == '(') { sstack.push(state); state.current = paren; } else if (cc == '{') { sstack.push(state); state.current = KMacroExpander::group; } pos++; } return sstack.empty(); } diff --git a/src/lib/text/kmacroexpander_win.cpp b/src/lib/text/kmacroexpander_win.cpp index 5d61423..fffce71 100644 --- a/src/lib/text/kmacroexpander_win.cpp +++ b/src/lib/text/kmacroexpander_win.cpp @@ -1,125 +1,112 @@ /* This file is part of the KDE libraries - Copyright (c) 2008 Oswald Buddenhagen + SPDX-FileCopyrightText: 2008 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kmacroexpander_p.h" #include "kshell_p.h" #include "kshell.h" #include #include bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos) { int len; int pos2; ushort uc; ushort ec = d->escapechar.unicode(); bool shellQuote = false; // shell is in quoted state bool crtQuote = false; // c runtime is in quoted state bool escaped = false; // previous char was a circumflex int bslashes = 0; // previous chars were backslashes int parens = 0; // parentheses nesting level QStringList rst; QString rsts; while (pos < str.length()) { ushort cc = str.unicode()[pos].unicode(); if (escaped) { // prevent anomalies due to expansion goto notcf; } if (ec != 0) { if (cc != ec) { goto nohit; } if (!(len = expandEscapedMacro(str, pos, rst))) { goto nohit; } } else { if (!(len = expandPlainMacro(str, pos, rst))) { goto nohit; } } if (len < 0) { pos -= len; continue; } if (shellQuote != crtQuote) { // Silly, isn't it? Ahoy to Redmond. return false; } if (shellQuote) { rsts = KShell::quoteArgInternal(rst.join(QLatin1Char(' ')), true); } else { if (rst.isEmpty()) { str.remove(pos, len); continue; } rsts = KShell::joinArgs(rst); } pos2 = 0; while (pos2 < rsts.length() && ((uc = rsts.unicode()[pos2].unicode()) == '\\' || uc == '^')) { pos2++; } if (pos2 < rsts.length() && rsts.unicode()[pos2].unicode() == '"') { QString bsl; bsl.reserve(bslashes); for (; bslashes; bslashes--) { bsl.append(QLatin1String("\\")); } rsts.prepend(bsl); } bslashes = 0; rst.clear(); str.replace(pos, len, rsts); pos += rsts.length(); continue; nohit: if (!escaped && !shellQuote && cc == '^') { escaped = true; } else { notcf: if (cc == '\\') { bslashes++; } else { if (cc == '"') { if (!escaped) { shellQuote = !shellQuote; } if (!(bslashes & 1)) { crtQuote = !crtQuote; } } else if (!shellQuote) { if (cc == '(') { parens++; } else if (cc == ')') if (--parens < 0) { break; } } bslashes = 0; } escaped = false; } pos++; } return true; } diff --git a/src/lib/text/kstringhandler.cpp b/src/lib/text/kstringhandler.cpp index 3641df9..10cdbcc 100644 --- a/src/lib/text/kstringhandler.cpp +++ b/src/lib/text/kstringhandler.cpp @@ -1,390 +1,379 @@ -/* This file is part of the KDE libraries - Copyright (C) 1999 Ian Zepp (icszepp@islc.net) - Copyright (C) 2006 by Dominic Battre - Copyright (C) 2006 by Martin Pool - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Ian Zepp + SPDX-FileCopyrightText: 2006 Dominic Battre + SPDX-FileCopyrightText: 2006 Martin Pool + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kstringhandler.h" #include // random() #include // for the word ranges #include #include #include #include // // Capitalization routines // QString KStringHandler::capwords(const QString &text) { if (text.isEmpty()) { return text; } const QString strippedText = text.trimmed(); const QString space = QString(QLatin1Char(' ')); const QStringList words = capwords(strippedText.split(space)); QString result = text; result.replace(strippedText, words.join(space)); return result; } QStringList KStringHandler::capwords(const QStringList &list) { QStringList tmp = list; for (QStringList::Iterator it = tmp.begin(); it != tmp.end(); ++it) { *it = (*it)[ 0 ].toUpper() + (*it).midRef(1); } return tmp; } QString KStringHandler::lsqueeze(const QString &str, int maxlen) { if (str.length() > maxlen) { int part = maxlen - 3; return QLatin1String("...") + str.rightRef(part); } else { return str; } } QString KStringHandler::csqueeze(const QString &str, int maxlen) { if (str.length() > maxlen && maxlen > 3) { const int part = (maxlen - 3) / 2; return str.leftRef(part) + QLatin1String("...") + str.rightRef(part); } else { return str; } } QString KStringHandler::rsqueeze(const QString &str, int maxlen) { if (str.length() > maxlen) { int part = maxlen - 3; return str.leftRef(part) + QLatin1String("..."); } else { return str; } } QStringList KStringHandler::perlSplit(const QString &sep, const QString &s, int max) { bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = s.indexOf(sep, searchStart); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + sep.length(); tokenStart = s.indexOf(sep, searchStart); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } QStringList KStringHandler::perlSplit(const QChar &sep, const QString &s, int max) { bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = s.indexOf(sep, searchStart); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + 1; tokenStart = s.indexOf(sep, searchStart); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 67) QStringList KStringHandler::perlSplit(const QRegExp &sep, const QString &s, int max) { // nothing to split if (s.isEmpty()) { return QStringList(); } bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = sep.indexIn(s, searchStart); int len = sep.matchedLength(); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + len; tokenStart = sep.indexIn(s, searchStart); len = sep.matchedLength(); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } #endif QStringList KStringHandler::perlSplit(const QRegularExpression &sep, const QString &s, int max) { // nothing to split if (s.isEmpty()) { return QStringList(); } bool ignoreMax = max == 0; QStringList list; int start = 0; QRegularExpressionMatchIterator iter = sep.globalMatch(s); QRegularExpressionMatch match; QString chunk; while (iter.hasNext() && (ignoreMax || list.count() < max - 1)) { match = iter.next(); chunk = s.mid(start, match.capturedStart() - start); if (!chunk.isEmpty()) { list.append(chunk); } start = match.capturedEnd(); } // catch the remainder chunk = s.mid(start, s.size() - start); if (!chunk.isEmpty()) { list.append(chunk); } return list; } QString KStringHandler::tagUrls(const QString &text) { QString richText(text); const QRegularExpression urlEx(QStringLiteral("(www\\.(?!\\.)|(fish|ftp|http|https)://[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$\\(\\)]+)")); // the reference \1 is going to be replaced by the matched url const QLatin1String regexBackRef(QLatin1String("\\1")); const QString anchor = QLatin1String("") + regexBackRef + QLatin1String(""); richText.replace(urlEx, anchor); return richText; } QString KStringHandler::obscure(const QString &str) { QString result; const QChar *unicode = str.unicode(); for (int i = 0; i < str.length(); ++i) // yes, no typo. can't encode ' ' or '!' because // they're the unicode BOM. stupid scrambling. stupid. result += (unicode[ i ].unicode() <= 0x21) ? unicode[ i ] : QChar(0x1001F - unicode[ i ].unicode()); return result; } bool KStringHandler::isUtf8(const char *buf) { int i, n; unsigned char c; bool gotone = false; if (!buf) { return true; // whatever, just don't crash } #define F 0 /* character never appears in text */ #define T 1 /* character appears in plain ASCII text */ #define I 2 /* character appears in ISO-8859 text */ #define X 3 /* character appears in non-ISO extended ASCII (Mac, IBM PC) */ static const unsigned char text_chars[256] = { /* BEL BS HT LF FF CR */ F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, /* 0x0X */ /* ESC */ F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, /* 0x1X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x2X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x3X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x4X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x5X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x6X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, /* 0x7X */ /* NEL */ X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, /* 0x8X */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 0x9X */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xaX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xbX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xcX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xdX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xeX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I /* 0xfX */ }; /* *ulen = 0; */ for (i = 0; (c = buf[i]); ++i) { if ((c & 0x80) == 0) { /* 0xxxxxxx is plain ASCII */ /* * Even if the whole file is valid UTF-8 sequences, * still reject it if it uses weird control characters. */ if (text_chars[c] != T) { return false; } } else if ((c & 0x40) == 0) { /* 10xxxxxx never 1st byte */ return false; } else { /* 11xxxxxx begins UTF-8 */ int following; if ((c & 0x20) == 0) { /* 110xxxxx */ following = 1; } else if ((c & 0x10) == 0) { /* 1110xxxx */ following = 2; } else if ((c & 0x08) == 0) { /* 11110xxx */ following = 3; } else if ((c & 0x04) == 0) { /* 111110xx */ following = 4; } else if ((c & 0x02) == 0) { /* 1111110x */ following = 5; } else { return false; } for (n = 0; n < following; ++n) { i++; if (!(c = buf[i])) { goto done; } if ((c & 0x80) == 0 || (c & 0x40)) { return false; } } gotone = true; } } done: return gotone; /* don't claim it's UTF-8 if it's all 7-bit */ } #undef F #undef T #undef I #undef X QString KStringHandler::from8Bit(const char *str) { if (!str) { return QString(); } if (!*str) { static const QLatin1String emptyString(""); return emptyString; } return KStringHandler::isUtf8(str) ? QString::fromUtf8(str) : QString::fromLocal8Bit(str); } QString KStringHandler::preProcessWrap(const QString &text) { const QChar zwsp(0x200b); QString result; result.reserve(text.length()); for (int i = 0; i < text.length(); i++) { const QChar c = text[i]; bool openingParens = (c == QLatin1Char('(') || c == QLatin1Char('{') || c == QLatin1Char('[')); bool singleQuote = (c == QLatin1Char('\'')); bool closingParens = (c == QLatin1Char(')') || c == QLatin1Char('}') || c == QLatin1Char(']')); bool breakAfter = (closingParens || c.isPunct() || c.isSymbol()); bool nextIsSpace = (i == (text.length() - 1) || text[i + 1].isSpace()); bool prevIsSpace = (i == 0 || text[i - 1].isSpace() || result[result.length() - 1] == zwsp); // Provide a breaking opportunity before opening parenthesis if (openingParens && !prevIsSpace) { result += zwsp; } // Provide a word joiner before the single quote if (singleQuote && !prevIsSpace) { result += QChar(0x2060); } result += c; if (breakAfter && !openingParens && !nextIsSpace && !singleQuote) { result += zwsp; } } return result; } int KStringHandler::logicalLength(const QString& text) { int length = 0; auto chrs = text.toUcs4(); for (auto chr : chrs) { auto script = QChar::script(chr); if (script == QChar::Script_Han || script == QChar::Script_Hangul || script == QChar::Script_Hiragana || script == QChar::Script_Katakana || script == QChar::Script_Yi || QChar::isHighSurrogate(chr)) { length += 2; } else { length += 1; } } return length; } diff --git a/src/lib/text/kstringhandler.h b/src/lib/text/kstringhandler.h index 6647f40..9aaba52 100644 --- a/src/lib/text/kstringhandler.h +++ b/src/lib/text/kstringhandler.h @@ -1,258 +1,248 @@ -/* This file is part of the KDE libraries - Copyright (C) 1999 Ian Zepp (icszepp@islc.net) - Copyright (C) 2000 Rik Hemsley (rikkus) - Copyright (C) 2006 by Dominic Battre - Copyright (C) 2006 by Martin Pool +/* + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 1999 Ian Zepp + SPDX-FileCopyrightText: 2000 Rik Hemsley (rikkus) + SPDX-FileCopyrightText: 2006 Dominic Battre + SPDX-FileCopyrightText: 2006 Martin Pool - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KSTRINGHANDLER_H #define KSTRINGHANDLER_H #include #include class QChar; class QRegExp; class QRegularExpression; class QString; class QStringList; /** * This namespace contains utility functions for handling strings. * * The functions here are intended to provide an easy way to * cut/slice/splice words inside sentences in whatever order desired. * While the main focus of KStringHandler is words (ie characters * separated by spaces/tabs), the two core functions here (split() * and join()) will allow you to use any character as a separator * This will make it easy to redefine what a 'word' means in the * future if needed. * * The function names and calling styles are based on python and mIRC's * scripting support. * * The ranges are a fairly powerful way of getting/stripping words from * a string. These ranges function, for the large part, as they would in * python. See the word(const QString&, int) and remword(const QString&, int) * functions for more detail. * * The methods here are completely stateless. All strings are cut * on the fly and returned as new qstrings/qstringlists. * * @short Namespace for manipulating words and sentences in strings * @author Ian Zepp * @see KShell */ namespace KStringHandler { /** Capitalizes each word in the string * "hello there" becomes "Hello There" (string) * @param text the text to capitalize * @return the resulting string */ KCOREADDONS_EXPORT QString capwords(const QString &text); /** Capitalizes each word in the list * [hello, there] becomes [Hello, There] (list) * @param list the list to capitalize * @return the resulting list */ KCOREADDONS_EXPORT QStringList capwords(const QStringList &list); /** Substitute characters at the beginning of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString lsqueeze(const QString &str, int maxlen = 40); /** Substitute characters at the middle of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen = 40); /** Substitute characters at the end of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString rsqueeze(const QString &str, int maxlen = 40); /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit("__", "some__string__for__you__here", 4) * QStringList contains: "some", "string", "for", "you__here" * \endcode * * @param sep is the string to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. */ KCOREADDONS_EXPORT QStringList perlSplit(const QString &sep, const QString &s, int max = 0); /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit(' ', "kparts reaches the parts other parts can't", 3) * QStringList contains: "kparts", "reaches", "the parts other parts can't" * \endcode * * @param sep is the character to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. */ KCOREADDONS_EXPORT QStringList perlSplit(const QChar &sep, const QString &s, int max = 0); #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 67) /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit(QRegExp("[! ]"), "Split me up ! I'm bored ! OK ?", 3) * QStringList contains: "Split", "me", "up ! I'm bored ! OK ?" * \endcode * * @param sep is the regular expression to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. * * @deprecated Since 5.67, use perlSplit(const QRegularExpression &sep, * const QString &s, int max = 0) instead. */ KCOREADDONS_DEPRECATED_VERSION(5, 67, "Use KStringHandler::perlSplit(const QRegularExpression &, const QString &, int)") KCOREADDONS_EXPORT QStringList perlSplit(const QRegExp &sep, const QString &s, int max = 0); #endif /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit(QRegularExpression("[! ]"), "Split me up ! I'm bored ! OK ?", 3) * QStringList contains: "Split", "me", "up ! I'm bored ! OK ?" * \endcode * * @param sep is the regular expression to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. * * @since 5.67 */ KCOREADDONS_EXPORT QStringList perlSplit(const QRegularExpression &sep, const QString &s, int max = 0); /** * This method auto-detects URLs in strings, and adds HTML markup to them * so that richtext or HTML-enabled widgets will display the URL correctly. * @param text the string which may contain URLs * @return the resulting text */ KCOREADDONS_EXPORT QString tagUrls(const QString &text); /** Obscure string by using a simple symmetric encryption. Applying the function to a string obscured by this function will result in the original string. The function can be used to obscure passwords stored to configuration files. Note that this won't give you any more security than preventing that the password is directly copied and pasted. @param str string to be obscured @return obscured string */ KCOREADDONS_EXPORT QString obscure(const QString &str); /** Guess whether a string is UTF8 encoded. @param str the string to check @return true if UTF8. If false, the string is probably in Local8Bit. */ KCOREADDONS_EXPORT bool isUtf8(const char *str); /** Construct QString from a c string, guessing whether it is UTF8- or Local8Bit-encoded. @param str the input string @return the (hopefully correctly guessed) QString representation of @p str @see KEncodingProber */ KCOREADDONS_EXPORT QString from8Bit(const char *str); /** Preprocesses the given string in order to provide additional line breaking opportunities for QTextLayout. This is done by inserting ZWSP (Zero-width space) characters in the string at points that wouldn't normally be considered word boundaries by QTextLayout, but where wrapping the text will produce good results. Examples of such points includes after punctuation signs, underscores and dashes, that aren't followed by spaces. @since 4.4 */ KCOREADDONS_EXPORT QString preProcessWrap(const QString &text); /** Returns the length that reflects the density of information in the text. In general the character from CJK languages are assigned with weight 2, while other Latin characters are assigned with 1. @since 5.41 */ KCOREADDONS_EXPORT int logicalLength(const QString &text); } #endif diff --git a/src/lib/text/ktexttohtml.cpp b/src/lib/text/ktexttohtml.cpp index 2c470f4..de43dce 100644 --- a/src/lib/text/ktexttohtml.cpp +++ b/src/lib/text/ktexttohtml.cpp @@ -1,607 +1,594 @@ /* - Copyright (c) 2002 Dave Corrie - Copyright (c) 2014 Daniel Vrátil - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2002 Dave Corrie + SPDX-FileCopyrightText: 2014 Daniel Vrátil + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "ktexttohtml.h" #include "ktexttohtml_p.h" #include "ktexttohtmlemoticonsinterface.h" #include #include #include #include #include #include #include #include "kcoreaddons_debug.h" static KTextToHTMLEmoticonsInterface *s_emoticonsInterface = nullptr; static void loadEmoticonsPlugin() { static bool triedLoadPlugin = false; if (!triedLoadPlugin) { triedLoadPlugin = true; // Check if QGuiApplication::platformName property exists. This is a // hackish way of determining whether we are running QGuiApplication, // because we cannot load the FrameworkIntegration plugin into a // QCoreApplication, as it would crash immediately if (qApp->metaObject()->indexOfProperty("platformName") > -1) { QPluginLoader lib(QStringLiteral("kf5/KEmoticonsIntegrationPlugin")); QObject *rootObj = lib.instance(); if (rootObj) { s_emoticonsInterface = rootObj->property(KTEXTTOHTMLEMOTICONS_PROPERTY).value(); } } } if (!s_emoticonsInterface) { s_emoticonsInterface = new KTextToHTMLEmoticonsDummy(); } } KTextToHTMLHelper::KTextToHTMLHelper(const QString &plainText, int pos, int maxUrlLen, int maxAddressLen) : mText(plainText) , mMaxUrlLen(maxUrlLen) , mMaxAddressLen(maxAddressLen) , mPos(pos) { } KTextToHTMLEmoticonsInterface* KTextToHTMLHelper::emoticonsInterface() const { if (!s_emoticonsInterface) { loadEmoticonsPlugin(); } return s_emoticonsInterface; } QString KTextToHTMLHelper::getEmailAddress() { QString address; if (mText[mPos] == QLatin1Char('@')) { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~"); // determine the local part of the email address int start = mPos - 1; while (start >= 0 && mText[start].unicode() < 128 && (mText[start].isLetterOrNumber() || mText[start] == QLatin1Char('@') || // allow @ to find invalid email addresses allowedSpecialChars.indexOf(mText[start]) != -1)) { if (mText[start] == QLatin1Char('@')) { return QString(); // local part contains '@' -> no email address } --start; } ++start; // we assume that an email address starts with a letter or a digit while ((start < mPos) && !mText[start].isLetterOrNumber()) { ++start; } if (start == mPos) { return QString(); // local part is empty -> no email address } // determine the domain part of the email address int dotPos = INT_MAX; int end = mPos + 1; while (end < mText.length() && (mText[end].isLetterOrNumber() || mText[end] == QLatin1Char('@') || // allow @ to find invalid email addresses mText[end] == QLatin1Char('.') || mText[end] == QLatin1Char('-'))) { if (mText[end] == QLatin1Char('@')) { return QString(); // domain part contains '@' -> no email address } if (mText[end] == QLatin1Char('.')) { dotPos = qMin(dotPos, end); // remember index of first dot in domain } ++end; } // we assume that an email address ends with a letter or a digit while ((end > mPos) && !mText[end - 1].isLetterOrNumber()) { --end; } if (end == mPos) { return QString(); // domain part is empty -> no email address } if (dotPos >= end) { return QString(); // domain part doesn't contain a dot } if (end - start > mMaxAddressLen) { return QString(); // too long -> most likely no email address } address = mText.mid(start, end - start); mPos = end - 1; } return address; } QString KTextToHTMLHelper::getPhoneNumber() { if (!mText[mPos].isDigit() && mText[mPos] != QLatin1Char('+')) { return {}; } const QString allowedBeginSeparators = QStringLiteral(" \r\t\n:"); if (mPos > 0 && !allowedBeginSeparators.contains(mText[mPos - 1])) { return {}; } // this isn't 100% accurate, we filter stuff below that is too hard to capture with a regexp static const QRegularExpression telPattern(QStringLiteral(R"([+0](( |( ?[/-] ?)?)\(?\d+\)?+){6,30})")); const auto match = telPattern.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); if (match.hasMatch()) { auto m = match.captured(); // check for maximum number of digits (15), see https://en.wikipedia.org/wiki/Telephone_numbering_plan if (std::count_if(m.begin(), m.end(), [](const QChar &c) { return c.isDigit(); }) > 15) { return {}; } // only one / is allowed, otherwise we trigger on dates if (std::count(m.begin(), m.end(), QLatin1Char('/')) > 1) { return {}; } // parenthesis need to be balanced, and must not be nested int openIdx = -1; for (int i = 0; i < m.size(); ++i) { if ((m[i] == QLatin1Char('(') && openIdx >= 0) || (m[i] == QLatin1Char(')') && openIdx < 0)) { return {}; } if (m[i] == QLatin1Char('(')) { openIdx = i; } else if (m[i] == QLatin1Char(')')) { openIdx = -1; } } if (openIdx > 0) { m = m.leftRef(openIdx - 1).trimmed().toString(); } // check if there's a plausible separator at the end const QString allowedEndSeparators = QStringLiteral(" \r\t\n,."); const auto l = m.size(); if (mText.size() > mPos + l && !allowedEndSeparators.contains(mText[mPos + l])) { return {}; } mPos += l - 1; return m; } return {}; } static QString normalizePhoneNumber(const QString &str) { QString res; res.reserve(str.size()); for (const auto c : str) { if (c.isDigit() || c == QLatin1Char('+')) { res.push_back(c); } } return res; } bool KTextToHTMLHelper::atUrl() const { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~"); // the character directly before the URL must not be a letter, a number or // any other character allowed in a dot-atom (RFC 2822). if ((mPos > 0) && (mText[mPos - 1].isLetterOrNumber() || (allowedSpecialChars.indexOf(mText[mPos - 1]) != -1))) { return false; } QChar ch = mText[mPos]; return (ch == QLatin1Char('h') && (mText.midRef(mPos, 7) == QLatin1String("http://") || mText.midRef(mPos, 8) == QLatin1String("https://"))) || (ch == QLatin1Char('v') && mText.midRef(mPos, 6) == QLatin1String("vnc://")) || (ch == QLatin1Char('f') && (mText.midRef(mPos, 7) == QLatin1String("fish://") || mText.midRef(mPos, 6) == QLatin1String("ftp://") || mText.midRef(mPos, 7) == QLatin1String("ftps://"))) || (ch == QLatin1Char('s') && (mText.midRef(mPos, 7) == QLatin1String("sftp://") || mText.midRef(mPos, 6) == QLatin1String("smb://"))) || (ch == QLatin1Char('m') && mText.midRef(mPos, 7) == QLatin1String("mailto:")) || (ch == QLatin1Char('w') && mText.midRef(mPos, 4) == QLatin1String("www.")) || (ch == QLatin1Char('f') && (mText.midRef(mPos, 4) == QLatin1String("ftp.") || mText.midRef(mPos, 7) == QLatin1String("file://"))) || (ch == QLatin1Char('n') && mText.midRef(mPos, 5) == QLatin1String("news:")) || (ch == QLatin1Char('t') && mText.midRef(mPos, 4) == QLatin1String("tel:")); } bool KTextToHTMLHelper::isEmptyUrl(const QString &url) const { return url.isEmpty() || url == QLatin1String("http://") || url == QLatin1String("https://") || url == QLatin1String("fish://") || url == QLatin1String("ftp://") || url == QLatin1String("ftps://") || url == QLatin1String("sftp://") || url == QLatin1String("smb://") || url == QLatin1String("vnc://") || url == QLatin1String("mailto") || url == QLatin1String("www") || url == QLatin1String("ftp") || url == QLatin1String("news") || url == QLatin1String("news://") || url == QLatin1String("tel") || url == QLatin1String("tel:"); } QString KTextToHTMLHelper::getUrl(bool *badurl) { QString url; if (atUrl()) { // NOTE: see http://tools.ietf.org/html/rfc3986#appendix-A and especially appendix-C // Appendix-C mainly says, that when extracting URLs from plain text, line breaks shall // be allowed and should be ignored when the URI is extracted. // This implementation follows this recommendation and // allows the URL to be enclosed within different kind of brackets/quotes // If an URL is enclosed, whitespace characters are allowed and removed, otherwise // the URL ends with the first whitespace // Also, if the URL is enclosed in brackets, the URL itself is not allowed // to contain the closing bracket, as this would be detected as the end of the URL QChar beforeUrl, afterUrl; // detect if the url has been surrounded by brackets or quotes if (mPos > 0) { beforeUrl = mText[mPos - 1]; /*if ( beforeUrl == '(' ) { afterUrl = ')'; } else */if (beforeUrl == QLatin1Char('[')) { afterUrl = QLatin1Char(']'); } else if (beforeUrl == QLatin1Char('<')) { afterUrl = QLatin1Char('>'); } else if (beforeUrl == QLatin1Char('>')) { // for e.g. http://..... afterUrl = QLatin1Char('<'); } else if (beforeUrl == QLatin1Char('"')) { afterUrl = QLatin1Char('"'); } } url.reserve(mMaxUrlLen); // avoid allocs int start = mPos; bool previousCharIsSpace = false; bool previousCharIsADoubleQuote = false; bool previousIsAnAnchor = false; while ((mPos < mText.length()) && (mText[mPos].isPrint() || mText[mPos].isSpace()) && ((afterUrl.isNull() && !mText[mPos].isSpace()) || (!afterUrl.isNull() && mText[mPos] != afterUrl))) { if (!previousCharIsSpace && (mText[mPos] == QLatin1Char('<')) && ((mPos + 1) < mText.length())) { // Fix Bug #346132: allow "http://www.foo.bar" // < inside a URL is not allowed, however there is a test which // checks that "http://some/path" should be allowed // Therefore: check if what follows is another URL and if so, stop here mPos++; if (atUrl()) { mPos--; break; } mPos--; } if (!previousCharIsSpace && (mText[mPos] == QLatin1Char(' ')) && ((mPos + 1) < mText.length())) { // Fix kmail bug: allow "http://www.foo.bar http://foo.bar/" // Therefore: check if what follows is another URL and if so, stop here mPos++; if (atUrl()) { mPos--; break; } mPos--; } if (mText[mPos].isSpace()) { previousCharIsSpace = true; } else if (!previousIsAnAnchor && mText[mPos] == QLatin1Char('[')) { break; } else if (!previousIsAnAnchor && mText[mPos] == QLatin1Char(']')) { break; } else { // skip whitespace if (previousCharIsSpace && mText[mPos] == QLatin1Char('<')) { url.append(QLatin1Char(' ')); break; } previousCharIsSpace = false; if (mText[mPos] == QLatin1Char('>') && previousCharIsADoubleQuote) { //it's an invalid url if (badurl) { *badurl = true; } return QString(); } if (mText[mPos] == QLatin1Char('"')) { previousCharIsADoubleQuote = true; } else { previousCharIsADoubleQuote = false; } if (mText[mPos] == QLatin1Char('#')) { previousIsAnAnchor = true; } url.append(mText[mPos]); if (url.length() > mMaxUrlLen) { break; } } ++mPos; } if (isEmptyUrl(url) || (url.length() > mMaxUrlLen)) { mPos = start; url.clear(); return url; } else { --mPos; } } // HACK: This is actually against the RFC. However, most people don't properly escape the URL in // their text with "" or <>. That leads to people writing an url, followed immediately by // a dot to finish the sentence. That would lead the parser to include the dot in the url, // even though that is not wanted. So work around that here. // Most real-life URLs hopefully don't end with dots or commas. const QString wordBoundaries = QStringLiteral(".,:!?)>"); if (url.length() > 1) { do { if (wordBoundaries.contains(url.at(url.length() - 1))) { url.chop(1); --mPos; } else { break; } } while (url.length() > 1); } return url; } QString KTextToHTMLHelper::highlightedText() { // formating symbols must be prepended with a whitespace if ((mPos > 0) && !mText[mPos - 1].isSpace()) { return QString(); } const QChar ch = mText[mPos]; if (ch != QLatin1Char('/') && ch != QLatin1Char('*') && ch != QLatin1Char('_') && ch != QLatin1Char('-')) { return QString(); } QRegularExpression re(QStringLiteral("\\%1([^\\s|^\\%1].*[^\\s|^\\%1])\\%1").arg(ch)); re.setPatternOptions(QRegularExpression::InvertedGreedinessOption); const auto match = re.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); if (match.hasMatch()) { if (match.capturedStart() == mPos) { int length = match.capturedLength(); // there must be a whitespace after the closing formating symbol if (mPos + length < mText.length() && !mText[mPos + length].isSpace()) { return QString(); } mPos += length - 1; switch (ch.toLatin1()) { case '*': return QLatin1String("*") + match.capturedRef(1) + QLatin1String("*"); case '_': return QLatin1String("_") + match.capturedRef(1) + QLatin1String("_"); case '/': return QLatin1String("/") + match.capturedRef(1) + QLatin1String("/"); case '-': return QLatin1String("-") + match.capturedRef(1) + QLatin1String("-"); } } } return QString(); } QString KTextToHTMLHelper::pngToDataUrl(const QString &iconPath) const { if (iconPath.isEmpty()) { return QString(); } QFile pngFile(iconPath); if (!pngFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { return QString(); } QByteArray ba = pngFile.readAll(); pngFile.close(); return QLatin1String("data:image/png;base64,") + QLatin1String(ba.toBase64().constData()); } QString KTextToHTML::convertToHtml(const QString &plainText, const KTextToHTML::Options &flags, int maxUrlLen, int maxAddressLen) { KTextToHTMLHelper helper(plainText, maxUrlLen, maxAddressLen); QString str; QString result(static_cast(nullptr), helper.mText.length() * 2); QChar ch; int x; bool startOfLine = true; for (helper.mPos = 0, x = 0; helper.mPos < helper.mText.length(); ++helper.mPos, ++x) { ch = helper.mText[helper.mPos]; if (flags & PreserveSpaces) { if (ch == QLatin1Char(' ')) { if (helper.mPos + 1 < helper.mText.length()) { if (helper.mText[helper.mPos + 1] != QLatin1Char(' ')) { // A single space, make it breaking if not at the start or end of the line const bool endOfLine = helper.mText[helper.mPos + 1] == QLatin1Char('\n'); if (!startOfLine && !endOfLine) { result += QLatin1Char(' '); } else { result += QLatin1String(" "); } } else { // Whitespace of more than one space, make it all non-breaking while (helper.mPos < helper.mText.length() && helper.mText[helper.mPos] == QLatin1Char(' ')) { result += QLatin1String(" "); ++helper.mPos; ++x; } // We incremented once to often, undo that --helper.mPos; --x; } } else { // Last space in the text, it is non-breaking result += QLatin1String(" "); } if (startOfLine) { startOfLine = false; } continue; } else if (ch == QLatin1Char('\t')) { do { result += QLatin1String(" "); ++x; } while ((x & 7) != 0); --x; startOfLine = false; continue; } } if (ch == QLatin1Char('\n')) { result += QLatin1String("
\n"); // Keep the \n, so apps can figure out the quoting levels correctly. startOfLine = true; x = -1; continue; } startOfLine = false; if (ch == QLatin1Char('&')) { result += QLatin1String("&"); } else if (ch == QLatin1Char('"')) { result += QLatin1String("""); } else if (ch == QLatin1Char('<')) { result += QLatin1String("<"); } else if (ch == QLatin1Char('>')) { result += QLatin1String(">"); } else { const int start = helper.mPos; if (!(flags & IgnoreUrls)) { bool badUrl = false; str = helper.getUrl(&badUrl); if (badUrl) { QString resultBadUrl; const int helperTextSize(helper.mText.count()); for (int i = 0; i < helperTextSize; ++i) { const QChar chBadUrl = helper.mText[i]; if (chBadUrl == QLatin1Char('&')) { resultBadUrl += QLatin1String("&"); } else if (chBadUrl == QLatin1Char('"')) { resultBadUrl += QLatin1String("""); } else if (chBadUrl == QLatin1Char('<')) { resultBadUrl += QLatin1String("<"); } else if (chBadUrl == QLatin1Char('>')) { resultBadUrl += QLatin1String(">"); } else { resultBadUrl += chBadUrl; } } return resultBadUrl; } if (!str.isEmpty()) { QString hyperlink; if (str.startsWith(QLatin1String("www."))) { hyperlink = QLatin1String("http://") + str; } else if (str.startsWith(QLatin1String("ftp."))) { hyperlink = QLatin1String("ftp://") + str; } else { hyperlink = str; } result += QLatin1String("") + str.toHtmlEscaped() + QLatin1String(""); x += helper.mPos - start; continue; } str = helper.getEmailAddress(); if (!str.isEmpty()) { // len is the length of the local part int len = str.indexOf(QLatin1Char('@')); QString localPart = str.left(len); // remove the local part from the result (as '&'s have been expanded to // & we have to take care of the 4 additional characters per '&') result.truncate(result.length() - len - (localPart.count(QLatin1Char('&')) * 4)); x -= len; result += QLatin1String("") + str + QLatin1String(""); x += str.length() - 1; continue; } if (flags & ConvertPhoneNumbers) { str = helper.getPhoneNumber(); if (!str.isEmpty()) { result += QLatin1String("") + str + QLatin1String(""); x += str.length() - 1; continue; } } } if (flags & HighlightText) { str = helper.highlightedText(); if (!str.isEmpty()) { result += str; x += helper.mPos - start; continue; } } result += ch; } } if (flags & ReplaceSmileys) { const QStringList exclude = { QStringLiteral("(c)"), QStringLiteral("(C)"), QStringLiteral(">:-("), QStringLiteral(">:("), QStringLiteral("(B)"), QStringLiteral("(b)"), QStringLiteral("(P)"), QStringLiteral("(p)") , QStringLiteral("(O)"), QStringLiteral("(o)"), QStringLiteral("(D)"), QStringLiteral("(d)"), QStringLiteral("(E)"), QStringLiteral("(e)"), QStringLiteral("(K)"), QStringLiteral("(k)") , QStringLiteral("(I)"), QStringLiteral("(i)"), QStringLiteral("(L)"), QStringLiteral("(l)"), QStringLiteral("(8)"), QStringLiteral("(T)"), QStringLiteral("(t)"), QStringLiteral("(G)") , QStringLiteral("(g)"), QStringLiteral("(F)"), QStringLiteral("(f)"), QStringLiteral("(H)") , QStringLiteral("8)"), QStringLiteral("(N)"), QStringLiteral("(n)"), QStringLiteral("(Y)"), QStringLiteral("(y)"), QStringLiteral("(U)"), QStringLiteral("(u)"), QStringLiteral("(W)"), QStringLiteral("(w)") , QStringLiteral("(6)")}; result = helper.emoticonsInterface()->parseEmoticons(result, true, exclude); } return result; } diff --git a/src/lib/text/ktexttohtml.h b/src/lib/text/ktexttohtml.h index a7f1589..3efd66f 100644 --- a/src/lib/text/ktexttohtml.h +++ b/src/lib/text/ktexttohtml.h @@ -1,100 +1,87 @@ /* - Copyright (c) 2002 Dave Corrie - Copyright (c) 2014 Daniel Vrátil + SPDX-FileCopyrightText: 2002 Dave Corrie + SPDX-FileCopyrightText: 2014 Daniel Vrátil - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KCOREADDONS_KTEXTTOHTML_H #define KCOREADDONS_KTEXTTOHTML_H #include #include /** * @author Dave Corrie \ */ namespace KTextToHTML { /** * @since 5.5.0 */ enum Option { /** * Preserve white-space formatting of the text */ PreserveSpaces = 1 << 1, /** * Replace text emoticons smileys by emoticons images. * * @note * This option works only when KEmoticons framework is available at runtime, * and requires QGuiApplication, otherwise the flag is simply ignored. */ ReplaceSmileys = 1 << 2, /** * Don't parse and replace any URLs. */ IgnoreUrls = 1 << 3, /** * Interpret text highlighting markup, like *bold*, _underline_ and /italic/, * and wrap them in corresponding HTML entities. */ HighlightText = 1 << 4, /** * Replace phone numbers with tel: links. * @since 5.56.0 */ ConvertPhoneNumbers = 1 << 5 }; Q_DECLARE_FLAGS(Options, Option) /** * Converts plaintext into html. The following characters are converted * to HTML entities: & " < >. Newlines are also preserved. * * @param plainText The text to be converted into HTML. * @param options The options to use when processing @p plainText. * @param maxUrlLen The maximum length of permitted URLs. The reason for * this limit is that there may be possible security * implications in handling URLs of unlimited length. * @param maxAddressLen The maximum length of permitted email addresses. * The reason for this limit is that there may be possible * security implications in handling addresses of unlimited * length. * * @return An HTML version of the text supplied in the 'plainText' * parameter, suitable for inclusion in the BODY of an HTML document. * * @since 5.5.0 */ KCOREADDONS_EXPORT QString convertToHtml(const QString &plainText, const KTextToHTML::Options &options, int maxUrlLen = 4096, int maxAddressLen = 255); } Q_DECLARE_OPERATORS_FOR_FLAGS(KTextToHTML::Options) #endif diff --git a/src/lib/text/ktexttohtml_p.h b/src/lib/text/ktexttohtml_p.h index fb96f28..8cdcf70 100644 --- a/src/lib/text/ktexttohtml_p.h +++ b/src/lib/text/ktexttohtml_p.h @@ -1,62 +1,49 @@ /* - Copyright (c) 2002 Dave Corrie - Copyright (c) 2014 Daniel Vrátil - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2002 Dave Corrie + SPDX-FileCopyrightText: 2014 Daniel Vrátil + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KTEXTTOHTML_P_H #define KTEXTTOHTML_P_H #include "kcoreaddons_export.h" #include "ktexttohtmlemoticonsinterface.h" class KTextToHTMLEmoticonsDummy : public KTextToHTMLEmoticonsInterface { public: QString parseEmoticons(const QString &text, bool strictParse = false, const QStringList &exclude = QStringList()) override { Q_UNUSED(strictParse); Q_UNUSED(exclude); return text; } }; class KTextToHTMLHelper { public: KTextToHTMLHelper(const QString &plainText, int pos = 0, int maxUrlLen = 4096, int maxAddressLen = 255); KTextToHTMLEmoticonsInterface *emoticonsInterface() const; QString getEmailAddress(); QString getPhoneNumber(); bool atUrl() const; bool isEmptyUrl(const QString &url) const; QString getUrl(bool *badurl = nullptr); QString pngToDataUrl(const QString &pngPath) const; QString highlightedText(); QString mText; int mMaxUrlLen; int mMaxAddressLen; int mPos; }; #endif diff --git a/src/lib/text/ktexttohtmlemoticonsinterface.h b/src/lib/text/ktexttohtmlemoticonsinterface.h index 4aca47a..6eab3b4 100644 --- a/src/lib/text/ktexttohtmlemoticonsinterface.h +++ b/src/lib/text/ktexttohtmlemoticonsinterface.h @@ -1,46 +1,33 @@ /* - Copyright (c) 2014 Daniel Vrátil - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2014 Daniel Vrátil + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KTEXTTOHTMLEMOTICONSINTERFACE_H #define KTEXTTOHTMLEMOTICONSINTERFACE_H #include #include /** * @internal * Used internally by KTextToHTML, implemented by plugin, for dynamic dependency on KEmoticons */ class KTextToHTMLEmoticonsInterface { public: KTextToHTMLEmoticonsInterface() {} virtual ~KTextToHTMLEmoticonsInterface() {} // KF6 TODO: de-inline (-Wweak-vtables) virtual QString parseEmoticons(const QString &text, bool strictParse = false, const QStringList &exclude = QStringList()) = 0; }; Q_DECLARE_METATYPE(KTextToHTMLEmoticonsInterface *) #define KTEXTTOHTMLEMOTICONS_PROPERTY "KTextToHTMLEmoticons" #endif diff --git a/src/lib/util/kdelibs4configmigrator.cpp b/src/lib/util/kdelibs4configmigrator.cpp index 2e7d4b2..3524395 100644 --- a/src/lib/util/kdelibs4configmigrator.cpp +++ b/src/lib/util/kdelibs4configmigrator.cpp @@ -1,133 +1,121 @@ -/* This file is part of the KDE Frameworks +/* + This file is part of the KDE Frameworks - Copyright 2014 Montel Laurent + SPDX-FileCopyrightText: 2014 Montel Laurent - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kdelibs4configmigrator.h" #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(MIGRATOR) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(MIGRATOR, "kf5.kcoreaddons.kdelibs4configmigrator", QtWarningMsg) class Q_DECL_HIDDEN Kdelibs4ConfigMigrator::Private { public: Private(const QString &_appName) : appName(_appName) { } QStringList configFiles; QStringList uiFiles; QString appName; }; Kdelibs4ConfigMigrator::Kdelibs4ConfigMigrator(const QString &appName) : d(new Private(appName)) { } Kdelibs4ConfigMigrator::~Kdelibs4ConfigMigrator() { delete d; } void Kdelibs4ConfigMigrator::setConfigFiles(const QStringList &configFileNameList) { d->configFiles = configFileNameList; } void Kdelibs4ConfigMigrator::setUiFiles(const QStringList &uiFileNameList) { d->uiFiles = uiFileNameList; } bool Kdelibs4ConfigMigrator::migrate() { // Testing for kdehome Kdelibs4Migration migration; if (!migration.kdeHomeFound()) { return false; } bool didSomething = false; for (const QString &configFileName : qAsConst(d->configFiles)) { const QString newConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + configFileName; if (QFile(newConfigLocation).exists()) { continue; } //Be safe QFileInfo fileInfo(newConfigLocation); QDir().mkpath(fileInfo.absolutePath()); const QString oldConfigFile(migration.locateLocal("config", configFileName)); if (!oldConfigFile.isEmpty()) { if (QFile(oldConfigFile).copy(newConfigLocation)) { didSomething = true; qCDebug(MIGRATOR) << "config file" << oldConfigFile << "was migrated to" << newConfigLocation; } } } if (d->appName.isEmpty() && !d->uiFiles.isEmpty()) { qCritical() << " We can not migrate ui file. AppName is missing"; } else { for (const QString &uiFileName : qAsConst(d->uiFiles)) { const QString newConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/") + d->appName + QLatin1Char('/') + uiFileName; if (QFile(newConfigLocation).exists()) { continue; } QFileInfo fileInfo(newConfigLocation); QDir().mkpath(fileInfo.absolutePath()); const QString oldConfigFile(migration.locateLocal("data", d->appName + QLatin1Char('/') + uiFileName)); if (!oldConfigFile.isEmpty()) { if (QFile(oldConfigFile).copy(newConfigLocation)) { didSomething = true; qCDebug(MIGRATOR) << "ui file" << oldConfigFile << "was migrated to" << newConfigLocation; } } } } // Trigger KSharedConfig::openConfig()->reparseConfiguration() via the framework integration plugin if (didSomething) { QPluginLoader lib(QStringLiteral("kf5/FrameworkIntegrationPlugin")); QObject *rootObj = lib.instance(); if (rootObj) { QMetaObject::invokeMethod(rootObj, "reparseConfiguration"); } } return true; } diff --git a/src/lib/util/kdelibs4configmigrator.h b/src/lib/util/kdelibs4configmigrator.h index aa58f8f..1e6f1d8 100644 --- a/src/lib/util/kdelibs4configmigrator.h +++ b/src/lib/util/kdelibs4configmigrator.h @@ -1,89 +1,77 @@ -/* This file is part of the KDE Frameworks +/* + This file is part of the KDE Frameworks - Copyright 2014 Montel Laurent + SPDX-FileCopyrightText: 2014 Montel Laurent - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KDELIBS4CONFIGMIGRATOR_H #define KDELIBS4CONFIGMIGRATOR_H #include #include /** * @class Kdelibs4ConfigMigrator kdelibs4configmigrator.h Kdelibs4ConfigMigrator * * Kdelibs4ConfigMigrator migrates selected config files and ui files * from the kdelibs 4.x location ($KDEHOME, as used by KStandardDirs) * to the Qt 5.x location ($XDG_*_HOME, as used by QStandardPaths). * * @short Class for migration of config files and ui file from kdelibs4. * @since 5.2 */ class KCOREADDONS_EXPORT Kdelibs4ConfigMigrator { public: /** * Constructs a Kdelibs4ConfigMigrator * * @param appName The application name, which is used for the directory * containing the .ui files. */ explicit Kdelibs4ConfigMigrator(const QString &appName); /** * Destructor */ ~Kdelibs4ConfigMigrator(); Kdelibs4ConfigMigrator(const Kdelibs4ConfigMigrator &) = delete; Kdelibs4ConfigMigrator &operator=(const Kdelibs4ConfigMigrator &) = delete; /** * Migrate the files, if any. * * Returns true if the migration happened. * It will return false if there was nothing to migrate (no KDEHOME). * This return value is unrelated to error handling. It is just a way to skip anything else * related to migration on a clean system, by writing * @code * if (migrate()) { * look for old data to migrate as well * } * @endcode */ bool migrate(); /** * Set the list of config files that need to be migrated. * @param configFileNameList list of config files */ void setConfigFiles(const QStringList &configFileNameList); /** * Set the list of ui files to migrate. * @param uiFileNameList list of ui files */ void setUiFiles(const QStringList &uiFileNameList); private: class Private; friend class Private; Private *const d; }; #endif // KDELIBS4CONFIGMIGRATOR_H diff --git a/src/lib/util/kdelibs4migration.cpp b/src/lib/util/kdelibs4migration.cpp index 8db48a4..22b56e0 100644 --- a/src/lib/util/kdelibs4migration.cpp +++ b/src/lib/util/kdelibs4migration.cpp @@ -1,134 +1,122 @@ -/* This file is part of the KDE Frameworks +/* + This file is part of the KDE Frameworks - Copyright 2014 David Faure - Copyright 2014 Ivan Cukic + SPDX-FileCopyrightText: 2014 David Faure + SPDX-FileCopyrightText: 2014 Ivan Cukic - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kdelibs4migration.h" #include "config-kde4home.h" #include #include "kcoreaddons_debug.h" #include #ifdef Q_OS_WIN # include #endif class Kdelibs4MigrationPrivate { public: QString m_kdeHome; }; Kdelibs4Migration::Kdelibs4Migration() : d(new Kdelibs4MigrationPrivate) { if (qEnvironmentVariableIsSet("KDEHOME")) { //qCDebug(KCOREADDONS_DEBUG) << "Using KDEHOME as the location of the old config file"; d->m_kdeHome = QString::fromLocal8Bit(qgetenv("KDEHOME")); } else { QDir homeDir = QDir::home(); QVector testSubdirs; testSubdirs << QStringLiteral(KDE4_DEFAULT_HOME) << QStringLiteral(".kde4") << QStringLiteral(".kde"); #ifdef Q_OS_WIN WCHAR wPath[MAX_PATH + 1]; if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK) { testSubdirs << QDir::fromNativeSeparators(QString::fromUtf16((const ushort *) wPath)) + QLatin1String("/" KDE4_DEFAULT_HOME); } #endif for (const QString &testSubdir : qAsConst(testSubdirs)) { if (homeDir.exists(testSubdir)) { //qCDebug(KCOREADDONS_DEBUG) << "Using" << testSubdir << "as the location of the old config file"; d->m_kdeHome = homeDir.filePath(testSubdir); break; } } if (d->m_kdeHome.isEmpty()) { d->m_kdeHome = homeDir.filePath(QStringLiteral(KDE4_DEFAULT_HOME)); } } if (!d->m_kdeHome.isEmpty() && !d->m_kdeHome.endsWith(QLatin1Char('/'))) { d->m_kdeHome.append(QLatin1Char('/')); } } Kdelibs4Migration::~Kdelibs4Migration() { delete d; } bool Kdelibs4Migration::kdeHomeFound() const { return !d->m_kdeHome.isEmpty() && QDir(d->m_kdeHome).exists(); } QString Kdelibs4Migration::kdeHome() const { return d->m_kdeHome; } QString Kdelibs4Migration::locateLocal(const char *type, const QString &filename) const { if (d->m_kdeHome.isEmpty()) { return QString(); } const QString dir = saveLocation(type); if (dir.isEmpty()) { return QString(); } const QString file = dir + filename; if (QFile::exists(file)) { return file; } return QString(); } static const struct { const char *type; const char *subdir; } s_subdirs[] = { { "config", "share/config/" }, { "data", "share/apps/" }, { "services", "share/kde4/services" }, { "servicetypes", "share/kde4/servicetypes" }, { "wallpaper", "share/wallpapers" }, { "emoticons", "share/emoticons" }, { "templates", "share/templates" } }; QString Kdelibs4Migration::saveLocation(const char *type, const QString &suffix) const { if (d->m_kdeHome.isEmpty()) { return QString(); } static const int numResources = sizeof(s_subdirs) / sizeof(*s_subdirs); for (uint i = 0; i < numResources; ++i) { if (qstrcmp(s_subdirs[i].type, type) == 0) { QString dir = d->m_kdeHome + QString::fromLatin1(s_subdirs[i].subdir) + suffix; if (!dir.endsWith(QLatin1Char('/'))) { dir += QLatin1Char('/'); } return dir; } } qCWarning(KCOREADDONS_DEBUG) << "No such resource" << type; return QString(); } diff --git a/src/lib/util/kdelibs4migration.h b/src/lib/util/kdelibs4migration.h index 2279383..edbf32d 100644 --- a/src/lib/util/kdelibs4migration.h +++ b/src/lib/util/kdelibs4migration.h @@ -1,120 +1,108 @@ -/* This file is part of the KDE Frameworks +/* + This file is part of the KDE Frameworks - Copyright 2014 David Faure - Copyright 2014 Ivan Cukic + SPDX-FileCopyrightText: 2014 David Faure + SPDX-FileCopyrightText: 2014 Ivan Cukic - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KDELIBS4MIGRATION_H #define KDELIBS4MIGRATION_H #include #include class Kdelibs4MigrationPrivate; /** * \file kdelibs4migration.h */ /** * @class Kdelibs4Migration kdelibs4migration.h Kdelibs4Migration * * Kdelibs4Migration provides support for locating config files * and application data files saved by kdelibs 4 in the user's home directory * ($KDEHOME, i.e. typically ~/.kde). * * Distributions that built kdelibs4 with a custom KDE home with * the CMake option _KDE_DEFAULT_HOME_POSTFIX should use the same option * here with _KDE4_DEFAULT_HOME_POSTFIX * * The purpose is to be able to let the application migrate these files * to the KF5/Qt5 location for these files (QStandardPaths). * * Files from the "config" resource (as saved by KConfig) should be migrated to * QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) * * Files from the "data" resource should be migrated to a subdirectory of * QStandardPaths::writableLocation(QStandardPaths::DataLocation) * * The following resources are supported:
  • config
  • data
  • services
  • servicetypes
  • wallpaper
  • emoticons
  • templates
* Use kdeHome() for anything else. * * @short Class for migration of config files from kdelibs4 * @since 5.0 */ class KCOREADDONS_EXPORT Kdelibs4Migration Q_DECL_FINAL { public: /** * Constructs a Kdelibs4Migration instance. * The constructor attempts to locate the user's "kdehome" from kdelibs4. */ explicit Kdelibs4Migration(); /** * Destructor */ ~Kdelibs4Migration(); Kdelibs4Migration(const Kdelibs4Migration &) = delete; Kdelibs4Migration &operator=(const Kdelibs4Migration &) = delete; /** * Returns true if a "kdehome" was found. * Otherwise, there is nothing to migrate. */ bool kdeHomeFound() const; /** * Returns the kdehome that was found. * Don't use this method if you can use locateLocal or saveLocation * @since 5.13 */ QString kdeHome() const; /** * Finds a local file in a resource. * This API is inspired by KStandardDirs::locateLocal for ease of porting. * @param type The type of wanted resource. * @param filename A relative filename of the resource. */ QString locateLocal(const char *type, const QString &filename) const; /** * Finds a location to save files into for the given type * in the user's home directory. * @param type The type of location to return. * @param suffix A subdirectory name. */ QString saveLocation(const char *type, const QString &suffix = QString()) const; private: Kdelibs4MigrationPrivate *d; }; #endif // KFORMAT_H diff --git a/src/lib/util/kformat.cpp b/src/lib/util/kformat.cpp index dec25ea..82a19f0 100644 --- a/src/lib/util/kformat.cpp +++ b/src/lib/util/kformat.cpp @@ -1,101 +1,88 @@ /* This file is part of the KDE Frameworks - Copyright (C) 2013 Alex Merry - Copyright (C) 2013 John Layt - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2013 Alex Merry + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kformatprivate_p.h" KFormat::KFormat(const QLocale &locale) : d(new KFormatPrivate(locale)) { } KFormat::KFormat(const KFormat &other) : d(other.d) { } KFormat& KFormat::operator=(const KFormat &other) { d = other.d; return *this; } KFormat::~KFormat() { } QString KFormat::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const { return d->formatByteSize(size, precision, dialect, units); } QString KFormat::formatValue(double value, KFormat::Unit unit, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const { return d->formatValue(value, unit, QString(), precision, prefix, dialect); } QString KFormat::formatValue(double value, const QString& unit, int precision, KFormat::UnitPrefix prefix) const { return d->formatValue(value, KFormat::Unit::Other, unit, precision, prefix, MetricBinaryDialect); } QString KFormat::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const { return d->formatDuration(msecs, options); } QString KFormat::formatDecimalDuration(quint64 msecs, int decimalPlaces) const { return d->formatDecimalDuration(msecs, decimalPlaces); } QString KFormat::formatSpelloutDuration(quint64 msecs) const { return d->formatSpelloutDuration(msecs); } QString KFormat::formatRelativeDate(const QDate &date, QLocale::FormatType format) const { return d->formatRelativeDate(date, format); } QString KFormat::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const { return d->formatRelativeDateTime(dateTime, format); } #include "moc_kformat.cpp" diff --git a/src/lib/util/kformat.h b/src/lib/util/kformat.h index 44dea35..ebceac0 100644 --- a/src/lib/util/kformat.h +++ b/src/lib/util/kformat.h @@ -1,407 +1,395 @@ -/* This file is part of the KDE Frameworks - - Copyright (C) 2013 Alex Merry - Copyright (C) 2013 John Layt - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2013 Alex Merry + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KFORMAT_H #define KFORMAT_H #include #include #include #include class QDate; class QDateTime; class KFormatPrivate; /** * \file kformat.h */ /* The code in this class was copied from the old KLocale and modified by John Layt (and also Alex Merry) in the KDELIBS 4 to KDE Frameworks 5 transition in 2013. Albert Astals Cid is the original author of formatSpelloutDuration() originally named KLocale::prettyFormatDuration(). Michael Pyne is the original author of formatByteSize(). Michael Leupold is the original author of formatRelativeDate(() originally part of KFormat::formatDate(). */ /** * @class KFormat kformat.h KFormat * * KFormat provides support for formatting numbers and datetimes in * formats that are not supported by QLocale. * * @author John Layt , * Michael Pyne , * Albert Astals Cid , * * @short Class for formatting numbers and datetimes. * @since 5.0 */ class KCOREADDONS_EXPORT KFormat Q_DECL_FINAL { Q_GADGET public: /** * These binary units are used in KDE by the formatByteSize() * function. * * NOTE: There are several different units standards: * 1) SI (i.e. metric), powers-of-10. * 2) IEC, powers-of-2, with specific units KiB, MiB, etc. * 3) JEDEC, powers-of-2, used for solid state memory sizing which * is why you see flash cards labels as e.g. 4GB. These (ab)use * the metric units. Although JEDEC only defines KB, MB, GB, if * JEDEC is selected all units will be powers-of-2 with metric * prefixes for clarity in the event of sizes larger than 1024 GB. * * Although 3 different dialects are possible this enum only uses * metric names since adding all 3 different names of essentially the same * unit would be pointless. Use BinaryUnitDialect to control the exact * units returned. * * @see BinaryUnitDialect * @see formatByteSize */ enum BinarySizeUnits { /// Auto-choose a unit such that the result is in the range [0, 1000 or 1024) DefaultBinaryUnits = -1, // The first real unit must be 0 for the current implementation! UnitByte, ///< B 1 byte UnitKiloByte, ///< KiB/KB/kB 1024/1000 bytes. UnitMegaByte, ///< MiB/MB/MB 2^20/10^06 bytes. UnitGigaByte, ///< GiB/GB/GB 2^30/10^09 bytes. UnitTeraByte, ///< TiB/TB/TB 2^40/10^12 bytes. UnitPetaByte, ///< PiB/PB/PB 2^50/10^15 bytes. UnitExaByte, ///< EiB/EB/EB 2^60/10^18 bytes. UnitZettaByte, ///< ZiB/ZB/ZB 2^70/10^21 bytes. UnitYottaByte, ///< YiB/YB/YB 2^80/10^24 bytes. UnitLastUnit = UnitYottaByte }; /** * These units are used in KDE by the formatValue() function. * * @see formatValue * @since 5.49 */ enum class Unit { Other, Bit, ///< "bit" Byte, ///< "B" Meter, ///< "m" Hertz, ///< "Hz" }; /** * These prefixes are used in KDE by the formatValue() * function. * * IEC prefixes are only defined for integral units of information, e.g. * bits and bytes. * * @see BinarySizeUnits * @see formatValue * @since 5.49 */ enum class UnitPrefix { /// Auto-choose a unit such that the result is in the range [0, 1000 or 1024) AutoAdjust = -128, Yocto = 0, ///< --/-/y 10^-24 Zepto, ///< --/-/z 10^-21 Atto, ///< --/-/a 10^-18 Femto, ///< --/-/f 10^-15 Pico, ///< --/-/p 10^-12 Nano, ///< --/-/n 10^-9 Micro, ///< --/-/µ 10^-6 Milli, ///< --/-/m 10^-3 Centi, ///< --/-/c 0.01 Deci, ///< --/-/d 0.1 Unity, ///< "" 1 Deca, ///< --/-/da 10 Hecto, ///< --/-/h 100 Kilo, ///< Ki/K/k 1024/1000 Mega, ///< Mi/M/M 2^20/10^06 Giga, ///< Gi/G/G 2^30/10^09 Tera, ///< Ti/T/T 2^40/10^12 Peta, ///< Pi/P/P 2^50/10^15 Exa, ///< Ei/E/E 2^60/10^18 Zetta, ///< Zi/Z/Z 2^70/10^21 Yotta, ///< Yi/Y/Y 2^80/10^24 }; /** * This enum chooses what dialect is used for binary units. * * Note: Although JEDEC abuses the metric prefixes and can therefore be * confusing, it has been used to describe *memory* sizes for quite some time * and programs should therefore use either Default, JEDEC, or IEC 60027-2 * for memory sizes. * * On the other hand network transmission rates are typically in metric so * Default, Metric, or IEC (which is unambiguous) should be chosen. * * Normally choosing DefaultBinaryDialect is the best option as that uses * the user's selection for units. If the user has not selected a preference, * IECBinaryDialect will typically be used. * * @see BinarySizeUnits * @see formatByteSize */ enum BinaryUnitDialect { DefaultBinaryDialect = -1, ///< Used if no specific preference IECBinaryDialect, ///< KiB, MiB, etc. 2^(10*n) JEDECBinaryDialect, ///< KB, MB, etc. 2^(10*n) MetricBinaryDialect, ///< SI Units, kB, MB, etc. 10^(3*n) LastBinaryDialect = MetricBinaryDialect }; /** * Format flags for formatDuration() */ enum DurationFormatOption { DefaultDuration = 0x0, ///< Default formatting in localized 1:23:45 format InitialDuration = 0x1, ///< Default formatting in localized 1h23m45s format ShowMilliseconds = 0x2, ///< Include milliseconds in format, e.g. 1:23:45.678 HideSeconds = 0x4, ///< Hide the seconds, e.g. 1:23 or 1h23m, overrides ShowMilliseconds FoldHours = 0x8 ///< Fold the hours into the minutes, e.g. 83:45 or 83m45s, overrides HideSeconds }; Q_DECLARE_FLAGS(DurationFormatOptions, DurationFormatOption) Q_FLAG(DurationFormatOption) /** * Constructs a KFormat. * * @param locale the locale to use, defaults to the system locale */ explicit KFormat(const QLocale &locale = QLocale()); /** * Copy constructor */ KFormat(const KFormat &other); KFormat& operator=(const KFormat &other); /** * Destructor */ ~KFormat(); /** * Converts @p size from bytes to the appropriate string representation * using the binary unit dialect @p dialect and the specific units @p units. * * Example: * @code * QString metric, iec, jedec, small; * metric = formatByteSize(1000, 1, KFormat::MetricBinaryDialect, KFormat::UnitKiloByte); * iec = formatByteSize(1000, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte); * jedec = formatByteSize(1000, 1, KFormat::JEDECBinaryDialect, KFormat::UnitKiloByte); * small = formatByteSize(100); * // metric == "1.0 kB", iec == "1.0 KiB", jedec == "1.0 KB", small == "100 B" * @endcode * * @param size size in bytes * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. Whenever KFormat::UnitByte is used * (either explicitly or autoselected from KFormat::DefaultBinaryUnits), * the fractional part is always omitted. * @param dialect binary unit standard to use. Use DefaultBinaryDialect to * use the localized user selection unless you need to use a specific * unit type (such as displaying a flash memory size in JEDEC). * @param units specific unit size to use in result. Use * DefaultBinaryUnits to automatically select a unit that will return * a sanely-sized number. * @return converted size as a translated string including the units. * E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric). * @see BinarySizeUnits * @see BinaryUnitDialect */ QString formatByteSize(double size, int precision = 1, KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units = KFormat::DefaultBinaryUnits) const; /** * Given a number of milliseconds, converts that to a string containing * the localized equivalent, e.g. 1:23:45 * * @param msecs Time duration in milliseconds * @param options options to use in the duration format * @return converted duration as a string - e.g. "1:23:45" "1h23m" */ QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const; /** * Given a number of milliseconds, converts that to a string containing * the localized equivalent to the requested decimal places. * * e.g. given formatDuration(60000), returns "1.0 minutes" * * @param msecs Time duration in milliseconds * @param decimalPlaces Decimal places to round off to, defaults to 2 * @return converted duration as a string - e.g. "5.5 seconds" "23.0 minutes" */ QString formatDecimalDuration(quint64 msecs, int decimalPlaces = 2) const; /** * Given a number of milliseconds, converts that to a spell-out string containing * the localized equivalent. * * e.g. given formatSpelloutDuration(60001) returns "1 minute" * given formatSpelloutDuration(62005) returns "1 minute and 2 seconds" * given formatSpelloutDuration(90060000) returns "1 day and 1 hour" * * @param msecs Time duration in milliseconds * @return converted duration as a string. * Units not interesting to the user, for example seconds or minutes when the first * unit is day, are not returned because they are irrelevant. The same applies for * seconds when the first unit is hour. */ QString formatSpelloutDuration(quint64 msecs) const; /** * Returns a string formatted to a relative date style. * * If the @p date falls within one week before or after the current date * then a relative date string will be returned, such as: * * Yesterday * * Today * * Tomorrow * * Last Tuesday * * Next Wednesday * * If the @p date falls outside this period then the @p format is used. * * @param date the date to be formatted * @param format the date format to use * * @return the date as a string */ QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const; /** * Returns a string formatted to a relative datetime style. * * If the @p dateTime falls within one week before or after the current date * then a relative date string will be returned, such as: * * Yesterday, 3:00pm * * Today, 3:00pm * * Tomorrow, 3:00pm * * Last Tuesday, 3:00pm * * Next Wednesday, 3:00pm * * If the @p dateTime falls outside this period then the @p format is used. * * @param dateTime the date to be formatted * @param format the date format to use * * @return the date as a string */ QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const; /** * Converts @p value to the appropriate string representation * * Example: * @code * // sets formatted to "1.0 kbit" * auto formatted = format.formatValue(1000, KFormat::Unit::Bit, 1, KFormat::UnitPrefix::Kilo); * @endcode * * @param value value to be formatted * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. * @param unit unit to use in result. * @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust * to automatically select an appropriate prefix. * @param dialect prefix standard to use. Use DefaultBinaryDialect to * use the localized user selection unless you need to use a specific * unit type. Only meaningful for KFormat::Unit::Byte, and ignored for * all other units. * @return converted size as a translated string including prefix and unit. * E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric), "1.2 kbit". * @see Unit * @see UnitPrefix * @see BinaryUnitDialect * @since 5.49 */ QString formatValue(double value, KFormat::Unit unit, int precision = 1, KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust, KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect) const; /** * Converts @p value to the appropriate string representation * * Example: * @code * QString bits, slow, fast; * // sets bits to "1.0 kbit", slow to "1.0 kbit/s" and fast to "12.3 Mbit/s". * bits = format.formatValue(1000, QStringLiteral("bit"), 1, KFormat::UnitPrefix::Kilo); * slow = format.formatValue(1000, QStringLiteral("bit/s"); * fast = format.formatValue(12.3e6, QStringLiteral("bit/s"); * @endcode * * @param value value to be formatted * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. * @param unit unit to use in result. * @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust * to automatically select an appropriate prefix. * @return converted size as a translated string including prefix and unit. * E.g. "1.2 kbit", "2.4 kB", "12.3 Mbit/s" * @see UnitPrefix * @since 5.49 */ QString formatValue(double value, const QString& unit, int precision = 1, KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust) const; private: QSharedDataPointer d; }; #endif // KFORMAT_H diff --git a/src/lib/util/kformatprivate.cpp b/src/lib/util/kformatprivate.cpp index 9521d0d..a5bcbfa 100644 --- a/src/lib/util/kformatprivate.cpp +++ b/src/lib/util/kformatprivate.cpp @@ -1,542 +1,530 @@ -/* This file is part of the KDE Frameworks - - Copyright (C) 2013 Alex Merry - Copyright (C) 2013 John Layt - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2013 Alex Merry + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kformatprivate_p.h" #include #include KFormatPrivate::KFormatPrivate(const QLocale &locale) { m_locale = locale; } KFormatPrivate::~KFormatPrivate() { } constexpr double bpow(int exp) { return (exp > 0) ? 2.0 * bpow(exp - 1) : (exp < 0) ? 0.5 * bpow(exp + 1) : 1.0; } QString KFormatPrivate::formatValue(double value, KFormat::Unit unit, QString unitString, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const { if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) { dialect = KFormat::IECBinaryDialect; } if (static_cast(prefix) < static_cast(KFormat::UnitPrefix::Yocto) || static_cast(prefix) > static_cast(KFormat::UnitPrefix::Yotta)) { prefix = KFormat::UnitPrefix::AutoAdjust; } double multiplier = 1024.0; if (dialect == KFormat::MetricBinaryDialect) { multiplier = 1000.0; } int power = 0; if (prefix == KFormat::UnitPrefix::AutoAdjust) { double adjustValue = qAbs(value); while (adjustValue >= multiplier) { adjustValue /= multiplier; power += 1; } while (adjustValue && adjustValue < 1.0) { adjustValue *= multiplier; power -= 1; } const KFormat::UnitPrefix map[] = { KFormat::UnitPrefix::Yocto, // -8 KFormat::UnitPrefix::Zepto, KFormat::UnitPrefix::Atto, KFormat::UnitPrefix::Femto, KFormat::UnitPrefix::Pico, KFormat::UnitPrefix::Nano, KFormat::UnitPrefix::Micro, KFormat::UnitPrefix::Milli, KFormat::UnitPrefix::Unity, // 0 KFormat::UnitPrefix::Kilo, KFormat::UnitPrefix::Mega, KFormat::UnitPrefix::Giga, KFormat::UnitPrefix::Tera, KFormat::UnitPrefix::Peta, KFormat::UnitPrefix::Exa, KFormat::UnitPrefix::Zetta, KFormat::UnitPrefix::Yotta, // 8 }; power = std::max(-8, std::min(8, power)); prefix = map[power + 8]; } if (prefix == KFormat::UnitPrefix::Unity && unit == KFormat::Unit::Byte) { precision = 0; } struct PrefixMapEntry { KFormat::UnitPrefix prefix; double decimalFactor; double binaryFactor; QString prefixCharSI; QString prefixCharIEC; }; const PrefixMapEntry map[] = { { KFormat::UnitPrefix::Yocto, 1e-24, bpow(-80), tr("y", "SI prefix for 10^⁻24"), QString() }, { KFormat::UnitPrefix::Zepto, 1e-21, bpow(-70), tr("z", "SI prefix for 10^⁻21"), QString() }, { KFormat::UnitPrefix::Atto, 1e-18, bpow(-60), tr("a", "SI prefix for 10^⁻18"), QString() }, { KFormat::UnitPrefix::Femto, 1e-15, bpow(-50), tr("f", "SI prefix for 10^⁻15"), QString() }, { KFormat::UnitPrefix::Pico, 1e-12, bpow(-40), tr("p", "SI prefix for 10^⁻12"), QString() }, { KFormat::UnitPrefix::Nano, 1e-9, bpow(-30), tr("n", "SI prefix for 10^⁻9") , QString() }, { KFormat::UnitPrefix::Micro, 1e-6, bpow(-20), tr("µ", "SI prefix for 10^⁻6") , QString() }, { KFormat::UnitPrefix::Milli, 1e-3, bpow(-10), tr("m", "SI prefix for 10^⁻3") , QString() }, { KFormat::UnitPrefix::Unity, 1.0, 1.0 , QString() , QString() }, { KFormat::UnitPrefix::Kilo, 1e3, bpow(10) , tr("k", "SI prefix for 10^3") , tr("Ki", "IEC binary prefix for 2^10") }, { KFormat::UnitPrefix::Mega, 1e6, bpow(20) , tr("M", "SI prefix for 10^6") , tr("Mi", "IEC binary prefix for 2^20") }, { KFormat::UnitPrefix::Giga, 1e9, bpow(30) , tr("G", "SI prefix for 10^9") , tr("Gi", "IEC binary prefix for 2^30") }, { KFormat::UnitPrefix::Tera, 1e12, bpow(40) , tr("T", "SI prefix for 10^12") , tr("Ti", "IEC binary prefix for 2^40") }, { KFormat::UnitPrefix::Peta, 1e15, bpow(50) , tr("P", "SI prefix for 10^15") , tr("Pi", "IEC binary prefix for 2^50") }, { KFormat::UnitPrefix::Exa, 1e18, bpow(60) , tr("E", "SI prefix for 10^18") , tr("Ei", "IEC binary prefix for 2^60") }, { KFormat::UnitPrefix::Zetta, 1e21, bpow(70) , tr("Z", "SI prefix for 10^21") , tr("Zi", "IEC binary prefix for 2^70") }, { KFormat::UnitPrefix::Yotta, 1e24, bpow(80) , tr("Y", "SI prefix for 10^24") , tr("Yi", "IEC binary prefix for 2^80") }, }; auto entry = std::find_if(std::begin(map), std::end(map), [prefix](const PrefixMapEntry& e) { return e.prefix == prefix; }); switch (unit) { case KFormat::Unit::Bit: unitString = tr("bit", "Symbol of binary digit"); break; case KFormat::Unit::Byte: unitString = tr("B", "Symbol of byte"); break; case KFormat::Unit::Meter: unitString = tr("m", "Symbol of meter"); break; case KFormat::Unit::Hertz: unitString = tr("Hz", "Symbol of hertz"); break; case KFormat::Unit::Other: break; } if (prefix == KFormat::UnitPrefix::Unity) { QString numString = m_locale.toString(value, 'f', precision); //: value without prefix, format " " return tr("%1 %2", "no Prefix").arg(numString, unitString); } QString prefixString; if (dialect == KFormat::MetricBinaryDialect) { value /= entry->decimalFactor; prefixString = entry->prefixCharSI; } else { value /= entry->binaryFactor; if (dialect == KFormat::IECBinaryDialect) { prefixString = entry->prefixCharIEC; } else { prefixString = entry->prefixCharSI.toUpper(); } } QString numString = m_locale.toString(value, 'f', precision); //: value with prefix, format " " return tr("%1 %2%3", "MetricBinaryDialect").arg(numString, prefixString, unitString); } QString KFormatPrivate::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const { // Current KDE default is IECBinaryDialect if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) { dialect = KFormat::IECBinaryDialect; } // Current KDE default is to auto-adjust so the size falls in the range 0 to 1000/1024 if (units < KFormat::DefaultBinaryUnits || units > KFormat::UnitLastUnit) { units = KFormat::DefaultBinaryUnits; } int unit = 0; // Selects what unit to use double multiplier = 1024.0; if (dialect == KFormat::MetricBinaryDialect) { multiplier = 1000.0; } // If a specific unit conversion is given, use it directly. Otherwise // search until the result is in [0, multiplier] (or out of our range). if (units == KFormat::DefaultBinaryUnits) { while (qAbs(size) >= multiplier && unit < int(KFormat::UnitYottaByte)) { size /= multiplier; ++unit; } } else { // A specific unit is in use unit = static_cast(units); if (unit > 0) { size /= pow(multiplier, unit); } } // Bytes, no rounding if (unit == 0) { precision = 0; } QString numString = m_locale.toString(size, 'f', precision); // Do not remove "//:" comments below, they are used by the translators. // NB: we cannot pass pluralization arguments, as the size may be negative if (dialect == KFormat::MetricBinaryDialect) { switch (unit) { case KFormat::UnitByte: //: MetricBinaryDialect size in bytes return tr("%1 B", "MetricBinaryDialect").arg(numString); case KFormat::UnitKiloByte: //: MetricBinaryDialect size in 1000 bytes return tr("%1 kB", "MetricBinaryDialect").arg(numString); case KFormat::UnitMegaByte: //: MetricBinaryDialect size in 10^6 bytes return tr("%1 MB", "MetricBinaryDialect").arg(numString); case KFormat::UnitGigaByte: //: MetricBinaryDialect size in 10^9 bytes return tr("%1 GB", "MetricBinaryDialect").arg(numString); case KFormat::UnitTeraByte: //: MetricBinaryDialect size in 10^12 bytes return tr("%1 TB", "MetricBinaryDialect").arg(numString); case KFormat::UnitPetaByte: //: MetricBinaryDialect size in 10^15 bytes return tr("%1 PB", "MetricBinaryDialect").arg(numString); case KFormat::UnitExaByte: //: MetricBinaryDialect size in 10^18 byte return tr("%1 EB", "MetricBinaryDialect").arg(numString); case KFormat::UnitZettaByte: //: MetricBinaryDialect size in 10^21 bytes return tr("%1 ZB", "MetricBinaryDialect").arg(numString); case KFormat::UnitYottaByte: //: MetricBinaryDialect size in 10^24 bytes return tr("%1 YB", "MetricBinaryDialect").arg(numString); } } else if (dialect == KFormat::JEDECBinaryDialect) { switch (unit) { case KFormat::UnitByte: //: JEDECBinaryDialect memory size in bytes return tr("%1 B", "JEDECBinaryDialect").arg(numString); case KFormat::UnitKiloByte: //: JEDECBinaryDialect memory size in 1024 bytes return tr("%1 KB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitMegaByte: //: JEDECBinaryDialect memory size in 10^20 bytes return tr("%1 MB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitGigaByte: //: JEDECBinaryDialect memory size in 10^30 bytes return tr("%1 GB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitTeraByte: //: JEDECBinaryDialect memory size in 10^40 bytes return tr("%1 TB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitPetaByte: //: JEDECBinaryDialect memory size in 10^50 bytes return tr("%1 PB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitExaByte: //: JEDECBinaryDialect memory size in 10^60 bytes return tr("%1 EB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitZettaByte: //: JEDECBinaryDialect memory size in 10^70 bytes return tr("%1 ZB", "JEDECBinaryDialect").arg(numString); case KFormat::UnitYottaByte: //: JEDECBinaryDialect memory size in 10^80 bytes return tr("%1 YB", "JEDECBinaryDialect").arg(numString); } } else { // KFormat::IECBinaryDialect, KFormat::DefaultBinaryDialect switch (unit) { case KFormat::UnitByte: //: IECBinaryDialect size in bytes return tr("%1 B", "IECBinaryDialect").arg(numString); case KFormat::UnitKiloByte: //: IECBinaryDialect size in 1024 bytes return tr("%1 KiB", "IECBinaryDialect").arg(numString); case KFormat::UnitMegaByte: //: IECBinaryDialect size in 10^20 bytes return tr("%1 MiB", "IECBinaryDialect").arg(numString); case KFormat::UnitGigaByte: //: IECBinaryDialect size in 10^30 bytes return tr("%1 GiB", "IECBinaryDialect").arg(numString); case KFormat::UnitTeraByte: //: IECBinaryDialect size in 10^40 bytes return tr("%1 TiB", "IECBinaryDialect").arg(numString); case KFormat::UnitPetaByte: //: IECBinaryDialect size in 10^50 bytes return tr("%1 PiB", "IECBinaryDialect").arg(numString); case KFormat::UnitExaByte: //: IECBinaryDialect size in 10^60 bytes return tr("%1 EiB", "IECBinaryDialect").arg(numString); case KFormat::UnitZettaByte: //: IECBinaryDialect size in 10^70 bytes return tr("%1 ZiB", "IECBinaryDialect").arg(numString); case KFormat::UnitYottaByte: //: IECBinaryDialect size in 10^80 bytes return tr("%1 YiB", "IECBinaryDialect").arg(numString); } } // Should never reach here Q_ASSERT(false); return numString; } enum TimeConstants { MSecsInDay = 86400000, MSecsInHour = 3600000, MSecsInMinute = 60000, MSecsInSecond = 1000 }; QString KFormatPrivate::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const { quint64 ms = msecs; if (options & KFormat::HideSeconds) { //round to nearest minute ms = qRound64(ms / (qreal)MSecsInMinute) * MSecsInMinute ; } else if (!(options & KFormat::ShowMilliseconds)) { //round to nearest second ms = qRound64(ms / (qreal)MSecsInSecond) * MSecsInSecond ; } int hours = ms / MSecsInHour; ms = ms % MSecsInHour; int minutes = ms / MSecsInMinute; ms = ms % MSecsInMinute; int seconds = ms / MSecsInSecond; ms = ms % MSecsInSecond; if ((options & KFormat::InitialDuration) == KFormat::InitialDuration) { if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) { //: @item:intext Duration format minutes, seconds and milliseconds return tr("%1m%2.%3s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')) .arg(ms, 3, 10, QLatin1Char('0')); } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) { //: @item:intext Duration format minutes and seconds return tr("%1m%2s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')); } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) { //: @item:intext Duration format hours and minutes return tr("%1h%2m").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')); } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) { //: @item:intext Duration format hours, minutes, seconds, milliseconds return tr("%1h%2m%3.%4s").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')) .arg(ms, 3, 10, QLatin1Char('0')); } else { // Default //: @item:intext Duration format hours, minutes, seconds return tr("%1h%2m%3s").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')); } } else { if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) { //: @item:intext Duration format minutes, seconds and milliseconds return tr("%1:%2.%3").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')) .arg(ms, 3, 10, QLatin1Char('0')); } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) { //: @item:intext Duration format minutes and seconds return tr("%1:%2").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')); } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) { //: @item:intext Duration format hours and minutes return tr("%1:%2").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')); } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) { //: @item:intext Duration format hours, minutes, seconds, milliseconds return tr("%1:%2:%3.%4").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')) .arg(ms, 3, 10, QLatin1Char('0')); } else { // Default //: @item:intext Duration format hours, minutes, seconds return tr("%1:%2:%3").arg(hours, 1, 10, QLatin1Char('0')) .arg(minutes, 2, 10, QLatin1Char('0')) .arg(seconds, 2, 10, QLatin1Char('0')); } } Q_UNREACHABLE(); return QString(); } QString KFormatPrivate::formatDecimalDuration(quint64 msecs, int decimalPlaces) const { if (msecs >= MSecsInDay) { //: @item:intext %1 is a real number, e.g. 1.23 days return tr("%1 days").arg(m_locale.toString(msecs / (MSecsInDay * 1.0), 'f', decimalPlaces)); } else if (msecs >= MSecsInHour) { //: @item:intext %1 is a real number, e.g. 1.23 hours return tr("%1 hours").arg(m_locale.toString(msecs / (MSecsInHour * 1.0), 'f', decimalPlaces)); } else if (msecs >= MSecsInMinute) { //: @item:intext %1 is a real number, e.g. 1.23 minutes return tr("%1 minutes").arg(m_locale.toString(msecs / (MSecsInMinute * 1.0), 'f', decimalPlaces)); } else if (msecs >= MSecsInSecond) { //: @item:intext %1 is a real number, e.g. 1.23 seconds return tr("%1 seconds").arg(m_locale.toString(msecs / (MSecsInSecond * 1.0), 'f', decimalPlaces)); } //: @item:intext %1 is a whole number //~ singular %n millisecond //~ plural %n milliseconds return tr("%n millisecond(s)", nullptr, msecs); } enum DurationUnits { Days = 0, Hours, Minutes, Seconds }; static QString formatSingleDuration(DurationUnits units, int n) { // NB: n is guaranteed to be non-negative switch (units) { case Days: //: @item:intext %n is a whole number //~ singular %n day //~ plural %n days return KFormatPrivate::tr("%n day(s)", nullptr, n); case Hours: //: @item:intext %n is a whole number //~ singular %n hour //~ plural %n hours return KFormatPrivate::tr("%n hour(s)", nullptr, n); case Minutes: //: @item:intext %n is a whole number //~ singular %n minute //~ plural %n minutes return KFormatPrivate::tr("%n minute(s)", nullptr, n); case Seconds: //: @item:intext %n is a whole number //~ singular %n second //~ plural %n seconds return KFormatPrivate::tr("%n second(s)", nullptr, n); } Q_ASSERT(false); return QString(); } QString KFormatPrivate::formatSpelloutDuration(quint64 msecs) const { quint64 ms = msecs; int days = ms / MSecsInDay; ms = ms % (MSecsInDay); int hours = ms / MSecsInHour; ms = ms % MSecsInHour; int minutes = ms / MSecsInMinute; ms = ms % MSecsInMinute; int seconds = qRound(ms / 1000.0); // Handle correctly problematic case #1 (look at KFormatTest::prettyFormatDuration()) if (seconds == 60) { return formatSpelloutDuration(msecs - ms + MSecsInMinute); } if (days && hours) { /*: @item:intext days and hours. This uses the previous item:intext messages. If this does not fit the grammar of your language please contact the i18n team to solve the problem */ return tr("%1 and %2").arg(formatSingleDuration(Days, days), formatSingleDuration(Hours, hours)); } else if (days) { return formatSingleDuration(Days, days); } else if (hours && minutes) { /*: @item:intext hours and minutes. This uses the previous item:intext messages. If this does not fit the grammar of your language please contact the i18n team to solve the problem */ return tr("%1 and %2").arg(formatSingleDuration(Hours, hours), formatSingleDuration(Minutes, minutes)); } else if (hours) { return formatSingleDuration(Hours, hours); } else if (minutes && seconds) { /*: @item:intext minutes and seconds. This uses the previous item:intext messages. If this does not fit the grammar of your language please contact the i18n team to solve the problem */ return tr("%1 and %2").arg(formatSingleDuration(Minutes, minutes), formatSingleDuration(Seconds, seconds)); } else if (minutes) { return formatSingleDuration(Minutes, minutes); } else { return formatSingleDuration(Seconds, seconds); } } QString KFormatPrivate::formatRelativeDate(const QDate &date, QLocale::FormatType format) const { if (!date.isValid()) { return tr("Invalid date", "used when a relative date string can't be generated because the date is invalid"); } const qint64 daysTo = QDate::currentDate().daysTo(date); if (daysTo > 2 || daysTo < -2) { return m_locale.toString(date, format); } switch (daysTo) { case 2: return tr("In two days"); case 1: return tr("Tomorrow"); case 0: return tr("Today"); case -1: return tr("Yesterday"); case -2: return tr("Two days ago"); } Q_UNREACHABLE(); } QString KFormatPrivate::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const { const qint64 daysTo = QDate::currentDate().daysTo(dateTime.date()); if (daysTo > 2 || daysTo < -2) { return m_locale.toString(dateTime, format); } /*: relative datetime with %1 result of formatReleativeDate() and %2 the formatted time If this does not fit the grammar of your language please contact the i18n team to solve the problem */ return tr("%1, %2").arg(formatRelativeDate(dateTime.date(), format), m_locale.toString(dateTime.time(), format)); } diff --git a/src/lib/util/kformatprivate_p.h b/src/lib/util/kformatprivate_p.h index 1824541..0d796b1 100644 --- a/src/lib/util/kformatprivate_p.h +++ b/src/lib/util/kformatprivate_p.h @@ -1,72 +1,60 @@ -/* This file is part of the KDE Frameworks - - Copyright (C) 2013 Alex Merry - Copyright (C) 2013 John Layt - Copyright (C) 2010 Michael Leupold - Copyright (C) 2009 Michael Pyne - Copyright (C) 2008 Albert Astals Cid - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2013 Alex Merry + SPDX-FileCopyrightText: 2013 John Layt + SPDX-FileCopyrightText: 2010 Michael Leupold + SPDX-FileCopyrightText: 2009 Michael Pyne + SPDX-FileCopyrightText: 2008 Albert Astals Cid + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KFORMATPRIVATE_P_H #define KFORMATPRIVATE_P_H #include "kformat.h" #include // for Q_DECLARE_TR_FUNCTIONS class KFormatPrivate : public QSharedData { Q_DECLARE_TR_FUNCTIONS(KFormat) public: explicit KFormatPrivate(const QLocale &locale); virtual ~KFormatPrivate(); QString formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const; QString formatValue(double value, KFormat::Unit unit, QString unitString, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const; QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const; QString formatDecimalDuration(quint64 msecs, int decimalPlaces) const; QString formatSpelloutDuration(quint64 msecs) const; QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const; QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const; private: QLocale m_locale; }; #endif // KFORMATPRIVATE_P_H diff --git a/src/lib/util/klistopenfilesjob.h b/src/lib/util/klistopenfilesjob.h index 6cdeaba..4e3bc03 100644 --- a/src/lib/util/klistopenfilesjob.h +++ b/src/lib/util/klistopenfilesjob.h @@ -1,80 +1,69 @@ /* - * This file is part of the KDE project - * Copyright (C) 2010 by Jacopo De Simoi - * Copyright (C) 2014 by Lukáš Tinkl - * Copyright (C) 2016 by Kai Uwe Broulik - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + + SPDX-FileCopyrightText: 2010 Jacopo De Simoi + SPDX-FileCopyrightText: 2014 Lukáš Tinkl + SPDX-FileCopyrightText: 2016 Kai Uwe Broulik + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KLISTOPENFILESJOB_H #define KLISTOPENFILESJOB_H #include #include #include #include #include #include class KListOpenFilesJobPrivate; /** * @brief Provides information about processes that have open files in a given path or subdirectory of path. * * When start() is invoked it starts to collect information about processes that have any files open in path or a * subdirectory of path. When it is done the KJob::result signal is emitted and the result can be retrieved with the * processInfoList function. * * On Unix like systems the lsof utility is used to get the list of processes. * On Windows the listing always fails with error code NotSupported. * * @since 5.63 */ class KCOREADDONS_EXPORT KListOpenFilesJob : public KJob { Q_OBJECT public: explicit KListOpenFilesJob(const QString &path); ~KListOpenFilesJob() override; void start() override; /** * @brief Returns the list of processes with open files for the requested path * @return The list of processes with open files for the requested path */ KProcessList::KProcessInfoList processInfoList() const; public: /** * @brief Special error codes emitted by KListOpenFilesJob * * The KListOpenFilesJob uses the error codes defined here besides the standard error codes defined by KJob */ enum class Error { /*** Indicates that the platform doesn't support listing open files by processes */ NotSupported = KJob::UserDefinedError + 1, /*** Internal error has ocurred */ InternalError = KJob::UserDefinedError + 2, /*** The specified path does not exist */ DoesNotExist = KJob::UserDefinedError + 11, }; private: friend class KListOpenFilesJobPrivate; QScopedPointer d; }; #endif // KLISTOPENFILESJOB_H diff --git a/src/lib/util/klistopenfilesjob_unix.cpp b/src/lib/util/klistopenfilesjob_unix.cpp index 076b302..b60cc1e 100644 --- a/src/lib/util/klistopenfilesjob_unix.cpp +++ b/src/lib/util/klistopenfilesjob_unix.cpp @@ -1,115 +1,104 @@ /* - * This file is part of the KDE project - * Copyright (C) 2010 by Jacopo De Simoi - * Copyright (C) 2014 by Lukáš Tinkl - * Copyright (C) 2016 by Kai Uwe Broulik - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + + SPDX-FileCopyrightText: 2010 Jacopo De Simoi + SPDX-FileCopyrightText: 2014 Lukáš Tinkl + SPDX-FileCopyrightText: 2016 Kai Uwe Broulik + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #include "klistopenfilesjob.h" #include #include #include #include class KListOpenFilesJobPrivate { public: KListOpenFilesJobPrivate(KListOpenFilesJob *Job, const QDir &Path) : job(Job) , path(Path) , hasEmittedResult(false) { QObject::connect(&lsofProcess, &QProcess::errorOccurred, [this](QProcess::ProcessError error) { lsofError(error); }); QObject::connect(&lsofProcess, QOverload::of(&QProcess::finished), [this](int exitCode, QProcess::ExitStatus exitStatus) { lsofFinished(exitCode, exitStatus); }); } void start() { if (!path.exists()) { emitResult(static_cast(KListOpenFilesJob::Error::DoesNotExist), QObject::tr("Path %1 doesn't exist").arg(path.path())); return; } lsofProcess.start(QStringLiteral("lsof"), {QStringLiteral("-t"), QStringLiteral("+d"), path.path()}); } KProcessList::KProcessInfoList getProcessInfoList() const { return processInfoList; } private: void lsofError(QProcess::ProcessError processError) { emitResult(static_cast(KListOpenFilesJob::Error::InternalError), QObject::tr("Failed to execute `lsof' error code %1").arg(processError)); } void lsofFinished(int, QProcess::ExitStatus) { if (hasEmittedResult) { return; } const QString out(QString::fromLocal8Bit(lsofProcess.readAll())); const QVector pidList = out.splitRef(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts); for (const auto &pidStr : pidList) { qint64 pid = pidStr.toLongLong(); if (!pid) { continue; } processInfoList << KProcessList::processInfo(pid); } job->emitResult(); } void emitResult(int error, const QString& errorText) { if (hasEmittedResult) { return; } job->setError(error); job->setErrorText(errorText); job->emitResult(); hasEmittedResult = true; } private: KListOpenFilesJob *job; const QDir path; bool hasEmittedResult; QProcess lsofProcess; KProcessList::KProcessInfoList processInfoList; }; KListOpenFilesJob::KListOpenFilesJob(const QString& path) : d(new KListOpenFilesJobPrivate(this, path)) { } KListOpenFilesJob::~KListOpenFilesJob() = default; void KListOpenFilesJob::start() { d->start(); } KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const { return d->getProcessInfoList(); } #include "moc_klistopenfilesjob.cpp" diff --git a/src/lib/util/klistopenfilesjob_win.cpp b/src/lib/util/klistopenfilesjob_win.cpp index 3c87a3d..85dba2e 100644 --- a/src/lib/util/klistopenfilesjob_win.cpp +++ b/src/lib/util/klistopenfilesjob_win.cpp @@ -1,48 +1,37 @@ /* - * This file is part of the KDE project - * Copyright (C) 2019 David Hallas - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + This file is part of the KDE project + + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.0-only */ #include "klistopenfilesjob.h" #include class KListOpenFilesJobPrivate { }; KListOpenFilesJob::KListOpenFilesJob(const QString&) : d(nullptr) { } KListOpenFilesJob::~KListOpenFilesJob() = default; void KListOpenFilesJob::start() { QTimer::singleShot(0, [this](){ setError(static_cast(KListOpenFilesJob::Error::NotSupported)); setErrorText(QObject::tr("KListOpenFilesJob is not supported on Windows")); emitResult(); }); } KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const { return KProcessList::KProcessInfoList(); } #include "moc_klistopenfilesjob.cpp" diff --git a/src/lib/util/kosrelease.cpp b/src/lib/util/kosrelease.cpp index 0469aba..51d9610 100644 --- a/src/lib/util/kosrelease.cpp +++ b/src/lib/util/kosrelease.cpp @@ -1,305 +1,291 @@ /* - Copyright (C) 2014-2019 Harald Sitter - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . + SPDX-FileCopyrightText: 2014-2019 Harald Sitter + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "kosrelease.h" #include #include "kcoreaddons_debug.h" #include "kshell.h" // Sets a QString var static void setVar(QString *var, const QString &value) { // Values may contain quotation marks, strip them as we have no use for them. KShell::Errors error; QStringList args = KShell::splitArgs(value, KShell::NoOptions, &error); if (error != KShell::NoError) { // Failed to parse. return; } *var = args.join(QLatin1Char(' ')); } // Sets a QStringList var (i.e. splits a string value) static void setVar(QStringList *var, const QString &value) { // Instead of passing the verbatim value we manually strip any initial quotes // and then run it through KShell. At this point KShell will actually split // by spaces giving us the final QStringList. // NOTE: Splitting like this does not actually allow escaped substrings to // be handled correctly, so "kitteh \"french fries\"" would result in // three list entries. I'd argue that if someone makes an id like that // they are at fault for the bogus parsing here though as id explicitly // is required to not contain spaces even if more advanced shell escaping // is also allowed... QString value_ = value; if (value_.at(0) == QLatin1Char('"') && value_.at(value_.size()-1) == QLatin1Char('"')) { value_.remove(0, 1); value_.remove(-1, 1); } KShell::Errors error; QStringList args = KShell::splitArgs(value_, KShell::NoOptions, &error); if (error != KShell::NoError) { // Failed to parse. return; } *var = args; } static QStringList splitEntry(const QString &line) { QStringList list; const int separatorIndex = line.indexOf(QLatin1Char('=')); list << line.mid(0, separatorIndex); if (separatorIndex != -1) { list << line.mid(separatorIndex + 1, -1); } return list; } static QString defaultFilePath() { if (QFile::exists(QStringLiteral("/etc/os-release"))) { return QStringLiteral("/etc/os-release"); } else if (QFile::exists(QStringLiteral("/usr/lib/os-release"))) { return QStringLiteral("/usr/lib/os-release"); } else { return QString(); } } class Q_DECL_HIDDEN KOSRelease::Private { public: Private(QString filePath) : name(QStringLiteral("Linux")) , id(QStringLiteral("linux")) , prettyName(QStringLiteral("Linux")) { // Default values for non-optional fields set above ^. QHash stringHash = { { QStringLiteral("NAME"), &name }, { QStringLiteral("VERSION"), &version }, { QStringLiteral("ID"), &id }, // idLike is not a QString, special handling below! { QStringLiteral("VERSION_CODENAME"), &versionCodename }, { QStringLiteral("VERSION_ID"), &versionId }, { QStringLiteral("PRETTY_NAME"), &prettyName }, { QStringLiteral("ANSI_COLOR"), &ansiColor }, { QStringLiteral("CPE_NAME"), &cpeName }, { QStringLiteral("HOME_URL"), &homeUrl }, { QStringLiteral("DOCUMENTATION_URL"), &documentationUrl }, { QStringLiteral("SUPPORT_URL"), &supportUrl }, { QStringLiteral("BUG_REPORT_URL"), &bugReportUrl }, { QStringLiteral("PRIVACY_POLICY_URL"), &privacyPolicyUrl }, { QStringLiteral("BUILD_ID"), &buildId }, { QStringLiteral("VARIANT"), &variant }, { QStringLiteral("VARIANT_ID"), &variantId }, { QStringLiteral("LOGO"), &logo } }; if (filePath.isEmpty()) { filePath = defaultFilePath(); } if (filePath.isEmpty()) { qCWarning(KCOREADDONS_DEBUG) << "Failed to find os-release file!"; return; } QFile file(filePath); // NOTE: The os-release specification defines default values for specific // fields which means that even if we can not read the os-release file // we have sort of expected default values to use. // TODO: it might still be handy to indicate to the outside whether // fallback values are being used or not. file.open(QIODevice::ReadOnly | QIODevice::Text); QString line; QStringList parts; while (!file.atEnd()) { // Trimmed to handle indented comment lines properly line = QString::fromLatin1(file.readLine()).trimmed(); if (line.startsWith(QLatin1Char('#'))) { // Comment line // Lines beginning with "#" shall be ignored as comments. continue; } parts = splitEntry(line); if (parts.size() != 2) { // Line has no =, must be invalid. qCDebug(KCOREADDONS_DEBUG) << "Unexpected/invalid os-release line:" << line; continue; } QString key = parts.at(0); QString value = parts.at(1).trimmed(); if (QString *var = stringHash.value(key, nullptr)) { setVar(var, value); continue; } // ID_LIKE is a list and parsed as such (rather than a QString). if (key == QLatin1String("ID_LIKE")) { setVar(&idLike, value); continue; } // os-release explicitly allows for vendor specific additions, we'll // collect them as strings and exposes them as "extras". QString parsedValue; setVar(&parsedValue, value); extras.insert(key, parsedValue); } } QString name; QString version; QString id; QStringList idLike; QString versionCodename; QString versionId; QString prettyName; QString ansiColor; QString cpeName; QString homeUrl; QString documentationUrl; QString supportUrl; QString bugReportUrl; QString privacyPolicyUrl; QString buildId; QString variant; QString variantId; QString logo; QHash extras; }; KOSRelease::KOSRelease(const QString &filePath) : d(new Private(filePath)) { } KOSRelease::~KOSRelease() { delete d; } QString KOSRelease::name() const { return d->name; } QString KOSRelease::version() const { return d->version; } QString KOSRelease::id() const { return d->id; } QStringList KOSRelease::idLike() const { return d->idLike; } QString KOSRelease::versionCodename() const { return d->versionCodename; } QString KOSRelease::versionId() const { return d->versionId; } QString KOSRelease::prettyName() const { return d->prettyName; } QString KOSRelease::ansiColor() const { return d->ansiColor; } QString KOSRelease::cpeName() const { return d->cpeName; } QString KOSRelease::homeUrl() const { return d->homeUrl; } QString KOSRelease::documentationUrl() const { return d->documentationUrl; } QString KOSRelease::supportUrl() const { return d->supportUrl; } QString KOSRelease::bugReportUrl() const { return d->bugReportUrl; } QString KOSRelease::privacyPolicyUrl() const { return d->privacyPolicyUrl; } QString KOSRelease::buildId() const { return d->buildId; } QString KOSRelease::variant() const { return d->variant; } QString KOSRelease::variantId() const { return d->variantId; } QString KOSRelease::logo() const { return d->logo; } QStringList KOSRelease::extraKeys() const { return d->extras.keys(); } QString KOSRelease::extraValue(const QString &key) const { return d->extras.value(key); } diff --git a/src/lib/util/kosrelease.h b/src/lib/util/kosrelease.h index bc41742..1aa34d6 100644 --- a/src/lib/util/kosrelease.h +++ b/src/lib/util/kosrelease.h @@ -1,108 +1,94 @@ /* - Copyright (C) 2014-2019 Harald Sitter + SPDX-FileCopyrightText: 2014-2019 Harald Sitter - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef KOSRELEASE_H #define KOSRELEASE_H #include #include #include /** * @brief The OSRelease class parses /etc/os-release files * * https://www.freedesktop.org/software/systemd/man/os-release.html * * os-release is a free desktop standard for describing an operating system. * This class parses and models os-release files. * * @since 5.58.0 */ class KCOREADDONS_EXPORT KOSRelease Q_DECL_FINAL { public: /** * Constructs a new OSRelease instance. Parsing happens in the constructor * and the data is not cached across instances. * * @note The format specification makes no assertions about trailing # * comments being supported. They result in undefined behavior. * * @param filePath The path to the os-release file. By default the first * available file of the paths specified in the os-release manpage is * parsed. */ explicit KOSRelease(const QString &filePath = QString()); ~KOSRelease(); /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#NAME= */ QString name() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION= */ QString version() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ID= */ QString id() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ID_LIKE= */ QStringList idLike() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION_CODENAME= */ QString versionCodename() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VERSION_ID= */ QString versionId() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#PRETTY_NAME= */ QString prettyName() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#ANSI_COLOR= */ QString ansiColor() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#CPE_NAME= */ QString cpeName() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ QString homeUrl() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ QString documentationUrl() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ QString supportUrl() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ QString bugReportUrl() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#HOME_URL= */ QString privacyPolicyUrl() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#BUILD_ID= */ QString buildId() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VARIANT= */ QString variant() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#VARIANT_ID= */ QString variantId() const; /** @see https://www.freedesktop.org/software/systemd/man/os-release.html#LOGO= */ QString logo() const; /** * Extra keys are keys that are unknown or specified by a vendor. */ QStringList extraKeys() const; /** Extra values are values assoicated with keys that are unknown. */ QString extraValue(const QString &key) const; private: Q_DISABLE_COPY(KOSRelease) class Private; Private *const d = nullptr; }; #endif // KOSRELEASE_H diff --git a/src/lib/util/kprocesslist.cpp b/src/lib/util/kprocesslist.cpp index 934d871..5848a58 100644 --- a/src/lib/util/kprocesslist.cpp +++ b/src/lib/util/kprocesslist.cpp @@ -1,101 +1,80 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** Copyright (c) 2019 David Hallas -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial +*/ #include "kprocesslist.h" #include "kprocesslist_p.h" using namespace KProcessList; KProcessInfoPrivate::KProcessInfoPrivate() : valid(false), pid(-1) { } KProcessInfo::KProcessInfo() : d_ptr(new KProcessInfoPrivate) { } KProcessInfo::KProcessInfo(qint64 pid, const QString& command, const QString& user) : KProcessInfo(pid, command, command, user) {} KProcessInfo::KProcessInfo(qint64 pid, const QString& command, const QString &name, const QString& user) : d_ptr(new KProcessInfoPrivate) { d_ptr->valid = true; d_ptr->pid = pid; d_ptr->name = name; d_ptr->command = command; d_ptr->user = user; } KProcessInfo::KProcessInfo(const KProcessInfo &other) : d_ptr(new KProcessInfoPrivate) { *this = other; } KProcessInfo::~KProcessInfo() { } KProcessInfo &KProcessInfo::operator=(const KProcessInfo &other) { d_ptr = other.d_ptr; return *this; } bool KProcessInfo::isValid() const { return d_ptr->valid; } qint64 KProcessInfo::pid() const { return d_ptr->pid; } QString KProcessInfo::name() const { return d_ptr->name; } QString KProcessInfo::command() const { return d_ptr->command; } QString KProcessInfo::user() const { return d_ptr->user; } diff --git a/src/lib/util/kprocesslist.h b/src/lib/util/kprocesslist.h index 7b8e8b7..f556dfa 100644 --- a/src/lib/util/kprocesslist.h +++ b/src/lib/util/kprocesslist.h @@ -1,105 +1,84 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** Copyright (c) 2019 David Hallas -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial +*/ #ifndef KPROCESSLIST_H #define KPROCESSLIST_H #include #include #include #include namespace KProcessList { class KProcessInfoPrivate; /** * @brief Contains information about a process. This class is usually not used alone but rather returned by * processInfoList and processInfo. To check if the data contained in this class is valid use the isValid method. * @since 5.58 */ class KCOREADDONS_EXPORT KProcessInfo { public: KProcessInfo(); KProcessInfo(qint64 pid, const QString &command, const QString &user); KProcessInfo(qint64 pid, const QString &command, const QString &name, const QString &user); KProcessInfo(const KProcessInfo &other); ~KProcessInfo(); KProcessInfo &operator=(const KProcessInfo &other); /** * @brief If the KProcessInfo contains valid information. If it returns true the pid, name and user function * returns valid information, otherwise they return value is undefined. */ bool isValid() const; /** * @brief The pid of the process */ qint64 pid() const; /** * @brief The name of the process. The class will try to get the full path to the executable file for the process * but if it is not available the name of the process will be used instead. * e.g /bin/ls */ QString name() const; /** * @brief The username the process is running under. */ QString user() const; /** * @brief The command line running this process * e.g /bin/ls /some/path -R * @since 5.61 */ QString command() const; private: QSharedDataPointer d_ptr; }; typedef QList KProcessInfoList; /** * @brief Retrieves the list of currently active processes. * @since 5.58 */ KCOREADDONS_EXPORT KProcessInfoList processInfoList(); /** * @brief Retrieves process information for a specific process-id. If the process is not found a KProcessInfo with * isValid == false will be returned. * @param pid The process-id to retrieve information for. * @since 5.58 */ KCOREADDONS_EXPORT KProcessInfo processInfo(qint64 pid); } // KProcessList namespace #endif // KPROCESSLIST_H diff --git a/src/lib/util/kprocesslist_p.h b/src/lib/util/kprocesslist_p.h index 344440a..97f3f46 100644 --- a/src/lib/util/kprocesslist_p.h +++ b/src/lib/util/kprocesslist_p.h @@ -1,53 +1,32 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** Copyright (c) 2019 David Hallas -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial +*/ #ifndef KPROCESSLIST_P_H #define KPROCESSLIST_P_H #include #include "kprocesslist.h" namespace KProcessList { class KProcessInfoPrivate : public QSharedData { public: KProcessInfoPrivate(); bool valid; qint64 pid; QString name; QString user; QString command; }; } // KProcessList namespace #endif // KPROCESSLIST_P_H diff --git a/src/lib/util/kprocesslist_unix.cpp b/src/lib/util/kprocesslist_unix.cpp index 30c5892..02c6036 100644 --- a/src/lib/util/kprocesslist_unix.cpp +++ b/src/lib/util/kprocesslist_unix.cpp @@ -1,173 +1,152 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** Copyright (c) 2019 David Hallas -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial +*/ #include "kprocesslist.h" #include #include using namespace KProcessList; namespace { bool isUnixProcessId(const QString &procname) { for (int i = 0; i != procname.size(); ++i) { if (!procname.at(i).isDigit()) return false; } return true; } // Determine UNIX processes by running ps KProcessInfoList unixProcessListPS() { KProcessInfoList rc; QProcess psProcess; const QStringList args { QStringLiteral("-e"), QStringLiteral("-o"), #ifdef Q_OS_MAC // command goes last, otherwise it is cut off QStringLiteral("pid state user comm command"), #else QStringLiteral("pid,state,user,comm,cmd"), #endif }; psProcess.start(QStringLiteral("ps"), args); if (!psProcess.waitForStarted()) return rc; psProcess.waitForFinished(); QByteArray output = psProcess.readAllStandardOutput(); // Split "457 S+ /Users/foo.app" const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n')); const int lineCount = lines.size(); const QChar blank = QLatin1Char(' '); for (int l = 1; l < lineCount; l++) { // Skip header const QString line = lines.at(l).simplified(); // we can't just split on blank as the process name might // contain them const int endOfPid = line.indexOf(blank); const int endOfState = line.indexOf(blank, endOfPid+1); const int endOfUser = line.indexOf(blank, endOfState+1); const int endOfName = line.indexOf(blank, endOfUser+1); if (endOfPid >= 0 && endOfState >= 0 && endOfUser >= 0) { qint64 pid = line.leftRef(endOfPid).toUInt(); QString user = line.mid(endOfState+1, endOfUser-endOfState-1); QString name = line.mid(endOfUser+1, endOfName-endOfUser-1); QString command = line.right(line.size()-endOfName-1); rc.push_back(KProcessInfo(pid, command, name, user)); } } return rc; } bool getProcessInfo(const QString& procId, KProcessInfo& processInfo) { if (!isUnixProcessId(procId)) return false; #ifdef Q_OS_FREEBSD QString statusFileName(QStringLiteral("/status")); #else QString statusFileName(QStringLiteral("/stat")); #endif QString filename = QStringLiteral("/proc/"); filename += procId; filename += statusFileName; QFile file(filename); if (!file.open(QIODevice::ReadOnly)) return false; // process may have exited const QStringList data = QString::fromLocal8Bit(file.readAll()).split(QLatin1Char(' ')); qint64 pid = procId.toUInt(); QString name = data.at(1); if (name.startsWith(QLatin1Char('(')) && name.endsWith(QLatin1Char(')'))) { name.chop(1); name.remove(0, 1); } // State is element 2 // PPID is element 3 QString user = QFileInfo(file).owner(); file.close(); QString command = name; QFile cmdFile(QLatin1String("/proc/") + procId + QLatin1String("/cmdline")); if (cmdFile.open(QFile::ReadOnly)) { QByteArray cmd = cmdFile.readAll(); if (!cmd.isEmpty()) { // extract non-truncated name from cmdline int zeroIndex = cmd.indexOf('\0'); int processNameStart = cmd.lastIndexOf('/', zeroIndex); if (processNameStart == -1) { processNameStart = 0; } else { processNameStart++; } name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart)); cmd.replace('\0', ' '); command = QString::fromLocal8Bit(cmd).trimmed(); } } cmdFile.close(); processInfo = KProcessInfo(pid, command, name, user); return true; } } // unnamed namespace // Determine UNIX processes by reading "/proc". Default to ps if // it does not exist KProcessInfoList KProcessList::processInfoList() { const QDir procDir(QStringLiteral("/proc/")); if (!procDir.exists()) return unixProcessListPS(); KProcessInfoList rc; const QStringList procIds = procDir.entryList(); for (const QString &procId : procIds) { KProcessInfo processInfo; if (getProcessInfo(procId, processInfo)) { rc.push_back(processInfo); } } return rc; } KProcessInfo KProcessList::processInfo(qint64 pid) { KProcessInfo processInfo; getProcessInfo(QString::number(pid), processInfo); return processInfo; } diff --git a/src/lib/util/kprocesslist_unix_procstat.cpp b/src/lib/util/kprocesslist_unix_procstat.cpp index c10e58c..8190161 100644 --- a/src/lib/util/kprocesslist_unix_procstat.cpp +++ b/src/lib/util/kprocesslist_unix_procstat.cpp @@ -1,60 +1,52 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2019 Tobias C. Berner -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -**************************************************************************/ +/* + + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2019 Tobias C. Berner + + SPDX-License-Identifier: LGPL-2.1-only +*/ #include "kprocesslist.h" #include "kprocesslist_unix_procstat_p.h" #include #include using namespace KProcessList; // Determine UNIX processes by using the procstat library KProcessInfoList KProcessList::processInfoList() { KProcessInfoList rc; ProcStat pstat; if (!pstat) { return rc; } ProcStatProcesses procs(pstat); for (const auto& process_info: procs) { rc.push_back(process_info); } return rc; } KProcessInfo KProcessList::processInfo(qint64 pid) { KProcessInfoList processInfoList = KProcessList::processInfoList(); auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [pid](const KProcessList::KProcessInfo& info) { return info.pid() == pid; }); if (testProcessIterator != processInfoList.end()) { return *testProcessIterator; } return KProcessInfo(); } diff --git a/src/lib/util/kprocesslist_unix_procstat_p.h b/src/lib/util/kprocesslist_unix_procstat_p.h index 3129643..5aae77e 100644 --- a/src/lib/util/kprocesslist_unix_procstat_p.h +++ b/src/lib/util/kprocesslist_unix_procstat_p.h @@ -1,127 +1,119 @@ -/************************************************************************** - ** - ** This file is part of the KDE Frameworks - ** - ** Copyright (c) 2019 Tobias C. Berner - ** - ** GNU Lesser General Public License Usage - ** - ** This file may be used under the terms of the GNU Lesser General Public - ** License version 2.1 as published by the Free Software Foundation and - ** appearing in the file LICENSE.LGPL included in the packaging of this file. - ** Please review the following information to ensure the GNU Lesser General - ** Public License version 2.1 requirements will be met: - ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. - ** - **************************************************************************/ +/* + + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2019 Tobias C. Berner + + SPDX-License-Identifier: LGPL-2.1-only +*/ #pragma once #include #include #include #include #include #include namespace KProcessList { struct ProcStat { public: struct procstat *pstat; ProcStat() { pstat = procstat_open_sysctl(); } ~ProcStat() { procstat_close(pstat); } operator bool() const { return pstat; } }; struct ProcStatProcesses { private: ProcStat& parent; unsigned int proc_count; struct kinfo_proc *procs; public: ProcStatProcesses(ProcStat& pstat) : parent(pstat) { procs = procstat_getprocs(parent.pstat, KERN_PROC_PROC, 0, &proc_count); } ~ProcStatProcesses() { if (procs) { procstat_freeprocs(parent.pstat, procs); } } operator bool() const { return procs && proc_count > 0; } unsigned int count() const { return proc_count; } class ProcessIterator { private: const ProcStatProcesses& processes; unsigned int pos; public: ProcessIterator(const ProcStatProcesses& processes, unsigned int pos) : processes(processes), pos(pos) {}; bool operator!=(const ProcessIterator& other) const { return pos != other.pos; } ProcessIterator& operator++() { if (pos < processes.count()) { ++pos; } return *this; } const KProcessInfo operator*() { QStringList command_line; QString command; char pathname[PATH_MAX]; struct kinfo_proc *proc = &processes.procs[pos]; if (procstat_getpathname(processes.parent.pstat, proc, pathname, sizeof(pathname)) != 0) { command = QString::fromLocal8Bit(pathname); } else { command = QString::fromLocal8Bit(proc->ki_comm); } char **args; args = procstat_getargv(processes.parent.pstat, proc, 0); if (args) { for (int i = 0; args[i] != nullptr; i++) { command_line << QString::fromLocal8Bit(args[i]); } } pid_t pid = proc->ki_pid; QString user = QString::fromLocal8Bit(proc->ki_login); return KProcessInfo(pid, command_line.join(QString::fromLocal8Bit(" ")), command, user); } }; ProcessIterator begin() const { return ProcessIterator(*this, 0); } ProcessIterator end() const { return ProcessIterator(*this, this->count()); } }; } diff --git a/src/lib/util/kprocesslist_win.cpp b/src/lib/util/kprocesslist_win.cpp index f18d31b..3796d41 100644 --- a/src/lib/util/kprocesslist_win.cpp +++ b/src/lib/util/kprocesslist_win.cpp @@ -1,149 +1,128 @@ -/************************************************************************** -** -** This file is part of the KDE Frameworks -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** Copyright (c) 2019 David Hallas -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies). + SPDX-FileCopyrightText: 2019 David Hallas + + SPDX-License-Identifier: LGPL-2.1-only WITH Qt-LGPL-exception-1.1 OR LicenseRef-Qt-Commercial +*/ #include "kprocesslist.h" #include #include // Enable Win API of XP SP1 and later #ifdef Q_OS_WIN # if !defined(_WIN32_WINNT) # define _WIN32_WINNT 0x0502 # endif # include # if !defined(PROCESS_SUSPEND_RESUME) // Check flag for MinGW # define PROCESS_SUSPEND_RESUME (0x0800) # endif // PROCESS_SUSPEND_RESUME #endif // Q_OS_WIN #include #include using namespace KProcessList; // Resolve QueryFullProcessImageNameW out of kernel32.dll due // to incomplete MinGW import libs and it not being present // on Windows XP. static inline BOOL queryFullProcessImageName(HANDLE h, DWORD flags, LPWSTR buffer, DWORD *size) { // Resolve required symbols from the kernel32.dll typedef BOOL (WINAPI *QueryFullProcessImageNameWProtoType)(HANDLE, DWORD, LPWSTR, PDWORD); static QueryFullProcessImageNameWProtoType queryFullProcessImageNameW = 0; if (!queryFullProcessImageNameW) { QLibrary kernel32Lib(QLatin1String("kernel32.dll"), 0); if (kernel32Lib.isLoaded() || kernel32Lib.load()) { queryFullProcessImageNameW = (QueryFullProcessImageNameWProtoType)kernel32Lib.resolve( "QueryFullProcessImageNameW"); } } if (!queryFullProcessImageNameW) return FALSE; // Read out process return (*queryFullProcessImageNameW)(h, flags, buffer, size); } struct ProcessInfo { QString processOwner; }; static inline ProcessInfo winProcessInfo(DWORD processId) { ProcessInfo pi; HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, TOKEN_READ, processId); if (handle == INVALID_HANDLE_VALUE) return pi; HANDLE processTokenHandle = NULL; if (!OpenProcessToken(handle, TOKEN_READ, &processTokenHandle) || !processTokenHandle) return pi; DWORD size = 0; GetTokenInformation(processTokenHandle, TokenUser, NULL, 0, &size); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { QByteArray buf; buf.resize(size); PTOKEN_USER userToken = reinterpret_cast(buf.data()); if (userToken && GetTokenInformation(processTokenHandle, TokenUser, userToken, size, &size)) { SID_NAME_USE sidNameUse; TCHAR user[MAX_PATH] = { 0 }; DWORD userNameLength = MAX_PATH; TCHAR domain[MAX_PATH] = { 0 }; DWORD domainNameLength = MAX_PATH; if (LookupAccountSid(NULL, userToken->User.Sid, user, &userNameLength, domain, &domainNameLength, &sidNameUse)) pi.processOwner = QString::fromUtf16(reinterpret_cast(user)); } } CloseHandle(processTokenHandle); CloseHandle(handle); return pi; } KProcessInfoList KProcessList::processInfoList() { KProcessInfoList rc; PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) return rc; for (bool hasNext = Process32First(snapshot, &pe); hasNext; hasNext = Process32Next(snapshot, &pe)) { const ProcessInfo processInf = winProcessInfo(pe.th32ProcessID); rc.push_back(KProcessInfo(pe.th32ProcessID, QString::fromUtf16(reinterpret_cast(pe.szExeFile)), processInf.processOwner)); } CloseHandle(snapshot); return rc; } KProcessInfo KProcessList::processInfo(qint64 pid) { KProcessInfoList processInfoList = KProcessList::processInfoList(); auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [pid](const KProcessList::KProcessInfo& info) { return info.pid() == pid; }); if (testProcessIterator != processInfoList.end()) { return *testProcessIterator; } return KProcessInfo(); } diff --git a/src/lib/util/kshell.cpp b/src/lib/util/kshell.cpp index 751990a..e55de36 100644 --- a/src/lib/util/kshell.cpp +++ b/src/lib/util/kshell.cpp @@ -1,83 +1,70 @@ /* This file is part of the KDE libraries - Copyright (c) 2003,2007 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kshell.h" #include "kshell_p.h" #include "kuser.h" #include QString KShell::homeDir(const QString &user) { if (user.isEmpty()) { return QDir::homePath(); } return KUser(user).homeDir(); } QString KShell::joinArgs(const QStringList &args) { QString ret; for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { if (!ret.isEmpty()) { ret.append(QLatin1Char(' ')); } ret.append(quoteArg(*it)); } return ret; } #ifdef Q_OS_WIN # define ESCAPE '^' #else # define ESCAPE '\\' #endif QString KShell::tildeExpand(const QString &fname) { if (!fname.isEmpty() && fname[0] == QLatin1Char('~')) { int pos = fname.indexOf(QLatin1Char('/')); if (pos < 0) { return homeDir(fname.mid(1)); } QString ret = homeDir(fname.mid(1, pos - 1)); if (!ret.isNull()) { ret += fname.midRef(pos); } return ret; } else if (fname.length() > 1 && fname[0] == QLatin1Char(ESCAPE) && fname[1] == QLatin1Char('~')) { return fname.mid(1); } return fname; } QString KShell::tildeCollapse(const QString &path) { if (!path.isEmpty()) { const auto homePath = QDir::homePath(); if (path.startsWith(homePath)) { auto newPath = path; newPath.replace(0, homePath.length(), QLatin1Char('~')); return newPath; } } return path; } diff --git a/src/lib/util/kshell.h b/src/lib/util/kshell.h index 0aa36ad..328a8fb 100644 --- a/src/lib/util/kshell.h +++ b/src/lib/util/kshell.h @@ -1,209 +1,196 @@ /* This file is part of the KDE libraries - Copyright (c) 2003,2007 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KSHELL_H #define KSHELL_H #include #include class QStringList; class QString; /** * \namespace KShell * Emulates some basic system shell functionality. * @see KStringHandler */ namespace KShell { /** * Flags for splitArgs(). */ enum Option { NoOptions = 0, /** * Perform tilde expansion. * On Windows, this flag is ignored, as the Windows shell has no * equivalent functionality. */ TildeExpand = 1, /** * Put the parser into full shell mode and bail out if a too complex * construct is encountered. * A particular purpose of this flag is finding out whether the * command line being split would be executable directly (via * KProcess::setProgram()) or whether it needs to be run through * a real shell (via KProcess::setShellCommand()). Note, however, * that shell builtins are @em not recognized - you need to do that * yourself (compare with a list of known commands or verify that an * executable exists for the named command). * * Meta characters that cause a bail-out are the command separators * @c semicolon and @c ampersand, the redirection symbols @c less-than, * @c greater-than and the @c pipe @c symbol and the grouping symbols * opening and closing @c parentheses. * * Further meta characters on *NIX are the grouping symbols * opening and closing @c braces, the command substitution symbol * @c backquote, the generic substitution symbol @c dollar (if * not followed by an apostrophe), the wildcards @c asterisk, * @c question @c mark and opening and closing @c square @c brackets * and the comment symbol @c hash @c mark. * Additionally, a variable assignment in the first word is recognized. * * A further meta character on Windows is the environment variable * expansion symbol @c percent. Occurrences of @c \%PERCENT_SIGN% as * inserted by quoteArg() are converted back and cause no bail-out, * though. */ AbortOnMeta = 2 }; Q_DECLARE_FLAGS(Options, Option) /** * Status codes from splitArgs() */ enum Errors { /** * Success. */ NoError = 0, /** * Indicates a parsing error, like an unterminated quoted string. */ BadQuoting, /** * The AbortOnMeta flag was set and an unhandled shell meta character * was encountered. */ FoundMeta }; /** * Splits @p cmd according to system shell word splitting and quoting rules. * Can optionally perform tilde expansion and/or abort if it finds shell * meta characters it cannot process. * * On *NIX the behavior is based on the POSIX shell and bash: * - Whitespace splits tokens * - The backslash quotes the following character * - A string enclosed in single quotes is not split. No shell meta * characters are interpreted. * - A string enclosed in double quotes is not split. Within the string, * the backslash quotes shell meta characters - if it is followed * by a "meaningless" character, the backslash is output verbatim. * - A string enclosed in $'' is not split. Within the string, the * backslash has a similar meaning to the one in C strings. Consult * the bash manual for more information. * * On Windows, the behavior is defined by the Microsoft C runtime. Qt and * many other implementations comply with this standard, but many do not. * - Whitespace splits tokens * - A string enclosed in double quotes is not split * - 2N double quotes within a quoted string yield N literal quotes. * This is not documented on MSDN. * - Backslashes have special semantics iff they are followed by a double * quote: * - 2N backslashes + double quote => N backslashes and begin/end quoting * - 2N+1 backslashes + double quote => N backslashes + literal quote * * If AbortOnMeta is used on Windows, this function applies cmd shell * semantics before proceeding with word splitting: * - Cmd ignores @em all special chars between double quotes. * Note that the quotes are @em not removed at this stage - the * tokenization rules described above still apply. * - The @c circumflex is the escape char for everything including * itself. * * @param cmd the command to split * @param flags operation flags, see \ref Option * @param err if not NULL, a status code will be stored at the pointer * target, see \ref Errors * @return a list of unquoted words or an empty list if an error occurred */ KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags = NoOptions, Errors *err = nullptr); /** * Quotes and joins @p args together according to system shell rules. * * If the output is fed back into splitArgs(), the AbortOnMeta flag * needs to be used on Windows. On *NIX, no such requirement exists. * * See quoteArg() for more info. * * @param args a list of strings to quote and join * @return a command suitable for shell execution */ KCOREADDONS_EXPORT QString joinArgs(const QStringList &args); /** * Quotes @p arg according to system shell rules. * * This function can be used to quote an argument string such that * the shell processes it properly. This is e.g. necessary for * user-provided file names which may contain spaces or quotes. * It also prevents expansion of wild cards and environment variables. * * On *NIX, the output is POSIX shell compliant. * On Windows, it is compliant with the argument splitting code of the * Microsoft C runtime and the cmd shell used together. * Occurrences of the @c percent @c sign are replaced with * @c \%PERCENT_SIGN% to prevent spurious variable expansion; * related KDE functions are prepared for this. * * @param arg the argument to quote * @return the quoted argument */ KCOREADDONS_EXPORT QString quoteArg(const QString &arg); /** * Performs tilde expansion on @p path. Interprets "~/path" and * "~user/path". If the path starts with an escaped tilde ("\~" on UNIX, * "^~" on Windows), the escape char is removed and the path is returned * as is. * * Note that if @p path starts with a tilde but cannot be properly expanded, * this function will return an empty string. * * @param path the path to tilde-expand * @return the expanded path */ KCOREADDONS_EXPORT QString tildeExpand(const QString &path); /** * Performs tilde collapse on @p path. If path did not start by the user * homedir returns path unchanged. * * @param path the path to tilde-collpase * @return the collapsed path * @since 5.67 */ KCOREADDONS_EXPORT QString tildeCollapse(const QString &path); } Q_DECLARE_OPERATORS_FOR_FLAGS(KShell::Options) #endif /* KSHELL_H */ diff --git a/src/lib/util/kshell_p.h b/src/lib/util/kshell_p.h index eae6655..70a6e1f 100644 --- a/src/lib/util/kshell_p.h +++ b/src/lib/util/kshell_p.h @@ -1,38 +1,25 @@ /* This file is part of the KDE libraries - Copyright (c) 2008 Oswald Buddenhagen - - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2008 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KSHELL_P_H #define KSHELL_P_H class QString; namespace KShell { QString homeDir(const QString &user); QString quoteArgInternal(const QString &arg, bool _inquote); } #define PERCENT_VARIABLE QLatin1String("PERCENT_SIGN") #define PERCENT_ESCAPE QLatin1String("%PERCENT_SIGN%") #endif /* KSHELL_P_H */ diff --git a/src/lib/util/kshell_unix.cpp b/src/lib/util/kshell_unix.cpp index 6d4dcdf..a33f03c 100644 --- a/src/lib/util/kshell_unix.cpp +++ b/src/lib/util/kshell_unix.cpp @@ -1,321 +1,308 @@ /* This file is part of the KDE libraries - Copyright (c) 2003,2007 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kshell.h" #include "kshell_p.h" #include #include #include static int fromHex(QChar cUnicode) { char c = cUnicode.toLatin1(); if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } return -1; } inline static bool isQuoteMeta(QChar cUnicode) { #if 0 // it's not worth it, especially after seeing gcc's asm output ... static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00 }; // \'"$ return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); #else char c = cUnicode.toLatin1(); return c == '\\' || c == '\'' || c == '"' || c == '$'; #endif } inline static bool isMeta(QChar cUnicode) { static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38 }; // \'"$`<>|;&(){}*?#[] uint c = cUnicode.unicode(); return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } QStringList KShell::splitArgs(const QString &args, Options flags, Errors *err) { QStringList ret; bool firstword = flags & AbortOnMeta; for (int pos = 0;;) { QChar c; do { if (pos >= args.length()) { goto okret; } c = args.unicode()[pos++]; } while (c == QLatin1Char(' ')); QString cret; if ((flags & TildeExpand) && c == QLatin1Char('~')) { int opos = pos; for (;; pos++) { if (pos >= args.length()) { break; } c = args.unicode()[pos]; if (c == QLatin1Char('/') || c == QLatin1Char(' ')) { break; } if (isQuoteMeta(c)) { pos = opos; c = QLatin1Char('~'); goto notilde; } if ((flags & AbortOnMeta) && isMeta(c)) { goto metaerr; } } QString ccret = homeDir(args.mid(opos, pos - opos)); if (ccret.isEmpty()) { pos = opos; c = QLatin1Char('~'); goto notilde; } if (pos >= args.length()) { ret += ccret; goto okret; } pos++; if (c == QLatin1Char(' ')) { ret += ccret; firstword = false; continue; } cret = ccret; } // before the notilde label, as a tilde does not match anyway if (firstword) { if (c == QLatin1Char('_') || (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || (c >= QLatin1Char('a') && c <= QLatin1Char('z'))) { int pos2 = pos; QChar cc; do { if (pos2 >= args.length()) { // Exactly one word ret += args.mid(pos - 1); goto okret; } cc = args.unicode()[pos2++]; } while (cc == QLatin1Char('_') || (cc >= QLatin1Char('A') && cc <= QLatin1Char('Z')) || (cc >= QLatin1Char('a') && cc <= QLatin1Char('z')) || (cc >= QLatin1Char('0') && cc <= QLatin1Char('9'))); if (cc == QLatin1Char('=')) { goto metaerr; } } } notilde: do { if (c == QLatin1Char('\'')) { int spos = pos; do { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; } while (c != QLatin1Char('\'')); cret += args.midRef(spos, pos - spos - 1); } else if (c == QLatin1Char('"')) { for (;;) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c == QLatin1Char('"')) { break; } if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c != QLatin1Char('"') && c != QLatin1Char('\\') && !((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`')))) { cret += QLatin1Char('\\'); } } else if ((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`'))) { goto metaerr; } cret += c; } } else if (c == QLatin1Char('$') && pos < args.length() && args.unicode()[pos] == QLatin1Char('\'')) { pos++; for (;;) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c == QLatin1Char('\'')) { break; } if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; switch (c.toLatin1()) { case 'a': cret += QLatin1Char('\a'); break; case 'b': cret += QLatin1Char('\b'); break; case 'e': cret += QLatin1Char('\033'); break; case 'f': cret += QLatin1Char('\f'); break; case 'n': cret += QLatin1Char('\n'); break; case 'r': cret += QLatin1Char('\r'); break; case 't': cret += QLatin1Char('\t'); break; case '\\': cret += QLatin1Char('\\'); break; case '\'': cret += QLatin1Char('\''); break; case 'c': if (pos >= args.length()) { goto quoteerr; } cret += args.unicode()[pos++].toLatin1() & 31; break; case 'x': { if (pos >= args.length()) { goto quoteerr; } int hv = fromHex(args.unicode()[pos++]); if (hv < 0) { goto quoteerr; } if (pos < args.length()) { int hhv = fromHex(args.unicode()[pos]); if (hhv > 0) { hv = hv * 16 + hhv; pos++; } cret += QChar(hv); } break; } default: if (c.toLatin1() >= '0' && c.toLatin1() <= '7') { char cAscii = c.toLatin1(); int hv = cAscii - '0'; for (int i = 0; i < 2; i++) { if (pos >= args.length()) { break; } c = args.unicode()[pos]; if (c.toLatin1() < '0' || c.toLatin1() > '7') { break; } hv = hv * 8 + (c.toLatin1() - '0'); pos++; } cret += QChar(hv); } else { cret += QLatin1Char('\\'); cret += c; } break; } } else { cret += c; } } } else { if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; } else if ((flags & AbortOnMeta) && isMeta(c)) { goto metaerr; } cret += c; } if (pos >= args.length()) { break; } c = args.unicode()[pos++]; } while (c != QLatin1Char(' ')); ret += cret; firstword = false; } okret: if (err) { *err = NoError; } return ret; quoteerr: if (err) { *err = BadQuoting; } return QStringList(); metaerr: if (err) { *err = FoundMeta; } return QStringList(); } inline static bool isSpecial(QChar cUnicode) { static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 }; // 0-32 \'"$`<>|;&(){}*?#!~[] uint c = cUnicode.unicode(); return ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))); } QString KShell::quoteArg(const QString &arg) { if (!arg.length()) { return QStringLiteral("''"); } for (int i = 0; i < arg.length(); i++) if (isSpecial(arg.unicode()[i])) { QChar q(QLatin1Char('\'')); return q + QString(arg).replace(q, QLatin1String("'\\''")) + q; } return arg; } diff --git a/src/lib/util/kshell_win.cpp b/src/lib/util/kshell_win.cpp index 47af887..5d7745e 100644 --- a/src/lib/util/kshell_win.cpp +++ b/src/lib/util/kshell_win.cpp @@ -1,277 +1,264 @@ /* This file is part of the KDE libraries - Copyright (c) 2007 Bernhard Loos - Copyright (c) 2007,2008 Oswald Buddenhagen + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-FileCopyrightText: 2007, 2008 Oswald Buddenhagen - This library 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 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kshell.h" #include "kshell_p.h" #include #include #include #include /* * A short introduction into cmd semantics: * - Variable expansion is done first, without regard to *any* escaping - * if something looks like an existing variable, it is replaced. * - Then follows regular tokenization by the shell. &, &&, | and || are * command delimiters. ( and ) are command grouping operators; they are * recognized only a the start resp. end of a command; mismatched )s are * an error if any (s are present. <, > are just like under UNIX - they can * appear *anywhere* in a command, perform their function and are cut out. * @ at the start of a command is eaten (local echo off - no function as * far as cmd /c is concerned). : at the start of a command declares a label, * which effectively means the remainder of the line is a comment - note that * command separators are not recognized past that point. * ^ is the escape char for everything including itself. * cmd ignores *all* special chars between double quotes, so there is no * way to escape the closing quote. Note that the quotes are *not* removed * from the resulting command line. * - Then follows delayed variable expansion if it is enabled and at least * one exclamation mark is present. This involves another layer of ^ * escaping, regardless of quotes. (Win2k+) * - Then follows argument splitting as described in * http://msdn2.microsoft.com/en-us/library/ms880421.aspx . * Note that this is done by the called application and therefore might * be subject to completely different semantics, in fact. */ inline static bool isMetaChar(ushort c) { static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 }; // &()<>| return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } inline static bool isSpecialChar(ushort c) { // Chars that should be quoted (TM). This includes: // - control chars & space // - the shell meta chars &()<>^| // - the potential separators ,;= static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 }; return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } inline static bool isWhiteSpace(ushort c) { return c == ' ' || c == '\t'; } QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err) { QString args(_args); QStringList ret; const QLatin1Char bs('\\'), dq('\"'); if (flags & AbortOnMeta) { args.remove(PERCENT_ESCAPE); if (args.indexOf(QLatin1Char('%')) >= 0) { if (err) { *err = FoundMeta; } return QStringList(); } args = _args; args.replace(PERCENT_ESCAPE, QLatin1String("%")); if (!args.isEmpty() && args[0].unicode() == '@') { args.remove(0, 1); } for (int p = 0; p < args.length(); p++) { ushort c = args[p].unicode(); if (c == '^') { args.remove(p, 1); } else if (c == '"') { while (++p < args.length() && args[p].unicode() != '"') ; } else if (isMetaChar(c)) { if (err) { *err = FoundMeta; } return QStringList(); } } } if (err) { *err = NoError; } int p = 0; const int length = args.length(); forever { while (p < length && isWhiteSpace(args[p].unicode())) { ++p; } if (p == length) { return ret; } QString arg; bool inquote = false; forever { bool copy = true; // copy this char int bslashes = 0; // number of preceding backslashes to insert while (p < length && args[p] == bs) { ++p; ++bslashes; } if (p < length && args[p] == dq) { if (bslashes % 2 == 0) { // Even number of backslashes, so the quote is not escaped. if (inquote) { if (p + 1 < length && args[p + 1] == dq) { // Two consecutive quotes make a literal quote. // This is not documented on MSDN. ++p; } else { // Closing quote copy = false; inquote = !inquote; } } else { // Opening quote copy = false; inquote = !inquote; } } bslashes /= 2; } while (--bslashes >= 0) { arg.append(bs); } if (p == length || (!inquote && isWhiteSpace(args[p].unicode()))) { ret.append(arg); if (inquote) { if (err) { *err = BadQuoting; } return QStringList(); } break; } if (copy) { arg.append(args[p]); } ++p; } } //not reached } QString KShell::quoteArgInternal(const QString &arg, bool _inquote) { // Escape quotes, preceding backslashes are doubled. Surround with quotes. // Note that cmd does not understand quote escapes in quoted strings, // so the quoting needs to be "suspended". const QLatin1Char bs('\\'), dq('\"'); QString ret; bool inquote = _inquote; int bslashes = 0; for (int p = 0; p < arg.length(); p++) { if (arg[p] == bs) { bslashes++; } else if (arg[p] == dq) { if (inquote) { ret.append(dq); inquote = false; } for (; bslashes; bslashes--) { ret.append(QLatin1String("\\\\")); } ret.append(QLatin1String("\\^\"")); } else { if (!inquote) { ret.append(dq); inquote = true; } for (; bslashes; bslashes--) { ret.append(bs); } ret.append(arg[p]); } } ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); if (bslashes) { // Ensure that we don't have directly trailing backslashes, // so concatenating with another string won't cause surprises. if (!inquote && !_inquote) { ret.append(dq); } for (; bslashes; bslashes--) { ret.append(QLatin1String("\\\\")); } ret.append(dq); if (inquote && _inquote) { ret.append(dq); } } else if (inquote != _inquote) { ret.append(dq); } return ret; } QString KShell::quoteArg(const QString &arg) { if (arg.isEmpty()) { return QStringLiteral("\"\""); } // Ensure that we don't have directly trailing backslashes, // so concatenating with another string won't cause surprises. if (arg.endsWith(QLatin1Char('\\'))) { return quoteArgInternal(arg, false); } for (int x = arg.length() - 1; x >= 0; --x) if (isSpecialChar(arg[x].unicode())) { return quoteArgInternal(arg, false); } // Escape quotes. Preceding backslashes are doubled. // Note that the remaining string is not quoted. QString ret(arg); ret.replace(QRegularExpression(QStringLiteral("(\\\\*)\"")), QStringLiteral("\\1\\1\\^\"")); ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); return ret; } diff --git a/src/lib/util/kuser.h b/src/lib/util/kuser.h index ff1514e..315b748 100644 --- a/src/lib/util/kuser.h +++ b/src/lib/util/kuser.h @@ -1,643 +1,631 @@ /* - * KUser - represent a user/account - * Copyright (C) 2002-2003 Tim Jansen - * Copyright (C) 2003 Oswald Buddenhagen - * Copyright (C) 2004 Jan Schaefer - * Copyright (C) 2014 Alex Richardson - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + KUser - represent a user/account + + SPDX-FileCopyrightText: 2002-2003 Tim Jansen + SPDX-FileCopyrightText: 2003 Oswald Buddenhagen + SPDX-FileCopyrightText: 2004 Jan Schaefer + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef KUSER_H #define KUSER_H #include #include #include #include class KUserGroup; class QString; class QStringList; #ifdef Q_OS_WIN typedef void *K_UID; typedef void *K_GID; struct WindowsSIDWrapper; #else #include typedef uid_t K_UID; typedef gid_t K_GID; struct passwd; struct group; #endif // The following is to avoid compile errors using msvc, and it is done // using a common #define to avoid helpful people accidentally cleaning this // not quite pretty thing and breaking it for people on windows. // See https://git.reviewboard.kde.org/r/127598/ for details #define KCOREADDONS_UINT_MAX (std::numeric_limits::max)() /** A platform independent user or group ID. * * * This struct is required since Windows does not have an integer uid_t/gid_t type * but instead uses an opaque binary blob (SID) which must free allocated memory. * On UNIX this is simply a uid_t/gid_t and all operations are inline, so there is * no runtime overhead over using the uid_t/gid_t directly. On Windows this is an implicitly * shared class that frees the underlying SID once no more references remain. * * Unlike KUser/KUserGroup this does not query additional information, it is simply * an abstraction over the native user/group ID type. If more information is necessary, a * KUser or KUserGroup instance can be constructed from this ID * * @internal * @author Alex Richardson */ template struct KCOREADDONS_EXPORT KUserOrGroupId { typedef T NativeType; protected: /** Creates an invalid KUserOrGroupId */ KUserOrGroupId(); /** Creates a KUserOrGroupId from a native user/group ID. On windows this will not take * ownership over the passed SID, a copy will be created instead. */ explicit KUserOrGroupId(NativeType nativeId); /** Copy constructor. This is very fast, objects can be passed by value */ KUserOrGroupId(const KUserOrGroupId &other); KUserOrGroupId &operator=(const KUserOrGroupId &other); ~KUserOrGroupId(); public: /** @return true if this object references a valid user/group ID. * @note If this returns true it doesn't necessarily mean that the referenced user/group exists, * it only checks whether this value could be a valid user/group ID. */ bool isValid() const; /** * @return A user/group ID that can be used in operating system specific functions * @note On Windows the returned pointer will be freed once the last KUserOrGroupId referencing * this user/group ID is deleted. Make sure that the KUserOrGroupId object remains valid as * long as the native pointer is needed. */ NativeType nativeId() const; /** @return A string representation of this user ID, not the name of the user * On UNIX this is a simple integer, e.g. "0" for root. On Windows this is a string * like e.g. "S-1-5-32-544" for the Administrators group */ QString toString() const; /** @return whether this KUserOrGroupId is equal to @p other */ bool operator==(const KUserOrGroupId &other) const; /** @return whether this KUserOrGroupId is not equal to @p other */ bool operator!=(const KUserOrGroupId &other) const; private: #ifdef Q_OS_WIN QExplicitlySharedDataPointer data; #else NativeType id; #endif }; #ifdef Q_OS_WIN template<> KUserOrGroupId::KUserOrGroupId(); template<> KUserOrGroupId::~KUserOrGroupId(); template<> KUserOrGroupId::KUserOrGroupId(KUserOrGroupId::NativeType nativeId); template<> KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other); template<> KUserOrGroupId& KUserOrGroupId::operator=(const KUserOrGroupId &other); template<> bool KUserOrGroupId::isValid() const; template<> KUserOrGroupId::NativeType KUserOrGroupId::nativeId() const; template<> QString KUserOrGroupId::toString() const; template<> bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const; template<> bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const; #endif /** A platform independent user ID. * @see KUserOrGroupId * @since 5.0 * @author Alex Richardson */ struct KCOREADDONS_EXPORT KUserId : public KUserOrGroupId { /** Creates an invalid KUserId */ KUserId() {} /** Creates an KUserId from the native user ID type */ explicit KUserId(K_UID uid) : KUserOrGroupId(uid) {} KUserId(const KUserId &other) : KUserOrGroupId(other) {} ~KUserId() {} /** @return a KUserId for the user with name @p name, or an invalid KUserId if no * user with this name was found on the system */ static KUserId fromName(const QString &name); /** @return a KUserId for the current user. This is like ::getuid() on UNIX. */ static KUserId currentUserId(); /** @return a KUserId for the current effective user. This is like ::geteuid() on UNIX. * @note Windows does not have setuid binaries, so on Windows this will always be the same * as KUserId::currentUserId() */ static KUserId currentEffectiveUserId(); }; /** A platform independent group ID. * @see KUserOrGroupId * @since 5.0 * @author Alex Richardson */ struct KCOREADDONS_EXPORT KGroupId : public KUserOrGroupId { /** Creates an invalid KGroupId */ KGroupId() {} /** Creates an KGroupId from the native group ID type */ explicit KGroupId(K_GID gid) : KUserOrGroupId(gid) {} KGroupId(const KGroupId &other) : KUserOrGroupId(other) {} ~KGroupId() {} /** @return A KGroupId for the user with name @p name, or an invalid KGroupId if no * user with this name was found on the system */ static KGroupId fromName(const QString &name); /** @return a KGroupId for the current user. This is like ::getgid() on UNIX. */ static KGroupId currentGroupId(); /** @return a KGroupId for the current effective user. This is like ::getegid() on UNIX. * @note Windows does not have setuid binaries, so on Windows this will always be the same * as KGroupId::currentGroupId() */ static KGroupId currentEffectiveGroupId(); }; #ifndef Q_OS_WIN inline uint qHash(const KUserId &id, uint seed = 0) { return qHash(id.nativeId(), seed); } inline uint qHash(const KGroupId &id, uint seed = 0) { return qHash(id.nativeId(), seed); } #else // can't be inline on windows, because we would need to include windows.h (which can break code) KCOREADDONS_EXPORT uint qHash(const KUserId &id, uint seed = 0); KCOREADDONS_EXPORT uint qHash(const KGroupId &id, uint seed = 0); #endif /** * \class KUser kuser.h * * @short Represents a user on your system * * This class represents a user on your system. You can either get * information about the current user, of fetch information about * a user on the system. Instances of this class will be explicitly shared, * so copying objects is very cheap and you can safely pass objects by value. * * @author Tim Jansen */ class KCOREADDONS_EXPORT KUser { public: enum UIDMode { UseEffectiveUID, ///< Use the effective user id. UseRealUserID ///< Use the real user id. }; /** * Creates an object that contains information about the current user. * (as returned by getuid(2) or geteuid(2), taking $LOGNAME/$USER into * account). * @param mode if #UseEffectiveUID is passed the effective * user is returned. * If #UseRealUserID is passed the real user will be * returned. * The real UID will be different than the effective UID in setuid * programs; in * such a case use the effective UID for checking permissions, and * the real UID for displaying information about the user. */ explicit KUser(UIDMode mode = UseEffectiveUID); /** * Creates an object for the user with the given user id. * If the user does not exist isValid() will return false. * @param uid the user id */ explicit KUser(K_UID uid); /** * Creates an object for the user with the given user id. * If the KUserId object is invalid this one will be, too. * @param uid the user id */ explicit KUser(KUserId uid); /** * Creates an object that contains information about the user with the given * name. If the user does not exist isValid() will return false. * * @param name the name of the user */ explicit KUser(const QString &name); /** * Creates an object that contains information about the user with the given * name. If the user does not exist isValid() will return false. * * @param name the name of the user */ explicit KUser(const char *name); #ifndef Q_OS_WIN /** * Creates an object from a passwd structure. * If the pointer is null isValid() will return false. * * @param p the passwd structure to create the user from */ explicit KUser(const passwd *p); #endif /** * Creates an object from another KUser object * @param user the user to create the new object from */ KUser(const KUser &user); /** * Copies a user * @param user the user to copy * @return this object */ KUser &operator =(const KUser &user); /** * Two KUser objects are equal if the userId() are identical. * Invalid users never compare equal. */ bool operator ==(const KUser &user) const; /** * Two KUser objects are not equal if userId() are not identical. * Invalid users always compare unequal. */ bool operator !=(const KUser &user) const; /** * Returns true if the user is valid. A KUser object can be invalid if * you created it with an non-existing uid or name. * @return true if the user is valid */ bool isValid() const; /** @return the native user id of the user. */ KUserId userId() const; /** @return the native user id of the user. */ KGroupId groupId() const; #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns the group id of the user. * @return the id of the group or -1 if user is invalid * @deprecated since 5.0 use KUser::groupId() */ KCOREADDONS_DEPRECATED_VERSION(5, 0, "Use KUser::groupId()") K_GID gid() const { return groupId().nativeId(); } #endif /** * Checks whether the user is the super user (root). * @return true if the user is root */ bool isSuperUser() const; /** * The login name of the user. * @return the login name of the user or QString() if user is invalid */ QString loginName() const; #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 0) /** * The full name of the user. * @return the full name of the user or QString() if user is invalid * @deprecated Since 5.0, use property(KUser::FullName) instead */ KCOREADDONS_DEPRECATED_VERSION(5, 0, "Use KUser::property(KUser::FullName).toString()") QString fullName() const { return property(FullName).toString(); } /** * Returns the user id of the user. * @return the id of the user or -1 (UNIX)/ null(Windows) if user is invalid * @deprecated since 5.0 use KUser::userId() */ KCOREADDONS_DEPRECATED_VERSION(5, 0, "Use KUser::userId().nativeId()") K_UID uid() const { return userId().nativeId(); } #endif /** * The path to the user's home directory. * @return the home directory of the user or QString() if the * user is invalid */ QString homeDir() const; /** * The path to the user's face file. * @return the path to the user's face file or QString() if no * face has been set */ QString faceIconPath() const; /** * The path to the user's login shell. * @return the login shell of the user or QString() if the * user is invalid */ QString shell() const; /** * @param maxCount the maximum number of groups to return * @return all groups of the user */ QList groups(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * @param maxCount the maximum number of groups to return * @return all group names of the user */ QStringList groupNames(uint maxCount = KCOREADDONS_UINT_MAX) const; enum UserProperty { FullName, RoomNumber, WorkPhone, HomePhone }; /** * Returns an extended property. * * Under Windows, @p RoomNumber, @p WorkPhone and @p HomePhone are unsupported. * * @return a QVariant with the value of the property or an invalid QVariant, * if the property is not set */ QVariant property(UserProperty which) const; /** * Destructor. */ ~KUser(); /** * @param maxCount the maximum number of users to return * @return all users of the system. */ static QList allUsers(uint maxCount = KCOREADDONS_UINT_MAX); /** * @param maxCount the maximum number of users to return * @return all user names of the system. */ static QStringList allUserNames(uint maxCount = KCOREADDONS_UINT_MAX); private: class Private; QExplicitlySharedDataPointer d; }; /** * \class KUserGroup kuser.h * * @short Represents a group on your system * * This class represents a group on your system. You can either get * information about the group of the current user, of fetch information about * a group on the system. Instances of this class will be explicitly shared, * so copying objects is very cheap and you can safely pass objects by value. * * @author Jan Schaefer */ class KCOREADDONS_EXPORT KUserGroup { public: /** * Create an object from a group name. * If the group does not exist, isValid() will return false. * @param name the name of the group */ explicit KUserGroup(const QString &name); /** * Create an object from a group name. * If the group does not exist, isValid() will return false. * @param name the name of the group */ explicit KUserGroup(const char *name); /** * Creates an object for the group with the given group id. * If the KGroupId object is invalid this one will be, too. * @param gid the group id */ explicit KUserGroup(KGroupId gid); /** * Create an object from the group of the current user. * @param mode if #KUser::UseEffectiveUID is passed the effective user * will be used. If #KUser::UseRealUserID is passed the real user * will be used. * The real UID will be different than the effective UID in setuid * programs; in such a case use the effective UID for checking * permissions, and the real UID for displaying information about * the group associated with the user. */ explicit KUserGroup(KUser::UIDMode mode = KUser::UseEffectiveUID); /** * Create an object from a group id. * If the group does not exist, isValid() will return false. * @param gid the group id */ explicit KUserGroup(K_GID gid); #ifndef Q_OS_WIN /** * Creates an object from a group structure. * If the pointer is null, isValid() will return false. * @param g the group structure to create the group from. */ explicit KUserGroup(const group *g); #endif /** * Creates a new KUserGroup instance from another KUserGroup object * @param group the KUserGroup to copy */ KUserGroup(const KUserGroup &group); /** * Copies a group * @param group the group that should be copied * @return this group */ KUserGroup &operator =(const KUserGroup &group); /** * Two KUserGroup objects are equal if their gid()s are identical. * Invalid groups never compare equal. * @return true if the groups are identical */ bool operator ==(const KUserGroup &group) const; /** * Two KUserGroup objects are not equal if their gid()s are not identical. * Invalid groups always compare unequal. * @return true if the groups are not identical */ bool operator !=(const KUserGroup &group) const; /** * Returns whether the group is valid. * A KUserGroup object can be invalid if it is * created with a non-existing gid or name. * @return true if the group is valid */ bool isValid() const; #if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns the group id of the group. * @return the group id of the group or -1 if the group is invalid * @deprecated since 5.0 use KUserGroup::groupId() */ KCOREADDONS_DEPRECATED_VERSION(5, 0, "Use KUserGroup::groupId().nativeId()") K_GID gid() const { return groupId().nativeId(); } #endif /** @return the native group id of the user. */ KGroupId groupId() const; /** * The name of the group. * @return the name of the group */ QString name() const; /** * @param maxCount the maximum number of users to return * @return a list of all users of the group */ QList users(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * @param maxCount the maximum number of groups to return * @return a list of all user login names of the group */ QStringList userNames(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * Destructor. */ ~KUserGroup(); /** * @param maxCount the maximum number of groups to return * @return a list of all groups on this system */ static QList allGroups(uint maxCount = KCOREADDONS_UINT_MAX); /** * @param maxCount the maximum number of groups to return * @return a list of all group names on this system */ static QStringList allGroupNames(uint maxCount = KCOREADDONS_UINT_MAX); private: class Private; QSharedDataPointer d; }; #if !defined(Q_OS_WIN) // inline UNIX implementation of KUserOrGroupId template inline bool KUserOrGroupId::isValid() const { return id != NativeType(-1); } template inline bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const { return id == other.id; } template inline bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const { return id != other.id; } template inline typename KUserOrGroupId::NativeType KUserOrGroupId::nativeId() const { return id; } template inline QString KUserOrGroupId::toString() const { return QString::number(id); } template inline KUserOrGroupId::KUserOrGroupId() : id(-1) { } template inline KUserOrGroupId::KUserOrGroupId(KUserOrGroupId::NativeType nativeId) : id(nativeId) { } template inline KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other) : id(other.id) { } template inline KUserOrGroupId &KUserOrGroupId::operator=(const KUserOrGroupId &other) { id = other.id; return *this; } template inline KUserOrGroupId::~KUserOrGroupId() { } #endif // !defined(Q_OS_WIN) inline bool KUser::operator!=(const KUser &other) const { return !operator==(other); } inline bool KUserGroup::operator!=(const KUserGroup &other) const { return !operator==(other); } #endif diff --git a/src/lib/util/kuser_unix.cpp b/src/lib/util/kuser_unix.cpp index 28713a4..8b45ebd 100644 --- a/src/lib/util/kuser_unix.cpp +++ b/src/lib/util/kuser_unix.cpp @@ -1,562 +1,550 @@ /* - * KUser - represent a user/account - * Copyright (C) 2002 Tim Jansen - * Copyright (C) 2014 Alex Richardson - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + KUser - represent a user/account + + SPDX-FileCopyrightText: 2002 Tim Jansen + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "kuser.h" #include "config-getgrouplist.h" #include "config-accountsservice.h" #include #include #include #include #include #include #include // std::find #include // std::function #if defined(__BIONIC__) && __ANDROID_API__ < 26 static inline struct passwd * getpwent() { return nullptr; } inline void setpwent() { } static inline void setgrent() { } static inline struct group * getgrent() { return nullptr; } inline void endpwent() { } static inline void endgrent() { } #endif class Q_DECL_HIDDEN KUser::Private : public QSharedData { public: uid_t uid; gid_t gid; QString loginName; QString homeDir, shell; QMap properties; Private() : uid(uid_t(-1)), gid(gid_t(-1)) {} Private(const char *name) : uid(uid_t(-1)), gid(gid_t(-1)) { fillPasswd(name ? ::getpwnam(name) : nullptr); } Private(const passwd *p) : uid(uid_t(-1)), gid(gid_t(-1)) { fillPasswd(p); } void fillPasswd(const passwd *p) { if (p) { #ifndef __BIONIC__ QString gecos = QString::fromLocal8Bit(p->pw_gecos); #else QString gecos = QString(); #endif QStringList gecosList = gecos.split(QLatin1Char(',')); // fill up the list, should be at least 4 entries while (gecosList.size() < 4) { gecosList << QString(); } uid = p->pw_uid; gid = p->pw_gid; loginName = QString::fromLocal8Bit(p->pw_name); properties[KUser::FullName] = QVariant(gecosList[0]); properties[KUser::RoomNumber] = QVariant(gecosList[1]); properties[KUser::WorkPhone] = QVariant(gecosList[2]); properties[KUser::HomePhone] = QVariant(gecosList[3]); if (uid == ::getuid() && uid == ::geteuid()) { homeDir = QFile::decodeName(qgetenv("HOME")); } if (homeDir.isEmpty()) { homeDir = QFile::decodeName(p->pw_dir); } shell = QString::fromLocal8Bit(p->pw_shell); } } }; KUser::KUser(UIDMode mode) { uid_t _uid = ::getuid(), _euid; if (mode == UseEffectiveUID && (_euid = ::geteuid()) != _uid) { d = new Private(::getpwuid(_euid)); } else { d = new Private(qgetenv("LOGNAME").constData()); if (d->uid != _uid) { d = new Private(qgetenv("USER").constData()); if (d->uid != _uid) { d = new Private(::getpwuid(_uid)); } } } } KUser::KUser(K_UID _uid) : d(new Private(::getpwuid(_uid))) { } KUser::KUser(KUserId _uid) : d(new Private(::getpwuid(_uid.nativeId()))) { } KUser::KUser(const QString &name) : d(new Private(name.toLocal8Bit().data())) { } KUser::KUser(const char *name) : d(new Private(name)) { } KUser::KUser(const passwd *p) : d(new Private(p)) { } KUser::KUser(const KUser &user) : d(user.d) { } KUser &KUser::operator =(const KUser &user) { d = user.d; return *this; } bool KUser::operator ==(const KUser &user) const { return isValid() && (d->uid == user.d->uid); } bool KUser::isValid() const { return d->uid != uid_t(-1); } KUserId KUser::userId() const { return KUserId(d->uid); } KGroupId KUser::groupId() const { return KGroupId(d->gid); } bool KUser::isSuperUser() const { return d->uid == 0; } QString KUser::loginName() const { return d->loginName; } QString KUser::homeDir() const { return d->homeDir; } QString KUser::faceIconPath() const { QString pathToFaceIcon; if (!d->loginName.isEmpty()) { pathToFaceIcon = QStringLiteral(ACCOUNTS_SERVICE_ICON_DIR) + QLatin1Char('/') + d->loginName; } if (QFile::exists(pathToFaceIcon)) { return pathToFaceIcon; } pathToFaceIcon = homeDir() + QLatin1Char('/') + QLatin1String(".face.icon"); if (QFileInfo(pathToFaceIcon).isReadable()) { return pathToFaceIcon; } return QString(); } QString KUser::shell() const { return d->shell; } template static void listGroupsForUser(const char *name, gid_t gid, uint maxCount, Func handleNextGroup) { if (Q_UNLIKELY(maxCount == 0)) { return; } uint found = 0; #if HAVE_GETGROUPLIST QVarLengthArray gid_buffer; gid_buffer.resize(100); int numGroups = gid_buffer.size(); int result = getgrouplist(name, gid, gid_buffer.data(), &numGroups); if (result < 0 && uint(numGroups) < maxCount) { // getgrouplist returns -1 if the buffer was too small to store all entries, the required size is in numGroups qDebug("Buffer was too small: %d, need %d", gid_buffer.size(), numGroups); gid_buffer.resize(numGroups); numGroups = gid_buffer.size(); getgrouplist(name, gid, gid_buffer.data(), &numGroups); } for (int i = 0; i < numGroups && found < maxCount; ++i) { struct group *g = getgrgid(gid_buffer[i]); // should never be null, but better be safe than crash if (g) { found++; handleNextGroup(g); } } #else // fall back to getgrent() and reading gr->gr_mem // This is slower than getgrouplist, but works as well // add the current gid, this is often not part of g->gr_mem (e.g. build.kde.org or my openSuSE 13.1 system) struct group *g = getgrgid(gid); if (g) { handleNextGroup(g); found++; if (found >= maxCount) { return; } } static const auto groupContainsUser = [](struct group * g, const char *name) -> bool { for (char **user = g->gr_mem; *user; user++) { if (strcmp(name, *user) == 0) { return true; } } return false; }; setgrent(); while ((g = getgrent())) { // don't add the current gid again if (g->gr_gid != gid && groupContainsUser(g, name)) { handleNextGroup(g); found++; if (found >= maxCount) { break; } } } endgrent(); #endif } QList KUser::groups(uint maxCount) const { QList result; listGroupsForUser( d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group * g) { result.append(KUserGroup(g)); } ); return result; } QStringList KUser::groupNames(uint maxCount) const { QStringList result; listGroupsForUser( d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group * g) { result.append(QString::fromLocal8Bit(g->gr_name)); } ); return result; } QVariant KUser::property(UserProperty which) const { return d->properties.value(which); } QList KUser::allUsers(uint maxCount) { QList result; passwd *p; setpwent(); for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { result.append(KUser(p)); } endpwent(); return result; } QStringList KUser::allUserNames(uint maxCount) { QStringList result; passwd *p; setpwent(); for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { result.append(QString::fromLocal8Bit(p->pw_name)); } endpwent(); return result; } KUser::~KUser() { } class Q_DECL_HIDDEN KUserGroup::Private : public QSharedData { public: gid_t gid; QString name; Private() : gid(gid_t(-1)) {} Private(const char *_name) : gid(gid_t(-1)) { fillGroup(_name ? ::getgrnam(_name) : nullptr); } Private(const ::group *p) : gid(gid_t(-1)) { fillGroup(p); } void fillGroup(const ::group *p) { if (p) { gid = p->gr_gid; name = QString::fromLocal8Bit(p->gr_name); } } }; KUserGroup::KUserGroup(KUser::UIDMode mode) { d = new Private(getgrgid(KUser(mode).groupId().nativeId())); } KUserGroup::KUserGroup(K_GID _gid) : d(new Private(getgrgid(_gid))) { } KUserGroup::KUserGroup(KGroupId _gid) : d(new Private(getgrgid(_gid.nativeId()))) { } KUserGroup::KUserGroup(const QString &_name) : d(new Private(_name.toLocal8Bit().data())) { } KUserGroup::KUserGroup(const char *_name) : d(new Private(_name)) { } KUserGroup::KUserGroup(const ::group *g) : d(new Private(g)) { } KUserGroup::KUserGroup(const KUserGroup &group) : d(group.d) { } KUserGroup &KUserGroup::operator =(const KUserGroup &group) { d = group.d; return *this; } bool KUserGroup::operator ==(const KUserGroup &group) const { return isValid() && (d->gid == group.d->gid); } bool KUserGroup::isValid() const { return d->gid != gid_t(-1); } KGroupId KUserGroup::groupId() const { return KGroupId(d->gid); } QString KUserGroup::name() const { return d->name; } static void listGroupMembers(gid_t gid, uint maxCount, std::function handleNextGroupUser) { if (maxCount == 0) { return; } struct group *g = getgrgid(gid); if (!g) { return; } uint found = 0; QVarLengthArray addedUsers; struct passwd *p = nullptr; for (char **user = g->gr_mem; *user; user++) { if ((p = getpwnam(*user))) { addedUsers.append(p->pw_uid); handleNextGroupUser(p); found++; if (found >= maxCount) { break; } } } //gr_mem doesn't contain users where the primary group == gid -> we have to iterate over all users setpwent(); while ((p = getpwent()) && found < maxCount) { if (p->pw_gid != gid) { continue; // only need primary gid since otherwise gr_mem already contains this user } // make sure we don't list a user twice if (std::find(addedUsers.cbegin(), addedUsers.cend(), p->pw_uid) == addedUsers.cend()) { handleNextGroupUser(p); found++; } } endpwent(); } QList KUserGroup::users(uint maxCount) const { QList result; listGroupMembers(d->gid, maxCount, [&](const passwd *p) { result.append(KUser(p)); }); return result; } QStringList KUserGroup::userNames(uint maxCount) const { QStringList result; listGroupMembers(d->gid, maxCount, [&](const passwd *p) { result.append(QString::fromLocal8Bit(p->pw_name)); }); return result; } QList KUserGroup::allGroups(uint maxCount) { QList result; ::group *g; setgrent(); for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { result.append(KUserGroup(g)); } endgrent(); return result; } QStringList KUserGroup::allGroupNames(uint maxCount) { QStringList result; ::group *g; setgrent(); for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { result.append(QString::fromLocal8Bit(g->gr_name)); } endgrent(); return result; } KUserGroup::~KUserGroup() { } KUserId KUserId::fromName(const QString &name) { if (name.isEmpty()) { return KUserId(); } QByteArray name8Bit = name.toLocal8Bit(); struct passwd *p = ::getpwnam(name8Bit.constData()); if (!p) { qWarning("Failed to lookup user %s: %s", name8Bit.constData(), strerror(errno)); return KUserId(); } return KUserId(p->pw_uid); } KGroupId KGroupId::fromName(const QString &name) { if (name.isEmpty()) { return KGroupId(); } QByteArray name8Bit = name.toLocal8Bit(); struct group *g = ::getgrnam(name8Bit.constData()); if (!g) { qWarning("Failed to lookup group %s: %s", name8Bit.constData(), strerror(errno)); return KGroupId(); } return KGroupId(g->gr_gid); } KUserId KUserId::currentUserId() { return KUserId(getuid()); } KUserId KUserId::currentEffectiveUserId() { return KUserId(geteuid()); } KGroupId KGroupId::currentGroupId() { return KGroupId(getgid()); } KGroupId KGroupId::currentEffectiveGroupId() { return KGroupId(getegid()); } diff --git a/src/lib/util/kuser_win.cpp b/src/lib/util/kuser_win.cpp index ec07ef6..c00adc9 100644 --- a/src/lib/util/kuser_win.cpp +++ b/src/lib/util/kuser_win.cpp @@ -1,911 +1,899 @@ /* - * KUser - represent a user/account (Windows) - * Copyright (C) 2007 Bernhard Loos - * Copyright (C) 2014 Alex Richardson - * - * This library 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 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + KUser - represent a user/account (Windows) + + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-FileCopyrightText: 2014 Alex Richardson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #include "kuser.h" #include "kcoreaddons_debug.h" #include #include #include // unique_ptr #include #include #include //Net* #include //GetProfilesDirectoryW #include //ConvertSidToStringSidW #include // this can't be a lambda due to a MSVC2012 bug // (works fine in 2010 and 2013) struct netApiBufferDeleter { void operator()(void *buffer) { if (buffer) { NetApiBufferFree(buffer); } } }; template class ScopedNetApiBuffer : public std::unique_ptr { public: // explicit scope resolution operator needed in ::netApiBufferDeleter // because of *another* MSVC bug :( inline explicit ScopedNetApiBuffer(T *data) : std::unique_ptr(data, ::netApiBufferDeleter()) {} }; const auto handleCloser = [](HANDLE h) { if (h != INVALID_HANDLE_VALUE) { CloseHandle(h); } }; typedef std::unique_ptr::type, decltype(handleCloser)> ScopedHANDLE; /** Make sure the NetApi functions are called with the correct level argument (for template functions) * This argument can be retrieved by using NetApiTypeInfo::level. In order to do so the type must be * registered by writing e.g. NETAPI_TYPE_INFO(GROUP_INFO, 0) for GROUP_INFO_0 */ template struct NetApiTypeInfo {}; #define NETAPI_TYPE_INFO(prefix, n) template<> struct NetApiTypeInfo { enum { level = n }; }; NETAPI_TYPE_INFO(GROUP_INFO, 0) NETAPI_TYPE_INFO(GROUP_INFO, 3) NETAPI_TYPE_INFO(USER_INFO, 0) NETAPI_TYPE_INFO(USER_INFO, 4) NETAPI_TYPE_INFO(USER_INFO, 11) NETAPI_TYPE_INFO(GROUP_USERS_INFO, 0) // T must be a USER_INFO_* structure template ScopedNetApiBuffer getUserInfo(LPCWSTR server, const QString &userName, NET_API_STATUS *errCode) { LPBYTE userInfoTmp = nullptr; // if level = 11 a USER_INFO_11 structure gets filled in and allocated by NetUserGetInfo(), etc. NET_API_STATUS status = NetUserGetInfo(server, (LPCWSTR)userName.utf16(), NetApiTypeInfo::level, &userInfoTmp); if (status != NERR_Success) { userInfoTmp = nullptr; } if (errCode) { *errCode = status; } return ScopedNetApiBuffer((T*)userInfoTmp); } //enumeration functions /** simplify calling the Net*Enum functions to prevent copy and paste for allUsers(), allUserNames(), allGroups(), allGroupNames() * @tparam T The type that is enumerated (e.g. USER_INFO_11) Must be registered using NETAPI_TYPE_INFO. * @param callback Callback for each listed object. Signature: void(const T&) * @param enumFunc This function enumerates the data using a Net* function. * It will be called in a loop as long as it returns ERROR_MORE_DATA. * */ template static void netApiEnumerate(uint maxCount, Callback callback, EnumFunction enumFunc) { NET_API_STATUS nStatus = NERR_Success; DWORD_PTR resumeHandle = 0; uint total = 0; int level = NetApiTypeInfo::level; do { LPBYTE buffer = nullptr; DWORD entriesRead = 0; DWORD totalEntries = 0; nStatus = enumFunc(level, &buffer, &entriesRead, &totalEntries, &resumeHandle); //qDebug("Net*Enum(level = %d) returned %d entries, total was (%d), status = %d, resume handle = %llx", // level, entriesRead, totalEntries, nStatus, resumeHandle); // buffer must always be freed, even if Net*Enum fails ScopedNetApiBuffer groupInfo((T*)buffer); if (nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) { for (DWORD i = 0; total < maxCount && i < entriesRead; i++, total++) { callback(groupInfo.get()[i]); } } else { qWarning("NetApi enumerate function failed: status = %d", (int)nStatus); } } while (nStatus == ERROR_MORE_DATA); } template void enumerateAllUsers(uint maxCount, Callback callback) { netApiEnumerate(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { // pass 0 as filter -> get all users // Why does this function take a DWORD* as resume handle and NetUserEnum/NetGroupGetUsers a UINT64* // Great API design by Microsoft... //casting the uint64* to uint32* is fine, it just writes to the first 32 bits return NetUserEnum(nullptr, level, 0, buffer, MAX_PREFERRED_LENGTH, count, total, (PDWORD)resumeHandle); }); } template void enumerateAllGroups(uint maxCount, Callback callback) { netApiEnumerate(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { return NetGroupEnum(nullptr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); }); } template void enumerateGroupsForUser(uint maxCount, const QString &name, Callback callback) { LPCWSTR nameStr = (LPCWSTR)name.utf16(); netApiEnumerate(maxCount, callback, [&](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) -> NET_API_STATUS { Q_UNUSED(resumeHandle); NET_API_STATUS ret = NetUserGetGroups(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total); // if we return ERROR_MORE_DATA here it will result in an endless loop if (ret == ERROR_MORE_DATA) { qCWarning(KCOREADDONS_DEBUG) << "NetUserGetGroups for user" << name << "returned ERROR_MORE_DATA. This should not happen!"; ret = NERR_Success; } return ret; }); } template void enumerateUsersForGroup(const QString &name, uint maxCount, Callback callback) { LPCWSTR nameStr = (LPCWSTR)name.utf16(); netApiEnumerate(maxCount, callback, [nameStr](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { return NetGroupGetUsers(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); }); } class Q_DECL_HIDDEN KUser::Private : public QSharedData { typedef QExplicitlySharedDataPointer Ptr; Private() : isAdmin(false) {} //takes ownership over userInfo_ Private(KUserId uid, KGroupId gid, const QString &loginName, const QString &fullName, const QString &domain, const QString &homeDir, bool isAdmin) : uid(uid), gid(gid), loginName(loginName), fullName(fullName), domain(domain), homeDir(homeDir), isAdmin(isAdmin) { Q_ASSERT(uid.isValid()); } static QString guessHomeDir(const QString &username, KUserId uid) { // usri11_home_dir/usri4_home_dir is often empty // check whether it is the homedir for the current user and if not then fall back to "\" if (uid == KUserId::currentUserId()) { return QDir::homePath(); } QString homeDir; WCHAR profileDirPath[MAX_PATH]; DWORD bufSize = MAX_PATH; BOOL result = GetProfilesDirectoryW(profileDirPath, &bufSize); if (result) { // This might not be correct: e.g. with local user and domain user with same // In that case it could be C:\Users\Foo (local user) vs C:\Users\Foo.DOMAIN (domain user) // However it is still much better than the previous code which just returned the current users home dir homeDir = QString::fromWCharArray(profileDirPath) + QLatin1Char('\\') + username; } return homeDir; } public: static Ptr sharedNull; KUserId uid; KGroupId gid; QString loginName; QString fullName; QString domain; QString homeDir; bool isAdmin; /** Creates a user info from a SID (never returns null) */ static Ptr create(KUserId uid) { if (!uid.isValid()) { return sharedNull; } // now find the fully qualified name for the user DWORD nameBufferLen = UNLEN + 1; WCHAR nameBuffer[UNLEN + 1]; DWORD domainBufferLen = UNLEN + 1; WCHAR domainBuffer[UNLEN + 1]; SID_NAME_USE use; if (!LookupAccountSidW(nullptr, uid.nativeId(), nameBuffer, &nameBufferLen, domainBuffer, &domainBufferLen, &use)) { qCWarning(KCOREADDONS_DEBUG) << "Could not lookup user " << uid.toString() << "error =" << GetLastError(); return sharedNull; } QString loginName = QString::fromWCharArray(nameBuffer); QString domainName = QString::fromWCharArray(domainBuffer); if (use != SidTypeUser && use != SidTypeDeletedAccount) { qCWarning(KCOREADDONS_DEBUG).nospace() << "SID for " << domainName << "\\" << loginName << " (" << uid.toString() << ") is not of type user (" << SidTypeUser << " or " << SidTypeDeletedAccount << "). Got type " << use << " instead."; return sharedNull; } // now get the server name to query (could be null for local machine) LPWSTR servernameTmp = nullptr; NET_API_STATUS status = NetGetAnyDCName(nullptr, 0, (LPBYTE *)&servernameTmp); if (status != NERR_Success) { // this always fails on my desktop system, don't spam the output // qDebug("NetGetAnyDCName failed with error %d", status); } ScopedNetApiBuffer servername(servernameTmp); QString fullName; QString homeDir; KGroupId group; bool isAdmin = false; // must NOT pass the qualified name ("domain\user") here or lookup fails -> just the name // try USER_INFO_4 first, MSDN says it is valid only on servers (whatever that means), it works on my desktop system // If it fails fall back to USER_INFO11, which has all the needed information except primary group if (auto userInfo4 = getUserInfo(servername.get(), loginName, &status)) { Q_ASSERT(KUserId(userInfo4->usri4_user_sid) == uid); // if this is not the same we have a logic error fullName = QString::fromWCharArray(userInfo4->usri4_full_name); homeDir = QString::fromWCharArray(userInfo4->usri4_home_dir); isAdmin = userInfo4->usri4_priv == USER_PRIV_ADMIN; // now determine the primary group: const DWORD primaryGroup = userInfo4->usri4_primary_group_id; // primary group is a relative identifier, i.e. in order to get the SID for that group // we have to take the user SID and replace the last subauthority value with the relative identifier group = KGroupId(uid.nativeId()); // constructor does not check whether the sid refers to a group Q_ASSERT(group.isValid()); UCHAR numSubauthorities = *GetSidSubAuthorityCount(group.nativeId()); PDWORD lastSubAutority = GetSidSubAuthority(group.nativeId(), numSubauthorities - 1); *lastSubAutority = primaryGroup; } else if (auto userInfo11 = getUserInfo(servername.get(), loginName, &status)) { fullName = QString::fromWCharArray(userInfo11->usri11_full_name); homeDir = QString::fromWCharArray(userInfo11->usri11_home_dir); isAdmin = userInfo11->usri11_priv == USER_PRIV_ADMIN; } else { qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not get information for user " << domainName << "\\" << loginName << ": error code = " << status; return sharedNull; } if (homeDir.isEmpty()) { homeDir = guessHomeDir(loginName, uid); } //if we couldn't find a primary group just take the first group found for this user if (!group.isValid()) { enumerateGroupsForUser(1, loginName, [&](const GROUP_USERS_INFO_0 &info) { group = KGroupId::fromName(QString::fromWCharArray(info.grui0_name)); }); } return Ptr(new Private(uid, group, loginName, fullName, domainName, homeDir, isAdmin)); } }; KUser::Private::Ptr KUser::Private::sharedNull(new KUser::Private()); KUser::KUser(UIDMode mode) { if (mode == UseEffectiveUID) { d = Private::create(KUserId::currentEffectiveUserId()); } else if (mode == UseRealUserID) { d = Private::create(KUserId::currentUserId()); } else { d = Private::sharedNull; } } KUser::KUser(K_UID uid) : d(Private::create(KUserId(uid))) { } KUser::KUser(KUserId uid) : d(Private::create(uid)) { } KUser::KUser(const QString &name) : d(Private::create(KUserId::fromName(name))) { } KUser::KUser(const char *name) : d(Private::create(KUserId::fromName(QString::fromLocal8Bit(name)))) { } KUser::KUser(const KUser &user) : d(user.d) { } KUser &KUser::operator=(const KUser &user) { d = user.d; return *this; } bool KUser::operator==(const KUser &user) const { return isValid() && d->uid == user.d->uid; } bool KUser::isValid() const { return d->uid.isValid(); } bool KUser::isSuperUser() const { return d->isAdmin; } QString KUser::loginName() const { return d->loginName; } QString KUser::homeDir() const { return d->homeDir; } // Some RAII objects to help uninitializing/destroying WinAPI stuff // used in faceIconPath. class COMInitializer { public: COMInitializer() : result(CoInitialize(nullptr)) {} ~COMInitializer() { if (SUCCEEDED(result)) { CoUninitialize(); } } HRESULT result; }; class W32Library { public: W32Library(HMODULE h): h(h) {} ~W32Library() { if (h) { FreeLibrary(h); } } operator HMODULE() { return h; } HMODULE h; }; // faceIconPath uses undocumented Windows API known as SHGetUserPicturePath, // only accessible by ordinal, unofficially documented at // http://undoc.airesoft.co.uk/shell32.dll/SHGetUserPicturePath.php // The function has a different ordinal and parameters on Windows XP and Vista/7. // These structs encapsulate the differences. struct FaceIconPath_XP { typedef HRESULT (WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR); static const int ordinal = 233; static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathXP, LPCWSTR username, LPWSTR buf, UINT bufsize) { Q_UNUSED(bufsize); // assumes the buffer is MAX_PATH in size return SHGetUserPicturePathXP(username, 0, buf); } }; struct FaceIconPath_Vista { typedef HRESULT (WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR, UINT); static const int ordinal = 261; static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathV, LPCWSTR username, LPWSTR buf, UINT bufsize) { return SHGetUserPicturePathV(username, 0, buf, bufsize); } }; template static QString faceIconPathImpl(LPCWSTR username) { static COMInitializer COMinit; static W32Library shellMod = LoadLibraryA("shell32.dll"); if (!shellMod) { return QString(); } static typename Platform::funcptr_t sgupp_ptr = reinterpret_cast( GetProcAddress(shellMod, MAKEINTRESOURCEA(Platform::ordinal))); if (!sgupp_ptr) { return QString(); } WCHAR pathBuf[MAX_PATH]; HRESULT res = Platform::getPicturePath(sgupp_ptr, username, pathBuf, MAX_PATH); if (res != S_OK) { return QString(); } return QString::fromWCharArray(pathBuf); } QString KUser::faceIconPath() const { if (!isValid()) { return QString(); } LPCWSTR username = reinterpret_cast(d->loginName.utf16()); return faceIconPathImpl(username); } QString KUser::shell() const { return isValid() ? QStringLiteral("cmd.exe") : QString(); } KUserId KUser::userId() const { return d->uid; } KGroupId KUser::groupId() const { return d->gid; } QVariant KUser::property(UserProperty which) const { if (which == FullName) { return QVariant(d->fullName); } return QVariant(); } KUser::~KUser() { } class Q_DECL_HIDDEN KUserGroup::Private : public QSharedData { public: QString name; KGroupId gid; Private() {} Private(const QString &name, KGroupId id) : name(name), gid(id) { if (!name.isEmpty()) { PBYTE groupInfoTmp = nullptr; NET_API_STATUS status = NetGroupGetInfo(nullptr, (LPCWSTR)name.utf16(), 0, &groupInfoTmp); // must always be freed, even on error ScopedNetApiBuffer groupInfo((GROUP_INFO_0 *)groupInfoTmp); if (status != NERR_Success) { qCWarning(KCOREADDONS_DEBUG) << "Failed to find group with name" << name << "error =" << status; groupInfo.reset(); } if (!id.isValid()) { gid = KGroupId::fromName(name); } } } }; KUserGroup::KUserGroup(const QString &_name) : d(new Private(_name, KGroupId())) { } KUserGroup::KUserGroup(const char *_name) : d(new Private(QLatin1String(_name), KGroupId())) { } static QString nameFromGroupId(KGroupId gid) { if (!gid.isValid()) { return QString(); } DWORD bufferLen = UNLEN + 1; WCHAR buffer[UNLEN + 1]; DWORD domainBufferLen = UNLEN + 1; WCHAR domainBuffer[UNLEN + 1]; SID_NAME_USE eUse; QString name; if (LookupAccountSidW(NULL, gid.nativeId(), buffer, &bufferLen, domainBuffer, &domainBufferLen, &eUse)) { if (eUse == SidTypeGroup || eUse == SidTypeWellKnownGroup) { name = QString::fromWCharArray(buffer); } else { qCWarning(KCOREADDONS_DEBUG) << QString::fromWCharArray(buffer) << "is not a group, SID type is" << eUse; } } return name; } KUserGroup::KUserGroup(KGroupId gid) : d(new Private(nameFromGroupId(gid), gid)) { } KUserGroup::KUserGroup(K_GID gid) { KGroupId groupId(gid); d = new Private(nameFromGroupId(groupId), groupId); } KUserGroup::KUserGroup(KUser::UIDMode mode) { KGroupId gid; if (mode == KUser::UseEffectiveUID) { gid = KGroupId::currentGroupId(); } else if (mode == KUser::UseRealUserID) { gid = KGroupId::currentEffectiveGroupId(); } d = new Private(nameFromGroupId(gid), gid); } KUserGroup::KUserGroup(const KUserGroup &group) : d(group.d) { } KUserGroup &KUserGroup::operator =(const KUserGroup &group) { d = group.d; return *this; } bool KUserGroup::operator==(const KUserGroup &group) const { return isValid() && d->gid == group.d->gid && d->name == group.d->name; } bool KUserGroup::isValid() const { return d->gid.isValid() && !d->name.isEmpty(); } QString KUserGroup::name() const { return d->name; } KGroupId KUserGroup::groupId() const { return d->gid; } KUserGroup::~KUserGroup() { } QList KUser::allUsers(uint maxCount) { QList result; // No advantage if we take a USER_INFO_11, since there is no way of copying it // and it is not owned by this function! // -> get a USER_INFO_0 instead and then use KUser(QString) // USER_INFO_23 or USER_INFO_23 would be ideal here since they contains a SID, // but that fails with error code 0x7c (bad level) enumerateAllUsers(maxCount, [&result](const USER_INFO_0 &info) { result.append(KUser(QString::fromWCharArray(info.usri0_name))); }); return result; } QStringList KUser::allUserNames(uint maxCount) { QStringList result; enumerateAllUsers(maxCount, [&result](const USER_INFO_0 &info) { result.append(QString::fromWCharArray(info.usri0_name)); }); return result; } QList KUserGroup::allGroups(uint maxCount) { QList result; // MSDN documentation say 3 is a valid level, however the function fails with invalid level!!! // User GROUP_INFO_0 instead... enumerateAllGroups(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { result.append(KUserGroup(QString::fromWCharArray(groupInfo.grpi0_name))); }); return result; } QStringList KUserGroup::allGroupNames(uint maxCount) { QStringList result; enumerateAllGroups(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { result.append(QString::fromWCharArray(groupInfo.grpi0_name)); }); return result; } QList KUser::groups(uint maxCount) const { QList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { result.append(KUserGroup(QString::fromWCharArray(info.grui0_name))); }); return result; } QStringList KUser::groupNames(uint maxCount) const { QStringList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { result.append(QString::fromWCharArray(info.grui0_name)); }); return result; } QList KUserGroup::users(uint maxCount) const { QList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { result.append(KUser(QString::fromWCharArray(info.grui0_name))); }); return result; } QStringList KUserGroup::userNames(uint maxCount) const { QStringList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { result.append(QString::fromWCharArray(info.grui0_name)); }); return result; } static const auto invalidSidString = QStringLiteral(""); static QString sidToString(void *sid) { if (!sid || !IsValidSid(sid)) { return invalidSidString; } WCHAR *sidStr; // allocated by ConvertStringSidToSidW, must be freed using LocalFree() if (!ConvertSidToStringSidW(sid, &sidStr)) { return invalidSidString; } QString ret = QString::fromWCharArray(sidStr); LocalFree(sidStr); return ret; } struct WindowsSIDWrapper : public QSharedData { char sidBuffer[SECURITY_MAX_SID_SIZE]; /** @return a copy of @p sid or null if sid is not valid or an error occurs */ static WindowsSIDWrapper *copySid(PSID sid) { if (!sid || !IsValidSid(sid)) { return nullptr; } //create a copy of sid WindowsSIDWrapper *copy = new WindowsSIDWrapper(); bool success = CopySid(SECURITY_MAX_SID_SIZE, copy->sidBuffer, sid); if (!success) { QString sidString = sidToString(sid); qWarning("Failed to copy SID %s, error = %d", qPrintable(sidString), (int)GetLastError()); delete copy; return nullptr; } return copy; } }; template<> KUserOrGroupId::KUserOrGroupId() { } template<> KUserOrGroupId::~KUserOrGroupId() { } template<> KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other) : data(other.data) { } template<> inline KUserOrGroupId &KUserOrGroupId::operator=(const KUserOrGroupId &other) { data = other.data; return *this; } template<> KUserOrGroupId::KUserOrGroupId(void *nativeId) : data(WindowsSIDWrapper::copySid(nativeId)) { } template<> bool KUserOrGroupId::isValid() const { return data; } template<> void *KUserOrGroupId::nativeId() const { if (!data) { return nullptr; } return data->sidBuffer; } template<> bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const { if (data) { if (!other.data) { return false; } return EqualSid(data->sidBuffer, other.data->sidBuffer); } return !other.data; //only equal if other data is also invalid } template<> bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const { return !(*this == other); } template<> QString KUserOrGroupId::toString() const { return sidToString(data ? data->sidBuffer : nullptr); } /** T must be either KUserId or KGroupId, Callback has signature T(PSID, SID_NAME_USE) */ template static T sidFromName(const QString &name, Callback callback) { if (name.isEmpty()) { // for some reason empty string will always return S-1-5-32 which is of type domain // we only want users or groups -> return invalid return T(); } char buffer[SECURITY_MAX_SID_SIZE]; DWORD sidLength = SECURITY_MAX_SID_SIZE; // ReferencedDomainName must be passed or LookupAccountNameW fails // Documentation says it is optional, however if not passed the function fails and returns the required size WCHAR domainBuffer[1024]; DWORD domainBufferSize = 1024; SID_NAME_USE sidType; bool ok = LookupAccountNameW(nullptr, (LPCWSTR)name.utf16(), buffer, &sidLength, domainBuffer, &domainBufferSize, &sidType); if (!ok) { qCWarning(KCOREADDONS_DEBUG) << "Failed to lookup account" << name << "error code =" << GetLastError(); return T(); } return callback(buffer, sidType); } KUserId KUserId::fromName(const QString &name) { return sidFromName(name, [&](PSID sid, SID_NAME_USE sidType) -> KUserId { if (sidType != SidTypeUser && sidType != SidTypeDeletedAccount) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) << " is not a user." " Got SID type " << sidType << " instead."; return KUserId(); } return KUserId(sid); }); } KGroupId KGroupId::fromName(const QString &name) { return sidFromName(name, [&](PSID sid, SID_NAME_USE sidType) -> KGroupId { if (sidType != SidTypeGroup && sidType != SidTypeWellKnownGroup) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) << " is not a group." " Got SID type " << sidType << " instead."; return KGroupId(); } return KGroupId(sid); }); } static std::unique_ptr queryProcessInformation(TOKEN_INFORMATION_CLASS type) { HANDLE _token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &_token)) { qWarning("Failed to get the token for the current process: %d", (int)GetLastError()); return nullptr; } ScopedHANDLE token(_token, handleCloser); // query required size DWORD requiredSize; if (!GetTokenInformation(token.get(), type, nullptr, 0, &requiredSize)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { qWarning("Failed to get the required size for the token information %d: %d", type, (int)GetLastError()); return nullptr; } } std::unique_ptr buffer(new char[requiredSize]); if (!GetTokenInformation(token.get(), type, buffer.get(), requiredSize, &requiredSize)) { qWarning("Failed to get token information %d from current process: %d", type, (int)GetLastError()); return nullptr; } return buffer; } KUserId KUserId::currentUserId() { std::unique_ptr userTokenBuffer = queryProcessInformation(TokenUser); TOKEN_USER *userToken = (TOKEN_USER *)userTokenBuffer.get(); return KUserId(userToken->User.Sid); } KGroupId KGroupId::currentGroupId() { std::unique_ptr primaryGroupBuffer = queryProcessInformation(TokenPrimaryGroup); TOKEN_PRIMARY_GROUP *primaryGroup = (TOKEN_PRIMARY_GROUP *)primaryGroupBuffer.get(); return KGroupId(primaryGroup->PrimaryGroup); } KUserId KUserId::currentEffectiveUserId() { return currentUserId(); } KGroupId KGroupId::currentEffectiveGroupId() { return currentGroupId(); } KCOREADDONS_EXPORT uint qHash(const KUserId &id, uint seed) { if (!id.isValid()) { return seed; } // we can't just hash the pointer since equal object must have the same hash -> hash contents char *sid = (char *)id.nativeId(); return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); } KCOREADDONS_EXPORT uint qHash(const KGroupId &id, uint seed) { if (!id.isValid()) { return seed; } // we can't just hash the pointer since equal object must have the same hash -> hash contents char *sid = (char *)id.nativeId(); return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); } diff --git a/tests/faceicontest.cpp b/tests/faceicontest.cpp index 7696f42..720918e 100644 --- a/tests/faceicontest.cpp +++ b/tests/faceicontest.cpp @@ -1,53 +1,41 @@ /* - * Copyright (C) 2014 Nicolás Alvarez - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2014 Nicolás Alvarez + + SPDX-License-Identifier: LGPL-2.0-only +*/ #include "faceicontest.h" #include #include #include #include int main(int argc, char **argv) { QApplication app(argc, argv); FaceIconTest *mainWin = new FaceIconTest(); mainWin->show(); return app.exec(); } FaceIconTest::FaceIconTest() { QVBoxLayout *layout = new QVBoxLayout(this); listWidget = new QListWidget(this); layout->addWidget(listWidget); const QList users = KUser::allUsers(); for (const KUser &u : users) { QPixmap pixmap(u.faceIconPath()); if (pixmap.isNull()) { pixmap = QPixmap(QSize(48, 48)); pixmap.fill(); } else { pixmap = pixmap.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QListWidgetItem *item = new QListWidgetItem(u.loginName(), listWidget); item->setData(Qt::DecorationRole, pixmap); } } diff --git a/tests/faceicontest.h b/tests/faceicontest.h index b472977..f112adb 100644 --- a/tests/faceicontest.h +++ b/tests/faceicontest.h @@ -1,34 +1,22 @@ /* - * Copyright (C) 2014 Nicols Alvarez - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2014 Nicolás Alvarez + + SPDX-License-Identifier: LGPL-2.0-only +*/ #ifndef FACEICONTEST_H #define FACEICONTEST_H #include class FaceIconTest : public QWidget { Q_OBJECT public: FaceIconTest(); private: class QListWidget *listWidget; }; #endif diff --git a/tests/kdirwatchtest.cpp b/tests/kdirwatchtest.cpp index f2b9a62..6b9984d 100644 --- a/tests/kdirwatchtest.cpp +++ b/tests/kdirwatchtest.cpp @@ -1,86 +1,76 @@ -/* This file is part of the KDE libraries - Copyright 1998 Sven Radej - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1998 Sven Radej + + SPDX-License-Identifier: LGPL-2.0-only */ #include "kdirwatchtest.h" #include #include #include // TODO debug crash when calling "./kdirwatchtest ./kdirwatchtest" int main(int argc, char **argv) { // TODO port to QCommandLineArguments once it exists //options.add("+[directory ...]", qi18n("Directory(ies) to watch")); QCoreApplication a(argc, argv); myTest testObject; KDirWatch *dirwatch1 = KDirWatch::self(); KDirWatch *dirwatch2 = new KDirWatch; testObject.connect(dirwatch1, SIGNAL(dirty(QString)), SLOT(dirty(QString))); testObject.connect(dirwatch1, SIGNAL(created(QString)), SLOT(created(QString))); testObject.connect(dirwatch1, SIGNAL(deleted(QString)), SLOT(deleted(QString))); // TODO port to QCommandLineArguments once it exists const QStringList args = a.arguments(); for (int i = 1; i < args.count(); ++i) { const QString arg = args.at(i); if (!arg.startsWith("-")) { qDebug() << "Watching: " << arg; dirwatch2->addDir(arg); } } QString home = QString(getenv("HOME")) + '/'; QString desk = home + "Desktop/"; qDebug() << "Watching: " << home; dirwatch1->addDir(home); qDebug() << "Watching file: " << home << "foo "; dirwatch1->addFile(home + "foo"); qDebug() << "Watching: " << desk; dirwatch1->addDir(desk); QString test = home + "test/"; qDebug() << "Watching: (but skipped) " << test; dirwatch1->addDir(test); dirwatch1->startScan(); dirwatch2->startScan(); if (!dirwatch1->stopDirScan(home)) { qDebug() << "stopDirscan: " << home << " error!"; } if (!dirwatch1->restartDirScan(home)) { qDebug() << "restartDirScan: " << home << "error!"; } if (!dirwatch1->stopDirScan(test)) { qDebug() << "stopDirScan: error"; } KDirWatch::statistics(); delete dirwatch2; KDirWatch::statistics(); return a.exec(); } diff --git a/tests/kdirwatchtest.h b/tests/kdirwatchtest.h index 5509408..88a2ed4 100644 --- a/tests/kdirwatchtest.h +++ b/tests/kdirwatchtest.h @@ -1,48 +1,38 @@ -/* This file is part of the KDE libraries - Copyright 1998 Sven Radej +/* + This file is part of the KDE libraries - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. + SPDX-FileCopyrightText: 1998 Sven Radej - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef _KDIRWATCHTEST_H_ #define _KDIRWATCHTEST_H_ #include #include #include #include "kdirwatch.h" class myTest : public QObject { Q_OBJECT public: myTest() { } public Q_SLOTS: void dirty(const QString &a) { printf("Dirty: %s\n", a.toLocal8Bit().constData()); } void created(const QString &f) { printf("Created: %s\n", f.toLocal8Bit().constData()); } void deleted(const QString &f) { printf("Deleted: %s\n", f.toLocal8Bit().constData()); } }; #endif diff --git a/tests/kdirwatchtest_gui.cpp b/tests/kdirwatchtest_gui.cpp index deb4d92..2fcb7fd 100644 --- a/tests/kdirwatchtest_gui.cpp +++ b/tests/kdirwatchtest_gui.cpp @@ -1,140 +1,128 @@ /* - * Copyright (C) 2006 Dirk Stoecker - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2006 Dirk Stoecker + + SPDX-License-Identifier: LGPL-2.0-only +*/ #include "kdirwatchtest_gui.h" #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { QApplication app(argc, argv); KDirWatchTest_GUI *mainWin = new KDirWatchTest_GUI(); mainWin->show(); return app.exec(); } KDirWatchTest_GUI::KDirWatchTest_GUI() : QWidget() { QPushButton *e, *f; QVBoxLayout *lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget(l1 = new QLineEdit(QLatin1String("Test 1"), this)); lay->addWidget(l2 = new QLineEdit(QLatin1String("Test 2"), this)); lay->addWidget(l3 = new QLineEdit(QLatin1String("Test 3"), this)); lay->addWidget(m_eventBrowser = new QTextBrowser(this)); lay->addWidget(d = new QLineEdit(QLatin1String("Status"), this)); lay->addWidget(e = new QPushButton(QLatin1String("new file"), this)); lay->addWidget(f = new QPushButton(QLatin1String("delete file"), this)); dir = QDir::currentPath(); file = dir + QLatin1String("/testfile_kdirwatchtest_gui"); w1 = new KDirWatch(); w1->setObjectName(QLatin1String("w1")); w2 = new KDirWatch(); w2->setObjectName(QLatin1String("w2")); w3 = new KDirWatch(); w3->setObjectName(QLatin1String("w3")); connect(w1, SIGNAL(dirty(QString)), this, SLOT(slotDir1(QString))); connect(w2, SIGNAL(dirty(QString)), this, SLOT(slotDir2(QString))); connect(w3, SIGNAL(dirty(QString)), this, SLOT(slotDir3(QString))); w1->addDir(dir); w2->addDir(dir); w3->addDir(dir); KDirWatch *w4 = new KDirWatch(this); w4->setObjectName(QLatin1String("w4")); w4->addDir(dir, KDirWatch::WatchFiles | KDirWatch::WatchSubDirs); connect(w1, SIGNAL(dirty(QString)), this, SLOT(slotDirty(QString))); connect(w1, SIGNAL(created(QString)), this, SLOT(slotCreated(QString))); connect(w1, SIGNAL(deleted(QString)), this, SLOT(slotDeleted(QString))); KDirWatch *w5 = new KDirWatch(this); w5->setObjectName(QLatin1String(QLatin1String("w5"))); w5->addFile(file); connect(w5, SIGNAL(dirty(QString)), this, SLOT(slotDirty(QString))); connect(w5, SIGNAL(created(QString)), this, SLOT(slotCreated(QString))); connect(w5, SIGNAL(deleted(QString)), this, SLOT(slotDeleted(QString))); lay->addWidget(new QLabel(QLatin1String("Directory = ") + dir, this)); lay->addWidget(new QLabel(QLatin1String("File = ") + file, this)); connect(e, SIGNAL(clicked()), this, SLOT(slotNewClicked())); connect(f, SIGNAL(clicked()), this, SLOT(slotDeleteClicked())); setMinimumWidth(800); setMinimumHeight(400); } void KDirWatchTest_GUI::slotDir1(const QString &a) { l1->setText(QLatin1String("Test 1 changed ") + a + QLatin1String(" at ") + QTime::currentTime().toString()); } void KDirWatchTest_GUI::slotDir2(const QString &a) { // This used to cause bug #119341, fixed now #if 1 w2->stopDirScan(QLatin1String(a.toLatin1().constData())); w2->restartDirScan(QLatin1String(a.toLatin1().constData())); #endif l2->setText(QLatin1String("Test 2 changed ") + a + QLatin1String(" at ") + QTime::currentTime().toString()); } void KDirWatchTest_GUI::slotDir3(const QString &a) { l3->setText(QLatin1String("Test 3 changed ") + a + QLatin1String(" at )") + QTime::currentTime().toString()); } void KDirWatchTest_GUI::slotDeleteClicked() { remove(file.toLatin1().constData()); d->setText(QLatin1String("Delete clicked at ") + QTime::currentTime().toString()); } void KDirWatchTest_GUI::slotNewClicked() { fclose(QT_FOPEN(file.toLatin1().constData(), "wb")); d->setText(QLatin1String("New clicked at ") + QTime::currentTime().toString()); } void KDirWatchTest_GUI::slotDirty(const QString &path) { m_eventBrowser->append(QLatin1String("Dirty(") + sender()->objectName() + QLatin1String("): ") + path + QLatin1Char('\n')); } void KDirWatchTest_GUI::slotCreated(const QString &path) { m_eventBrowser->append(QLatin1String("Created(") + sender()->objectName() + QLatin1String("): ") + path + QLatin1Char('\n')); } void KDirWatchTest_GUI::slotDeleted(const QString &path) { m_eventBrowser->append(QLatin1String("Deleted(") + sender()->objectName() + QLatin1String("): ") + path + QLatin1Char('\n')); } diff --git a/tests/kdirwatchtest_gui.h b/tests/kdirwatchtest_gui.h index 16c5a46..fbe487b 100644 --- a/tests/kdirwatchtest_gui.h +++ b/tests/kdirwatchtest_gui.h @@ -1,52 +1,40 @@ // krazy:excludeall=qclasses /* - * Copyright (C) 2006 Dirk Stoecker - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License version 2 as published by the Free Software Foundation; - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2006 Dirk Stoecker + + SPDX-License-Identifier: LGPL-2.0-only +*/ #ifndef KDIRWATCHTEST_GUI_H #define KDIRWATCHTEST_GUI_H #include class QTextBrowser; class KDirWatchTest_GUI : public QWidget { Q_OBJECT public: KDirWatchTest_GUI(); protected Q_SLOTS: void slotNewClicked(); void slotDeleteClicked(); void slotDir1(const QString &path); void slotDir2(const QString &path); void slotDir3(const QString &path); void slotDirty(const QString &); void slotCreated(const QString &); void slotDeleted(const QString &); private: class QLineEdit *d; QString file, dir; class KDirWatch *w1; class KDirWatch *w2; class KDirWatch *w3; class QLineEdit *l1, *l2, *l3; QTextBrowser *m_eventBrowser; }; #endif diff --git a/tests/krandomsequencetest.cpp b/tests/krandomsequencetest.cpp index 297e867..09fcefe 100644 --- a/tests/krandomsequencetest.cpp +++ b/tests/krandomsequencetest.cpp @@ -1,97 +1,87 @@ -/* This file is part of the KDE libraries - Copyright (c) 1999 Waldo Bastian - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Waldo Bastian + + SPDX-License-Identifier: LGPL-2.0-only */ #include #include #include "krandomsequence.h" #include "krandom.h" #include int main(/*int argc, char *argv[]*/) { long seed; KRandomSequence seq; seed = 2; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 0; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 0; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 2; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seq.setSeed(KRandom::random()); QList list; list.append(QLatin1String("A")); list.append(QLatin1String("B")); list.append(QLatin1String("C")); list.append(QLatin1String("D")); list.append(QLatin1String("E")); list.append(QLatin1String("F")); list.append(QLatin1String("G")); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); }