From 9efb859492a04513f0d99d7bb43f75a6832e1063 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Wed, 13 Jan 2021 01:32:41 +0000 Subject: [PATCH 1/1] Import kcoreaddons_5.78.0.orig.tar.xz [dgit import orig kcoreaddons_5.78.0.orig.tar.xz] --- .gitignore | 22 + CMakeLists.txt | 133 ++ KF5CoreAddonsConfig.cmake.in | 23 + KF5CoreAddonsMacros.cmake | 146 ++ LICENSES/BSD-2-Clause.txt | 22 + LICENSES/BSD-3-Clause.txt | 26 + LICENSES/CC0-1.0.txt | 119 + LICENSES/GPL-2.0-only.txt | 319 +++ LICENSES/GPL-2.0-or-later.txt | 319 +++ LICENSES/GPL-3.0-only.txt | 625 +++++ LICENSES/LGPL-2.0-only.txt | 446 ++++ LICENSES/LGPL-2.0-or-later.txt | 446 ++++ LICENSES/LGPL-2.1-only.txt | 467 ++++ LICENSES/LGPL-2.1-or-later.txt | 468 ++++ LICENSES/LGPL-3.0-only.txt | 163 ++ LICENSES/LicenseRef-KDE-Accepted-GPL.txt | 12 + LICENSES/LicenseRef-KDE-Accepted-LGPL.txt | 12 + LICENSES/LicenseRef-Qt-Commercial.txt | 7 + LICENSES/MPL-1.1.txt | 422 ++++ LICENSES/Qt-LGPL-exception-1.1.txt | 21 + README.md | 11 + autotests/CMakeLists.txt | 121 + autotests/ConfigureChecks.cmake | 9 + autotests/alwaysunloadplugin.cpp | 21 + autotests/alwaysunloadplugin.h | 21 + autotests/config-tests.h.in | 1 + autotests/data/appui1rc | 0 autotests/data/appuirc | 0 autotests/data/fakeplugin.desktop | 91 + autotests/data/foo1rc | 0 autotests/data/foorc | 0 autotests/data/hiddenplugin.desktop | 91 + autotests/data/os-release | 22 + .../servicetypes/bad-groups-input.desktop | 46 + .../bad-groups-servicetype.desktop | 34 + .../servicetypes/bool-servicetype.desktop | 6 + .../data/servicetypes/example-input.desktop | 39 + .../servicetypes/example-servicetype.desktop | 18 + .../data/servicetypes/fake-kdedmodule.desktop | 9 + .../servicetypes/fake-kdevelopplugin.desktop | 70 + .../invalid-missing-servicetype.desktop | 7 + autotests/data/testmetadata.json | 15 + autotests/data/twostepsparsetest.desktop | 20 + autotests/desktoptojsontest.cpp | 344 +++ autotests/jsonplugin.cpp | 18 + autotests/jsonplugin.h | 20 + autotests/jsonplugin.json | 12 + autotests/jsonplugin2.cpp | 18 + autotests/jsonplugin2.h | 20 + autotests/jsonplugin2.json | 13 + autotests/kaboutdataapplicationdatatest.cpp | 107 + autotests/kaboutdatatest.cpp | 364 +++ autotests/kautosavefiletest.cpp | 155 ++ autotests/kautosavefiletest.h | 31 + autotests/kcompositejobtest.cpp | 95 + autotests/kcompositejobtest.h | 58 + autotests/kdelibs4configmigratortest.cpp | 129 + autotests/kdelibs4migrationtest.cpp | 46 + autotests/kdirwatch_unittest.cpp | 909 +++++++ autotests/kfileutilstest.cpp | 53 + autotests/kfileutilstest.h | 22 + autotests/kformattest.cpp | 390 +++ autotests/kformattest.h | 31 + autotests/kjobtest.cpp | 534 +++++ autotests/kjobtest.h | 109 + autotests/klistopenfilesjobtest_unix.cpp | 107 + autotests/klistopenfilesjobtest_unix.h | 24 + autotests/klistopenfilesjobtest_win.cpp | 24 + autotests/klistopenfilesjobtest_win.h | 21 + autotests/kmacroexpandertest.cpp | 276 +++ autotests/kosreleasetest.cpp | 43 + autotests/kpluginfactorytest.cpp | 52 + autotests/kpluginloadertest.cpp | 456 ++++ autotests/kpluginmetadatatest.cpp | 382 +++ autotests/kprocesslisttest.cpp | 83 + autotests/kprocesslisttest.h | 24 + autotests/kprocesstest.cpp | 89 + autotests/kprocesstest_helper.cpp | 32 + autotests/kprocesstest_helper.h | 13 + autotests/krandomtest.cpp | 251 ++ autotests/kshareddatacachetest.cpp | 64 + autotests/kshelltest.cpp | 252 ++ autotests/kstringhandlertest.cpp | 198 ++ autotests/kstringhandlertest.h | 24 + autotests/ktexttohtmltest.cpp | 586 +++++ autotests/ktexttohtmltest.h | 28 + autotests/kurlmimedatatest.cpp | 160 ++ autotests/kurlmimedatatest.h | 24 + autotests/kusertest.cpp | 264 +++ autotests/multiplugin.cpp | 32 + autotests/multiplugin.h | 29 + autotests/pythontest.py | 21 + autotests/unversionedplugin.cpp | 21 + autotests/unversionedplugin.h | 21 + autotests/versionedplugin.cpp | 22 + autotests/versionedplugin.h | 21 + cmake/FindFAM.cmake | 26 + cmake/FindProcstat.cmake | 25 + cmake/rules_PyKF5.py | 66 + docs/Doxyfile.local | 7 + metainfo.yaml | 21 + po/af/kcoreaddons5_qt.po | 836 +++++++ po/ar/kcoreaddons5_qt.po | 776 ++++++ po/ar/kde5_xml_mimetypes.po | 422 ++++ po/as/kcoreaddons5_qt.po | 905 +++++++ po/ast/kcoreaddons5_qt.po | 743 ++++++ po/az/kcoreaddons5_qt.po | 744 ++++++ po/az/kde5_xml_mimetypes.po | 431 ++++ po/be/kcoreaddons5_qt.po | 908 +++++++ po/be@latin/kcoreaddons5_qt.po | 912 +++++++ po/bg/kcoreaddons5_qt.po | 957 ++++++++ po/bg/kde5_xml_mimetypes.po | 355 +++ po/bn/kcoreaddons5_qt.po | 959 ++++++++ po/bn_IN/kcoreaddons5_qt.po | 948 ++++++++ po/br/kcoreaddons5_qt.po | 877 +++++++ po/bs/kcoreaddons5_qt.po | 782 ++++++ po/bs/kde5_xml_mimetypes.po | 427 ++++ po/ca/kcoreaddons5_qt.po | 759 ++++++ po/ca/kde5_xml_mimetypes.po | 318 +++ po/ca@valencia/kcoreaddons5_qt.po | 766 ++++++ po/ca@valencia/kde5_xml_mimetypes.po | 433 ++++ po/crh/kcoreaddons5_qt.po | 888 +++++++ po/cs/kcoreaddons5_qt.po | 755 ++++++ po/cs/kde5_xml_mimetypes.po | 314 +++ po/csb/kcoreaddons5_qt.po | 962 ++++++++ po/cy/kcoreaddons5_qt.po | 852 +++++++ po/da/kcoreaddons5_qt.po | 752 ++++++ po/da/kde5_xml_mimetypes.po | 431 ++++ po/de/kcoreaddons5_qt.po | 760 ++++++ po/de/kde5_xml_mimetypes.po | 433 ++++ po/el/kcoreaddons5_qt.po | 765 ++++++ po/el/kde5_xml_mimetypes.po | 318 +++ po/en/kcoreaddons5_qt.po | 748 ++++++ po/en_GB/kcoreaddons5_qt.po | 749 ++++++ po/en_GB/kde5_xml_mimetypes.po | 314 +++ po/eo/kcoreaddons5_qt.po | 830 +++++++ po/eo/kde5_xml_mimetypes.po | 314 +++ po/es/kcoreaddons5_qt.po | 757 ++++++ po/es/kde5_xml_mimetypes.po | 314 +++ po/et/kcoreaddons5_qt.po | 752 ++++++ po/et/kde5_xml_mimetypes.po | 432 ++++ po/eu/kcoreaddons5_qt.po | 766 ++++++ po/eu/kde5_xml_mimetypes.po | 318 +++ po/fa/kcoreaddons5_qt.po | 980 ++++++++ po/fi/kcoreaddons5_qt.po | 768 ++++++ po/fi/kde5_xml_mimetypes.po | 436 ++++ po/fr/kcoreaddons5_qt.po | 770 ++++++ po/fr/kde5_xml_mimetypes.po | 323 +++ po/fy/kcoreaddons5_qt.po | 957 ++++++++ po/ga/kcoreaddons5_qt.po | 990 ++++++++ po/ga/kde5_xml_mimetypes.po | 421 ++++ po/gd/kcoreaddons5_qt.po | 794 +++++++ po/gd/kde5_xml_mimetypes.po | 423 ++++ po/gl/kcoreaddons5_qt.po | 761 ++++++ po/gl/kde5_xml_mimetypes.po | 438 ++++ po/gu/kcoreaddons5_qt.po | 951 ++++++++ po/ha/kcoreaddons5_qt.po | 766 ++++++ po/he/kcoreaddons5_qt.po | 791 +++++++ po/hi/kcoreaddons5_qt.po | 958 ++++++++ po/hne/kcoreaddons5_qt.po | 906 +++++++ po/hr/kcoreaddons5_qt.po | 972 ++++++++ po/hr/kde5_xml_mimetypes.po | 426 ++++ po/hsb/kcoreaddons5_qt.po | 919 ++++++++ po/hu/kcoreaddons5_qt.po | 749 ++++++ po/hu/kde5_xml_mimetypes.po | 315 +++ po/hy/kcoreaddons5_qt.po | 973 ++++++++ po/ia/kcoreaddons5_qt.po | 748 ++++++ po/ia/kde5_xml_mimetypes.po | 313 +++ po/id/kcoreaddons5_qt.po | 751 ++++++ po/id/kde5_xml_mimetypes.po | 431 ++++ po/is/kcoreaddons5_qt.po | 789 +++++++ po/is/kde5_xml_mimetypes.po | 425 ++++ po/it/kcoreaddons5_qt.po | 757 ++++++ po/it/kde5_xml_mimetypes.po | 314 +++ po/ja/kcoreaddons5_qt.po | 885 +++++++ po/ja/kde5_xml_mimetypes.po | 419 ++++ po/ka/kcoreaddons5_qt.po | 836 +++++++ po/kk/kcoreaddons5_qt.po | 954 ++++++++ po/kk/kde5_xml_mimetypes.po | 422 ++++ po/km/kcoreaddons5_qt.po | 948 ++++++++ po/km/kde5_xml_mimetypes.po | 422 ++++ po/kn/kcoreaddons5_qt.po | 947 ++++++++ po/ko/kcoreaddons5_qt.po | 745 ++++++ po/ko/kde5_xml_mimetypes.po | 431 ++++ po/ku/kcoreaddons5_qt.po | 960 ++++++++ po/lb/kcoreaddons5_qt.po | 846 +++++++ po/lt/kcoreaddons5_qt.po | 765 ++++++ po/lt/kde5_xml_mimetypes.po | 316 +++ po/lv/kcoreaddons5_qt.po | 968 ++++++++ po/lv/kde5_xml_mimetypes.po | 425 ++++ po/mai/kcoreaddons5_qt.po | 948 ++++++++ po/mk/kcoreaddons5_qt.po | 949 ++++++++ po/ml/kcoreaddons5_qt.po | 793 +++++++ po/ml/kde5_xml_mimetypes.po | 419 ++++ po/mr/kcoreaddons5_qt.po | 820 +++++++ po/mr/kde5_xml_mimetypes.po | 421 ++++ po/ms/kcoreaddons5_qt.po | 958 ++++++++ po/ms/kde5_xml_mimetypes.po | 330 +++ po/nb/kcoreaddons5_qt.po | 750 ++++++ po/nb/kde5_xml_mimetypes.po | 315 +++ po/nds/kcoreaddons5_qt.po | 826 +++++++ po/nds/kde5_xml_mimetypes.po | 423 ++++ po/ne/kcoreaddons5_qt.po | 899 +++++++ po/nl/kcoreaddons5_qt.po | 762 ++++++ po/nl/kde5_xml_mimetypes.po | 313 +++ po/nn/kcoreaddons5_qt.po | 762 ++++++ po/nn/kde5_xml_mimetypes.po | 315 +++ po/oc/kcoreaddons5_qt.po | 851 +++++++ po/or/kcoreaddons5_qt.po | 888 +++++++ po/pa/kcoreaddons5_qt.po | 827 +++++++ po/pa/kde5_xml_mimetypes.po | 411 ++++ po/pl/kcoreaddons5_qt.po | 775 ++++++ po/pl/kde5_xml_mimetypes.po | 433 ++++ po/ps/kcoreaddons5_qt.po | 906 +++++++ po/pt/kcoreaddons5_qt.po | 837 +++++++ po/pt/kde5_xml_mimetypes.po | 320 +++ po/pt_BR/kcoreaddons5_qt.po | 758 ++++++ po/pt_BR/kde5_xml_mimetypes.po | 315 +++ po/ro/kcoreaddons5_qt.po | 756 ++++++ po/ro/kde5_xml_mimetypes.po | 401 ++++ po/ru/kcoreaddons5_qt.po | 777 ++++++ po/ru/kde5_xml_mimetypes.po | 437 ++++ po/se/kcoreaddons5_qt.po | 771 ++++++ po/se/kde5_xml_mimetypes.po | 315 +++ po/si/kcoreaddons5_qt.po | 957 ++++++++ po/sk/kcoreaddons5_qt.po | 762 ++++++ po/sk/kde5_xml_mimetypes.po | 431 ++++ po/sl/kcoreaddons5_qt.po | 772 ++++++ po/sl/kde5_xml_mimetypes.po | 317 +++ po/sq/kcoreaddons5_qt.po | 902 +++++++ po/sq/kde5_xml_mimetypes.po | 355 +++ po/sr/kcoreaddons5_qt.po | 753 ++++++ po/sr/kde5_xml_mimetypes.po | 258 ++ po/sr@ijekavian/kcoreaddons5_qt.po | 752 ++++++ po/sr@ijekavian/kde5_xml_mimetypes.po | 257 ++ po/sr@ijekavianlatin/kcoreaddons5_qt.po | 752 ++++++ po/sr@ijekavianlatin/kde5_xml_mimetypes.po | 257 ++ po/sr@latin/kcoreaddons5_qt.po | 752 ++++++ po/sr@latin/kde5_xml_mimetypes.po | 258 ++ po/sv/kcoreaddons5_qt.po | 741 ++++++ po/sv/kde5_xml_mimetypes.po | 316 +++ po/ta/kcoreaddons5_qt.po | 956 ++++++++ po/ta/kde5_xml_mimetypes.po | 313 +++ po/te/kcoreaddons5_qt.po | 766 ++++++ po/tg/kcoreaddons5_qt.po | 763 ++++++ po/tg/kde5_xml_mimetypes.po | 429 ++++ po/th/kcoreaddons5_qt.po | 933 ++++++++ po/th/kde5_xml_mimetypes.po | 375 +++ po/tr/kcoreaddons5_qt.po | 804 +++++++ po/tr/kde5_xml_mimetypes.po | 424 ++++ po/tt/kcoreaddons5_qt.po | 956 ++++++++ po/ug/kcoreaddons5_qt.po | 952 ++++++++ po/ug/kde5_xml_mimetypes.po | 421 ++++ po/uk/kcoreaddons5_qt.po | 770 ++++++ po/uk/kde5_xml_mimetypes.po | 316 +++ po/uz/kcoreaddons5_qt.po | 844 +++++++ po/uz@cyrillic/kcoreaddons5_qt.po | 878 +++++++ po/vi/kcoreaddons5_qt.po | 744 ++++++ po/vi/kde5_xml_mimetypes.po | 313 +++ po/wa/kcoreaddons5_qt.po | 971 ++++++++ po/xh/kcoreaddons5_qt.po | 835 +++++++ po/zh_CN/kcoreaddons5_qt.po | 749 ++++++ po/zh_CN/kde5_xml_mimetypes.po | 320 +++ po/zh_HK/kcoreaddons5_qt.po | 859 +++++++ po/zh_TW/kcoreaddons5_qt.po | 764 ++++++ po/zh_TW/kde5_xml_mimetypes.po | 434 ++++ src/CMakeLists.txt | 10 + src/Messages.sh | 6 + src/desktoptojson/CMakeLists.txt | 32 + src/desktoptojson/desktoptojson.cpp | 132 ++ src/desktoptojson/desktoptojson.h | 40 + src/desktoptojson/main.cpp | 83 + src/lib/CMakeLists.txt | 369 +++ src/lib/caching/config-caching.h.cmake | 2 + src/lib/caching/kshareddatacache.cpp | 1711 ++++++++++++++ src/lib/caching/kshareddatacache.h | 215 ++ src/lib/caching/kshareddatacache_p.h | 493 ++++ src/lib/caching/kshareddatacache_win.cpp | 108 + src/lib/caching/posix_fallocate_mac.h | 88 + src/lib/io/config-kdirwatch.h.cmake | 3 + src/lib/io/kautosavefile.cpp | 229 ++ src/lib/io/kautosavefile.h | 237 ++ src/lib/io/kbackup.cpp | 185 ++ src/lib/io/kbackup.h | 141 ++ src/lib/io/kdirwatch.cpp | 2098 +++++++++++++++++ src/lib/io/kdirwatch.h | 305 +++ src/lib/io/kdirwatch_p.h | 237 ++ src/lib/io/kfilesystemtype.cpp | 136 ++ src/lib/io/kfilesystemtype.h | 38 + src/lib/io/kfileutils.cpp | 68 + src/lib/io/kfileutils.h | 57 + src/lib/io/kmessage.cpp | 90 + src/lib/io/kmessage.h | 116 + src/lib/io/kprocess.cpp | 323 +++ src/lib/io/kprocess.h | 332 +++ src/lib/io/kprocess_p.h | 31 + src/lib/io/kurlmimedata.cpp | 109 + src/lib/io/kurlmimedata.h | 96 + src/lib/jobs/kcompositejob.cpp | 105 + src/lib/jobs/kcompositejob.h | 112 + src/lib/jobs/kcompositejob_p.h | 30 + src/lib/jobs/kjob.cpp | 347 +++ src/lib/jobs/kjob.h | 686 ++++++ src/lib/jobs/kjob_p.h | 56 + src/lib/jobs/kjobtrackerinterface.cpp | 126 + src/lib/jobs/kjobtrackerinterface.h | 182 ++ src/lib/jobs/kjobuidelegate.cpp | 115 + src/lib/jobs/kjobuidelegate.h | 153 ++ src/lib/kaboutdata.cpp | 1260 ++++++++++ src/lib/kaboutdata.h | 1190 ++++++++++ src/lib/kcoreaddons.cpp | 22 + src/lib/kcoreaddons.h | 44 + src/lib/licenses/ARTISTIC | 124 + src/lib/licenses/BSD | 20 + src/lib/licenses/CMakeLists.txt | 8 + src/lib/licenses/GPL_V2 | 339 +++ src/lib/licenses/GPL_V3 | 674 ++++++ src/lib/licenses/LGPL_V2 | 481 ++++ src/lib/licenses/LGPL_V21 | 502 ++++ src/lib/licenses/LGPL_V3 | 165 ++ src/lib/licenses/QPL_V1.0 | 103 + src/lib/plugin/desktopfileparser.cpp | 628 +++++ src/lib/plugin/desktopfileparser_p.h | 61 + src/lib/plugin/kexportplugin.h | 48 + src/lib/plugin/kpluginfactory.cpp | 230 ++ src/lib/plugin/kpluginfactory.h | 779 ++++++ src/lib/plugin/kpluginfactory_p.h | 35 + src/lib/plugin/kpluginloader.cpp | 313 +++ src/lib/plugin/kpluginloader.h | 468 ++++ src/lib/plugin/kpluginmetadata.cpp | 421 ++++ src/lib/plugin/kpluginmetadata.h | 457 ++++ src/lib/randomness/krandom.cpp | 79 + src/lib/randomness/krandom.h | 84 + src/lib/randomness/krandomsequence.cpp | 210 ++ src/lib/randomness/krandomsequence.h | 162 ++ src/lib/text/kmacroexpander.cpp | 392 +++ src/lib/text/kmacroexpander.h | 399 ++++ src/lib/text/kmacroexpander_p.h | 21 + src/lib/text/kmacroexpander_unix.cpp | 247 ++ src/lib/text/kmacroexpander_win.cpp | 112 + src/lib/text/kstringhandler.cpp | 380 +++ src/lib/text/kstringhandler.h | 248 ++ src/lib/text/ktexttohtml.cpp | 602 +++++ src/lib/text/ktexttohtml.h | 90 + src/lib/text/ktexttohtml_p.h | 49 + src/lib/text/ktexttohtmlemoticonsinterface.h | 33 + src/lib/util/config-accountsservice.h.cmake | 1 + src/lib/util/config-getgrouplist.h.cmake | 1 + src/lib/util/config-kde4home.h.cmake | 2 + src/lib/util/kdelibs4configmigrator.cpp | 121 + src/lib/util/kdelibs4configmigrator.h | 77 + src/lib/util/kdelibs4migration.cpp | 122 + src/lib/util/kdelibs4migration.h | 108 + src/lib/util/kformat.cpp | 98 + src/lib/util/kformat.h | 431 ++++ src/lib/util/kformatprivate.cpp | 541 +++++ src/lib/util/kformatprivate_p.h | 60 + src/lib/util/klistopenfilesjob.h | 69 + src/lib/util/klistopenfilesjob_unix.cpp | 107 + src/lib/util/klistopenfilesjob_win.cpp | 37 + src/lib/util/kosrelease.cpp | 291 +++ src/lib/util/kosrelease.h | 94 + src/lib/util/kprocesslist.cpp | 78 + src/lib/util/kprocesslist.h | 84 + src/lib/util/kprocesslist_p.h | 32 + src/lib/util/kprocesslist_unix.cpp | 152 ++ src/lib/util/kprocesslist_unix_procstat.cpp | 52 + src/lib/util/kprocesslist_unix_procstat_p.h | 119 + src/lib/util/kprocesslist_win.cpp | 128 + src/lib/util/kshell.cpp | 70 + src/lib/util/kshell.h | 199 ++ src/lib/util/kshell_p.h | 25 + src/lib/util/kshell_unix.cpp | 308 +++ src/lib/util/kshell_win.cpp | 264 +++ src/lib/util/kuser.h | 631 +++++ src/lib/util/kuser_unix.cpp | 551 +++++ src/lib/util/kuser_win.cpp | 899 +++++++ src/mimetypes/CMakeLists.txt | 14 + src/mimetypes/XmlMessages.sh | 23 + src/mimetypes/kde5.xml | 1451 ++++++++++++ tests/CMakeLists.txt | 13 + tests/faceicontest.cpp | 41 + tests/faceicontest.h | 22 + tests/kdirwatchtest.cpp | 76 + tests/kdirwatchtest.h | 38 + tests/kdirwatchtest_gui.cpp | 128 + tests/kdirwatchtest_gui.h | 40 + tests/krandomsequencetest.cpp | 87 + 388 files changed, 149982 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 KF5CoreAddonsConfig.cmake.in create mode 100644 KF5CoreAddonsMacros.cmake create mode 100644 LICENSES/BSD-2-Clause.txt create mode 100644 LICENSES/BSD-3-Clause.txt create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 LICENSES/GPL-2.0-only.txt create mode 100644 LICENSES/GPL-2.0-or-later.txt create mode 100644 LICENSES/GPL-3.0-only.txt create mode 100644 LICENSES/LGPL-2.0-only.txt create mode 100644 LICENSES/LGPL-2.0-or-later.txt create mode 100644 LICENSES/LGPL-2.1-only.txt create mode 100644 LICENSES/LGPL-2.1-or-later.txt create mode 100644 LICENSES/LGPL-3.0-only.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-GPL.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-LGPL.txt create mode 100644 LICENSES/LicenseRef-Qt-Commercial.txt create mode 100644 LICENSES/MPL-1.1.txt create mode 100644 LICENSES/Qt-LGPL-exception-1.1.txt create mode 100644 README.md create mode 100644 autotests/CMakeLists.txt create mode 100644 autotests/ConfigureChecks.cmake create mode 100644 autotests/alwaysunloadplugin.cpp create mode 100644 autotests/alwaysunloadplugin.h create mode 100644 autotests/config-tests.h.in create mode 100644 autotests/data/appui1rc create mode 100644 autotests/data/appuirc create mode 100644 autotests/data/fakeplugin.desktop create mode 100644 autotests/data/foo1rc create mode 100644 autotests/data/foorc create mode 100644 autotests/data/hiddenplugin.desktop create mode 100644 autotests/data/os-release create mode 100644 autotests/data/servicetypes/bad-groups-input.desktop create mode 100644 autotests/data/servicetypes/bad-groups-servicetype.desktop create mode 100644 autotests/data/servicetypes/bool-servicetype.desktop create mode 100644 autotests/data/servicetypes/example-input.desktop create mode 100644 autotests/data/servicetypes/example-servicetype.desktop create mode 100644 autotests/data/servicetypes/fake-kdedmodule.desktop create mode 100644 autotests/data/servicetypes/fake-kdevelopplugin.desktop create mode 100644 autotests/data/servicetypes/invalid-missing-servicetype.desktop create mode 100644 autotests/data/testmetadata.json create mode 100644 autotests/data/twostepsparsetest.desktop create mode 100644 autotests/desktoptojsontest.cpp create mode 100644 autotests/jsonplugin.cpp create mode 100644 autotests/jsonplugin.h create mode 100644 autotests/jsonplugin.json create mode 100644 autotests/jsonplugin2.cpp create mode 100644 autotests/jsonplugin2.h create mode 100644 autotests/jsonplugin2.json create mode 100644 autotests/kaboutdataapplicationdatatest.cpp create mode 100644 autotests/kaboutdatatest.cpp create mode 100644 autotests/kautosavefiletest.cpp create mode 100644 autotests/kautosavefiletest.h create mode 100644 autotests/kcompositejobtest.cpp create mode 100644 autotests/kcompositejobtest.h create mode 100644 autotests/kdelibs4configmigratortest.cpp create mode 100644 autotests/kdelibs4migrationtest.cpp create mode 100644 autotests/kdirwatch_unittest.cpp create mode 100644 autotests/kfileutilstest.cpp create mode 100644 autotests/kfileutilstest.h create mode 100644 autotests/kformattest.cpp create mode 100644 autotests/kformattest.h create mode 100644 autotests/kjobtest.cpp create mode 100644 autotests/kjobtest.h create mode 100644 autotests/klistopenfilesjobtest_unix.cpp create mode 100644 autotests/klistopenfilesjobtest_unix.h create mode 100644 autotests/klistopenfilesjobtest_win.cpp create mode 100644 autotests/klistopenfilesjobtest_win.h create mode 100644 autotests/kmacroexpandertest.cpp create mode 100644 autotests/kosreleasetest.cpp create mode 100644 autotests/kpluginfactorytest.cpp create mode 100644 autotests/kpluginloadertest.cpp create mode 100644 autotests/kpluginmetadatatest.cpp create mode 100644 autotests/kprocesslisttest.cpp create mode 100644 autotests/kprocesslisttest.h create mode 100644 autotests/kprocesstest.cpp create mode 100644 autotests/kprocesstest_helper.cpp create mode 100644 autotests/kprocesstest_helper.h create mode 100644 autotests/krandomtest.cpp create mode 100644 autotests/kshareddatacachetest.cpp create mode 100644 autotests/kshelltest.cpp create mode 100644 autotests/kstringhandlertest.cpp create mode 100644 autotests/kstringhandlertest.h create mode 100644 autotests/ktexttohtmltest.cpp create mode 100644 autotests/ktexttohtmltest.h create mode 100644 autotests/kurlmimedatatest.cpp create mode 100644 autotests/kurlmimedatatest.h create mode 100644 autotests/kusertest.cpp create mode 100644 autotests/multiplugin.cpp create mode 100644 autotests/multiplugin.h create mode 100644 autotests/pythontest.py create mode 100644 autotests/unversionedplugin.cpp create mode 100644 autotests/unversionedplugin.h create mode 100644 autotests/versionedplugin.cpp create mode 100644 autotests/versionedplugin.h create mode 100644 cmake/FindFAM.cmake create mode 100644 cmake/FindProcstat.cmake create mode 100644 cmake/rules_PyKF5.py create mode 100644 docs/Doxyfile.local create mode 100644 metainfo.yaml create mode 100644 po/af/kcoreaddons5_qt.po create mode 100644 po/ar/kcoreaddons5_qt.po create mode 100644 po/ar/kde5_xml_mimetypes.po create mode 100644 po/as/kcoreaddons5_qt.po create mode 100644 po/ast/kcoreaddons5_qt.po create mode 100644 po/az/kcoreaddons5_qt.po create mode 100644 po/az/kde5_xml_mimetypes.po create mode 100644 po/be/kcoreaddons5_qt.po create mode 100644 po/be@latin/kcoreaddons5_qt.po create mode 100644 po/bg/kcoreaddons5_qt.po create mode 100644 po/bg/kde5_xml_mimetypes.po create mode 100644 po/bn/kcoreaddons5_qt.po create mode 100644 po/bn_IN/kcoreaddons5_qt.po create mode 100644 po/br/kcoreaddons5_qt.po create mode 100644 po/bs/kcoreaddons5_qt.po create mode 100644 po/bs/kde5_xml_mimetypes.po create mode 100644 po/ca/kcoreaddons5_qt.po create mode 100644 po/ca/kde5_xml_mimetypes.po create mode 100644 po/ca@valencia/kcoreaddons5_qt.po create mode 100644 po/ca@valencia/kde5_xml_mimetypes.po create mode 100644 po/crh/kcoreaddons5_qt.po create mode 100644 po/cs/kcoreaddons5_qt.po create mode 100644 po/cs/kde5_xml_mimetypes.po create mode 100644 po/csb/kcoreaddons5_qt.po create mode 100644 po/cy/kcoreaddons5_qt.po create mode 100644 po/da/kcoreaddons5_qt.po create mode 100644 po/da/kde5_xml_mimetypes.po create mode 100644 po/de/kcoreaddons5_qt.po create mode 100644 po/de/kde5_xml_mimetypes.po create mode 100644 po/el/kcoreaddons5_qt.po create mode 100644 po/el/kde5_xml_mimetypes.po create mode 100644 po/en/kcoreaddons5_qt.po create mode 100644 po/en_GB/kcoreaddons5_qt.po create mode 100644 po/en_GB/kde5_xml_mimetypes.po create mode 100644 po/eo/kcoreaddons5_qt.po create mode 100644 po/eo/kde5_xml_mimetypes.po create mode 100644 po/es/kcoreaddons5_qt.po create mode 100644 po/es/kde5_xml_mimetypes.po create mode 100644 po/et/kcoreaddons5_qt.po create mode 100644 po/et/kde5_xml_mimetypes.po create mode 100644 po/eu/kcoreaddons5_qt.po create mode 100644 po/eu/kde5_xml_mimetypes.po create mode 100644 po/fa/kcoreaddons5_qt.po create mode 100644 po/fi/kcoreaddons5_qt.po create mode 100644 po/fi/kde5_xml_mimetypes.po create mode 100644 po/fr/kcoreaddons5_qt.po create mode 100644 po/fr/kde5_xml_mimetypes.po create mode 100644 po/fy/kcoreaddons5_qt.po create mode 100644 po/ga/kcoreaddons5_qt.po create mode 100644 po/ga/kde5_xml_mimetypes.po create mode 100644 po/gd/kcoreaddons5_qt.po create mode 100644 po/gd/kde5_xml_mimetypes.po create mode 100644 po/gl/kcoreaddons5_qt.po create mode 100644 po/gl/kde5_xml_mimetypes.po create mode 100644 po/gu/kcoreaddons5_qt.po create mode 100644 po/ha/kcoreaddons5_qt.po create mode 100644 po/he/kcoreaddons5_qt.po create mode 100644 po/hi/kcoreaddons5_qt.po create mode 100644 po/hne/kcoreaddons5_qt.po create mode 100644 po/hr/kcoreaddons5_qt.po create mode 100644 po/hr/kde5_xml_mimetypes.po create mode 100644 po/hsb/kcoreaddons5_qt.po create mode 100644 po/hu/kcoreaddons5_qt.po create mode 100644 po/hu/kde5_xml_mimetypes.po create mode 100644 po/hy/kcoreaddons5_qt.po create mode 100644 po/ia/kcoreaddons5_qt.po create mode 100644 po/ia/kde5_xml_mimetypes.po create mode 100644 po/id/kcoreaddons5_qt.po create mode 100644 po/id/kde5_xml_mimetypes.po create mode 100644 po/is/kcoreaddons5_qt.po create mode 100644 po/is/kde5_xml_mimetypes.po create mode 100644 po/it/kcoreaddons5_qt.po create mode 100644 po/it/kde5_xml_mimetypes.po create mode 100644 po/ja/kcoreaddons5_qt.po create mode 100644 po/ja/kde5_xml_mimetypes.po create mode 100644 po/ka/kcoreaddons5_qt.po create mode 100644 po/kk/kcoreaddons5_qt.po create mode 100644 po/kk/kde5_xml_mimetypes.po create mode 100644 po/km/kcoreaddons5_qt.po create mode 100644 po/km/kde5_xml_mimetypes.po create mode 100644 po/kn/kcoreaddons5_qt.po create mode 100644 po/ko/kcoreaddons5_qt.po create mode 100644 po/ko/kde5_xml_mimetypes.po create mode 100644 po/ku/kcoreaddons5_qt.po create mode 100644 po/lb/kcoreaddons5_qt.po create mode 100644 po/lt/kcoreaddons5_qt.po create mode 100644 po/lt/kde5_xml_mimetypes.po create mode 100644 po/lv/kcoreaddons5_qt.po create mode 100644 po/lv/kde5_xml_mimetypes.po create mode 100644 po/mai/kcoreaddons5_qt.po create mode 100644 po/mk/kcoreaddons5_qt.po create mode 100644 po/ml/kcoreaddons5_qt.po create mode 100644 po/ml/kde5_xml_mimetypes.po create mode 100644 po/mr/kcoreaddons5_qt.po create mode 100644 po/mr/kde5_xml_mimetypes.po create mode 100644 po/ms/kcoreaddons5_qt.po create mode 100644 po/ms/kde5_xml_mimetypes.po create mode 100644 po/nb/kcoreaddons5_qt.po create mode 100644 po/nb/kde5_xml_mimetypes.po create mode 100644 po/nds/kcoreaddons5_qt.po create mode 100644 po/nds/kde5_xml_mimetypes.po create mode 100644 po/ne/kcoreaddons5_qt.po create mode 100644 po/nl/kcoreaddons5_qt.po create mode 100644 po/nl/kde5_xml_mimetypes.po create mode 100644 po/nn/kcoreaddons5_qt.po create mode 100644 po/nn/kde5_xml_mimetypes.po create mode 100644 po/oc/kcoreaddons5_qt.po create mode 100644 po/or/kcoreaddons5_qt.po create mode 100644 po/pa/kcoreaddons5_qt.po create mode 100644 po/pa/kde5_xml_mimetypes.po create mode 100644 po/pl/kcoreaddons5_qt.po create mode 100644 po/pl/kde5_xml_mimetypes.po create mode 100644 po/ps/kcoreaddons5_qt.po create mode 100644 po/pt/kcoreaddons5_qt.po create mode 100644 po/pt/kde5_xml_mimetypes.po create mode 100644 po/pt_BR/kcoreaddons5_qt.po create mode 100644 po/pt_BR/kde5_xml_mimetypes.po create mode 100644 po/ro/kcoreaddons5_qt.po create mode 100644 po/ro/kde5_xml_mimetypes.po create mode 100644 po/ru/kcoreaddons5_qt.po create mode 100644 po/ru/kde5_xml_mimetypes.po create mode 100644 po/se/kcoreaddons5_qt.po create mode 100644 po/se/kde5_xml_mimetypes.po create mode 100644 po/si/kcoreaddons5_qt.po create mode 100644 po/sk/kcoreaddons5_qt.po create mode 100644 po/sk/kde5_xml_mimetypes.po create mode 100644 po/sl/kcoreaddons5_qt.po create mode 100644 po/sl/kde5_xml_mimetypes.po create mode 100644 po/sq/kcoreaddons5_qt.po create mode 100644 po/sq/kde5_xml_mimetypes.po create mode 100644 po/sr/kcoreaddons5_qt.po create mode 100644 po/sr/kde5_xml_mimetypes.po create mode 100644 po/sr@ijekavian/kcoreaddons5_qt.po create mode 100644 po/sr@ijekavian/kde5_xml_mimetypes.po create mode 100644 po/sr@ijekavianlatin/kcoreaddons5_qt.po create mode 100644 po/sr@ijekavianlatin/kde5_xml_mimetypes.po create mode 100644 po/sr@latin/kcoreaddons5_qt.po create mode 100644 po/sr@latin/kde5_xml_mimetypes.po create mode 100644 po/sv/kcoreaddons5_qt.po create mode 100644 po/sv/kde5_xml_mimetypes.po create mode 100644 po/ta/kcoreaddons5_qt.po create mode 100644 po/ta/kde5_xml_mimetypes.po create mode 100644 po/te/kcoreaddons5_qt.po create mode 100644 po/tg/kcoreaddons5_qt.po create mode 100644 po/tg/kde5_xml_mimetypes.po create mode 100644 po/th/kcoreaddons5_qt.po create mode 100644 po/th/kde5_xml_mimetypes.po create mode 100644 po/tr/kcoreaddons5_qt.po create mode 100644 po/tr/kde5_xml_mimetypes.po create mode 100644 po/tt/kcoreaddons5_qt.po create mode 100644 po/ug/kcoreaddons5_qt.po create mode 100644 po/ug/kde5_xml_mimetypes.po create mode 100644 po/uk/kcoreaddons5_qt.po create mode 100644 po/uk/kde5_xml_mimetypes.po create mode 100644 po/uz/kcoreaddons5_qt.po create mode 100644 po/uz@cyrillic/kcoreaddons5_qt.po create mode 100644 po/vi/kcoreaddons5_qt.po create mode 100644 po/vi/kde5_xml_mimetypes.po create mode 100644 po/wa/kcoreaddons5_qt.po create mode 100644 po/xh/kcoreaddons5_qt.po create mode 100644 po/zh_CN/kcoreaddons5_qt.po create mode 100644 po/zh_CN/kde5_xml_mimetypes.po create mode 100644 po/zh_HK/kcoreaddons5_qt.po create mode 100644 po/zh_TW/kcoreaddons5_qt.po create mode 100644 po/zh_TW/kde5_xml_mimetypes.po create mode 100644 src/CMakeLists.txt create mode 100755 src/Messages.sh create mode 100644 src/desktoptojson/CMakeLists.txt create mode 100644 src/desktoptojson/desktoptojson.cpp create mode 100644 src/desktoptojson/desktoptojson.h create mode 100644 src/desktoptojson/main.cpp create mode 100644 src/lib/CMakeLists.txt create mode 100644 src/lib/caching/config-caching.h.cmake create mode 100644 src/lib/caching/kshareddatacache.cpp create mode 100644 src/lib/caching/kshareddatacache.h create mode 100644 src/lib/caching/kshareddatacache_p.h create mode 100644 src/lib/caching/kshareddatacache_win.cpp create mode 100644 src/lib/caching/posix_fallocate_mac.h create mode 100644 src/lib/io/config-kdirwatch.h.cmake create mode 100644 src/lib/io/kautosavefile.cpp create mode 100644 src/lib/io/kautosavefile.h create mode 100644 src/lib/io/kbackup.cpp create mode 100644 src/lib/io/kbackup.h create mode 100644 src/lib/io/kdirwatch.cpp create mode 100644 src/lib/io/kdirwatch.h create mode 100644 src/lib/io/kdirwatch_p.h create mode 100644 src/lib/io/kfilesystemtype.cpp create mode 100644 src/lib/io/kfilesystemtype.h create mode 100644 src/lib/io/kfileutils.cpp create mode 100644 src/lib/io/kfileutils.h create mode 100644 src/lib/io/kmessage.cpp create mode 100644 src/lib/io/kmessage.h create mode 100644 src/lib/io/kprocess.cpp create mode 100644 src/lib/io/kprocess.h create mode 100644 src/lib/io/kprocess_p.h create mode 100644 src/lib/io/kurlmimedata.cpp create mode 100644 src/lib/io/kurlmimedata.h create mode 100644 src/lib/jobs/kcompositejob.cpp create mode 100644 src/lib/jobs/kcompositejob.h create mode 100644 src/lib/jobs/kcompositejob_p.h create mode 100644 src/lib/jobs/kjob.cpp create mode 100644 src/lib/jobs/kjob.h create mode 100644 src/lib/jobs/kjob_p.h create mode 100644 src/lib/jobs/kjobtrackerinterface.cpp create mode 100644 src/lib/jobs/kjobtrackerinterface.h create mode 100644 src/lib/jobs/kjobuidelegate.cpp create mode 100644 src/lib/jobs/kjobuidelegate.h create mode 100644 src/lib/kaboutdata.cpp create mode 100644 src/lib/kaboutdata.h create mode 100644 src/lib/kcoreaddons.cpp create mode 100644 src/lib/kcoreaddons.h create mode 100644 src/lib/licenses/ARTISTIC create mode 100644 src/lib/licenses/BSD create mode 100644 src/lib/licenses/CMakeLists.txt create mode 100644 src/lib/licenses/GPL_V2 create mode 100644 src/lib/licenses/GPL_V3 create mode 100644 src/lib/licenses/LGPL_V2 create mode 100644 src/lib/licenses/LGPL_V21 create mode 100644 src/lib/licenses/LGPL_V3 create mode 100644 src/lib/licenses/QPL_V1.0 create mode 100644 src/lib/plugin/desktopfileparser.cpp create mode 100644 src/lib/plugin/desktopfileparser_p.h create mode 100644 src/lib/plugin/kexportplugin.h create mode 100644 src/lib/plugin/kpluginfactory.cpp create mode 100644 src/lib/plugin/kpluginfactory.h create mode 100644 src/lib/plugin/kpluginfactory_p.h create mode 100644 src/lib/plugin/kpluginloader.cpp create mode 100644 src/lib/plugin/kpluginloader.h create mode 100644 src/lib/plugin/kpluginmetadata.cpp create mode 100644 src/lib/plugin/kpluginmetadata.h create mode 100644 src/lib/randomness/krandom.cpp create mode 100644 src/lib/randomness/krandom.h create mode 100644 src/lib/randomness/krandomsequence.cpp create mode 100644 src/lib/randomness/krandomsequence.h create mode 100644 src/lib/text/kmacroexpander.cpp create mode 100644 src/lib/text/kmacroexpander.h create mode 100644 src/lib/text/kmacroexpander_p.h create mode 100644 src/lib/text/kmacroexpander_unix.cpp create mode 100644 src/lib/text/kmacroexpander_win.cpp create mode 100644 src/lib/text/kstringhandler.cpp create mode 100644 src/lib/text/kstringhandler.h create mode 100644 src/lib/text/ktexttohtml.cpp create mode 100644 src/lib/text/ktexttohtml.h create mode 100644 src/lib/text/ktexttohtml_p.h create mode 100644 src/lib/text/ktexttohtmlemoticonsinterface.h create mode 100644 src/lib/util/config-accountsservice.h.cmake create mode 100644 src/lib/util/config-getgrouplist.h.cmake create mode 100644 src/lib/util/config-kde4home.h.cmake create mode 100644 src/lib/util/kdelibs4configmigrator.cpp create mode 100644 src/lib/util/kdelibs4configmigrator.h create mode 100644 src/lib/util/kdelibs4migration.cpp create mode 100644 src/lib/util/kdelibs4migration.h create mode 100644 src/lib/util/kformat.cpp create mode 100644 src/lib/util/kformat.h create mode 100644 src/lib/util/kformatprivate.cpp create mode 100644 src/lib/util/kformatprivate_p.h create mode 100644 src/lib/util/klistopenfilesjob.h create mode 100644 src/lib/util/klistopenfilesjob_unix.cpp create mode 100644 src/lib/util/klistopenfilesjob_win.cpp create mode 100644 src/lib/util/kosrelease.cpp create mode 100644 src/lib/util/kosrelease.h create mode 100644 src/lib/util/kprocesslist.cpp create mode 100644 src/lib/util/kprocesslist.h create mode 100644 src/lib/util/kprocesslist_p.h create mode 100644 src/lib/util/kprocesslist_unix.cpp create mode 100644 src/lib/util/kprocesslist_unix_procstat.cpp create mode 100644 src/lib/util/kprocesslist_unix_procstat_p.h create mode 100644 src/lib/util/kprocesslist_win.cpp create mode 100644 src/lib/util/kshell.cpp create mode 100644 src/lib/util/kshell.h create mode 100644 src/lib/util/kshell_p.h create mode 100644 src/lib/util/kshell_unix.cpp create mode 100644 src/lib/util/kshell_win.cpp create mode 100644 src/lib/util/kuser.h create mode 100644 src/lib/util/kuser_unix.cpp create mode 100644 src/lib/util/kuser_win.cpp create mode 100644 src/mimetypes/CMakeLists.txt create mode 100755 src/mimetypes/XmlMessages.sh create mode 100644 src/mimetypes/kde5.xml create mode 100644 tests/CMakeLists.txt create mode 100644 tests/faceicontest.cpp create mode 100644 tests/faceicontest.h create mode 100644 tests/kdirwatchtest.cpp create mode 100644 tests/kdirwatchtest.h create mode 100644 tests/kdirwatchtest_gui.cpp create mode 100644 tests/kdirwatchtest_gui.h create mode 100644 tests/krandomsequencetest.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3330ae3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Ignore the following files +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* +.cmake/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cb5b175 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,133 @@ +cmake_minimum_required(VERSION 3.5) + +set(KF5_VERSION "5.78.0") # handled by release scripts +project(KCoreAddons VERSION ${KF5_VERSION}) + +include(FeatureSummary) +find_package(ECM 5.78.0 NO_MODULE) +set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") +feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) + + +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) + +include(ECMGenerateExportHeader) +include(CMakePackageConfigHelpers) +include(ECMSetupVersion) +include(ECMGenerateHeaders) +include(ECMQtDeclareLoggingCategory) +include(ECMAddQch) +include(ECMSetupQtPluginMacroNames) + +set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") + +option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) +add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") + +set(REQUIRED_QT_VERSION 5.14.0) +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core) + +ecm_setup_qtplugin_macro_names( + JSON_NONE + "K_PLUGIN_FACTORY" + JSON_ARG2 + "K_PLUGIN_FACTORY_WITH_JSON" + "K_PLUGIN_CLASS_WITH_JSON" + CONFIG_CODE_VARIABLE + PACKAGE_SETUP_AUTOMOC_VARIABLES +) + +find_package(Threads) + +# Configure checks for kdirwatch +find_package(FAM) + +set_package_properties(FAM PROPERTIES + PURPOSE "Provides file alteration notification facilities using a separate service. FAM provides additional support for NFS.") + +set(HAVE_FAM ${FAM_FOUND}) + +option(ENABLE_INOTIFY "Try to use inotify for directory monitoring" ON) +if(ENABLE_INOTIFY) + # Find libinotify + find_package(Inotify) + set_package_properties(Inotify PROPERTIES + PURPOSE "Filesystem alteration notifications using inotify") + set(HAVE_SYS_INOTIFY_H ${Inotify_FOUND}) +else() + set(HAVE_SYS_INOTIFY_H FALSE) +endif() + +option(ENABLE_PROCSTAT "Try to use libprocstat for process information" ON) +if (ENABLE_PROCSTAT) + # Find libprocstat + find_package(Procstat) + set_package_properties(Procstat PROPERTIES + PURPOSE "Process information using libprocstat") + set(HAVE_PROCSTAT ${PROCSTAT_FOUND}) +else() + set(HAVE_PROCSTAT FALSE) +endif() + +# Generate io/config-kdirwatch.h +configure_file(src/lib/io/config-kdirwatch.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/lib/io/config-kdirwatch.h) + +include(ECMPoQmTools) + +ecm_setup_version(PROJECT VARIABLE_PREFIX KCOREADDONS + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kcoreaddons_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5CoreAddonsConfigVersion.cmake" + SOVERSION 5) + + +if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") + ecm_install_po_files_as_qm(po) +endif() + +kde_enable_exceptions() + + +add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) +add_definitions(-DQT_NO_FOREACH) + +add_subdirectory(src) +if (BUILD_TESTING) + add_subdirectory(autotests) + add_subdirectory(tests) +endif() + +# create a Config.cmake and a ConfigVersion.cmake file and install them +set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5CoreAddons") + +if (BUILD_QCH) + ecm_install_qch_export( + TARGETS KF5CoreAddons_QCH + FILE KF5CoreAddonsQchTargets.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) + set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5CoreAddonsQchTargets.cmake\")") +endif() + +configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5CoreAddonsConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/KF5CoreAddonsConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} + ) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5CoreAddonsConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KF5CoreAddonsConfigVersion.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/KF5CoreAddonsMacros.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel ) + +install(EXPORT KF5CoreAddonsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5CoreAddonsTargets.cmake NAMESPACE KF5:: ) +install(EXPORT KF5CoreAddonsToolingTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5CoreAddonsToolingTargets.cmake NAMESPACE KF5:: ) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcoreaddons_version.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) + +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/KF5CoreAddonsConfig.cmake.in b/KF5CoreAddonsConfig.cmake.in new file mode 100644 index 0000000..f86b69f --- /dev/null +++ b/KF5CoreAddonsConfig.cmake.in @@ -0,0 +1,23 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt5Core @REQUIRED_QT_VERSION@) + +@PACKAGE_SETUP_AUTOMOC_VARIABLES@ + +if(CMAKE_CROSSCOMPILING AND KF5_HOST_TOOLING) + find_file(KCOREADDONS_TARGETSFILE KF5CoreAddons/KF5CoreAddonsToolingTargets.cmake + PATHS ${KF5_HOST_TOOLING} ${CMAKE_CURRENT_LIST_DIR} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH) + include("${KCOREADDONS_TARGETSFILE}") +else() + include("${CMAKE_CURRENT_LIST_DIR}/KF5CoreAddonsToolingTargets.cmake") + if(CMAKE_CROSSCOMPILING AND DESKTOPTOJSON_EXECUTABLE) + set_target_properties(KF5::desktoptojson PROPERTIES IMPORTED_LOCATION_NONE ${DESKTOPTOJSON_EXECUTABLE}) + set_target_properties(KF5::desktoptojson PROPERTIES IMPORTED_LOCATION ${DESKTOPTOJSON_EXECUTABLE}) + endif() +endif() +include("${CMAKE_CURRENT_LIST_DIR}/KF5CoreAddonsTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/KF5CoreAddonsMacros.cmake") +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/KF5CoreAddonsMacros.cmake b/KF5CoreAddonsMacros.cmake new file mode 100644 index 0000000..30617d1 --- /dev/null +++ b/KF5CoreAddonsMacros.cmake @@ -0,0 +1,146 @@ +# +# kcoreaddons_desktop_to_json(target desktopfile +# DEFAULT_SERVICE_TYPE | SERVICE_TYPES [ [...]] +# [OUTPUT_DIR dir] [COMPAT_MODE]) +# +# This macro uses desktoptojson to generate a json file from a plugin +# description in a .desktop file. The generated file can be compiled +# into the plugin using the K_PLUGIN_FACTORY_WITH_JSON (cpp) macro. +# +# All files in SERVICE_TYPES will be parsed by desktoptojson to ensure that the generated +# json uses the right data type (string, string list, int, double or bool) for all of the +# properties. If your application does not have any custom properties defined you should pass +# DEFAULT_SERVICE_TYPE instead. It is an error if neither of these arguments is given. +# This is done in order to ensure that all applications explicitly choose the right service +# type and don't have runtime errors because of the data being wrong (QJsonValue does not +# perform any type conversions). +# +# If COMPAT_MODE is passed as an argument the generated JSON file will be compatible with +# the metadata format used by KPluginInfo (from KService), otherwise it will default to +# the new format that is used by KPluginMetaData (from KCoreAddons). +# +# If OUTPUT_DIR is set the generated file will be created inside instead of in +# ${CMAKE_CURRENT_BINARY_DIR} +# +# Example: +# +# kcoreaddons_desktop_to_json(plasma_engine_time plasma-dataengine-time.desktop +# SERVICE_TYPES plasma-dataengine.desktop) + +function(kcoreaddons_desktop_to_json target desktop) + get_filename_component(desktop_basename ${desktop} NAME_WE) # allow passing an absolute path to the .desktop + cmake_parse_arguments(DESKTOP_TO_JSON "COMPAT_MODE;DEFAULT_SERVICE_TYPE" "OUTPUT_DIR" "SERVICE_TYPES" ${ARGN}) + + if(DESKTOP_TO_JSON_OUTPUT_DIR) + set(json "${DESKTOP_TO_JSON_OUTPUT_DIR}/${desktop_basename}.json") + else() + set(json "${CMAKE_CURRENT_BINARY_DIR}/${desktop_basename}.json") + endif() + + if(CMAKE_VERSION VERSION_LESS 2.8.12.20140127 OR "${target}" STREQUAL "") + _desktop_to_json_cmake28(${desktop} ${json} ${DESKTOP_TO_JSON_COMPAT_MODE}) + return() + elseif(MSVC_IDE AND CMAKE_VERSION VERSION_LESS 3.0) + # autogen dependencies for visual studio generator are broken until cmake commit 2ed0d06 + _desktop_to_json_cmake28(${desktop} ${json} ${DESKTOP_TO_JSON_COMPAT_MODE}) + return() + endif() + set(command KF5::desktoptojson -i ${desktop} -o ${json}) + if(DESKTOP_TO_JSON_COMPAT_MODE) + list(APPEND command -c) + endif() + if(DESKTOP_TO_JSON_SERVICE_TYPES) + foreach(type ${DESKTOP_TO_JSON_SERVICE_TYPES}) + if (EXISTS ${KDE_INSTALL_FULL_KSERVICETYPES5DIR}/${type}) + set(type ${KDE_INSTALL_FULL_KSERVICETYPES5DIR}/${type}) + endif() + list(APPEND command -s ${type}) + endforeach() + endif() + + file(RELATIVE_PATH relativejson ${CMAKE_CURRENT_BINARY_DIR} ${json}) + add_custom_command( + OUTPUT ${json} + COMMAND ${command} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${desktop} + COMMENT "Generating ${relativejson}" + ) + set_property(TARGET ${target} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${json}) +endfunction() + +function(_desktop_to_json_cmake28 desktop json compat) + # This function runs desktoptojson at *configure* time, ie, when CMake runs. + # This is necessary with CMake < 3.0.0 because the .json file must be + # generated before moc is run, and there was no way until CMake 3.0.0 to + # define a target as a dependency of the automoc target. + message("Using CMake 2.8 way to call desktoptojson") + get_target_property(DESKTOPTOJSON_LOCATION KF5::desktoptojson LOCATION) + if(compat) + execute_process( + COMMAND ${DESKTOPTOJSON_LOCATION} -i ${desktop} -o ${json} -c + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + else() + execute_process( + COMMAND ${DESKTOPTOJSON_LOCATION} -i ${desktop} -o ${json} + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endif() + + if (NOT result EQUAL 0) + message(FATAL_ERROR "Generating ${json} failed") + endif() +endfunction() + +# +# kcoreaddons_add_plugin(plugin_name SOURCES... [JSON "pluginname.json"] [INSTALL_NAMESPACE "servicename"]) +# +# This macro helps simplifying the creation of plugins for KPluginFactory +# based systems. +# It will create a plugin given the SOURCES list, the name of the JSON file +# that will define the plugin's metadata and the INSTALL_NAMESPACE so that +# the plugin is installed with the rest of the plugins from the same sub-system, +# within ${KDE_INSTALL_PLUGINDIR}. +# +# Example: +# kcoreaddons_add_plugin(kdeconnect_share JSON kdeconnect_share.json SOURCES ${kdeconnect_share_SRCS}) +# +# Since 5.10.0 + +function(kcoreaddons_add_plugin plugin) + set(options) + set(oneValueArgs JSON INSTALL_NAMESPACE) + set(multiValueArgs SOURCES) + cmake_parse_arguments(KCA_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT KCA_ADD_PLUGIN_SOURCES) + message(FATAL_ERROR "kcoreaddons_add_plugin called without SOURCES parameter") + endif() + get_filename_component(json "${KCA_ADD_PLUGIN_JSON}" REALPATH) + + add_library(${plugin} MODULE ${KCA_ADD_PLUGIN_SOURCES}) + set_property(TARGET ${plugin} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${json}) + # If find_package(ECM 5.38) or higher is called, output the plugin in a INSTALL_NAMESPACE subfolder. + # See https://community.kde.org/Guidelines_and_HOWTOs/Making_apps_run_uninstalled + if(NOT ("${ECM_GLOBAL_FIND_VERSION}" VERSION_LESS "5.38.0")) + set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${KCA_ADD_PLUGIN_INSTALL_NAMESPACE}") + endif() + + if (NOT KCA_ADD_PLUGIN_INSTALL_NAMESPACE) + message(FATAL_ERROR "Must specify INSTALL_NAMESPACE for ${plugin}") + endif() + + if(NOT ANDROID) + install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_PLUGINDIR}/${KCA_ADD_PLUGIN_INSTALL_NAMESPACE}) + else() + string(REPLACE "/" "_" pluginprefix "${KCA_ADD_PLUGIN_INSTALL_NAMESPACE}") + set_property(TARGET ${plugin} PROPERTY PREFIX "libplugins_") + if(NOT pluginprefix STREQUAL "") + set_property(TARGET ${plugin} APPEND_STRING PROPERTY PREFIX "${pluginprefix}_") + endif() + install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_PLUGINDIR}) + endif() +endfunction() 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/README.md b/README.md new file mode 100644 index 0000000..40479bf --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# KCoreAddons + +Qt addon library with a collection of non-GUI utilities + +## Introduction + +KCoreAddons provides classes built on top of QtCore to perform various tasks +such as manipulating mime types, autosaving files, creating backup files, +generating random sequences, performing text manipulations such as macro +replacement, accessing user information and many more. + diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 index 0000000..349ff33 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,121 @@ +include(ECMAddTests) +include(ConfigureChecks.cmake) #configure checks for QFileSystemWatcher + +find_package(Qt5Test ${REQUIRED_QT_VERSION} CONFIG QUIET) + +if(NOT Qt5Test_FOUND) + message(STATUS "Qt5Test not found, autotests will not be built.") + return() +endif() + +find_package(Threads REQUIRED) + +if(NOT CMAKE_BUILD_TYPE MATCHES "[Dd]ebug$") + set(ENABLE_BENCHMARKS 1) +endif() +configure_file(config-tests.h.in config-tests.h) + +macro(build_plugin pname) + add_library(${pname} MODULE ${ARGN}) + ecm_mark_as_test(${pname}) + target_link_libraries(${pname} KF5::CoreAddons) +endmacro() + +# Build some sample plugins +build_plugin(jsonplugin jsonplugin.cpp) +build_plugin(jsonplugin2 jsonplugin2.cpp) +build_plugin(versionedplugin versionedplugin.cpp) +build_plugin(unversionedplugin unversionedplugin.cpp) +build_plugin(multiplugin multiplugin.cpp) +build_plugin(alwaysunloadplugin alwaysunloadplugin.cpp) + +add_definitions( -DKDELIBS4CONFIGMIGRATOR_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) + +if (WIN32) + set(autotests_OPTIONAL_SRCS + ${autotests_OPTIONAL_SRCS} + klistopenfilesjobtest_win.cpp + ) +endif () + +if (UNIX) + set(autotests_OPTIONAL_SRCS + ${autotests_OPTIONAL_SRCS} + klistopenfilesjobtest_unix.cpp + ) +endif () + +ecm_add_tests( + kaboutdatatest.cpp + kaboutdataapplicationdatatest.cpp + kautosavefiletest.cpp + kcompositejobtest.cpp + kformattest.cpp + kjobtest.cpp + kosreleasetest.cpp + kpluginfactorytest.cpp + kpluginloadertest.cpp + kpluginmetadatatest.cpp + kprocesstest.cpp + krandomtest.cpp + kshareddatacachetest.cpp + kshelltest.cpp + kurlmimedatatest.cpp + kstringhandlertest.cpp + kusertest.cpp + kdelibs4migrationtest.cpp + kdelibs4configmigratortest.cpp + kprocesslisttest.cpp + kfileutilstest.cpp + ${autotests_OPTIONAL_SRCS} + LINK_LIBRARIES Qt5::Test KF5::CoreAddons +) + +if(NOT CMAKE_CROSSCOMPILING) + ecm_add_tests(desktoptojsontest.cpp LINK_LIBRARIES Qt5::Test KF5::CoreAddons) + target_compile_definitions(desktoptojsontest PRIVATE + DESKTOP_TO_JSON_EXE="$" + ) +endif() + +set(ktexttohtmltest_SRCS ktexttohtmltest.cpp ${CMAKE_SOURCE_DIR}/src/lib/text/ktexttohtml.cpp) +ecm_add_test(${ktexttohtmltest_SRCS} TEST_NAME ktexttohtmltest LINK_LIBRARIES Qt5::Test) +# include the binary dir in order to get kcoreaddons_export.h +target_include_directories(ktexttohtmltest PRIVATE ${KCoreAddons_BINARY_DIR}/src/lib) +# fake static linking to prevent the export macros on Windows from kicking in +target_compile_definitions(ktexttohtmltest PRIVATE -DKCOREADDONS_STATIC_DEFINE=1) + +add_executable(kprocesstest_helper kprocesstest_helper.cpp) +target_link_libraries(kprocesstest_helper KF5::CoreAddons) + +target_compile_definitions(kpluginloadertest PRIVATE + JSONPLUGIN_FILE="$" + VERSIONEDPLUGIN_FILE="$" + UNVERSIONEDPLUGIN_FILE="$" + MULTIPLUGIN_FILE="$" + ALWAYSUNLOADPLUGIN_FILE="$" +) + +set(KDIRWATCH_BACKENDS_TO_TEST Stat)#Stat is always compiled + +if (HAVE_SYS_INOTIFY_H) + list(APPEND KDIRWATCH_BACKENDS_TO_TEST INotify) +endif() + +if (HAVE_FAM) + list(APPEND KDIRWATCH_BACKENDS_TO_TEST Fam) +endif() + +if (HAVE_QFILESYSTEMWATCHER) + list(APPEND KDIRWATCH_BACKENDS_TO_TEST QFSWatch) +endif() + +foreach(_backendName ${KDIRWATCH_BACKENDS_TO_TEST}) + string(TOLOWER ${_backendName} _lowercaseBackendName) + set(BACKEND_TEST_TARGET kdirwatch_${_lowercaseBackendName}_unittest) + add_executable(${BACKEND_TEST_TARGET} kdirwatch_unittest.cpp) + target_link_libraries(${BACKEND_TEST_TARGET} Qt5::Test KF5::CoreAddons Threads::Threads) + ecm_mark_as_test(${BACKEND_TEST_TARGET}) + add_test(NAME ${BACKEND_TEST_TARGET} COMMAND ${BACKEND_TEST_TARGET}) + target_compile_definitions(${BACKEND_TEST_TARGET} PUBLIC -DKDIRWATCH_TEST_METHOD=\"${_backendName}\") +endforeach() diff --git a/autotests/ConfigureChecks.cmake b/autotests/ConfigureChecks.cmake new file mode 100644 index 0000000..d6ed942 --- /dev/null +++ b/autotests/ConfigureChecks.cmake @@ -0,0 +1,9 @@ +set(CMAKE_REQUIRED_LIBRARIES Qt5::Core) +check_cxx_source_compiles( +"#include +int main() +{ + QFileSystemWatcher *watcher = new QFileSystemWatcher(); + delete watcher; + return 0; +}" HAVE_QFILESYSTEMWATCHER) \ No newline at end of file diff --git a/autotests/alwaysunloadplugin.cpp b/autotests/alwaysunloadplugin.cpp new file mode 100644 index 0000000..b1603ab --- /dev/null +++ b/autotests/alwaysunloadplugin.cpp @@ -0,0 +1,21 @@ +/* + 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 new file mode 100644 index 0000000..1b28bcd --- /dev/null +++ b/autotests/alwaysunloadplugin.h @@ -0,0 +1,21 @@ +/* + 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/config-tests.h.in b/autotests/config-tests.h.in new file mode 100644 index 0000000..66bd948 --- /dev/null +++ b/autotests/config-tests.h.in @@ -0,0 +1 @@ +#cmakedefine01 ENABLE_BENCHMARKS diff --git a/autotests/data/appui1rc b/autotests/data/appui1rc new file mode 100644 index 0000000..e69de29 diff --git a/autotests/data/appuirc b/autotests/data/appuirc new file mode 100644 index 0000000..e69de29 diff --git a/autotests/data/fakeplugin.desktop b/autotests/data/fakeplugin.desktop new file mode 100644 index 0000000..4c33a92 --- /dev/null +++ b/autotests/data/fakeplugin.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Name=NSA Plugin +Name[ast]=Complementu NSA +Name[bs]=NSA dodatak +Name[ca]=Connector de la NSA +Name[ca@valencia]=Connector de la NSA +Name[cs]=Modul NSA +Name[da]=NSA-plugin +Name[de]=NSA-Modul +Name[el]=NSA Plugin +Name[en_GB]=NSA Plugin +Name[es]=Complemento NSA +Name[fi]=NSA-liitännäinen +Name[gd]=Plugan NSA +Name[gl]=Complemento de NSA +Name[he]=תוסף NSA +Name[hu]=NSA bővítmény +Name[it]=Estensione NSA +Name[ko]=NSA 플러그인 +Name[nb]=NSA programtillegg +Name[nl]=NSA-plug-in +Name[nn]=NSA-tillegg +Name[pl]=Wtyczka NSA +Name[pt]='Plugin' da NSA +Name[pt_BR]=Plugin NSA +Name[ru]=Модуль ФСБ +Name[sk]=NSA plugin +Name[sl]=Vstavek NSA +Name[sr]=НСА‑ов прикључак +Name[sr@ijekavian]=НСА‑ов прикључак +Name[sr@ijekavianlatin]=NSA‑ov priključak +Name[sr@latin]=NSA‑ov priključak +Name[sv]=NSA-insticksprogram +Name[tr]=NSA Eklentisi +Name[uk]=Додаток NSA +Name[x-test]=xxNSA Pluginxx +Name[zh_CN]=NSA 插件 +Name[zh_TW]=NSA 外掛程式 +Comment=Test Plugin Spy +Comment[ast]=Complementu de prueba qu'escluca +Comment[bs]=Å pijun provjere dodataka +Comment[ca]=Connector de proves espia +Comment[ca@valencia]=Connector de proves espia +Comment[cs]=Testovací modul Spy +Comment[da]=Test-plugin spion +Comment[de]=Spionage-Testmodul +Comment[el]=Test Plugin Spy +Comment[en_GB]=Test Plugin Spy +Comment[es]=Probar espía de complementos +Comment[fi]=Testivakoiluliitännäinen +Comment[gd]=Plugan deuchainneach brathadair +Comment[gl]=Complemento espía de proba +Comment[he]=בדיקת תוסף ריגול +Comment[hu]=Kémbővítmény tesztelése +Comment[it]=Estensione di prova Spy +Comment[ko]=테스트 플러그인 첩자 +Comment[nb]=Test tilleggsspion +Comment[nl]=Plug-in Spy testen +Comment[nn]=Spion for test-tillegg +Comment[pl]=Wypróbuj szpiega wtyczki +Comment[pt]=Espião dos 'Plugins' de Testes +Comment[pt_BR]=Plugin de teste de espionagem +Comment[ru]=Тестовый прослушивающий модуль +Comment[sk]=Testovací plugin Å¡pión +Comment[sl]=Preizkusni vohunski vstavek +Comment[sr]=Пробни прикључак шпијун +Comment[sr@ijekavian]=Пробни прикључак шпијун +Comment[sr@ijekavianlatin]=Probni priključak Å¡pijun +Comment[sr@latin]=Probni priključak Å¡pijun +Comment[sv]=Testa insticksprogramspion +Comment[tr]=Test Eklenti Ajanı +Comment[uk]=Тестовий додаток +Comment[x-test]=xxTest Plugin Spyxx +Comment[zh_CN]=Test Plugin Spy +Comment[zh_TW]=測試外掛程式 +Type=Service +Icon=preferences-system-time +MimeType=image/png;application/pdf; + +X-KDE-ServiceTypes=KService/NSA +X-KDE-Library=fakeplugin +X-KDE-FormFactors=mediacenter,desktop +X-KDE-PluginInfo-Author=Sebastian Kügler +X-KDE-PluginInfo-Email=sebas@kde.org +X-KDE-PluginInfo-Name=fakeplugin +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=https://kde.org/ +X-KDE-PluginInfo-Category=Examples +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=LGPL +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/autotests/data/foo1rc b/autotests/data/foo1rc new file mode 100644 index 0000000..e69de29 diff --git a/autotests/data/foorc b/autotests/data/foorc new file mode 100644 index 0000000..e69de29 diff --git a/autotests/data/hiddenplugin.desktop b/autotests/data/hiddenplugin.desktop new file mode 100644 index 0000000..11eb4a3 --- /dev/null +++ b/autotests/data/hiddenplugin.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Name=NSA Plugin +Name[ast]=Complementu NSA +Name[bs]=NSA dodatak +Name[ca]=Connector de la NSA +Name[ca@valencia]=Connector de la NSA +Name[cs]=Modul NSA +Name[da]=NSA-plugin +Name[de]=NSA-Modul +Name[el]=NSA Plugin +Name[en_GB]=NSA Plugin +Name[es]=Complemento NSA +Name[fi]=NSA-liitännäinen +Name[gd]=Plugan NSA +Name[gl]=Complemento de NSA +Name[he]=תוסף NSA +Name[hu]=NSA bővítmény +Name[it]=Estensione NSA +Name[ko]=NSA 플러그인 +Name[nb]=NSA programtillegg +Name[nl]=NSA-plug-in +Name[nn]=NSA-tillegg +Name[pl]=Wtyczka NSA +Name[pt]='Plugin' da NSA +Name[pt_BR]=Plugin NSA +Name[ru]=Модуль ФСБ +Name[sk]=NSA plugin +Name[sl]=Vstavek NSA +Name[sr]=НСА‑ов прикључак +Name[sr@ijekavian]=НСА‑ов прикључак +Name[sr@ijekavianlatin]=NSA‑ov priključak +Name[sr@latin]=NSA‑ov priključak +Name[sv]=NSA-insticksprogram +Name[tr]=NSA Eklentisi +Name[uk]=Додаток NSA +Name[x-test]=xxNSA Pluginxx +Name[zh_CN]=NSA 插件 +Name[zh_TW]=NSA 外掛程式 +Comment=Test Plugin Spy +Comment[ast]=Complementu de prueba qu'escluca +Comment[bs]=Å pijun provjere dodataka +Comment[ca]=Connector de proves espia +Comment[ca@valencia]=Connector de proves espia +Comment[cs]=Testovací modul Spy +Comment[da]=Test-plugin spion +Comment[de]=Spionage-Testmodul +Comment[el]=Test Plugin Spy +Comment[en_GB]=Test Plugin Spy +Comment[es]=Probar espía de complementos +Comment[fi]=Testivakoiluliitännäinen +Comment[gd]=Plugan deuchainneach brathadair +Comment[gl]=Complemento espía de proba +Comment[he]=בדיקת תוסף ריגול +Comment[hu]=Kémbővítmény tesztelése +Comment[it]=Estensione di prova Spy +Comment[ko]=테스트 플러그인 첩자 +Comment[nb]=Test tilleggsspion +Comment[nl]=Plug-in Spy testen +Comment[nn]=Spion for test-tillegg +Comment[pl]=Wypróbuj szpiega wtyczki +Comment[pt]=Espião dos 'Plugins' de Testes +Comment[pt_BR]=Plugin de teste de espionagem +Comment[ru]=Тестовый прослушивающий модуль +Comment[sk]=Testovací plugin Å¡pión +Comment[sl]=Preizkusni vohunski vstavek +Comment[sr]=Пробни прикључак шпијун +Comment[sr@ijekavian]=Пробни прикључак шпијун +Comment[sr@ijekavianlatin]=Probni priključak Å¡pijun +Comment[sr@latin]=Probni priključak Å¡pijun +Comment[sv]=Testa insticksprogramspion +Comment[tr]=Test Eklenti Ajanı +Comment[uk]=Тестовий додаток +Comment[x-test]=xxTest Plugin Spyxx +Comment[zh_CN]=Test Plugin Spy +Comment[zh_TW]=測試外掛程式 +Type=Service +Icon=preferences-system-time +Hidden=true + +X-KDE-ServiceTypes=KService/NSA +X-KDE-Library=fakeplugin + +X-KDE-PluginInfo-Author=Sebastian Kügler +X-KDE-PluginInfo-Email=sebas@kde.org +X-KDE-PluginInfo-Name=fakeplugin +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=https://kde.org/ +X-KDE-PluginInfo-Category=Examples +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=LGPL +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/autotests/data/os-release b/autotests/data/os-release new file mode 100644 index 0000000..583baf5 --- /dev/null +++ b/autotests/data/os-release @@ -0,0 +1,22 @@ +NAME="Name" +VERSION="100.5" +ID=theid +ID_LIKE="otherid otherotherid" +VERSION_CODENAME=versioncodename +VERSION_ID="500.1" +PRETTY_NAME="Pretty Name #1" +ANSI_COLOR="1;34" +CPE_NAME="cpe:/o:foo:bar:100" +HOME_URL="https://url.home" +DOCUMENTATION_URL="https://url.docs" +SUPPORT_URL="https://url.support" +BUG_REPORT_URL="https://url.bugs" +PRIVACY_POLICY_URL="https://url.privacy" +BUILD_ID="105.5" +# comment +VARIANT="Test = Edition" +BROKENLINE_SHOULD_BE_IGNORED +VARIANT_ID=test + # indented comment +LOGO=start-here-test +DEBIAN_BTS="debbugs://bugs.debian.org/" diff --git a/autotests/data/servicetypes/bad-groups-input.desktop b/autotests/data/servicetypes/bad-groups-input.desktop new file mode 100644 index 0000000..48da13f --- /dev/null +++ b/autotests/data/servicetypes/bad-groups-input.desktop @@ -0,0 +1,46 @@ +[Desktop Entry] +Name=Bad Groups +Name[ca]=Grups dolents +Name[ca@valencia]=Grups dolents +Name[da]=DÃ¥rlige grupper +Name[de]=Schlechte Gruppen +Name[el]=Κακές ομάδες +Name[en_GB]=Bad Groups +Name[es]=Grupos incorrectos +Name[fi]=Huonot ryhmät +Name[gl]=Grupos malos +Name[it]=Gruppi errati +Name[ko]=불량 그룹 +Name[nl]=Foute groepen +Name[pl]=Złe grupy +Name[pt]=Grupos Inválidos +Name[pt_BR]=Grupos inválidos +Name[sk]=Zlé skupiny +Name[sl]=Slabe skupine +Name[sr]=Лоше групе +Name[sr@ijekavian]=Лоше групе +Name[sr@ijekavianlatin]=LoÅ¡e grupe +Name[sr@latin]=LoÅ¡e grupe +Name[sv]=Felaktiga grupper +Name[uk]=Погані групи +Name[x-test]=xxBad Groupsxx +Name[zh_CN]=坏分组 +Type=Service +# one value for every property definition in bad-groups-servicetype.desktop +ThisIsOkay=10 +#empty +=11 +#missing terminator +MissingTerminator=12 +# empty and missing terminator +=13 +# completely empty +=14 +SomeOtherProperty=15 +# extra spaces in group name (should be okay) +TrailingSpacesAreOkay=16 +#missing type +MissingType=17 +InvalidType=18 +# ok again after invalid ones +ThisIsOkayAgain=19 diff --git a/autotests/data/servicetypes/bad-groups-servicetype.desktop b/autotests/data/servicetypes/bad-groups-servicetype.desktop new file mode 100644 index 0000000..b783dab --- /dev/null +++ b/autotests/data/servicetypes/bad-groups-servicetype.desktop @@ -0,0 +1,34 @@ +[Desktop Entry] +Type=ServiceType + +[PropertyDef::ThisIsOkay] +Type=int +# missing name +[PropertyDef::] +Type=int +# missing terminator +[PropertyDef::MissingTerminator +Type=int +# empty and missing terminator +[PropertyDef:: +Type=int +# completely empty group +[ +Type=int +# completely empty group +[DoesNotStartWithPropertyDef::SomeOtherProperty] +Type=int +# extra spaces +[PropertyDef::TrailingSpacesAreOkay ] +Type=int + +# missing Type=key +[PropertyDef::MissingType] +NoType=int + +# invalid Type=key +[PropertyDef::InvalidType] +Type=integer + +[PropertyDef::ThisIsOkayAgain] +Type=int diff --git a/autotests/data/servicetypes/bool-servicetype.desktop b/autotests/data/servicetypes/bool-servicetype.desktop new file mode 100644 index 0000000..06764af --- /dev/null +++ b/autotests/data/servicetypes/bool-servicetype.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=ServiceType + +[PropertyDef::X-Test-Bool] +Type=bool + diff --git a/autotests/data/servicetypes/example-input.desktop b/autotests/data/servicetypes/example-input.desktop new file mode 100644 index 0000000..52f77cb --- /dev/null +++ b/autotests/data/servicetypes/example-input.desktop @@ -0,0 +1,39 @@ +[Desktop Entry] +Name=Example +Name[ca]=Exemple +Name[ca@valencia]=Exemple +Name[da]=Eksempel +Name[de]=Beispiel +Name[el]=Παράδειγμα +Name[en_GB]=Example +Name[es]=Ejemplo +Name[fi]=Esimerkki +Name[gl]=Exemplo +Name[it]=Esempio +Name[ko]=예제 +Name[nb]=Eksempel +Name[nl]=Voorbeeld +Name[pl]=Przykład +Name[pt]=Exemplo +Name[pt_BR]=Exemplo +Name[sk]=Príklad +Name[sl]=Primer +Name[sr]=Пример +Name[sr@ijekavian]=Пример +Name[sr@ijekavianlatin]=Primer +Name[sr@latin]=Primer +Name[sv]=Exempel +Name[uk]=Приклад +Name[x-test]=xxExamplexx +Name[zh_CN]=例子 +Type=Service +X-KDE-ServiceTypes=example/servicetype,bar/foo +X-Test-Integer=42 +X-Test-Double=42.42 +X-Test-List=a,b,c,def +X-Test-String=foobar +X-Test-Bool=true +# not defined -> string +X-Test-Unknown=true +# QSize not supported -> string +X-Test-Size=10,20 diff --git a/autotests/data/servicetypes/example-servicetype.desktop b/autotests/data/servicetypes/example-servicetype.desktop new file mode 100644 index 0000000..db84caa --- /dev/null +++ b/autotests/data/servicetypes/example-servicetype.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=example/servicetype + +[PropertyDef::X-Test-Integer] +Type=int +[PropertyDef::X-Test-Double] +Type=double +[PropertyDef::X-Test-Bool] +Type=bool +[PropertyDef::X-Test-List] +Type=QStringList +[PropertyDef::X-Test-String] +Type=QString +# this is not supported -> should not convert +# was used by KDE4 plasma-applet.desktop but that is no longer the case +[PropertyDef::X-Test-Size] +Type=QSize diff --git a/autotests/data/servicetypes/fake-kdedmodule.desktop b/autotests/data/servicetypes/fake-kdedmodule.desktop new file mode 100644 index 0000000..d1d3741 --- /dev/null +++ b/autotests/data/servicetypes/fake-kdedmodule.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KDEDModule +[PropertyDef::X-KDE-FactoryName] +Type=QString +[PropertyDef::X-KDE-Kded-autoload] +Type=bool +[PropertyDef::X-KDE-Kded-load-on-demand] +Type=bool diff --git a/autotests/data/servicetypes/fake-kdevelopplugin.desktop b/autotests/data/servicetypes/fake-kdevelopplugin.desktop new file mode 100644 index 0000000..d905179 --- /dev/null +++ b/autotests/data/servicetypes/fake-kdevelopplugin.desktop @@ -0,0 +1,70 @@ +# this is a copy of kdevelopplugin.desktop as an example of a real service type definition + +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KDevelop/NonExistentPlugin +X-KDE-Derived=KPluginInfo +#Name=KDevelop Plugin + +# mandatory, versioning - prevent DLL hell +[PropertyDef::X-KDevelop-Version] +Type=int + +# optional, determines whether a plugin is loaded only after +# a project is opened, or is a global plugin. +# If it is not set, the plugin can only be loaded by the +# user or via requesting one of its dependencies +# allowed values: Global, Project +[PropertyDef::X-KDevelop-Category] +Type=QString + +# mandatory, GUI-Operation Mode, determines whether a plugin +# can work without having a mainwindow/partcontroller +# running +# allowed values: GUI, NoGUI +[PropertyDef::X-KDevelop-Mode] +Type=QString + +# optional, Arguments to pass to the plugin +[PropertyDef::X-KDevelop-Args] +Type=QString + +# optional, Interfaces that a plugin implements +# usually values start with org.kdevelop +[PropertyDef::X-KDevelop-Interfaces] +Type=QStringList + +# optional, interfaces that this plugin depends +# on +[PropertyDef::X-KDevelop-IRequired] +Type=QStringList + +# optional, interfaces that this plugin can use, +# but the plugin still works if the interfaces are +# not available. +[PropertyDef::X-KDevelop-IOptional] +Type=QStringList + +# optional, mimetypes supported by a language plugin +[PropertyDef::X-KDevelop-SupportedMimeTypes] +Type=QStringList + +# optional, language supported by a language plugin +[PropertyDef::X-KDevelop-Language] +Type=QString + +# optional, defines whether the plugin can be disabled +# by the user. Possible values are "AlwaysOn" and "UserSelectable". +# If the property is missing then UserSelectable is assumed +[PropertyDef::X-KDevelop-LoadMode] +Type=QString + +# optional, list of filters for "projectfiles" for the project plugin +# For example: Makefile,Makefile.* for Makefile's +[PropertyDef::X-KDevelop-ProjectFilesFilter] +Type=QStringList + +# optional, description for the projectfiles filter +[PropertyDef::X-KDevelop-ProjectFilesFilterDescription] +Type=QString + diff --git a/autotests/data/servicetypes/invalid-missing-servicetype.desktop b/autotests/data/servicetypes/invalid-missing-servicetype.desktop new file mode 100644 index 0000000..88357a5 --- /dev/null +++ b/autotests/data/servicetypes/invalid-missing-servicetype.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +# Type must be ServiceType otherwise this file is invalid +Type=Service + +# as this file is invalid check that this property is not converted +[PropertyDef::ShouldNotBeConvertedToInt] +Type=int diff --git a/autotests/data/testmetadata.json b/autotests/data/testmetadata.json new file mode 100644 index 0000000..43bda1d --- /dev/null +++ b/autotests/data/testmetadata.json @@ -0,0 +1,15 @@ +{ + "KPlugin": { + "Authors": [ + { + "Name": "Aleix Pol" + } + ], + "Description": "Test stuff.", + "Icon": "kdevelop", + "License": "GPL", + "Name": "Test" + }, + "X-Plasma-MainScript": "ui/main.qml", + "X-Purpose-PluginTypes": [ "Export" ] +} diff --git a/autotests/data/twostepsparsetest.desktop b/autotests/data/twostepsparsetest.desktop new file mode 100644 index 0000000..616feb1 --- /dev/null +++ b/autotests/data/twostepsparsetest.desktop @@ -0,0 +1,20 @@ +[Desktop Entry] +Name=Parse Test +Comment=Two Steps Parsing Test +Type=Service +Icon=preferences-system-time +MimeType=image/png;application/pdf; + +X-Test-List=first,second +X-KDE-ServiceTypes=example/servicetype +X-KDE-Library=fakeplugin +X-KDE-FormFactors=mediacenter,desktop +X-KDE-PluginInfo-Author=Sebastian Kügler +X-KDE-PluginInfo-Email=sebas@kde.org +X-KDE-PluginInfo-Name=fakeplugin +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=https://kde.org/ +X-KDE-PluginInfo-Category=Examples +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=LGPL +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/autotests/desktoptojsontest.cpp b/autotests/desktoptojsontest.cpp new file mode 100644 index 0000000..956466b --- /dev/null +++ b/autotests/desktoptojsontest.cpp @@ -0,0 +1,344 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2014 Alex Richardson + + 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 new file mode 100644 index 0000000..f2eff39 --- /dev/null +++ b/autotests/jsonplugin.cpp @@ -0,0 +1,18 @@ +/* + 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 new file mode 100644 index 0000000..1ffe6ae --- /dev/null +++ b/autotests/jsonplugin.h @@ -0,0 +1,20 @@ +/* + 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/jsonplugin.json b/autotests/jsonplugin.json new file mode 100644 index 0000000..f3c7c40 --- /dev/null +++ b/autotests/jsonplugin.json @@ -0,0 +1,12 @@ +{ + "KPlugin": { + "Description": "This is a plugin", + "Description[nl]": "Dit is een plug-in", + "Description[pt]": "Isto é um 'plugin'", + "Description[pt_BR]": "Isto é um plugin", + "Description[sv]": "Det här är ett insticksprogram", + "Description[uk]": "Це додаток", + "Description[x-test]": "xxThis is a pluginxx", + "MimeTypes": [ "text/plain", "image/png" ] + } +} diff --git a/autotests/jsonplugin2.cpp b/autotests/jsonplugin2.cpp new file mode 100644 index 0000000..c325d53 --- /dev/null +++ b/autotests/jsonplugin2.cpp @@ -0,0 +1,18 @@ +/* + 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 new file mode 100644 index 0000000..a70390a --- /dev/null +++ b/autotests/jsonplugin2.h @@ -0,0 +1,20 @@ +/* + 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/jsonplugin2.json b/autotests/jsonplugin2.json new file mode 100644 index 0000000..4cd4ba9 --- /dev/null +++ b/autotests/jsonplugin2.json @@ -0,0 +1,13 @@ +{ + "KPlugin": { + "Description": "This is another plugin", + "Description[nl]": "Dit is een andere plug-in", + "Description[pt]": "Este é outro 'plugin'", + "Description[pt_BR]": "Isto é outro plugin", + "Description[sv]": "Det här är ännu ett insticksprogram", + "Description[uk]": "Це інший додаток", + "Description[x-test]": "xxThis is another pluginxx", + "Id": "foobar", + "MimeTypes": [ "text/html" ] + } +} diff --git a/autotests/kaboutdataapplicationdatatest.cpp b/autotests/kaboutdataapplicationdatatest.cpp new file mode 100644 index 0000000..8cbc6fb --- /dev/null +++ b/autotests/kaboutdataapplicationdatatest.cpp @@ -0,0 +1,107 @@ +/* + 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(); +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) + void testRegisterPluginData(); +#endif +}; + + +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)); +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) +void KAboutDataApplicationDataTest::testRegisterPluginData() +{ + for (const auto &name : {QStringLiteral("foo"), QStringLiteral("bar")}) { + QVERIFY(!KAboutData::pluginData(name)); + KAboutData::registerPluginData(KAboutData(name)); + + auto v1 = KAboutData::pluginData(name); + QVERIFY(v1); + QCOMPARE(v1->componentName(), name); + + // re-registering will overwrite and not trigger memory leaks (check LSAN) + KAboutData::registerPluginData(KAboutData(name)); + + // the pointer staid the same, as QHash is node based + QCOMPARE(KAboutData::pluginData(name), v1); + } +} +#endif + +QTEST_MAIN(KAboutDataApplicationDataTest) + +#include "kaboutdataapplicationdatatest.moc" diff --git a/autotests/kaboutdatatest.cpp b/autotests/kaboutdatatest.cpp new file mode 100644 index 0000000..062d664 --- /dev/null +++ b/autotests/kaboutdatatest.cpp @@ -0,0 +1,364 @@ +/* + 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 new file mode 100644 index 0000000..fc2b5cd --- /dev/null +++ b/autotests/kautosavefiletest.cpp @@ -0,0 +1,155 @@ +/* + 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 new file mode 100644 index 0000000..7534153 --- /dev/null +++ b/autotests/kautosavefiletest.h @@ -0,0 +1,31 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2006 Jacob R Rideout + + 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 new file mode 100644 index 0000000..bb62067 --- /dev/null +++ b/autotests/kcompositejobtest.cpp @@ -0,0 +1,95 @@ +/* + 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 new file mode 100644 index 0000000..3e73091 --- /dev/null +++ b/autotests/kcompositejobtest.h @@ -0,0 +1,58 @@ +/* + 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 new file mode 100644 index 0000000..17fc0d4 --- /dev/null +++ b/autotests/kdelibs4configmigratortest.cpp @@ -0,0 +1,129 @@ +/* + 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 new file mode 100644 index 0000000..9ab54ce --- /dev/null +++ b/autotests/kdelibs4migrationtest.cpp @@ -0,0 +1,46 @@ +/* + 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 new file mode 100644 index 0000000..4b98e98 --- /dev/null +++ b/autotests/kdirwatch_unittest.cpp @@ -0,0 +1,909 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2009 David Faure + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include + +#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_stat = dirW->internalMethod() == KDirWatch::Stat; + m_slow = (dirW->internalMethod() == KDirWatch::FAM || m_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 benchCreateTree(); + void benchCreateWatcher(); + void benchNotifyWatcher(); + void testRefcounting(); + +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; + bool m_stat; +}; + +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)); +#ifdef Q_OS_FREEBSD + // FreeBSD has inotify implemented as user-space library over native kevent API. + // When using it, one has to open() a file to start watching it, so workaround + // test breakage by giving inotify time to react to file creation. + // Full context: https://github.com/libinotify-kqueue/libinotify-kqueue/issues/10 + if(!m_slow) + QThread::msleep(1); +#endif + 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 std::move(spyDirty); + } + spyDirty.wait(50); + } + return std::move(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. + // + // In addition, it allows dirty() to be emitted (for that same path) as an alternative + + const QString expectedPath = removeTrailingSlash(path); + QSignalSpy spyDirty(&watch, &KDirWatch::dirty); + QSignalSpy spyDeleted(&watch, &KDirWatch::deleted); + QSignalSpy spyCreated(&watch, &KDirWatch::created); + + int numTries = 0; + while (spyDeleted.isEmpty() && spyDirty.isEmpty()) { + if (++numTries > s_maxTries) { + return false; + } + spyDeleted.wait(50); + while (!spyDirty.isEmpty()) { + if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated + spyDirty.removeFirst(); + } + } + } + if (!spyDirty.isEmpty()) { + return true; + } + + // 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 std::move(spyDeleted); + } + spyDeleted.wait(50); + } + return std::move(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))); + 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); + // This triggers bug #374075. + watch.addDir(QStringLiteral(":/kio5/newfile-templates")); + watch.startScan(); + if (watch.internalMethod() != KDirWatch::INotify) { + waitUntilNewSecond(); // necessary for mtime checks in scanEntry + } + 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 + } + createFile(1); + 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) { + + if (m_slow || watch.internalMethod() == KDirWatch::QFSWatch) { + waitUntilNewSecond(); + } + + qDebug() << "Attempt #" << (i+1) << "removing+recreating" << file1; + + // 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. + // + // In addition, allow dirty() to be emitted (for that same path) as an alternative + + const QString expectedPath = file1; + QSignalSpy spyDirty(&watch, &KDirWatch::dirty); + QSignalSpy spyDeleted(&watch, &KDirWatch::deleted); + QSignalSpy spyCreated(&watch, &KDirWatch::created); + + // WHEN + 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! + + // THEN + int numTries = 0; + while (spyDeleted.isEmpty() && spyDirty.isEmpty()) { + if (++numTries > s_maxTries) { + QFAIL("Failed to detect file deletion and recreation through either a deleted/created signal pair or through a dirty signal!"); + return; + } + spyDeleted.wait(50); + while (!spyDirty.isEmpty()) { + if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated + spyDirty.removeFirst(); + } else { + break; + } + } + } + if (!spyDirty.isEmpty()) { + continue; // all ok + } + + // 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) (" << expectedPath << ")"; + QFAIL("Timeout waiting for KDirWatch signal created, after deleted was emitted"); + return; + } + + QVERIFY(verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath)); + } + + 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::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(); + } +} + +void KDirWatch_UnitTest::testRefcounting() +{ +#if QT_CONFIG(cxx11_future) + bool initialExists = false; + bool secondExists = true; // the expectation is it will be set false + auto thread = QThread::create([&] { + QTemporaryDir dir; + { + KDirWatch watch; + watch.addFile(dir.path()); + initialExists = KDirWatch::exists(); + } // out of scope, the internal private should have been unset + secondExists = KDirWatch::exists(); + }); + thread->start(); + thread->wait(); + delete thread; + QVERIFY(initialExists); + QVERIFY(!secondExists); +#endif +} + +#include "kdirwatch_unittest.moc" diff --git a/autotests/kfileutilstest.cpp b/autotests/kfileutilstest.cpp new file mode 100644 index 0000000..982c859 --- /dev/null +++ b/autotests/kfileutilstest.cpp @@ -0,0 +1,53 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2000-2005 David Faure + + 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 new file mode 100644 index 0000000..425a5d7 --- /dev/null +++ b/autotests/kfileutilstest.h @@ -0,0 +1,22 @@ +/* + This file is part of the KDE libraries + SPDX-FileCopyrightText: 2000-2005 David Faure + + 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 new file mode 100644 index 0000000..42fe869 --- /dev/null +++ b/autotests/kformattest.cpp @@ -0,0 +1,390 @@ +/* + 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")); + + QDateTime now = QDateTime::currentDateTime(); + + // 1 minute ago + testDateTime = now.addSecs(-1); + QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("Just now")); + + // 5 minutes ago + testDateTime = now.addSecs(-300); + QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("5 minutes ago")); + + 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 new file mode 100644 index 0000000..b7585da --- /dev/null +++ b/autotests/kformattest.h @@ -0,0 +1,31 @@ +/* + 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 new file mode 100644 index 0000000..c4aa62f --- /dev/null +++ b/autotests/kjobtest.cpp @@ -0,0 +1,534 @@ +/* + 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 +#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, [this](KJob *job) { + slotResult(job); + loop.quit(); + }); + + QFETCH(int, errorCode); + QFETCH(QString, errorText); + + job->setError(errorCode); + job->setErrorText(errorText); + + QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); + job->start(); + QVERIFY(!job->isFinished()); + loop.exec(); + QVERIFY(job->isFinished()); + + 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)); + + processed_spy.clear(); + total_spy.clear(); + percent_spy.clear(); + + /** + * Try again with Files as the progress unit + */ + testJob->setProgressUnit(KJob::Files); + testJob->setProcessedSize(16); + QCOMPARE(percent_spy.size(), 0); // no impact on percent + + testJob->setTotalFiles(5); + QCOMPARE(percent_spy.size(), 1); + QCOMPARE(percent_spy.at(0).at(1).value(), static_cast(0)); + + testJob->setProcessedFiles(2); + QCOMPARE(percent_spy.size(), 2); + QCOMPARE(percent_spy.at(1).at(1).value(), static_cast(40)); + + 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*))); + + QVERIFY(!job->isFinished()); + bool status = job->exec(); + QVERIFY(job->isFinished()); + + 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() +{ + auto *const job = setupErrorResultFinished(); + QSignalSpy destroyed_spy(job, &QObject::destroyed); + + QFETCH(int, killVerbosity); + QFETCH(int, errorCode); + QFETCH(QString, errorText); + QFETCH(int, resultEmitCount); + QFETCH(int, finishedEmitCount); + + QVERIFY(!job->isFinished()); + job->kill(static_cast(killVerbosity)); + QVERIFY(job->isFinished()); + 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::testDestroy() +{ + auto *const job = setupErrorResultFinished(); + QVERIFY(!job->isFinished()); + delete job; + QCOMPARE(m_lastError, static_cast(KJob::NoError)); + QCOMPARE(m_lastErrorText, QString{}); + QCOMPARE(m_resultCount, 0); + QCOMPARE(m_finishedCount, 1); +} + +void KJobTest::testEmitAtMostOnce_data() +{ + QTest::addColumn("autoDelete"); + QTest::addColumn>("actions"); + + const auto actionName = [](Action action) { + return QMetaEnum::fromType().valueToKey(static_cast(action)); + }; + + for (bool autoDelete : {true, false}) { + for (Action a : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) { + for (Action b : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) { + const auto dataTag = std::string{actionName(a)} + '-' + actionName(b) + + (autoDelete ? "-autoDelete" : ""); + QTest::newRow(dataTag.c_str()) << autoDelete << QVector{a, b}; + } + } + } +} + +void KJobTest::testEmitAtMostOnce() +{ + auto *const job = setupErrorResultFinished(); + QSignalSpy destroyed_spy(job, &QObject::destroyed); + + QFETCH(bool, autoDelete); + job->setAutoDelete(autoDelete); + + QFETCH(QVector, actions); + for (auto action : actions) { + switch (action) { + case Action::Start: + job->start(); // in effect calls QTimer::singleShot(0, ... emitResult) + break; + case Action::KillQuietly: + QTimer::singleShot(0, job, [=] { job->kill(KJob::Quietly); }); + break; + case Action::KillVerbosely: + QTimer::singleShot(0, job, [=] { job->kill(KJob::EmitResult); }); + break; + } + } + + QVERIFY(!job->isFinished()); + loop.processEvents(QEventLoop::AllEvents, 2000); + QCOMPARE(destroyed_spy.size(), autoDelete); + if (!autoDelete) { + QVERIFY(job->isFinished()); + } + + QVERIFY(!actions.empty()); + // The first action alone should determine the job's error and result. + const auto firstAction = actions.front(); + + const int errorCode = firstAction == Action::Start ? KJob::NoError + : KJob::KilledJobError; + QCOMPARE(m_lastError, errorCode); + QCOMPARE(m_lastErrorText, QString{}); + if (!autoDelete) { + QCOMPARE(job->error(), m_lastError); + QCOMPARE(job->errorText(), m_lastErrorText); + } + + QCOMPARE(m_resultCount, firstAction == Action::KillQuietly ? 0 : 1); + QCOMPARE(m_finishedCount, 1); + + if (!autoDelete) { + delete job; + } +} + +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) +{ + const auto testJob = qobject_cast(job); + QVERIFY(testJob); + QVERIFY(testJob->isFinished()); + + if (job->error()) { + m_lastError = job->error(); + m_lastErrorText = job->errorText(); + } else { + m_lastError = KJob::NoError; + m_lastErrorText.clear(); + } + + m_resultCount++; +} + +void KJobTest::slotFinished(KJob *job) +{ + // qobject_cast and dynamic_cast to TestJob* fail when finished() signal is emitted from + // ~KJob(). The static_cast allows to call the otherwise protected KJob::isFinished(). + // WARNING: don't use this trick in production code, because static_cast-ing + // to a wrong type and then dereferencing the pointer is undefined behavior. + // Normally a KJob and its subclasses should manage their finished state on their own. + // If you *really* need KJob::isFinished() to be public, request this access + // modifier change in the KJob class. + QVERIFY(static_cast(job)->isFinished()); + + if (job->error()) { + m_lastError = job->error(); + m_lastErrorText = job->errorText(); + } else { + m_lastError = KJob::NoError; + m_lastErrorText.clear(); + } + + m_finishedCount++; +} + +TestJob *KJobTest::setupErrorResultFinished() +{ + m_lastError = KJob::UserDefinedError; + m_lastErrorText.clear(); + m_resultCount = 0; + m_finishedCount = 0; + + auto *job = new TestJob; + connect(job, &KJob::result, this, &KJobTest::slotResult); + connect(job, &KJob::finished, this, &KJobTest::slotFinished); + return job; +} + +TestJob::TestJob() : KJob() +{ + +} + +TestJob::~TestJob() +{ + +} + +void TestJob::start() +{ + QTimer::singleShot(0, this, [this] { emitResult(); }); +} + +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::setProcessedFiles(qulonglong files) +{ + KJob::setProcessedAmount(KJob::Files, files); +} + +void TestJob::setTotalFiles(qulonglong files) +{ + KJob::setTotalAmount(KJob::Files, files); +} + +void TestJob::setPercent(unsigned long percentage) +{ + KJob::setPercent(percentage); +} + +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 new file mode 100644 index 0000000..be02a4d --- /dev/null +++ b/autotests/kjobtest.h @@ -0,0 +1,109 @@ +/* + 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; + using KJob::isFinished; + using KJob::setProgressUnit; + +protected: + bool doKill() override; + +public: + void setError(int errorCode); + void setErrorText(const QString &errorText); + void setProcessedSize(qulonglong size); + void setTotalSize(qulonglong size); + void setProcessedFiles(qulonglong files); + void setTotalFiles(qulonglong files); + void setPercent(unsigned long percentage); +}; + +class TestJobUiDelegate : public KJobUiDelegate +{ + Q_OBJECT +protected: + virtual void connectJob(KJob *job); +}; + +class WaitJob; + +class KJobTest : public QObject +{ + Q_OBJECT +public: + enum class Action { + Start, + KillQuietly, + KillVerbosely + }; + Q_ENUM(Action) + + 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 testDestroy(); + void testEmitAtMostOnce_data(); + void testEmitAtMostOnce(); + void testDelegateUsage(); + void testNestedExec(); + + void slotResult(KJob *job); + void slotFinished(KJob *job); + +private: + TestJob *setupErrorResultFinished(); + + 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 new file mode 100644 index 0000000..e24f9bf --- /dev/null +++ b/autotests/klistopenfilesjobtest_unix.cpp @@ -0,0 +1,107 @@ +/* + 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"); + } + QTemporaryDir tempDir; + QFile tempFile(tempDir.path() + QStringLiteral("/file")); + QVERIFY(tempFile.open(QIODevice::WriteOnly)); + auto job = new KListOpenFilesJob(tempDir.path()); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + 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()); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + 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); + QVERIFY(!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()); + QVERIFY(!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 new file mode 100644 index 0000000..156e438 --- /dev/null +++ b/autotests/klistopenfilesjobtest_unix.h @@ -0,0 +1,24 @@ +/* + 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 new file mode 100644 index 0000000..218b2f2 --- /dev/null +++ b/autotests/klistopenfilesjobtest_win.cpp @@ -0,0 +1,24 @@ +/* + 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 new file mode 100644 index 0000000..73bdfbb --- /dev/null +++ b/autotests/klistopenfilesjobtest_win.h @@ -0,0 +1,21 @@ +/* + 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 new file mode 100644 index 0000000..5ff790a --- /dev/null +++ b/autotests/kmacroexpandertest.cpp @@ -0,0 +1,276 @@ +/* + 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 new file mode 100644 index 0000000..8f99b38 --- /dev/null +++ b/autotests/kosreleasetest.cpp @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2014-2019 Harald Sitter + + 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 new file mode 100644 index 0000000..ffe1cce --- /dev/null +++ b/autotests/kpluginfactorytest.cpp @@ -0,0 +1,52 @@ +/* + 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 new file mode 100644 index 0000000..0184c8d --- /dev/null +++ b/autotests/kpluginloadertest.cpp @@ -0,0 +1,456 @@ +/* + 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 new file mode 100644 index 0000000..b0b8fea --- /dev/null +++ b/autotests/kpluginmetadatatest.cpp @@ -0,0 +1,382 @@ +/* + 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 +#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 + bool m_canMessage = false; + + void doMessagesWorkInternal() + { + } + + Q_REQUIRED_RESULT bool doMessagesWork() + { + // Q_SKIP returns, but since this is called in multiple tests we want to return a bool so the caller can + // return easily. + auto internalCheck = [this] { + // Make sure output is well formed AND generated. To that end we cannot run this test when any of the + // overriding environment variables are set. + // https://bugs.kde.org/show_bug.cgi?id=387006 + if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) { + QSKIP("QT_MESSAGE_PATTERN prevents warning expectations from matching"); + } + if (qEnvironmentVariableIsSet("QT_LOGGING_RULES")) { + QSKIP("QT_LOGGING_RULES prevents warning expectations from matching"); + } + if (qEnvironmentVariableIsSet("QT_LOGGING_CONF")) { + QSKIP("QT_LOGGING_CONF prevents warning expectations from matching"); + } + m_canMessage = true; + // Ensure all frameworks output is enabled so the expectations can match. + // qtlogging.ini may have disabled it but we can fix that because setFilterRules overrides the ini files. + QLoggingCategory::setFilterRules(QStringLiteral("kf.*=true")); + }; + internalCheck(); + return m_canMessage; + } +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() + { + if (!doMessagesWork()) { + return; + } + 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") << QString() << 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() + { + if (!doMessagesWork()) { + return; + } + 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() + { + if (!doMessagesWork()) { + return; + } + 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 new file mode 100644 index 0000000..9bc8054 --- /dev/null +++ b/autotests/kprocesslisttest.cpp @@ -0,0 +1,83 @@ +/* + 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 new file mode 100644 index 0000000..55dae5b --- /dev/null +++ b/autotests/kprocesslisttest.h @@ -0,0 +1,24 @@ +/* + 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 new file mode 100644 index 0000000..badcea9 --- /dev/null +++ b/autotests/kprocesstest.cpp @@ -0,0 +1,89 @@ +/* + This file is part of the KDE libraries + + 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 new file mode 100644 index 0000000..83dd073 --- /dev/null +++ b/autotests/kprocesstest_helper.cpp @@ -0,0 +1,32 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include +#include "kprocesstest_helper.h" + +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 2) { + printf("Missing parameter"); + return -1; + } + 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 new file mode 100644 index 0000000..044b130 --- /dev/null +++ b/autotests/kprocesstest_helper.h @@ -0,0 +1,13 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2007 Oswald Buddenhagen + + 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 new file mode 100644 index 0000000..d64641a --- /dev/null +++ b/autotests/krandomtest.cpp @@ -0,0 +1,251 @@ +/* + 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; + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) +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; +} +#endif + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) +// 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; +} +#endif + +class KRandomTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) + void test_random(); +#endif + void test_randomString(); + void test_randomStringThreaded(); +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) + void test_KRS(); +#endif + void test_shuffle(); +}; + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) +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)); +} +#endif + +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()); +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) +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; })); +} +#endif + +void KRandomTest::test_shuffle() +{ + { + QRandomGenerator rg(1); + QList list = {1, 2, 3, 4, 5}; + const QList shuffled = {5, 2, 4, 3, 1}; + KRandom::shuffle(list, &rg); + QCOMPARE(list, shuffled); + } + + { + QRandomGenerator rg(1); + QVector vector = {1, 2, 3, 4, 5}; + const QVector shuffled = {5, 2, 4, 3, 1}; + KRandom::shuffle(vector, &rg); + QCOMPARE(vector, shuffled); + } + + { + QRandomGenerator rg(1); + std::vector std_vector = {1, 2, 3, 4, 5}; + const std::vector shuffled = {5, 2, 4, 3, 1}; + KRandom::shuffle(std_vector, &rg); + QCOMPARE(std_vector, shuffled); + } +} + +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]; + } +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) +// 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); +} +#endif + +// Manually implemented to dispatch to child process if needed to support +// subtests +int main(int argc, char *argv[]) +{ +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) + if (argc > 1) { + childGenRandom(std::atoi(argv[1])); + Q_UNREACHABLE(); + } +#endif + + binpath = argv[0]; + KRandomTest randomTest; + return QTest::qExec(&randomTest); +} + +#include "krandomtest.moc" diff --git a/autotests/kshareddatacachetest.cpp b/autotests/kshareddatacachetest.cpp new file mode 100644 index 0000000..8af9dfd --- /dev/null +++ b/autotests/kshareddatacachetest.cpp @@ -0,0 +1,64 @@ +/* + 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 new file mode 100644 index 0000000..70678fd --- /dev/null +++ b/autotests/kshelltest.cpp @@ -0,0 +1,252 @@ +/* + 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/kstringhandlertest.cpp b/autotests/kstringhandlertest.cpp new file mode 100644 index 0000000..c36c587 --- /dev/null +++ b/autotests/kstringhandlertest.cpp @@ -0,0 +1,198 @@ + +#include "kstringhandlertest.h" + +#include +#include + +QTEST_MAIN(KStringHandlerTest) + +#include "kstringhandler.h" + +QString KStringHandlerTest::test = QStringLiteral("The quick brown fox jumped over the lazy bridge. "); + +void KStringHandlerTest::capwords() +{ + QCOMPARE(KStringHandler::capwords(test), + QStringLiteral("The Quick Brown Fox Jumped Over The Lazy Bridge. ")); +} + +void KStringHandlerTest::tagURLs() +{ + QString test = QStringLiteral("Click on https://foo@bar:www.kde.org/yoyo/dyne.html#a1 for info."); + QCOMPARE(KStringHandler::tagUrls(test), + QStringLiteral("Click on https://foo@bar:www.kde.org/yoyo/dyne.html#a1 for info.")); + + test = QStringLiteral("http://www.foo.org/story$806"); + QCOMPARE(KStringHandler::tagUrls(test), + QStringLiteral("http://www.foo.org/story$806")); + +#if 0 + // XFAIL - i.e. this needs to be fixed, but has never been + test = "<a href=www.foo.com>"; + check("tagURLs()", KStringHandler::tagURLs(test), + "<a href=www.foo.com>"); +#endif + + test = QStringLiteral("http://www.foo.org/bla-(bli)"); + QCOMPARE(KStringHandler::tagUrls(test), + QStringLiteral("http://www.foo.org/bla-(bli)")); + + test = QStringLiteral("http://www.foo.org/bla-bli"); + QCOMPARE(KStringHandler::tagUrls(test), + QStringLiteral("http://www.foo.org/bla-bli")); +} + +void KStringHandlerTest::perlSplit() +{ + QStringList expected; + expected << QStringLiteral("some") << QStringLiteral("string") << QStringLiteral("for") + << QStringLiteral("you__here"); + QCOMPARE(KStringHandler::perlSplit(QStringLiteral("__"), QStringLiteral("some__string__for__you__here"), 4), expected); + + expected.clear(); + expected << QStringLiteral("kparts") << QStringLiteral("reaches") << QStringLiteral("the parts other parts can't"); + QCOMPARE(KStringHandler::perlSplit(QLatin1Char(' '), + QStringLiteral("kparts reaches the parts other parts can't"), 3), expected); + + expected.clear(); + expected << QStringLiteral("Split") << QStringLiteral("me") << QStringLiteral("up ! I'm bored ! OK ?"); +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 67) + QCOMPARE(KStringHandler::perlSplit(QRegExp(QStringLiteral("[! ]")), + QStringLiteral("Split me up ! I'm bored ! OK ?"), 3), expected); +#endif + QCOMPARE(KStringHandler::perlSplit(QRegularExpression(QStringLiteral("[! ]")), + QStringLiteral("Split me up ! I'm bored ! OK ?"), 3), expected); +} + +void KStringHandlerTest::obscure() +{ + // See bug 167900, obscure() produced chars that could not properly be converted to and from + // UTF8. The result was that storing passwords with '!' in them did not work. + QString test = QStringLiteral("!TEST!"); + QString obscured = KStringHandler::obscure(test); + QByteArray obscuredBytes = obscured.toUtf8(); + QCOMPARE(KStringHandler::obscure(QString::fromUtf8(obscuredBytes.constData())), test); +} + +void KStringHandlerTest::preProcessWrap_data() +{ + const QChar zwsp(0x200b); + + QTest::addColumn("string"); + QTest::addColumn("expected"); + + // Should result in no additional breaks + QTest::newRow("spaces") << "foo bar baz" << "foo bar baz"; + + // Should insert a ZWSP after each '_' + QTest::newRow("underscores") << "foo_bar_baz" + << QString(QStringLiteral("foo_") + zwsp + QStringLiteral("bar_") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP after each '-' + QTest::newRow("hyphens") << "foo-bar-baz" + << QString(QStringLiteral("foo-") + zwsp + + QStringLiteral("bar-") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP after each '.' + QTest::newRow("periods") << "foo.bar.baz" + << QString(QStringLiteral("foo.") + + zwsp + QStringLiteral("bar.") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP after each ',' + QTest::newRow("commas") << "foo,bar,baz" + << QString(QStringLiteral("foo,") + zwsp + + QStringLiteral("bar,") + zwsp + QStringLiteral("baz")); + + // Should result in no additional breaks since the '_'s are followed by spaces + QTest::newRow("mixed underscores and spaces") + << "foo_ bar_ baz" << "foo_ bar_ baz"; + + // Should result in no additional breaks since the '_' is the last char + QTest::newRow("ends with underscore") << "foo_" << "foo_"; + + // Should insert a ZWSP before '(' and after ')' + QTest::newRow("parens") << "foo(bar)baz" + << QString(QStringLiteral("foo") + zwsp + + QStringLiteral("(bar)") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP before '[' and after ']' + QTest::newRow("brackets") << "foo[bar]baz" + << QString(QStringLiteral("foo") + zwsp + + QStringLiteral("[bar]") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP before '{' and after '}' + QTest::newRow("curly braces") << "foo{bar}baz" + << QString(QStringLiteral("foo") + zwsp + + QStringLiteral("{bar}") + zwsp + QStringLiteral("baz")); + + // Should insert a ZWSP before '(' but not after ')' since it's the last char + QTest::newRow("ends with ')'") << "foo(bar)" + << QString(QStringLiteral("foo") + zwsp + QStringLiteral("(bar)")); + + // Should insert a single ZWSP between the '_' and the '(' + QTest::newRow("'_' followed by '('") << "foo_(bar)" + << QString(QStringLiteral("foo_") + zwsp + QStringLiteral("(bar)")); + + // Should insert ZWSP's between the '_' and the '[', between the double + // '['s and the double ']'s, but not before and after 'bar' + QTest::newRow("'_' before double brackets") << "foo_[[bar]]" + << QString(QStringLiteral("foo_") + zwsp + + QStringLiteral("[") + zwsp + QStringLiteral("[bar]") + zwsp + + QStringLiteral("]")); + + // Should only insert ZWSP's between the double '['s and the double ']'s + QTest::newRow("space before double brackets") << "foo [[bar]]" + << QString(QStringLiteral("foo [") + zwsp + QStringLiteral("[bar]") + zwsp + QStringLiteral("]")); + + // Shouldn't result in any additional breaks since the '(' is preceded + // by a space, and the ')' is followed by a space. + QTest::newRow("parens with spaces") << "foo (bar) baz" << "foo (bar) baz"; + + // Should insert a WJ (Word Joiner) before a single quote + const QChar wj(0x2060); + QTest::newRow("single quote") << "foo'bar" << QString(QStringLiteral("foo") + QString(wj) + QStringLiteral("'bar")); +} + +static QString replaceZwsp(const QString &string) +{ + const QChar zwsp(0x200b); + + QString result; + for (int i = 0; i < string.length(); i++) + if (string[i] == zwsp) { + result += QStringLiteral(""); + } else { + result += string[i]; + } + + return result; +} + +void KStringHandlerTest::preProcessWrap() +{ + QFETCH(QString, string); + QFETCH(QString, expected); + + QCOMPARE(replaceZwsp(KStringHandler::preProcessWrap(string)), + replaceZwsp(expected)); +} + +void KStringHandlerTest::logicalLength_data() +{ + QTest::addColumn("string"); + QTest::addColumn("expected"); + + QTest::newRow("Latin") << "foo bar baz" << 11; + QTest::newRow("Chinese") << QString::fromUtf8("\xe4\xbd\xa0\xe5\xa5\xbd") << 4; + QTest::newRow("Japanese") << QString::fromUtf8("\xe9\x9d\x92\xe3\x81\x84\xe7\xa9\xba") << 6; + QTest::newRow("Korean") << QString::fromUtf8("\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4") << 6; + QTest::newRow("Mixed") << QString::fromUtf8("KDE\xe6\xa1\x8c\xe9\x9d\xa2") << 7; +} + +void KStringHandlerTest::logicalLength() +{ + QFETCH(QString, string); + QFETCH(int, expected); + QCOMPARE(KStringHandler::logicalLength(string), expected); +} + diff --git a/autotests/kstringhandlertest.h b/autotests/kstringhandlertest.h new file mode 100644 index 0000000..e016f01 --- /dev/null +++ b/autotests/kstringhandlertest.h @@ -0,0 +1,24 @@ +#ifndef KSTRINGHANDLERTEST_H +#define KSTRINGHANDLERTEST_H + +#include + +class KStringHandlerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void capwords(); + void tagURLs(); + void perlSplit(); + void obscure(); + void preProcessWrap_data(); + void preProcessWrap(); + void logicalLength_data(); + void logicalLength(); + +private: + static QString test; +}; + +#endif diff --git a/autotests/ktexttohtmltest.cpp b/autotests/ktexttohtmltest.cpp new file mode 100644 index 0000000..466fecd --- /dev/null +++ b/autotests/ktexttohtmltest.cpp @@ -0,0 +1,586 @@ +/* + 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 << QString() << QString(); // 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)) { + // 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) { + qCCritical(KCOREADDONS_DEBUG) << "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; + } + switch (d->shm->evictionPolicy.loadRelaxed()) { + 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)) { + qCCritical(KCOREADDONS_DEBUG) << "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) { + qCCritical(KCOREADDONS_DEBUG) << "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) { + qCCritical(KCOREADDONS_DEBUG) + << "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 (QRandomGenerator::global()->bounded(RAND_MAX) >= 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) { + qCCritical(KCOREADDONS_DEBUG) << "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 new file mode 100644 index 0000000..09419af --- /dev/null +++ b/src/lib/caching/kshareddatacache.h @@ -0,0 +1,215 @@ +/* + 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 new file mode 100644 index 0000000..b91e20a --- /dev/null +++ b/src/lib/caching/kshareddatacache_p.h @@ -0,0 +1,493 @@ +/* + 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 "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 + m_spinlock.storeRelaxed(0); + 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: + qCCritical(KCOREADDONS_DEBUG) << "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) { + qCCritical(KCOREADDONS_DEBUG) << "No space left on device. Check filesystem free space at your XDG_CACHE_HOME!"; + } + qCCritical(KCOREADDONS_DEBUG) << "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 new file mode 100644 index 0000000..e4761f6 --- /dev/null +++ b/src/lib/caching/kshareddatacache_win.cpp @@ -0,0 +1,108 @@ +/* + 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 new file mode 100644 index 0000000..fa52488 --- /dev/null +++ b/src/lib/caching/posix_fallocate_mac.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + + 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 +// 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/config-kdirwatch.h.cmake b/src/lib/io/config-kdirwatch.h.cmake new file mode 100644 index 0000000..9633c62 --- /dev/null +++ b/src/lib/io/config-kdirwatch.h.cmake @@ -0,0 +1,3 @@ +#cmakedefine01 HAVE_FAM + +#cmakedefine01 HAVE_SYS_INOTIFY_H diff --git a/src/lib/io/kautosavefile.cpp b/src/lib/io/kautosavefile.cpp new file mode 100644 index 0000000..c94caf7 --- /dev/null +++ b/src/lib/io/kautosavefile.cpp @@ -0,0 +1,229 @@ +/* + 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 "krandom.h" +#include "kcoreaddons_debug.h" + +class KAutoSaveFilePrivate +{ +public: + enum {NamePadding=8}; + + QString tempFileName(); + QUrl managedFile; + QLockFile *lock = nullptr; + bool managedFileNameChanged = false; +}; + +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 new file mode 100644 index 0000000..577eb63 --- /dev/null +++ b/src/lib/io/kautosavefile.h @@ -0,0 +1,237 @@ +/* + 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 new file mode 100644 index 0000000..576bc5e --- /dev/null +++ b/src/lib/io/kbackup.cpp @@ -0,0 +1,185 @@ +/* + 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 + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) +#include +#include +#endif + +namespace KBackup +{ + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) +bool backupFile(const QString &qFilename, const QString &backupDir) +{ + return (simpleBackupFile(qFilename, backupDir, QStringLiteral("~"))); +} +#endif + +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); +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) +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; + } +} +#endif + +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 new file mode 100644 index 0000000..333f0d6 --- /dev/null +++ b/src/lib/io/kbackup.h @@ -0,0 +1,141 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Waldo Bastian + SPDX-FileCopyrightText: 2006 Jaison Lee + SPDX-FileCopyrightText: 2011 Romain Perier + + 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 +{ +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 75) +/** + * @brief Function to create a backup file before saving. + * + * @warning This code lost its former functionality during the conversion from KDELibs4 to KDE Framrworks 5. + * It now only forwards and calls + * @code +KBackup::simpleBackupFile(filename, backupDir, QStringLiteral("~"))); + * @endcode + * To restore the former functionality for your software, which read + * the backup type (simple or numbered), extension string, and maximum + * number of backup files from the user's global configuration, + * you could use code like this: + * @code + KConfigGroup configGroup(KSharedConfig::openConfig(), "Backups"); // look in the Backups section + const QString type = configGroup.readEntry("Type", QStringLiteral("simple")); + const QString extension = configGroup.readEntry("Extension", QStringLiteral("~")); + bool success = false; + if (type == QLatin1String("numbered")) { + const int maxNumber = configGroup.readEntry("MaxBackups", 10); + success = numberedBackupFile(filename, backupDir, extension, maxNumber); + } else { + success = simpleBackupFile(filename, backupDir, extension); + } + * @endcode + * + * @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. + * @return true if successful, or false if an error has occurred. + * + * @deprecated Since 5.0, use simpleBackupFile() or numberedBackupFile() directly + */ +KCOREADDONS_DEPRECATED_VERSION_BELATED(5, 75, 5, 0, "Use simpleBackupFile() or numberedBackupFile() directly") +KCOREADDONS_EXPORT bool backupFile(const QString &filename, + const QString &backupDir = QString()); +#endif + +/** +* @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 + ); + +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 75) +/** + * @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. + * @deprecated Since 5.75, no known users + */ +KCOREADDONS_DEPRECATED_VERSION(5, 75, "No known users") +KCOREADDONS_EXPORT bool rcsBackupFile(const QString &filename, + const QString &backupDir = QString(), + const QString &backupMessage = QString() + ); +#endif +} + +#endif diff --git a/src/lib/io/kdirwatch.cpp b/src/lib/io/kdirwatch.cpp new file mode 100644 index 0000000..6d79a33 --- /dev/null +++ b/src/lib/io/kdirwatch.cpp @@ -0,0 +1,2098 @@ +/* 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 Rafal Rzepecki + SPDX-FileCopyrightText: 2010 David Faure + SPDX-FileCopyrightText: 2020 Harald Sitter + + 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, "kf.coreaddons.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(); +} +static void destroyPrivate() +{ + dwp_self.localData()->deleteLater(); + dwp_self.setLocalData(nullptr); +} + +// 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), + m_references(0) +{ + // 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) + const int iterationCap = 80; + for (int i = 0; i <= iterationCap; ++i) { // we'll not wait forever; blocking for 4s seems plenty + famEventReceived(); + // NB: check use_fam, if fam is defunct event receiving might disable fam support! + if (use_fam && 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); + } else if (use_fam && i == iterationCap) { + disableFAM(); + return false; + } else { + break; + } + } + + 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; +#else + case KDirWatch::FAM: entryAdded = false; break; +#endif +#if HAVE_SYS_INOTIFY_H + case KDirWatch::INotify: entryAdded = useINotify(e); break; +#else + case KDirWatch::INotify: entryAdded = false; break; +#endif +#if HAVE_QFILESYSTEMWATCHER + case KDirWatch::QFSWatch: entryAdded = useQFSWatch(e); break; +#else + case KDirWatch::QFSWatch: entryAdded = false; 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; +} + +void KDirWatchPrivate::ref() +{ + ++m_references; +} + +void KDirWatchPrivate::unref() +{ + --m_references; + if (m_references == 0) { + destroyPrivate(); + } +} + +#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) { + disableFAM(); + } else { + checkFAMEvent(&fe); + } + } + + QTimer::singleShot(0, this, SLOT(slotRemoveDelayed())); +} + +void KDirWatchPrivate::disableFAM() +{ + qCWarning(KCOREADDONS_DEBUG) << "FAM connection problem, switching to a different system."; + use_fam = false; + delete sn; + sn = nullptr; + + // Replace all FAMMode entries with another system (INotify/QFSW/Stat) + for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { + if ((*it).m_mode == FAMMode && !(*it).m_clients.empty()) { + Entry *e = &(*it); + addWatch(e); + } + } +} + +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()) +{ + d->ref(); + 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); + d->unref(); + } +} + +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) { + case KDirWatch::FAM: +#if HAVE_FAM + if (d->use_fam) { + return KDirWatch::FAM; + } +#endif + break; + case KDirWatch::INotify: +#if HAVE_SYS_INOTIFY_H + if (d->supports_inotify) { + return KDirWatch::INotify; + } +#endif + break; + case KDirWatch::QFSWatch: +#if HAVE_QFILESYSTEMWATCHER + return KDirWatch::QFSWatch; +#else + break; +#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 new file mode 100644 index 0000000..1ed5a6e --- /dev/null +++ b/src/lib/io/kdirwatch.h @@ -0,0 +1,305 @@ +/* + 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 + * @see WatchModes + **/ + 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 + }; + /** + * Stores a combination of #WatchMode values. + */ + 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 new file mode 100644 index 0000000..d3d3d24 --- /dev/null +++ b/src/lib/io/kdirwatch_p.h @@ -0,0 +1,237 @@ +/* + 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-FileCopyrightText: 2020 Harald Sitter + + 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); + + void ref(); + void unref(); + +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); + void disableFAM(); +#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; + +private: + // Public objects that reference this thread-local private instance. + uint m_references; +}; + +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 new file mode 100644 index 0000000..b09d1f6 --- /dev/null +++ b/src/lib/io/kfilesystemtype.cpp @@ -0,0 +1,136 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2011 David Faure + + 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 new file mode 100644 index 0000000..b7211ec --- /dev/null +++ b/src/lib/io/kfilesystemtype.h @@ -0,0 +1,38 @@ +/* + 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 new file mode 100644 index 0000000..9775724 --- /dev/null +++ b/src/lib/io/kfileutils.cpp @@ -0,0 +1,68 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2000-2005 David Faure + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#include "kfileutils.h" + +#include +#include +#include + +QString KFileUtils::makeSuggestedName(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)"); + } + + return basename + nameSuffix; +} + + +QString KFileUtils::suggestName(const QUrl &baseURL, const QString &oldName) +{ + QString suggestedName = makeSuggestedName(oldName); + + if (baseURL.isLocalFile()) { + const QString basePath = baseURL.toLocalFile() + QLatin1Char('/'); + while (QFileInfo::exists(basePath + suggestedName)) { + suggestedName = makeSuggestedName(suggestedName); + } + } + + return suggestedName; +} diff --git a/src/lib/io/kfileutils.h b/src/lib/io/kfileutils.h new file mode 100644 index 0000000..4997252 --- /dev/null +++ b/src/lib/io/kfileutils.h @@ -0,0 +1,57 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2000-2005 David Faure + + 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 string representing a file or directory + * (which usually exist already), this function returns a suggested name + * for a file/directory that doesn't exist in @p baseURL. + * + * The suggested file name is of the form "foo (1)", "foo (2)" etc. + * + * For local URLs, this function will check if there is already a file/directory + * with the new suggested name and will keep incrementing the number in the above + * format until it finds one that doesn't exist. Note that this function uses a + * blocking I/O call (using QFileInfo) to check the existence of the file/directory, + * this could be problematic for network mounts (e.g. SMB, NFS) as these are treated + * as local files by the upstream QFile code. An alternative is to use makeSuggestedName() + * and use KIO to stat the new file/directory in an asynchronous way. + * + * @since 5.61 + */ +KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); + +/** + * Given a string, "foo", representing a file/directory (which usually exists already), + * this function returns a suggested name for a file/directory in the form "foo (1)", + * "foo (2)" etc. + * + * Unlike the suggestName() method, this function doesn't check if there is a file/directory + * with the newly suggested name; the idea being that this responsibility falls on + * the caller, e.g. one can use KIO::stat() to check asynchronously whether the new + * name already exists (in its parent directory) or not. + * + * @since 5.76 + */ +KCOREADDONS_EXPORT QString makeSuggestedName(const QString &oldName); + +} +#endif diff --git a/src/lib/io/kmessage.cpp b/src/lib/io/kmessage.cpp new file mode 100644 index 0000000..7e1d02b --- /dev/null +++ b/src/lib/io/kmessage.cpp @@ -0,0 +1,90 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2006 Michaël Larouche + + 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 new file mode 100644 index 0000000..814765b --- /dev/null +++ b/src/lib/io/kmessage.h @@ -0,0 +1,116 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2006 Michaël Larouche + + 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 new file mode 100644 index 0000000..4e468d0 --- /dev/null +++ b/src/lib/io/kprocess.cpp @@ -0,0 +1,323 @@ +/* + This file is part of the KDE libraries + + 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); +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 78) +int KProcess::pid() const +{ +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") +#ifdef Q_OS_UNIX + return static_cast(QProcess::pid()); +#else + return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; +#endif +QT_WARNING_POP +} +#endif + +#include "moc_kprocess.cpp" diff --git a/src/lib/io/kprocess.h b/src/lib/io/kprocess.h new file mode 100644 index 0000000..a7a1a7d --- /dev/null +++ b/src/lib/io/kprocess.h @@ -0,0 +1,332 @@ +/* + This file is part of the KDE libraries + + 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); + +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 78) + /** + * 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 + * @deprecated since 5.78, use processId() + */ + KCOREADDONS_DEPRECATED_VERSION(5, 78, "Use processId()") + int pid() const; +#endif + +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 new file mode 100644 index 0000000..edce10f --- /dev/null +++ b/src/lib/io/kprocess_p.h @@ -0,0 +1,31 @@ +/* + This file is part of the KDE libraries + + 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 new file mode 100644 index 0000000..30815e6 --- /dev/null +++ b/src/lib/io/kurlmimedata.cpp @@ -0,0 +1,109 @@ +/* + 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 new file mode 100644 index 0000000..85d6e07 --- /dev/null +++ b/src/lib/io/kurlmimedata.h @@ -0,0 +1,96 @@ +/* + 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 new file mode 100644 index 0000000..0c444ab --- /dev/null +++ b/src/lib/jobs/kcompositejob.cpp @@ -0,0 +1,105 @@ +/* + This file is part of the KDE project + + 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 new file mode 100644 index 0000000..44550a0 --- /dev/null +++ b/src/lib/jobs/kcompositejob.h @@ -0,0 +1,112 @@ +/* + This file is part of the KDE project + + 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 new file mode 100644 index 0000000..e722be9 --- /dev/null +++ b/src/lib/jobs/kcompositejob_p.h @@ -0,0 +1,30 @@ +/* + This file is part of the KDE project + + 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 new file mode 100644 index 0000000..989c2f2 --- /dev/null +++ b/src/lib/jobs/kjob.cpp @@ -0,0 +1,347 @@ +/* + This file is part of the KDE project + + 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() +{ +} + +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) { + d_ptr->isFinished = true; + 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); + Q_ASSERT(!d->isFinished); + 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) +{ + Q_D(KJob); + if (d->isFinished) { + return true; + } + + if (doKill()) { + // A subclass can (but should not) call emitResult() or kill() + // from doKill() and thus set isFinished to true. + if (!d->isFinished) { + 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; +} + +bool KJob::isFinished() const +{ + return d_func()->isFinished; +} + +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::setProgressUnit(Unit unit) +{ + Q_D(KJob); + d->progressUnit = unit; +} + +void KJob::setPercent(unsigned long percentage) +{ + Q_D(KJob); + if (d->percentage != percentage) { + d->percentage = percentage; + emit percent(this, percentage); + } +} + +void KJob::emitResult() +{ + if (!d_func()->isFinished) { + 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 new file mode 100644 index 0000000..1fd4a1f --- /dev/null +++ b/src/lib/jobs/kjob.h @@ -0,0 +1,686 @@ +/* + This file is part of the KDE project + + 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(ulong percent READ percent NOTIFY percent) // KF6 TODO: make "int", is enough + Q_PROPERTY(Capabilities capabilities READ capabilities CONSTANT) + +public: + /** + * Describes the unit used in the methods that handle reporting the job progress info. + * @see totalAmount + */ + enum Unit { + Bytes, ///< Directory and file sizes in bytes + Files, ///< The number of files handled by the job + Directories, ///< The number of directories handled by the job + Items, ///< The number of items (e.g. both directories and files) handled by the job + ///< @since 5.72 + }; + Q_ENUM(Unit) + + /** + * @see Capabilities + */ + enum Capability { + NoCapabilities = 0x0000, ///< None of the capabilities exist + Killable = 0x0001, ///< The job can be killed + Suspendable = 0x0002, ///< The job can be suspended + }; + Q_ENUM(Capability) + + /** + * Stores a combination of #Capability values. + */ + 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. + * + * @since 5.75: this signal is guaranteed to be emitted exactly once. + * + * 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). + * + * @since 5.75: this signal is guaranteed to be emitted at most once. + * + * 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: + /** + * Returns if the job has been finished and has emitted the finished() signal. + * + * @return if the job has been finished + * @see finished() + * @since 5.75 + */ + bool isFinished() const; + + /** + * 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 unit that will be used internally to calculate + * the progress percentage. + * The default progress unit is Bytes. + * @since 5.76 + */ + void setProgressUnit(Unit unit); + + /** + * Sets the overall progress of the job. The percent() signal + * is emitted if the value changed. + * + * The job takes care of this if you call setProcessedAmount + * in Bytes (or the unit set by setProgressUnit). + * This method allows you to set your own progress, as an alternative. + * + * @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 new file mode 100644 index 0000000..5523bd1 --- /dev/null +++ b/src/lib/jobs/kjob_p.h @@ -0,0 +1,56 @@ +/* + This file is part of the KDE project + + 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 = nullptr; + + KJobUiDelegate *uiDelegate = nullptr; + QString errorText; + int error = KJob::NoError; + KJob::Unit progressUnit = KJob::Bytes; + QMap processedAmount; + QMap totalAmount; + unsigned long percentage = 0; + QTimer *speedTimer = nullptr; + QEventLoop *eventLoop = nullptr; + // eventLoopLocker prevents QCoreApplication from exiting when the last + // window is closed until the job has finished running + QEventLoopLocker eventLoopLocker; + KJob::Capabilities capabilities = KJob::NoCapabilities; + bool suspended = false; + bool isAutoDelete = true; + + void _k_speedTimeout(); + + bool isFinished = false; + + Q_DECLARE_PUBLIC(KJob) +}; + +#endif diff --git a/src/lib/jobs/kjobtrackerinterface.cpp b/src/lib/jobs/kjobtrackerinterface.cpp new file mode 100644 index 0000000..1de2f9d --- /dev/null +++ b/src/lib/jobs/kjobtrackerinterface.cpp @@ -0,0 +1,126 @@ +/* + This file is part of the KDE project + + 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) +{ + connect(job, &KJob::finished, this, &KJobTrackerInterface::unregisterJob); + connect(job, &KJob::finished, this, &KJobTrackerInterface::finished); + connect(job, &KJob::suspended, this, &KJobTrackerInterface::suspended); + connect(job, &KJob::resumed,this, &KJobTrackerInterface::resumed); + + connect(job, &KJob::description, this, &KJobTrackerInterface::description); + connect(job, &KJob::infoMessage, this, &KJobTrackerInterface::infoMessage); + connect(job, &KJob::warning, this, &KJobTrackerInterface::warning); + + connect(job, QOverload::of(&KJob::totalAmount), + this, &KJobTrackerInterface::totalAmount); + connect(job, QOverload::of(&KJob::processedAmount), + this, &KJobTrackerInterface::processedAmount); + connect(job, QOverload::of(&KJob::percent), + this, &KJobTrackerInterface::percent); + connect(job, &KJob::speed, this, &KJobTrackerInterface::speed); +} + +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 new file mode 100644 index 0000000..05551c0 --- /dev/null +++ b/src/lib/jobs/kjobtrackerinterface.h @@ -0,0 +1,182 @@ +/* + This file is part of the KDE project + + 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 new file mode 100644 index 0000000..15ba130 --- /dev/null +++ b/src/lib/jobs/kjobuidelegate.cpp @@ -0,0 +1,115 @@ +/* + 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), + autoErrorHandling(false), + autoWarningHandling(true) { } + + KJobUiDelegate *const q; + + KJob *job = nullptr; + bool autoErrorHandling : 1; + bool autoWarningHandling : 1; + + void connectJob(KJob *job); + void _k_result(); +}; + +KJobUiDelegate::KJobUiDelegate() + : QObject(), d(new Private(this)) +{ +} + +KJobUiDelegate::KJobUiDelegate(Flags flags) + : QObject(), d(new Private(this)) +{ + if (flags & AutoErrorHandlingEnabled) { + d->autoErrorHandling = true; + } + if (flags & AutoWarningHandlingEnabled) { + d->autoWarningHandling = true; + } +} + +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 new file mode 100644 index 0000000..b4dd45f --- /dev/null +++ b/src/lib/jobs/kjobuidelegate.h @@ -0,0 +1,153 @@ +/* + 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: + + /** + * Flags for the constructor, to enable automatic handling of errors and/or warnings + * @see Flags + * @since 5.70 + */ + enum Flag { + AutoHandlingDisabled = 0, ///< No automatic handling (default) + AutoErrorHandlingEnabled = 1, ///< Equivalent to setAutoErrorHandlingEnabled(true) + AutoWarningHandlingEnabled = 2, ///< Equivalent to setAutoWarningHandlingEnabled(true) + AutoHandlingEnabled = AutoErrorHandlingEnabled | AutoWarningHandlingEnabled ///< Enables both error and warning handling + }; + /** + * Stores a combination of #Flag values. + */ + Q_DECLARE_FLAGS(Flags, Flag) + + /** + * Constructs a new KJobUiDelegate. + */ + KJobUiDelegate(); + + /** + * Constructs a new KJobUiDelegate with a flags argument. + * @param flags allows to enable automatic error/warning handling + * @since 5.70 + */ + explicit KJobUiDelegate(Flags flags); // KF6 TODO merge with default constructor, using AutoHandlingDisabled as default value + + /** + * 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; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KJobUiDelegate::Flags) + +#endif // KJOBUIDELEGATE_H diff --git a/src/lib/kaboutdata.cpp b/src/lib/kaboutdata.cpp new file mode 100644 index 0000000..2222493 --- /dev/null +++ b/src/lib/kaboutdata.cpp @@ -0,0 +1,1260 @@ +/* + 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, "kf.coreaddons.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")) { +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + emailList = translatorEmail.split(QLatin1Char(','), QString::KeepEmptyParts); +#else + emailList = translatorEmail.split(QLatin1Char(','), Qt::KeepEmptyParts); +#endif + } + + 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; +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) + qDeleteAll(m_pluginData); +#endif + } + KAboutDataRegistry(const KAboutDataRegistry &) = delete; + KAboutDataRegistry &operator=(const KAboutDataRegistry &) = delete; + + KAboutData *m_appData; +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) + QHash m_pluginData; +#endif +}; + +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. +} + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) +void KAboutData::registerPluginData(const KAboutData &aboutData) +{ + auto &data = s_registry->m_pluginData[aboutData.componentName()]; + if (data) { + // silently ignore double registration, assuming it's for the same plugin + // all of this is getting deprecated anyways, we just don't want to leak anything + return; + } + data = new KAboutData(aboutData); +} +#endif + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) +KAboutData *KAboutData::pluginData(const QString &componentName) +{ + KAboutData *ad = s_registry->m_pluginData.value(componentName); + return ad; +} +#endif + +// 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 new file mode 100644 index 0000000..0ff468a --- /dev/null +++ b/src/lib/kaboutdata.h @@ -0,0 +1,1190 @@ +/* + 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); + +// BUILD, not ENABLE, as producers need to support the registry for backward-compat +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76) + /** + * 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. + * @deprecated Since 5.76. The central registry is to be removed in the future + * in favour of plugin type specific local registries, using KPluginMetaData. + */ + KCOREADDONS_DEPRECATED_VERSION(5, 76, "See API docs") + static void registerPluginData(const KAboutData &aboutData); +#endif + +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 76) + /** + * Return the KAboutData for the given plugin identified by @p componentName. + * @deprecated Since 5.76. The central registry is to be removed in the future + * in favour of plugin type specific local registries, using KPluginMetaData. + */ + KCOREADDONS_DEPRECATED_VERSION(5, 76, "See API docs") + static KAboutData *pluginData(const QString &componentName); +#endif + +#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 new file mode 100644 index 0000000..d24b0fb --- /dev/null +++ b/src/lib/kcoreaddons.cpp @@ -0,0 +1,22 @@ +/* + 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 "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 new file mode 100644 index 0000000..3bfa82f --- /dev/null +++ b/src/lib/kcoreaddons.h @@ -0,0 +1,44 @@ +/* + 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/licenses/ARTISTIC b/src/lib/licenses/ARTISTIC new file mode 100644 index 0000000..8f9bdef --- /dev/null +++ b/src/lib/licenses/ARTISTIC @@ -0,0 +1,124 @@ +The "Artistic License" + + Preamble + + The intent of this document is to state the conditions under which a + Package may be copied, such that the Copyright Holder maintains some + semblance of artistic control over the development of the package, + while giving the users of the package the right to use and distribute + the Package in a more-or-less customary fashion, plus the right to + make reasonable modifications. + + Definitions + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes of the + Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing this + Package. + + "Reasonable copying fee" is whatever you can justify on the basis + of media cost, duplication charges, time of people involved, and so + on. (You will not be required to justify it to the Copyright + Holder, but only to the computing community at large as a market + that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. It + also means that recipients of the item may redistribute it under + the same conditions they received it. + + 1. You may make and give away verbatim copies of the source form of + the Standard Version of this Package without restriction, provided + that you duplicate all of the original copyright notices and + associated disclaimers. + 2. You may apply bug fixes, portability fixes and other modifications + derived from the Public Domain or from the Copyright Holder. A + Package modified in such a way shall still be considered the + Standard Version. + 3. You may otherwise modify your copy of this Package in any way, + provided that you insert a prominent notice in each changed file + stating how and when you changed that file, and provided that you + do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise make + them Freely Available, such as by posting said modifications to + Usenet or an equivalent medium, or placing the modifications on a + major archive site such as uunet.uu.net, or by allowing the + Copyright Holder to include your modifications in the Standard + Version of the Package. + b. use the modified Package only within your corporation or + organization. + c. rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and + provide a separate manual page for each non-standard executable + that clearly documents how it differs from the Standard Version. + d. make other distribution arrangements with the Copyright Holder. + + You may distribute the programs of this Package in object code or + executable form, provided that you do at least ONE of the following: + + a. distribute a Standard Version of the executables and library + files, together with instructions (in the manual page or + equivalent) on where to get the Standard Version. + b. accompany the distribution with the machine-readable source of the + Package with your modifications. + c. give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + d. make other distribution arrangements with the Copyright Holder. + + You may charge a reasonable copying fee for any distribution of this + Package. You may charge any fee you choose for support of this + Package. You may not charge a fee for this Package itself. However, + you may distribute this Package in aggregate with other (possibly + commercial) programs as part of a larger (possibly commercial) + software distribution provided that you do not advertise this Package + as a product of your own. You may embed this Package's interpreter + within an executable of yours (by linking); this shall be construed as + a mere form of aggregation, provided that the complete Standard + Version of the interpreter is so embedded. + + The scripts and library files supplied as input to or produced as + output from the programs of this Package do not automatically fall + under the copyright of this Package, but belong to whomever generated + them, and may be sold commercially, and may be aggregated with this + Package. If such scripts or library files are aggregated with this + Package via the so-called "undump" or "unexec" methods of producing a + binary executable image, then distribution of such an image shall + neither be construed as a distribution of this Package nor shall it + fall under the restrictions of Paragraphs 3 and 4, provided that you + do not represent such an executable image as a Standard Version of + this Package. + + C subroutines (or comparably compiled subroutines in other + languages) supplied by you and linked into this Package in order to + emulate subroutines and variables of the language defined by this + Package shall not be considered part of this Package, but are the + equivalent of input as in Paragraph 6, provided these subroutines do + not change the language in any way that would cause it to fail the + regression tests for the language. + + Aggregation of this Package with a commercial distribution is always + permitted provided that the use of this Package is embedded; that is, + when no overt attempt is made to make this Package's interfaces + visible to the end user of the commercial distribution. Such use shall + not be construed as a distribution of this Package. + + The name of the Copyright Holder may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/src/lib/licenses/BSD b/src/lib/licenses/BSD new file mode 100644 index 0000000..cca2a5c --- /dev/null +++ b/src/lib/licenses/BSD @@ -0,0 +1,20 @@ +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 AUTHOR ``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 AUTHOR 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/src/lib/licenses/CMakeLists.txt b/src/lib/licenses/CMakeLists.txt new file mode 100644 index 0000000..99ffe9b --- /dev/null +++ b/src/lib/licenses/CMakeLists.txt @@ -0,0 +1,8 @@ +# Install license files for use by KAboutLicense and KAboutData + +install( + FILES + BSD GPL_V2 GPL_V3 LGPL_V2 LGPL_V21 LGPL_V3 QPL_V1.0 ARTISTIC + DESTINATION + ${KDE_INSTALL_DATADIR}/kf5/licenses +) diff --git a/src/lib/licenses/GPL_V2 b/src/lib/licenses/GPL_V2 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/src/lib/licenses/GPL_V2 @@ -0,0 +1,339 @@ + 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. + + GNU GENERAL PUBLIC LICENSE + 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/src/lib/licenses/GPL_V3 b/src/lib/licenses/GPL_V3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/src/lib/licenses/GPL_V3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/src/lib/licenses/LGPL_V2 b/src/lib/licenses/LGPL_V2 new file mode 100644 index 0000000..5bc8fb2 --- /dev/null +++ b/src/lib/licenses/LGPL_V2 @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 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. + +[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. + + GNU LIBRARY GENERAL PUBLIC LICENSE + 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. + + + Copyright (C) + + 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 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/lib/licenses/LGPL_V21 b/src/lib/licenses/LGPL_V21 new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/src/lib/licenses/LGPL_V21 @@ -0,0 +1,502 @@ + 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. + + GNU LESSER GENERAL PUBLIC LICENSE + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/lib/licenses/LGPL_V3 b/src/lib/licenses/LGPL_V3 new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/src/lib/licenses/LGPL_V3 @@ -0,0 +1,165 @@ + 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/src/lib/licenses/QPL_V1.0 b/src/lib/licenses/QPL_V1.0 new file mode 100644 index 0000000..85bc635 --- /dev/null +++ b/src/lib/licenses/QPL_V1.0 @@ -0,0 +1,103 @@ + THE Q PUBLIC LICENSE + version 1.0 + + Copyright (C) 1999-2000 Troll Tech AS, Norway. + Everyone is permitted to copy and + distribute this license document. + +The intent of this license is to establish freedom to share and change the +software regulated by this license under the open source model. + +This license applies to any software containing a notice placed by the +copyright holder saying that it may be distributed under the terms of +the Q Public License version 1.0. Such software is herein referred to as +the Software. This license covers modification and distribution of the +Software, use of third-party application programs based on the Software, +and development of free software which uses the Software. + + Granted Rights + +1. You are granted the non-exclusive rights set forth in this license + provided you agree to and comply with any and all conditions in this + license. Whole or partial distribution of the Software, or software + items that link with the Software, in any form signifies acceptance of + this license. + +2. You may copy and distribute the Software in unmodified form provided + that the entire package, including - but not restricted to - copyright, + trademark notices and disclaimers, as released by the initial developer + of the Software, is distributed. + +3. You may make modifications to the Software and distribute your + modifications, in a form that is separate from the Software, such as + patches. The following restrictions apply to modifications: + + a. Modifications must not alter or remove any copyright notices in + the Software. + + b. When modifications to the Software are released under this + license, a non-exclusive royalty-free right is granted to the + initial developer of the Software to distribute your modification + in future versions of the Software provided such versions remain + available under these terms in addition to any other license(s) of + the initial developer. + +4. You may distribute machine-executable forms of the Software or + machine-executable forms of modified versions of the Software, provided + that you meet these restrictions: + + a. You must include this license document in the distribution. + + b. You must ensure that all recipients of the machine-executable forms + are also able to receive the complete machine-readable source code + to the distributed Software, including all modifications, without + any charge beyond the costs of data transfer, and place prominent + notices in the distribution explaining this. + + c. You must ensure that all modifications included in the + machine-executable forms are available under the terms of this + license. + +5. You may use the original or modified versions of the Software to + compile, link and run application programs legally developed by you + or by others. + +6. You may develop application programs, reusable components and other + software items that link with the original or modified versions of the + Software. These items, when distributed, are subject to the following + requirements: + + a. You must ensure that all recipients of machine-executable forms of + these items are also able to receive and use the complete + machine-readable source code to the items without any charge + beyond the costs of data transfer. + + b. You must explicitly license all recipients of your items to use + and re-distribute original and modified versions of the items in + both machine-executable and source code forms. The recipients must + be able to do so without any charges whatsoever, and they must be + able to re-distribute to anyone they choose. + + + c. If the items are not available to the general public, and the + initial developer of the Software requests a copy of the items, + then you must supply one. + + Limitations of Liability + +In no event shall the initial developers or copyright holders be liable +for any damages whatsoever, including - but not restricted to - lost +revenue or profits or other direct, indirect, special, incidental or +consequential damages, even if they have been advised of the possibility +of such damages, except to the extent invariable law, if any, provides +otherwise. + + No Warranty + +The Software and this license document are provided AS IS with NO WARRANTY +OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. + Choice of Law + +This license is governed by the Laws of Norway. Disputes shall be settled +by Oslo City Court. diff --git a/src/lib/plugin/desktopfileparser.cpp b/src/lib/plugin/desktopfileparser.cpp new file mode 100644 index 0000000..e8cab97 --- /dev/null +++ b/src/lib/plugin/desktopfileparser.cpp @@ -0,0 +1,628 @@ +/* + 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" + +#ifdef BUILDING_DESKTOPTOJSON_TOOL +#include "desktoptojson_debug.h" +#else +#include "desktopfileparser_debug.h" +#endif +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + # alternatively to X-KDE-PluginInfo-Author & X-KDE-PluginInfo-Email: + X-KDE-PluginInfo-Authors=Author A's Name;AuthorB's Name (since KF 5.77) + X-KDE-PluginInfo-Emails=authorA@foo.bar;authorB@foo.bar (since KF 5.77) + 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-Copyright=Copyright by Author's Name (since KF 5.77, translatable) + 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-Copyright")) { + kplugin[QStringLiteral("Copyright")] = value; + } else if (key.startsWith(QByteArrayLiteral("X-KDE-PluginInfo-Copyright["))) { + const QString languageSuffix = QString::fromUtf8(key.mid(qstrlen("X-KDE-PluginInfo-Copyright"))); + kplugin[QStringLiteral("Copyright") + languageSuffix] = 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("X-KDE-PluginInfo-Authors")) { + const auto authors = deserializeList(value); + QJsonArray oldArray = kplugin.value(QStringLiteral("Authors")).toArray(); + QJsonArray newArray; + for (int i = 0; i < authors.size(); ++i) { + QJsonObject authorsObject = oldArray.at(i).toObject(); + // if the authors object doesn't exist yet this will create it + authorsObject[QStringLiteral("Name")] = authors[i]; + newArray.append(authorsObject); + } + kplugin[QStringLiteral("Authors")] = newArray; + } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Emails")) { + const auto emails = deserializeList(value); + QJsonArray oldArray = kplugin.value(QStringLiteral("Authors")).toArray(); + QJsonArray newArray; + for (int i = 0; i < emails.size(); ++i) { + QJsonObject authorsObject = oldArray.at(i).toObject(); + // if the authors object doesn't exist yet this will create it + authorsObject[QStringLiteral("Email")] = emails[i]; + newArray.append(authorsObject); + } + kplugin[QStringLiteral("Authors")] = newArray; + } 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("Actions") || + 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 + // Actions= is used as hack at least with the Dolphin KPart to report the different view mode options + // no plans yet to port that hack to JSON, so gently ignore it for now + // 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 new file mode 100644 index 0000000..2462ad4 --- /dev/null +++ b/src/lib/plugin/desktopfileparser_p.h @@ -0,0 +1,61 @@ +/* + 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 +class QJsonObject; +class QJsonValue; + + +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 new file mode 100644 index 0000000..d059fbe --- /dev/null +++ b/src/lib/plugin/kexportplugin.h @@ -0,0 +1,48 @@ +/* + 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 new file mode 100644 index 0000000..b7e6a32 --- /dev/null +++ b/src/lib/plugin/kpluginfactory.cpp @@ -0,0 +1,230 @@ +/* + This file is part of the KDE project + + 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) + +KPluginFactory::KPluginFactory() + : d_ptr(new KPluginFactoryPrivate) +{ + factorycleanup()->add(this); +} + +KPluginFactory::KPluginFactory(KPluginFactoryPrivate &d) + : d_ptr(&d) +{ + factorycleanup()->add(this); +} + +KPluginFactory::~KPluginFactory() +{ + delete d_ptr; +} + +KPluginMetaData KPluginFactory::metaData() const +{ + Q_D(const KPluginFactory); + + return d->metaData; +} + +void KPluginFactory::setMetaData(const KPluginMetaData& metaData) +{ + Q_D(KPluginFactory); + d->metaData = metaData; +} + + +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)); + } +} + +void KPluginFactory::registerPlugin(const QString &keyword, const QMetaObject *metaObject, + CreateInstanceWithMetaDataFunction instanceFunction) +{ + Q_D(KPluginFactory); + + Q_ASSERT(metaObject); + + // we allow different interfaces to be registered without keyword + if (!keyword.isEmpty()) { + if (d->createInstanceWithMetaDataHash.contains(keyword)) { + qCWarning(KCOREADDONS_DEBUG) << "A plugin with the keyword" << keyword << "was already registered. A keyword must be unique!"; + } + d->createInstanceWithMetaDataHash.insert(keyword, {metaObject, instanceFunction}); + } else { + const QList clashes(d->createInstanceWithMetaDataHash.values(keyword)); + const QMetaObject *superClass = metaObject->superClass(); + if (superClass) { + for (const KPluginFactoryPrivate::PluginWithMetadata &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::PluginWithMetadata &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->createInstanceWithMetaDataHash.insert(keyword, {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) { + const QList candidates = + (d->createInstanceWithMetaDataHash.values(keyword)); + // for !keyword.isEmpty() candidates.count() is 0 or 1 + + for (const KPluginFactoryPrivate::PluginWithMetadata &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, d->metaData, 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 new file mode 100644 index 0000000..b1507ce --- /dev/null +++ b/src/lib/plugin/kpluginfactory.h @@ -0,0 +1,779 @@ +/* + This file is part of the KDE project + + 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 + +#include + +class QWidget; + +class KPluginFactoryPrivate; +namespace KParts +{ +class Part; +} +class KPluginMetaData; + +#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 (KF >= 5.77): + * \code + * #include + * #include + * + * class MyPlugin : public PluginInterface + * { + * public: + * MyPlugin(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + * : PluginInterface(parent) + * {} + * }; + * + * K_PLUGIN_FACTORY_WITH_JSON(MyPluginFactory, + * "metadata.json", + * registerPlugin(); + * ) + * + * #include + * \endcode + * + * Example (backward-compatible with KF < 5.77): + * \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 (KF >= 5.77): + * \code + * #include + * #include + * + * class MyPlugin : public PluginInterface + * { + * public: + * MyPlugin(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + * : PluginInterface(parent) + * {} + * }; + * + * K_PLUGIN_CLASS_WITH_JSON(MyPlugin, "metadata.json") + * + * #include + * \endcode + * + * Example (backward-compatible with KF < 5.77): + * \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 + * or, since KF 5.77, + * \code + * T(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + * \endcode + * \li if it is a QWidget, it must be of the form + * \code + * T(QWidget *parent, const QVariantList &args) + * \endcode + * or, since KF 5.77, + * \code + * T(QWidget *parent, const KPluginMetaData &metaData, const QVariantList &args) + * \endcode + * \li otherwise it must be of the form + * \code + * T(QObject *parent, const QVariantList &args) + * \endcode + * or, since KF 5.77, + * \code + * T(QObject *parent, const KPluginMetaData &metaData, 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 + + /** + * \returns the metadata of the plugin + * + * \since 5.77 + */ + KPluginMetaData metaData() const; + + /** + * Set the metadata about the plugin this factory generates. + * + * \param metaData the metadata about the plugin + * + * \since 5.77 + */ + void setMetaData(const KPluginMetaData &metaData); + + + /** + * \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 metadata-less plugin classes. + * You can inherit it, if you want to add new classes and still keep support for the old ones. + */ + template + struct InheritanceChecker { + /// property to control the availability of the registerPlugin overload taking default values + static constexpr bool enabled = + std::is_constructible::value || + std::is_constructible::value || + std::is_constructible::value; + + CreateInstanceFunction createInstanceFunction(KParts::Part *) + { + return &createPartInstance; + } + CreateInstanceFunction createInstanceFunction(QWidget *) + { + return &createInstance; + } + CreateInstanceFunction createInstanceFunction(...) + { + return &createInstance; + } + }; + + /** + * Function pointer type to a function that instantiates a plugin, also taking a plugin metadata argument. + * \since 5.77 + */ + using CreateInstanceWithMetaDataFunction = QObject *(*)(QWidget *, QObject *, const KPluginMetaData &, const QVariantList &); + + /** + * This is used to detect the arguments need for the constructor of metadata-taking plugin classes. + * You can inherit it, if you want to add new classes and still keep support for the old ones. + */ + template + struct InheritanceWithMetaDataChecker { + /// property to control the availability of the registerPlugin overload taking default values + static constexpr bool enabled = + std::is_constructible::value || + std::is_constructible::value || + std::is_constructible::value; + + CreateInstanceWithMetaDataFunction createInstanceFunction(KParts::Part *) + { + return &createPartWithMetaDataInstance; + } + CreateInstanceWithMetaDataFunction createInstanceFunction(QWidget *) + { + return &createWithMetaDataInstance; + } + CreateInstanceWithMetaDataFunction createInstanceFunction(...) + { + return &createWithMetaDataInstance; + } + }; + + explicit KPluginFactory(KPluginFactoryPrivate &dd); + + // Use std::enable_if_t once C++14 can be relied on + template< bool B, class T = void > + using enable_if_t = typename std::enable_if::type; + + /** + * Registers a metadata-less 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. + */ + template + void registerPlugin(const QString &keyword, CreateInstanceFunction instanceFunction) + { + registerPlugin(keyword, &T::staticMetaObject, instanceFunction); + } + + /** + * Overload for registerPlugin(const QString &keyword, CreateInstanceFunction instanceFunction) + * + * Uses a default instance creation function depending 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 + * + * If those constructor methods are not callable this overload is not available. + */ + template::enabled, int> = 0> + void registerPlugin(const QString &keyword = QString()) + { + CreateInstanceFunction instanceFunction = + InheritanceChecker().createInstanceFunction(static_cast(nullptr)); + registerPlugin(keyword, instanceFunction); + } + + /** + * Registers a metadata-taking 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. + */ + template + void registerPlugin(const QString &keyword, CreateInstanceWithMetaDataFunction instanceFunction) + { + registerPlugin(keyword, &T::staticMetaObject, instanceFunction); + } + + /** + * Overload for registerPlugin(const QString &keyword, CreateInstanceWithMetaDataFunction instanceFunction) + * + * Uses a default instance creation function depending 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 KPluginMetaData &metaData, const QVariantList &args) + * \endcode + * \li \c QWidget the function will call + * \code + * new T(QWidget *parent, const KPluginMetaData &metaData, const QVariantList &args) + * \endcode + * \li else the function will call + * \code + * new T(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + * \endcode + * + * If those constructor methods are not callable this overload is not available. + */ + template::enabled, int> = 0> + void registerPlugin(const QString &keyword = QString()) + { + CreateInstanceWithMetaDataFunction instanceFunction = + InheritanceWithMetaDataChecker().createInstanceFunction(static_cast(nullptr)); + registerPlugin(keyword, 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); + } + + template + static QObject *createWithMetaDataInstance(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + { + Q_UNUSED(parentWidget) + ParentType *p = nullptr; + if (parent) { + p = qobject_cast(parent); + Q_ASSERT(p); + } + return new impl(p, metaData, args); + } + + template + static QObject *createPartWithMetaDataInstance(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) + { + return new impl(parentWidget, parent, metaData, args); + } + +private: + void registerPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction); + void registerPlugin(const QString &keyword, const QMetaObject *metaObject, + CreateInstanceWithMetaDataFunction instanceFunction); +}; + +// Deprecation wrapper macro added only for 5.70, while backward typedef added in 4.0 +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 70) +/** + * Backward compatibility typedef for KPluginFactory + * @deprecated since 4.0, use KPluginFactory + */ +typedef KPluginFactory KLibFactory; +#endif + +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 new file mode 100644 index 0000000..fca6591 --- /dev/null +++ b/src/lib/plugin/kpluginfactory_p.h @@ -0,0 +1,35 @@ +/* + This file is part of the KDE project + + 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 +#include + +class KPluginFactoryPrivate +{ + friend class KPluginFactory; + +protected: + typedef QPair Plugin; + using PluginWithMetadata = QPair; + + KPluginFactoryPrivate() = default; + ~KPluginFactoryPrivate() + { + } + + KPluginMetaData metaData; + QMultiHash createInstanceHash; + QMultiHash createInstanceWithMetaDataHash; +}; + +#endif // KPLUGINFACTORY_P_H diff --git a/src/lib/plugin/kpluginloader.cpp b/src/lib/plugin/kpluginloader.cpp new file mode 100644 index 0000000..a099d6d --- /dev/null +++ b/src/lib/plugin/kpluginloader.cpp @@ -0,0 +1,313 @@ +/* + * 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) + {} + ~KPluginLoaderPrivate() + {} + + KPluginLoader *q_ptr = nullptr; + const QString name; + QString errorString; + QPluginLoader *loader = nullptr; + quint32 pluginVersion = ~0U; + bool pluginVersionResolved = false; + bool isPluginMetaDataSet = false; +}; + +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); + } + + if (!d->isPluginMetaDataSet && factory) { + factory->setMetaData(KPluginMetaData(*d->loader)); + d->isPluginMetaDataSet = true; + } + + 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; +#ifdef Q_OS_ANDROID + dirsToCheck << QCoreApplication::libraryPaths(); +#else + if (QDir::isAbsolutePath(directory)) { + dirsToCheck << directory; + } else { + const QStringList listPaths = QCoreApplication::libraryPaths(); + for (const QString &libDir : listPaths) { + dirsToCheck << libDir + QLatin1Char('/') + directory; + } + } +#endif + + qCDebug(KCOREADDONS_DEBUG) << "Checking for plugins in" << dirsToCheck; + + for (const QString &dir : qAsConst(dirsToCheck)) { + QDirIterator it(dir, QDir::Files); + while (it.hasNext()) { + it.next(); +#ifdef Q_OS_ANDROID + QString prefix(QLatin1String("libplugins_") + QString(directory).replace(QLatin1Char('/'), QLatin1String("_"))); + if (!prefix.endsWith(QLatin1Char('_'))) { + prefix.append(QLatin1Char('_')); + } + if (it.fileName().startsWith(prefix) && QLibrary::isLibrary(it.fileName())) { +#else + if (QLibrary::isLibrary(it.fileName())) { +#endif + 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 new file mode 100644 index 0000000..bf53d22 --- /dev/null +++ b/src/lib/plugin/kpluginloader.h @@ -0,0 +1,468 @@ +/* + 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. + * + * Since KF 5.77, the factory will have the metadata set fetched from + * any JSON metadata that is embedded into the plugin binary. + * + * \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 new file mode 100644 index 0000000..f351de4 --- /dev/null +++ b/src/lib/plugin/kpluginmetadata.cpp @@ -0,0 +1,421 @@ +/* + 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 "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::licenseText() const +{ + return KAboutLicense::byKeyword(license()).text(); +} + +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(); +} + +template +QVariantList listToVariant(const QList& values) +{ + QVariantList ret; + ret.reserve(values.count()); + for(const auto &license: values) { + ret << QVariant::fromValue(license); + } + return ret; +} + +QVariantList KPluginMetaData::authorsVariant() const +{ + return listToVariant(authors()); +} + +QVariantList KPluginMetaData::translatorsVariant() const +{ + return listToVariant(translators()); +} + +QVariantList KPluginMetaData::otherContributorsVariant() const +{ + return listToVariant(otherContributors()); +} + diff --git a/src/lib/plugin/kpluginmetadata.h b/src/lib/plugin/kpluginmetadata.h new file mode 100644 index 0000000..3824903 --- /dev/null +++ b/src/lib/plugin/kpluginmetadata.h @@ -0,0 +1,457 @@ +/* + 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 +{ + Q_GADGET + Q_PROPERTY(bool isValid READ isValid CONSTANT) + Q_PROPERTY(bool isHidden READ isHidden CONSTANT) + Q_PROPERTY(QString fileName READ fileName CONSTANT) + Q_PROPERTY(QString metaDataFileName READ metaDataFileName CONSTANT) + Q_PROPERTY(QJsonObject rawData READ rawData CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString extraInformation READ extraInformation CONSTANT) + Q_PROPERTY(QVariantList authors READ authorsVariant CONSTANT) + Q_PROPERTY(QVariantList translators READ translatorsVariant CONSTANT) + Q_PROPERTY(QVariantList otherContributors READ otherContributorsVariant CONSTANT) + Q_PROPERTY(QString category READ category CONSTANT) + Q_PROPERTY(QString iconName READ iconName CONSTANT) + Q_PROPERTY(QString license READ license CONSTANT) + Q_PROPERTY(QString licenseText READ licenseText CONSTANT) + Q_PROPERTY(QString copyrightText READ copyrightText CONSTANT) + Q_PROPERTY(QString pluginId READ pluginId CONSTANT) + Q_PROPERTY(QString version READ version CONSTANT) + Q_PROPERTY(QString website READ website CONSTANT) + Q_PROPERTY(QStringList dependencies READ dependencies CONSTANT) + Q_PROPERTY(QStringList serviceTypes READ serviceTypes CONSTANT) + Q_PROPERTY(QStringList mimeTypes READ mimeTypes CONSTANT) + Q_PROPERTY(QStringList formFactors READ formFactors CONSTANT) + Q_PROPERTY(bool isEnabledByDefault READ isEnabledByDefault CONSTANT) + Q_PROPERTY(int initialPreference READ isEnabledByDefault CONSTANT) + +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 the text of the license, equivalent to KAboutLicense::byKeyword(license()).text() + * @since 5.73 + */ + QString licenseText() 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: + QVariantList authorsVariant() const; + QVariantList translatorsVariant() const; + QVariantList otherContributorsVariant() const; + + 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 new file mode 100644 index 0000000..16ba7d5 --- /dev/null +++ b/src/lib/randomness/krandom.cpp @@ -0,0 +1,79 @@ +/* + 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 +*/ + +#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 + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72) +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)) { +// silence warnings about use of deprecated qsrand()/qrand() +// Porting to QRandomGenerator::global() instead might result in no new seed set for the generator behind qrand() +// which then might affect other places indirectly relying on this. +// So just keeping the old calls here, as this method is also deprecated and will disappear together with qsrand/qrand. +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") + // No /dev/urandom... try something else. + qsrand(getpid()); + seed = qrand() ^ time(nullptr) ^ reinterpret_cast(QThread::currentThread()); + } + qsrand(seed); + } + return qrand(); +QT_WARNING_POP +} +#endif + +QString KRandom::randomString(int length) +{ + if (length <= 0) { + return QString(); + } + + QString str; str.resize(length); + int i = 0; + while (length--) { + int r = QRandomGenerator::global()->bounded(62); + r += 48; + if (r > 57) { + r += 7; + } + if (r > 90) { + r += 6; + } + str[i++] = QLatin1Char(char(r)); + // so what if I work backwards? + } + return str; +} diff --git a/src/lib/randomness/krandom.h b/src/lib/randomness/krandom.h new file mode 100644 index 0000000..629258a --- /dev/null +++ b/src/lib/randomness/krandom.h @@ -0,0 +1,84 @@ +/* + 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 +#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 +{ +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 72) +/** + * Generates a uniform random number. + * @return A random number in the range [0, RAND_MAX). The RNG is seeded + * on first use. + * @deprecated Since 5.72, use QRandomGenerator::global(). The 1:1 port is bounded(RAND_MAX) but check all the methods that QRandomGenerator provides. + */ +KCOREADDONS_DEPRECATED_VERSION(5, 72, "Use QRandomGenerator::global(). The 1:1 port is bounded(RAND_MAX) but check see all the methods that QRandomGenerator provides.") +KCOREADDONS_EXPORT int random(); +#endif + +/** + * 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); + +/** + * Reorders the elements of the given container randomly using the given random number generator. + * + * The container needs to implement size() and T &operator[] + * + * @since 5.73 + */ +template +void shuffle(T &container, QRandomGenerator *generator) +{ + Q_ASSERT(container.size() <= std::numeric_limits::max()); + // Fisher-Yates algorithm + for (int index = container.size() - 1; index > 0; --index) { + const int swapIndex = generator->bounded(index + 1); + qSwap(container[index], container[swapIndex]); + } +} + +/** + * Reorders the elements of the given container randomly. + * + * The container needs to implement size() and T &operator[] + * + * @since 5.73 + */ +template +void shuffle(T &container) +{ + shuffle(container, QRandomGenerator::global()); +} + +} + +#endif + diff --git a/src/lib/randomness/krandomsequence.cpp b/src/lib/randomness/krandomsequence.cpp new file mode 100644 index 0000000..4533c54 --- /dev/null +++ b/src/lib/randomness/krandomsequence.cpp @@ -0,0 +1,210 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Sean Harmer + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "krandomsequence.h" + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75) + +#include + +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 = -((QRandomGenerator::global()->bounded(RAND_MAX) & ~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); +} + +#endif diff --git a/src/lib/randomness/krandomsequence.h b/src/lib/randomness/krandomsequence.h new file mode 100644 index 0000000..987764b --- /dev/null +++ b/src/lib/randomness/krandomsequence.h @@ -0,0 +1,162 @@ +/* + 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 + +#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 75) + +#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 + * + * @deprecated Since 5.75, use QRandomGenerator or KRandom::shuffle + */ +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 QRandomGenerator::global()->generate() instead. + * + * @param intSeed Seed to initialize the sequence with. + * If lngSeed is 0, the sequence is initialized with a value from + * [0, RAND_MAX) + * + * Do not use methods working with long type because on 64-bit + * their size is different. + */ + KCOREADDONS_DEPRECATED_VERSION(5, 75, "Use QRandomGenerator") + explicit KRandomSequence(int intSeed = 0); + KCOREADDONS_DEPRECATED_VERSION(5, 75, "Use QRandomGenerator") + 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 + * [0, RAND_MAX). + */ + 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 + * + * @deprecated Since 5.75, use KRandom::shuffle + */ + template + KCOREADDONS_DEPRECATED_VERSION(5, 75, "Use KRandom::shuffle") + 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 + +#endif diff --git a/src/lib/text/kmacroexpander.cpp b/src/lib/text/kmacroexpander.cpp new file mode 100644 index 0000000..81356e0 --- /dev/null +++ b/src/lib/text/kmacroexpander.cpp @@ -0,0 +1,392 @@ +/* + This file is part of the KDE libraries + + 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 new file mode 100644 index 0000000..cf1c7b6 --- /dev/null +++ b/src/lib/text/kmacroexpander.h @@ -0,0 +1,399 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen + SPDX-FileCopyrightText: 2003 Waldo Bastian + + 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 new file mode 100644 index 0000000..18a5016 --- /dev/null +++ b/src/lib/text/kmacroexpander_p.h @@ -0,0 +1,21 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2002-2003, 2007 Oswald Buddenhagen + + 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 new file mode 100644 index 0000000..5e424fc --- /dev/null +++ b/src/lib/text/kmacroexpander_unix.cpp @@ -0,0 +1,247 @@ +/* + This file is part of the KDE libraries + + 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 +#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 new file mode 100644 index 0000000..fffce71 --- /dev/null +++ b/src/lib/text/kmacroexpander_win.cpp @@ -0,0 +1,112 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2008 Oswald Buddenhagen + + 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 new file mode 100644 index 0000000..9c22ee9 --- /dev/null +++ b/src/lib/text/kstringhandler.cpp @@ -0,0 +1,380 @@ +/* + 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() + +#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 67) +#include // for the word ranges +#endif +#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 new file mode 100644 index 0000000..9aaba52 --- /dev/null +++ b/src/lib/text/kstringhandler.h @@ -0,0 +1,248 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1999 Ian Zepp + SPDX-FileCopyrightText: 2000 Rik Hemsley (rikkus) + SPDX-FileCopyrightText: 2006 Dominic Battre + SPDX-FileCopyrightText: 2006 Martin Pool + + 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 new file mode 100644 index 0000000..b001357 --- /dev/null +++ b/src/lib/text/ktexttohtml.cpp @@ -0,0 +1,602 @@ +/* + 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 "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 (mPos < mText.length() && mText.at(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.at(start).unicode() < 128 && + (mText.at(start).isLetterOrNumber() || + mText.at(start) == QLatin1Char('@') || // allow @ to find invalid email addresses + allowedSpecialChars.indexOf(mText.at(start)) != -1)) { + if (mText.at(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.at(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.at(end).isLetterOrNumber() || + mText.at(end) == QLatin1Char('@') || // allow @ to find invalid email addresses + mText.at(end) == QLatin1Char('.') || + mText.at(end) == QLatin1Char('-'))) { + if (mText.at(end) == QLatin1Char('@')) { + return QString(); // domain part contains '@' -> no email address + } + if (mText.at(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.at(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.at(mPos).isDigit() && mText.at(mPos) != QLatin1Char('+')) { + return {}; + } + + const QString allowedBeginSeparators = QStringLiteral(" \r\t\n:"); + if (mPos > 0 && !allowedBeginSeparators.contains(mText.at(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.at(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; +} + +// The following characters are allowed in a dot-atom (RFC 2822): +// a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ +static const char s_allowedSpecialChars[] = ".!#$%&'*+-/=?^_`{|}~"; + +bool KTextToHTMLHelper::atUrl() const +{ + // 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) { + const auto chBefore = mText.at(mPos - 1); + if (chBefore.isLetterOrNumber() || QLatin1String(s_allowedSpecialChars).contains(chBefore)) { + return false; + } + } + + const auto segment = mText.midRef(mPos); + return + segment.startsWith(QLatin1String("http://")) + || segment.startsWith(QLatin1String("https://")) + || segment.startsWith(QLatin1String("vnc://")) + || segment.startsWith(QLatin1String("fish://")) + || segment.startsWith(QLatin1String("ftp://")) + || segment.startsWith(QLatin1String("ftps://")) + || segment.startsWith(QLatin1String("sftp://")) + || segment.startsWith(QLatin1String("smb://")) + || segment.startsWith(QLatin1String("mailto:")) + || segment.startsWith(QLatin1String("www.")) + || segment.startsWith(QLatin1String("ftp.")) + || segment.startsWith(QLatin1String("file://")) + || segment.startsWith(QLatin1String("news:")) + || segment.startsWith(QLatin1String("tel:")) + || segment.startsWith(QLatin1String("xmpp:")); +} + +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("mailto:") || + url == QLatin1String("www") || + url == QLatin1String("ftp") || + url == QLatin1String("news:") || + url == QLatin1String("news://") || + url == QLatin1String("tel") || + url == QLatin1String("tel:") || + url == QLatin1String("xmpp:"); +} + +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.at(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.at(mPos).isPrint() || mText.at(mPos).isSpace()) && + ((afterUrl.isNull() && !mText.at(mPos).isSpace()) || + (!afterUrl.isNull() && mText.at(mPos) != afterUrl))) { + if (!previousCharIsSpace && (mText.at(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.at(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.at(mPos).isSpace()) { + previousCharIsSpace = true; + } else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char('[')) { + break; + } else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char(']')) { + break; + } else { // skip whitespace + if (previousCharIsSpace && mText.at(mPos) == QLatin1Char('<')) { + url.append(QLatin1Char(' ')); + break; + } + previousCharIsSpace = false; + if (mText.at(mPos) == QLatin1Char('>') && previousCharIsADoubleQuote) { + //it's an invalid url + if (badurl) { + *badurl = true; + } + return QString(); + } + if (mText.at(mPos) == QLatin1Char('"')) { + previousCharIsADoubleQuote = true; + } else { + previousCharIsADoubleQuote = false; + } + if (mText.at(mPos) == QLatin1Char('#')) { + previousIsAnAnchor = true; + } + url.append(mText.at(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.at(mPos - 1).isSpace()) { + return QString(); + } + + const QChar ch = mText.at(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.at(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, 0, 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.at(helper.mPos); + if (flags & PreserveSpaces) { + if (ch == QLatin1Char(' ')) { + if (helper.mPos + 1 < helper.mText.length()) { + if (helper.mText.at(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.at(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.at(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.at(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 new file mode 100644 index 0000000..dbf8a52 --- /dev/null +++ b/src/lib/text/ktexttohtml.h @@ -0,0 +1,90 @@ +/* + SPDX-FileCopyrightText: 2002 Dave Corrie + SPDX-FileCopyrightText: 2014 Daniel Vrátil + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KCOREADDONS_KTEXTTOHTML_H +#define KCOREADDONS_KTEXTTOHTML_H + +#include + +#include + +/** + * @author Dave Corrie \ + */ +namespace KTextToHTML +{ + +/** + * @see Options + * @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 +}; +/** + * Stores a combination of #Option values. + */ +Q_DECLARE_FLAGS(Options, Option) +Q_DECLARE_OPERATORS_FOR_FLAGS(Options) + +/** + * 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); + +} + +#endif diff --git a/src/lib/text/ktexttohtml_p.h b/src/lib/text/ktexttohtml_p.h new file mode 100644 index 0000000..8cdcf70 --- /dev/null +++ b/src/lib/text/ktexttohtml_p.h @@ -0,0 +1,49 @@ +/* + 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 new file mode 100644 index 0000000..6eab3b4 --- /dev/null +++ b/src/lib/text/ktexttohtmlemoticonsinterface.h @@ -0,0 +1,33 @@ +/* + 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/config-accountsservice.h.cmake b/src/lib/util/config-accountsservice.h.cmake new file mode 100644 index 0000000..5221a8d --- /dev/null +++ b/src/lib/util/config-accountsservice.h.cmake @@ -0,0 +1 @@ +#define ACCOUNTS_SERVICE_ICON_DIR "${ACCOUNTS_SERVICE_ICON_DIR}" diff --git a/src/lib/util/config-getgrouplist.h.cmake b/src/lib/util/config-getgrouplist.h.cmake new file mode 100644 index 0000000..8ed4b97 --- /dev/null +++ b/src/lib/util/config-getgrouplist.h.cmake @@ -0,0 +1 @@ +#cmakedefine01 HAVE_GETGROUPLIST diff --git a/src/lib/util/config-kde4home.h.cmake b/src/lib/util/config-kde4home.h.cmake new file mode 100644 index 0000000..ecf211a --- /dev/null +++ b/src/lib/util/config-kde4home.h.cmake @@ -0,0 +1,2 @@ + +#define KDE4_DEFAULT_HOME "${KDE4_DEFAULT_HOME}" diff --git a/src/lib/util/kdelibs4configmigrator.cpp b/src/lib/util/kdelibs4configmigrator.cpp new file mode 100644 index 0000000..dee4dba --- /dev/null +++ b/src/lib/util/kdelibs4configmigrator.cpp @@ -0,0 +1,121 @@ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2014 Montel Laurent + + 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, "kf.coreaddons.kdelibs4configmigrator", QtWarningMsg) + +class Q_DECL_HIDDEN Kdelibs4ConfigMigrator::Private +{ +public: + Private(const QString &_appName) + : appName(_appName) + { + + } + + QStringList configFiles; + QStringList uiFiles; + const 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()) { + qCCritical(MIGRATOR) << " 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 new file mode 100644 index 0000000..1e6f1d8 --- /dev/null +++ b/src/lib/util/kdelibs4configmigrator.h @@ -0,0 +1,77 @@ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2014 Montel Laurent + + 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 new file mode 100644 index 0000000..22b56e0 --- /dev/null +++ b/src/lib/util/kdelibs4migration.cpp @@ -0,0 +1,122 @@ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2014 David Faure + SPDX-FileCopyrightText: 2014 Ivan Cukic + + 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 new file mode 100644 index 0000000..edbf32d --- /dev/null +++ b/src/lib/util/kdelibs4migration.h @@ -0,0 +1,108 @@ +/* + This file is part of the KDE Frameworks + + SPDX-FileCopyrightText: 2014 David Faure + SPDX-FileCopyrightText: 2014 Ivan Cukic + + 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 new file mode 100644 index 0000000..f22b5e5 --- /dev/null +++ b/src/lib/util/kformat.cpp @@ -0,0 +1,98 @@ +/* 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" + +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); +} + +// TODO KF6 Merge both methods +QString KFormat::formatValue(double value, + const QString& unit, + int precision, + KFormat::UnitPrefix prefix, + KFormat::BinaryUnitDialect dialect) const +{ + return d->formatValue(value, KFormat::Unit::Other, unit, precision, prefix, dialect); +} + +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 new file mode 100644 index 0000000..3f2c8e6 --- /dev/null +++ b/src/lib/util/kformat.h @@ -0,0 +1,431 @@ +/* + 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() + * @see DurationFormatOptions + */ + 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 + }; + /** + * Stores a combination of #DurationFormatOption values. + */ + 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(1024, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte); + * jedec = formatByteSize(1024, 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; + /** + * Converts @p value to the appropriate string representation. + * + * Example: + * @code + * QString iec, jedec, metric; + * // Sets iec to "1.0 KiB/s", jedec to "1.0 KB/s" and metric to "1.0 kB/s" + * iec = format.formatValue(1024, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::IECBinaryDialect); + * jedec = format.formatValue(1024, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::JEDECBinaryDialect); + * metric = format.formatValue(1000, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::MetricBinaryDialect); + * @endcode + * + * @param value value to be formatted + * @param precision number of places after the decimal point to use. 1 is used by default; 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 + * @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.74 + */ + QString formatValue(double value, + const QString& unit, + int precision, + KFormat::UnitPrefix prefix, + KFormat::BinaryUnitDialect dialect) const; + + +private: + QSharedDataPointer d; +}; + +#endif // KFORMAT_H diff --git a/src/lib/util/kformatprivate.cpp b/src/lib/util/kformatprivate.cpp new file mode 100644 index 0000000..5fe49e8 --- /dev/null +++ b/src/lib/util/kformatprivate.cpp @@ -0,0 +1,541 @@ +/* + 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 QDateTime now = QDateTime::currentDateTime(); + const qint64 daysTo = dateTime.daysTo(now); + if (daysTo > 2 || daysTo < -2) { + return m_locale.toString(dateTime, format); + } + + const auto secsToNow = dateTime.secsTo(now); + if (secsToNow >= 0 && secsToNow < 60 * 60) { + const int minutesToNow = secsToNow / 60; + if (minutesToNow <= 1) { + return tr("Just now"); + } else { + return tr("%1 minutes ago").arg(minutesToNow); + } + } + + /*: 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 new file mode 100644 index 0000000..0d796b1 --- /dev/null +++ b/src/lib/util/kformatprivate_p.h @@ -0,0 +1,60 @@ +/* + 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 new file mode 100644 index 0000000..4e3bc03 --- /dev/null +++ b/src/lib/util/klistopenfilesjob.h @@ -0,0 +1,69 @@ +/* + 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 new file mode 100644 index 0000000..6cbe661 --- /dev/null +++ b/src/lib/util/klistopenfilesjob_unix.cpp @@ -0,0 +1,107 @@ +/* + 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) + { + 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())); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + const QVector pidList = out.splitRef(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts); +#else + const QVector pidList = out.splitRef(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); +#endif + 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 = false; + 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 new file mode 100644 index 0000000..85dba2e --- /dev/null +++ b/src/lib/util/klistopenfilesjob_win.cpp @@ -0,0 +1,37 @@ +/* + 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 new file mode 100644 index 0000000..51d9610 --- /dev/null +++ b/src/lib/util/kosrelease.cpp @@ -0,0 +1,291 @@ +/* + 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 new file mode 100644 index 0000000..1aa34d6 --- /dev/null +++ b/src/lib/util/kosrelease.h @@ -0,0 +1,94 @@ +/* + SPDX-FileCopyrightText: 2014-2019 Harald Sitter + + 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 new file mode 100644 index 0000000..44a6e4e --- /dev/null +++ b/src/lib/util/kprocesslist.cpp @@ -0,0 +1,78 @@ +/* + 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() +{ +} + +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 new file mode 100644 index 0000000..f556dfa --- /dev/null +++ b/src/lib/util/kprocesslist.h @@ -0,0 +1,84 @@ +/* + 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 new file mode 100644 index 0000000..9f6ecbf --- /dev/null +++ b/src/lib/util/kprocesslist_p.h @@ -0,0 +1,32 @@ +/* + 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 = false; + qint64 pid = -1; + 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 new file mode 100644 index 0000000..02c6036 --- /dev/null +++ b/src/lib/util/kprocesslist_unix.cpp @@ -0,0 +1,152 @@ +/* + 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 new file mode 100644 index 0000000..8190161 --- /dev/null +++ b/src/lib/util/kprocesslist_unix_procstat.cpp @@ -0,0 +1,52 @@ +/* + + 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 new file mode 100644 index 0000000..5aae77e --- /dev/null +++ b/src/lib/util/kprocesslist_unix_procstat_p.h @@ -0,0 +1,119 @@ +/* + + 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 new file mode 100644 index 0000000..3796d41 --- /dev/null +++ b/src/lib/util/kprocesslist_win.cpp @@ -0,0 +1,128 @@ +/* + 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 new file mode 100644 index 0000000..e55de36 --- /dev/null +++ b/src/lib/util/kshell.cpp @@ -0,0 +1,70 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen + + 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 new file mode 100644 index 0000000..3ed08d8 --- /dev/null +++ b/src/lib/util/kshell.h @@ -0,0 +1,199 @@ +/* + This file is part of the KDE libraries + + 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(). + * @see Options + */ +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 +}; +/** + * Stores a combination of #Option values. + */ +Q_DECLARE_FLAGS(Options, Option) +Q_DECLARE_OPERATORS_FOR_FLAGS(Options) + +/** + * 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); +} + +#endif /* KSHELL_H */ diff --git a/src/lib/util/kshell_p.h b/src/lib/util/kshell_p.h new file mode 100644 index 0000000..70a6e1f --- /dev/null +++ b/src/lib/util/kshell_p.h @@ -0,0 +1,25 @@ +/* + This file is part of the KDE libraries + + 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 new file mode 100644 index 0000000..b7addb2 --- /dev/null +++ b/src/lib/util/kshell_unix.cpp @@ -0,0 +1,308 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2003, 2007 Oswald Buddenhagen + + 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 += QChar::fromLatin1(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 new file mode 100644 index 0000000..5d7745e --- /dev/null +++ b/src/lib/util/kshell_win.cpp @@ -0,0 +1,264 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 2007 Bernhard Loos + SPDX-FileCopyrightText: 2007, 2008 Oswald Buddenhagen + + 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 new file mode 100644 index 0000000..315b748 --- /dev/null +++ b/src/lib/util/kuser.h @@ -0,0 +1,631 @@ +/* + 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 new file mode 100644 index 0000000..03b76ed --- /dev/null +++ b/src/lib/util/kuser_unix.cpp @@ -0,0 +1,551 @@ +/* + 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 "kcoreaddons_debug.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 + qCDebug(KCOREADDONS_DEBUG, "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) { + qCWarning(KCOREADDONS_DEBUG, "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) { + qCWarning(KCOREADDONS_DEBUG, "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 new file mode 100644 index 0000000..bb9e3b5 --- /dev/null +++ b/src/lib/util/kuser_win.cpp @@ -0,0 +1,899 @@ +/* + 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 { + qCWarning(KCOREADDONS_DEBUG, "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); + qCWarning(KCOREADDONS_DEBUG, "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)) { + qCWarning(KCOREADDONS_DEBUG, "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) { + qCWarning(KCOREADDONS_DEBUG, "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)) { + qCWarning(KCOREADDONS_DEBUG, "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/src/mimetypes/CMakeLists.txt b/src/mimetypes/CMakeLists.txt new file mode 100644 index 0000000..3799bbc --- /dev/null +++ b/src/mimetypes/CMakeLists.txt @@ -0,0 +1,14 @@ +# always install the mime-types +install(FILES kde5.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) + +# for KDE frameworks 5 we require at least version 1.3 +find_package(SharedMimeInfo 1.3) +set_package_properties(SharedMimeInfo PROPERTIES + TYPE OPTIONAL + PURPOSE "Allows KDE applications to determine file types" + ) + +# update XDG mime-types if shared mime info is around +if(SharedMimeInfo_FOUND) + update_xdg_mimetypes(${KDE_INSTALL_MIMEDIR}) +endif() diff --git a/src/mimetypes/XmlMessages.sh b/src/mimetypes/XmlMessages.sh new file mode 100755 index 0000000..e517106 --- /dev/null +++ b/src/mimetypes/XmlMessages.sh @@ -0,0 +1,23 @@ +function get_files +{ + echo kde5.xml +} + +function po_for_file +{ + case "$1" in + kde5.xml) + echo kde5_xml_mimetypes.po + ;; + esac +} + +function tags_for_file +{ + case "$1" in + kde5.xml) + echo comment + ;; + esac +} + diff --git a/src/mimetypes/kde5.xml b/src/mimetypes/kde5.xml new file mode 100644 index 0000000..9a8d2fa --- /dev/null +++ b/src/mimetypes/kde5.xml @@ -0,0 +1,1451 @@ + + + + + + + + + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + RELAX NG + + + + CD audio + CD d'àudio + CD ήχου + CD audio + Audio CD + Audio CDa + CD audio + CD-hang + Audio CD + Audio CD + Garso CD + CD-audio + CD-lyd + Áudio de CD + Áudio de CD + CD audio + Zvočni CD + Cd-ljud + звуковий компакт-диск + âm thanh CD + + + + SNF bitmap font + Tipus de lletra de mapa de bits SNF + Γραμματοσειρά bitmap SNF + SNF bitmap font + Tipo de letra de mapa de bits SNF + SNF bit-mapa letra-tipoa + Police « bitmap SNF » + SNF-betűtípus + font de bitmap SNF + Carattere bitmap SNF + SNF rastrinis šriftas + SNF-bitmap-lettertype + SNF-punktskrift + Tipo de letra por imagens SNF + Fonte bitmap SNF + font SNF hartă de biți + Bitna pisava SNF + SNF punktavbildat teckensnitt + растровий файл SNF + phông chữ kiểu bản đồ bit SNF + + + + + + Java applet + Miniaplicació Java + Μικροεφαρμογή Java + Java applet + Miniaplicación Java + Java aplikaziotxoa + Applet Java + Java-kisalkalmazás + Applet Java + Applet Java + Java programėlė + Java-applet + Java-applett + 'Applet' de Java + Miniaplicativo Java + miniaplicație Java + Aplet Java + Java-miniprogram + аплет Java + tiểu ứng dụng Java + + + KHTML Extension Adaptor + Adaptador de l'extensió KHTML + Προσαρμογέας επέκτασης KHTML + KHTML Extension Adaptor + Adaptador de extensión de KHTML + KHTML hedapen-egokitzailea + Adaptateur d'extension « KHTML » + KHTML-es kiterjesztéskezelő + Adaptator pro KHTML Extension + Adattatore di estensioni di KHTML + KHTML plėtinių adapteris + KHTML-extensie aanpassen + KHTML-utvidingstilpassar + Adaptador de Extensões do KHTML + Adaptador de extensões do KHTML + adaptor de extensii KHTML + Prilagodilnik razširitve KHTML + KHTML-utökningsanpassning + адаптер розширень KHTML + bộ tiếp hợp mở rộng KHTML + + + KDE color scheme + Esquema de color del KDE + Θέμα χρωμάτων KDE + KDE colour scheme + Esquema de color de KDE + KDE-ren kolore antolaera + Schéma de couleurs de KDE + KDE-színséma + Schema de color KDE + Schema di colori di KDE + KDE spalvų rinkinys + KDE-kleurenschema + KDE-fargetema + Esquema de cores do KDE + Esquema de cores do KDE + schemă de culori KDE + Barvna shema KDE + KDE-färgschema + схема кольорів KDE + quy hoạch màu KDE + KDE + K Desktop Environment + + + + + KNewStuff package + Paquet del KNewStuff + Πακέτο KNewStuff + KNewStuff package + Paquete de KNewStuff + KNewStuff-erako paketea + Paquet pour KNewStuff + KNewStuff-csomag + Pacchetto de KNewStuff + Pacchetto di KNewStuff + KNewStuff paketas + KNewStuff-pakket + KNewStuff-pakke + Pacote do KNewStuff + Pacote do KNewStuff + pachet KNewStuff + Paket KNewStuff + Heta nyheter-paket + пакунок KNewStuff + gói KNewStuff + + + + KWallet wallet + Cartera del KWallet + Πορτοφόλι KWallet + KWallet wallet + Cartera de KWallet + KWallet-eko zorroa + Portefeuille pour KWallet + KWallet-jelszófájl + Kwallet :portafolio + Portafogli KWallet + KWallet slaptažodinė + KWallet-portefeuille + KWallet-lommebok + Carteira da KWallet + Carteira do KWallet + portofel KWallet + Listnica Kwallet + Kwallet-plånbok + торбинка KWallet + ví KWallet + + + + + + + Kugar report template + Plantilla d'informe del Kugar + Πρότυπο αναφοράς Kugar + Kugar report template + Plantilla de informe de Kugar + Kugar-rerako txosten-txantiloia + Modèle de rapport pour Kugar + Kugar-jelentéssablon + Kugar : patrono de reporto + Modello di rapporto di Kugar + Kugar ataskaitos šablonas + Kugar-rapportsjabloon + Rapportmal for Kugar + Modelo de relatórios do Kugar + Modelo de relatório do Kugar + șablon de raport Kugar + Predloga za poročila Kugar + Kugar-rapportmall + шаблон звіту Kugar + mẫu báo cáo Kugar + + + + + plasmoid + Plasmoide + πλασμοειδές + plasmoid + plasmoide + plasmoidea + composant graphique + plasmoid + plasmoid + plasmoide + Plasma įskiepis + plasmoid + plasmoide + plasmóide + plasmoide + plasmoid + plasmoid + plasmoid + плазмоїд + plasmoid + + + + + SuperKaramba theme + Tema del SuperKaramba + Θέμα SuperKaramba + SuperKaramba theme + Tema de SuperKaramba + SuperKaramba-ko gaia + Thème pour SuperKaramba + SuperKaramba-téma + Thema de SuperKaramba + Tema di SuperKaramba + SuperKaramba apipavidalinimas + SuperKaramba-thema + SuperKaramba-tema + Tema do SuperKaramba + Tema do SuperKaramba + tematică SuperKaramba + Tema SuperKaramba + Superkaramba-tema + тема SuperKaramba + chủ đề SuperKaramba + + + + Calligra Plan project management document + Document per a la gestió de projectes del Calligra Plan + Έγγραφο διαχείρισης έργων Calligra Plan + Calligra Plan project management document + Documento de gestión de proyecto de Calligra Plan + Calligra Plan-eko proiektuak kudeatzeko dokumentua + Document de gestion de projets pour Calligra Plan + Calligra Plan projektkezelő dokumentum + Calligra Plan: Documento de gestion de projecto + Documento di gestione dei progetti di Calligra Plan + Calligra Plan projektų valdymo dokumentas + Calligra-plan projectbeheer-document + Prosjekthandsamings­dokument for Calligra Plan + Documento de gestão de projectos do Plan para o Calligra + Documento de gerenciamento de projetos do Calligra Plan + Dokument upravljanja projektov Calligra Plan + Calligra Plan-projekthanteringsdokument + документ керування роботами Plan зі складу Calligra + tài liệu quản lí dự án Calligra Plan + + + + Calligra Plan work package document + Document de paquet de treball del Calligra Plan + Έγγραφο πακέτου εργασίας Calligra Plan + Calligra Plan work package document + Documento de paquete de trabajo de Calligra Plan + Calligra Plan-eko lanerako pakete-dokumentua + Document de lots de travaux pour Calligra Plan + Calligra Plan munkacsomag dokumentum + Documento de pacchetto de labor de Calligra Plan + Documento dei pacchetti di lavoro di Calligra Plan + Calligra Plan darbo paketo dokumentas + Calligra-plan werkpakket document + Arbeidspakke­dokument for Calligra Plan + Documento de pacote de trabalho do Plan para o Calligra + Documento do pacote de trabalho do Calligra Plan + Dokument delovnega paketa Calligra Plan + Calligra Plan-arbetspaketsdokument + пакунок роботи Plan зі складу Calligra + tài liệu gói công việc Calligra Plan + + + + KPlato project management document + Document per a la gestió de projectes del KPlato + Έγγραφα διαχείρισης έργων KPlato + KPlato project management document + Documento de gestión de proyecto de KPlato + KPlato-ko proiektua kudeatzeko dokumentua + Document de gestion de projets pour KPlato + KPlato projektkezelő dokumentum + KPlato: Documento de gestion de projecto + Documento di gestione dei progetti di KPlato + KPlato projektų valdymo dokumentas + KPlato projectbeheer-document + KPlato-prosjekthandsamings­dokument + Documento de gestão de projectos do KPlato + Documento de gerenciamento de projetos do KPlato + Dokument upravljanja projektov KPlato + Kplato-projekthantering dokument + документ керування роботами KPlato + tài liệu quản lí dự án KPlato + + + + KPlato project management work package + Paquet de treball per a la gestió de projectes del KPlato + Πακέτο εργασιών διαχείρισης έργων KPlato + KPlato project management work package + Paquete de trabajo de gestión de proyecto de KPlato + KPlato-ko proiektuak kudeatzeko lan paketea + Lots de travaux du logiciel de gestion de projets pour KPlato + KPlato projektkezelő munkacsomag + KPlato: Pacchetto de travalio de gestion de projecto + Pacchetto di lavoro di gestione dei progetti di KPlato + KPlato projektų valdymo darbo paketas + KPlato projectbeheer-werkpakket + KPlato-prosjekthandsamings­arbeidspakke + Pacote de trabalho de gestão de projectos do KPlato + Pacote de trabalho de gerenciamento de projetos do KPlato + Delovni paket upravljanja projektov KPlato + Kplato-projekthantering arbetspaket + пакунок проєкту керування роботами KPlato + gói công việc quản lí dự án KPlato + + + + Kugar archive + Arxiu del Kugar + Αρχειοθήκη Kugar + Kugar archive + Archivo de Kugar + Kugar-reko artxiboa + Archive pour Kugar + Kugar-archívum + Kugar : archivo + Archivio di Kugar + Kugar archyvas + Kugar-archief + Kugar-arkiv + Pacote do Kugar + Arquivo do Kugar + arhivă Kugar + Arhiv Kugar + Kugar-arkiv + архів Kugar + kho trữ Kugar + + + + + + web archive + Arxiu web + Αρχειοθήκη ιστού + web archive + archivo web + web-artxiboa + Archive Internet + webes archívum + archivo web + Archivio web + saityno archyvas + webarchief + vevarkiv + pacote Web + Arquivo Web + arhivă web + spletni arhiv + webbarkiv + вебархів + kho trữ web + + + + + W3C XML schema + Esquema XML de la W3C + Σχήμα W3C XML + W3C XML schema + Esquema XML del W3C + W3C XML eskema + Schéma « XML W3C » + W3C XML-séma + schema XML de W3C + Schema XML W3C + W3C XML schema + W3C XML-schema + W3C XML-skjema + Esquema XML da W3C + Esquema XML da W3C + Shema W3C XML + W3C XML-schema + схема XML W3C + sơ đồ XML W3C + + + + RealAudio plugin file + Fitxer de connector RealAudio + Αρχείο προσθέτου RealAudio + RealAudio plugin file + Archivo de complemento RealAudio + RealAudio plugin-fitxategia + Fichier de module externe « Real-Audio » + RealAudio-bővítményfájl + File de plugin de RealAudio + File di estensioni RealAudio + RealAudio papildinio failas + RealAudio-bestand + RealAudio-programtilleggfil + Ficheiro de 'plugin' do RealAudio + Arquivo de plugin do RealAudio + Datoteka vstavka RealAudio + Realaudio-insticksprogramfil + файл додатка RealAudio + tệp phần cài cắm RealAudio + + + KPhotoAlbum import + Importació del KPhotoAlbum + Εισαγωγή KPhotoAlbum + KPhotoAlbum import + Importación de KPhotoAlbum + KPhotoAlbum inportatzea + Importation pour KPhotoAlbum + KPhotoAlbum-import + Importation de KPhotoAlbum + Importazione di KPhotoAlbum + KPhotoAlbum importavimas + KPhotoAlbum-import + KPhotoAlbum-import + Importação do KPhotoAlbum + Importação do KPhotoAlbum + Uvoz v KPhotoAlbum + Kfotoalbum-import + файли імпортування KPhotoAlbum + phần nhập KPhotoAlbum + + + + HDR image + Imatge HDR + Εικόνα HDR + HDR image + Imagen HDR + HDR irudia + Image Haute définition « HDR » + HDR-kép + Imagine HDR + Immagine HDR + HDR paveikslas + HDR-image + HDR-bilete + Imagem HDR + Imagem HDR + Slika HDR + HDR-bild + зображення HDR + ảnh HDR + HDR + High Dynamic Range + + + + + + KDE raw image formats + Formats d'imatge RAW del KDE + Τύποι ακατέργαστων τύπων εικόνας του KDE + KDE raw image formats + Formatos de imagen en bruto de KDE + KDE-ren RAW irudi formatuak + Formats d'image « raw » pour KDE + KDE-s nyers képformátumok + Formatos de imagines crude de KDE + Formati di immagine raw di KDE + KDE neapdorotų paveikslų formatai + KDE-rawimage-formats + KDE-råbilete + Formatos de imagem em bruto do KDE + Formatos de imagem RAW do KDE + KDE surovi formati slik + KDE-obehandlade bildformat + формати цифрових негативів KDE + các định dạng ảnh thô ở KDE + + + + + + + + + + + + + + + + + + Intel® hexadecimal object file + Fitxer objecte hexadecimal d'Intel® + Δεκαεξαδικό μεταφρασμένο αρχείο Intel® + Intel® hexadecimal object file + Archivo de código objeto hexadecimal de Intel® + Intel®-en objektu hamaseitarreko fitxategia + Fichier objet hexadécimal « Intel® » + Intel®-féle hexadecimális objektumfájl + File objecto hexadecimal de Intel(c) + File oggetto esadecimale Intel® + Intel® šešioliktainio objekto failas + Intel® hexadecimaal objectbestand + Intel®-heksobjektfil + Ficheiro-objecto em hexadecimal da Intel® + Arquivo objeto em hexadecimal da Intel® + Intel® šestnajstiška predmetna datoteka + Intel® hexadecimal objektfil + шістнадцятковий об'єктний файл Intel® + tệp đối tượng thập lục phân Intel® + + + + + Kate file list loader plugin list + Llista de connector carregador de la llista de fitxers del Kate + Λίστα αρχείου φόρτωσης λίστας πρόσθετων Kate + Kate file list loader plugin list + Lista de complementos de carga de listas de archivos de Kate + Kate-ko fitxategi-zerrenda zamatzeko plugin zerrenda + Liste de modules externes pour le chargeur de liste de fichiers pour Kate + Kate-listafájl (fájllista vagy bővítménylista) + Kate: Lista de plugin de cargator de lista de file + Elenco delle estensioni del caricatore di elenchi di file di Kate + Kate failų sąrašo įkėliklio papildinių sąrašas + Kate bestandenlijstlader voor pluginlijst + Programtillegg­liste for filliste-lastar i Kate + Lista de 'plugins' de carregamento de listas de ficheiros do Kate + Lista de plugins de carregamento de listas de arquivos do Kate + Seznam vstavka nalagalnika seznamov datotek za Kate + Kate insticksprogramlista för laddning av fillistor + список файлів Kate + danh sách của phần cài cắm "Bộ tải danh sách tệp" trong Kate + + + + + abc musical notation file + Fitxer de notació musical abc + Αρχείο μουσικών συμβόλων abc + abc musical notation file + Archivo de notación musical abc + abc-ko musika-notazio fitxategia + Fichier de notation musicale « ABC » + Abc-kottafájl + file de notation musical abc + File di notazione musicale abc + abc muzikos natų failas + abc-muzieknotatiebestand + abc-musikknotasjonsfil + Ficheiro de notação musical do 'abc' + arquivo de notação musical do abc + Datoteka glasbenega zapisa abc + abc-musiknotationsfil + файл нотного стану abc + tệp kí hiệu âm nhạc abc + + + + + + + + + fonts package + Paquet de tipus de lletra + πακέτο γραμματοσειρών + fonts package + paquete de tipos de letra + letra-tipoen paketea + Paquet de fontes de caractères + betűtípuscsomag + pacchettos de fonts + Pacchetto di caratteri + šriftų paketas + lettertypenpakket + skriftpakke + pacote de tipos de letra + pacote de fontes + pachet de fonturi + paket pisav + teckensnittspaket + пакунок шрифтів + gói phông chữ + + + + + + Windows server + Servidor de Windows + Εξυπηρετητής Windows + Windows server + Servidor de Windows + Windows zerbitzaria + Serveur Windows + Windows-kiszolgáló + Servitor de Windows + Server Windows + Windows serveris + Windows-server + Windows-tenar + Servidor do Windows + Servidor do Windows + server Windows + Strežnik Windows + Windows-server + сервер Windows + máy chủ Windows + + + + Windows workgroup + Treball en grup de Windows + Ομάδα εργασίας Windows + Windows workgroup + Grupo de trabajo de Windows + Windows lantaldea + Groupe de travail Windows + Windows munkacsoport + Gruppo de travalio de Windows + Gruppo di lavoro di Windows + Windows darbo grupė + Windows-werkgroep + Windows-arbeidsgruppe + Grupo de trabalho do Windows + Grupo de trabalho do Windows + grup de lucru Windows + Delovna skupina Windows + Windows-arbetsgrupp + робоча група Windows + nhóm làm việc Windows + + + + KDE system monitor + Monitor del sistema del KDE + Επόπτης συστήματος του KDE + KDE system monitor + Monitor del sistema de KDE + KDE-ren sistemako begiralea + Moniteur système pour KDE + KDE rendszermonitor + Monitor de Systema de KDE + Monitor di sistema di KDE + KDE sistemos prižiūryklė + KDE systeembewaking + KDE-systemovervaking + Monitor do sistema KDE + Monitor do sistema do KDE + monitor de sistem KDE + Sistemski nadzornik KDE + KDE-systemövervakare + монітор системи KDE + trình giám sát hệ thống KDE + + + + + KDE theme + Tema del KDE + Θέμα του KDE + KDE theme + Tema de KDE + KDE-ren gaia + Thème de KDE + KDE téma + Thema de KDE + Tema di KDE + KDE apipavidalinimas + KDE-thema + KDE-tema + Tema do KDE + Tema do KDE + tematică KDE + Tema KDE + KDE-tema + тема KDE + chủ đề KDE + + + + + + Quanta project + Projecte del Quanta + Έργο Quanta + Quanta project + Proyecto de Quanta + Quanta-ko proiektua + Projet pour Quanta + Quanta-projekt + Quanta : Projecto + Progetto Quanta + Quanta projektas + Quanta-project + Quanta-prosjekt + Projecto do Quanta + Projeto do Quanta + proiect Quanta + Projekt Quanta + Quanta-projekt + проєкт Quanta + dự án Quanta + + + + + Kommander file + Fitxer del Kommander + Αρχείο Kommander + Kommander file + Archivo de Kommander + Kommander-reko fitxategia + Fichier pour Kommander + Kommander-fájl + Kommander : file + File di Kommander + Kommander failas + Kommander-bestand + Kommander-fil + Ficheiro do Kommander + Arquivo do Kommander + fișier Kommander + Datoteka Kommanderja + Kommander-fil + файл Kommander + tệp Kommander + + + + + potato + Patata + πατάτα + potato + potato + potato + patate + potato + patata + patata + bulvė + aardappel + potato + batata + batata + cartof + krompirček + potatis + картопля + potato + + + + Kolf saved game + Joc desat del Kolf + Αποθηκευμένο παιχνίδι Kolf + Kolf saved game + Juego guardado de Kolf + Kolf-en gordetako jokoa + Partie enregistrée pour Kolf + Kolf mentett állás + Joco salveguardate de Kolf + Partita salvata di Kolf + Kolf įrašytas žaidimas + Opgeslagen Kolf-spel + Lagra Kolf-runde + Jogo gravado do Kolf + Jogo salvo do Kolf + joc salvat Kolf + Shranjena igra Kolf + Kolf sparat spel + збережена гра Kolf + bàn chơi Kolf đã lưu + + + + + + + Kolf course + Camp del Kolf + Πίστα Kolf + Kolf course + Campo de Kolf + Kolf zelaia + Parcours pour Kolf + Kolf-pálya + Kolf : curso + Campo di Kolf + Kolf laukas + Kolf-baan + Kolf-bane + Campo do Kolf + Campo de golfe + curs de Kolf + Igrišče Kolf + Kolfbana + майданчик Kolf + sân Kolf + + + + + + + + + + Okular document archive + Arxiu de document de l'Okular + Αρχειοθήκη εγγράφων Okular + Okular document archive + Archivo de documento de Okular + Okular-reko dokumentu-artxiboa + Archive de documents pour Okular + Okular-archívum + Okular : archivo de documento + Archivio di documenti di Okular + Okular dokumento archyvas + Okular-documentarchief + Okular-dokumentarkiv + Pacote de documentos do Okular + Arquivo de documento do Okular + Arhiv dokumentov za Okular + Okular-dokumentarkiv + архів документа Okular + kho trữ tài liệu Okular + + + + + Cabri figure + Figura del Cabri + Σχήμα Cabri + Cabri figure + Figura de Cabri + Cabri-ko irudia + Figure pour Cabri + Cabri-alakzat + Cabri : Figura + Figura di Cabri + Cabri figūra + Cabri-figuur + Cabri-figur + Imagem do Cabri + Imagem do Cabri + Lik Cabri + Cabri-figur + рисунок Cabri + hình Cabri + + + + + + + + Dr. Geo figure + Figura del Dr. Geo + Σχήμα Dr. Geo + Dr. Geo figure + Figura de Dr. Geo + Geo Dk. irudia + Figure pour « Dr. Geo » + Dr. Geo-alakzat + Dr.Geo : Figura + Figura di Dr. Geo + Dr. Geo figūra + Dr. Geo-figuur + Dr. Geo-figur + Imagem do Dr. Geo + Imagem do Dr. Geo + Lik Dr. Geo + Dr. Geo-figur + рисунок Dr. Geo + hình Dr. Geo + + + + + + + KGeo figure + Figura del KGeo + Σχήμα KGeo + KGeo figure + Figura de KGeo + KGeo-ko irudia + Figure pour KGeo + KGeo-alakzat + KGeo : Figura + Figura di KGeo + KGeo figūra + KGeo-figuur + KGeo-figur + Imagem do KGeo + Imagem do KGeo + Lik KGeo + Kgeo-figur + рисунок KGeo + hình KGeo + + + + Kig figure + Figura del Kig + Σχήμα Kig + Kig figure + Figura de Kig + Kig-eko irudia + Dessin pour Kig + Kig-alakzat + Kig : figura + Figura di Kig + Kig figūra + Kig-figuur + Kig-figur + Imagem do Kig + Imagem do Kig + Lik Kig + Kig-figur + рисунок Kig + hình Kig + + + + + KSeg document + Document del KSeg + Έγγραφο KSeg + KSeg document + Documento de KSeg + KSeg-eko dokumentua + Document pour KSeg + KSeg-dokumentum + KSeg :documento + Documento di KSeg + KSeg dokumentas + KSeg-document + KSeg-dokument + Documento do KSeg + Documento do KSeg + Dokument KSeg + Kseg-dokument + документ KSeg + tài liệu KSeg + + + + + vocabulary trainer document + Document de l'entrenador de vocabulari + έγγραφο εκπαιδευτή λεξιλογίου + vocabulary trainer document + documento de entrenador de vocabulario + hiztegi-trebatzaileko dokumentua + Document d'apprentissage du vocabulaire + szógyűjtemény + documento de instructor de vocabulario + Documento di allenamento a vocabolario + žodyno treniruoklio dokumentas + document voor woordenschattrainer + ordtreningsdokument + documento de treino de vocabulários + documento de treino de vocabulários + dokument za vadbo besedišča + dokument för ordförrådsövning + документ вправ зі словником + tài liệu huấn luyện viên từ vựng + + + + KmPlot file + Fitxer del KmPlot + Αρχείο KmPlot + KmPlot file + Archivo de KmPlot + KmPlot fitxategia + Fichier pour KmPlot + KmPlot-fájl + KmPlot: file + File di KmPlot + KmPlot failas + KmPlot-bestand + KmPlot-fil + Ficheiro do KmPlot + Arquivo do KmPlot + Datoteka KmPlot + Kmplot-fil + файл KmPlot + tệp KmPlot + + + + KWordQuiz vocabulary + Vocabulari del KWordQuiz + Λεξιλόγιο KWordQuiz + KWordQuiz vocabulary + Vocabulario de KWordQuiz + KWordQuiz-eko hiztegia + Vocabulaire pour KWordQuiz + KWordQuiz-szótár + Vocabulario de KWordQuiz + Vocabolario di KWordQuiz + KWordQuiz žodynas + KWordQuiz-woordenschat + KWordQuiz-vokabular + Vocabulário do KWordQuiz + Vocabulário do KWordQuiz + Besedišče KWordQuiz + Kwordquiz-ordförråd + словник KWordQuiz + từ vựng KWordQuiz + + + + + Cachegrind/Callgrind profile dump + Bolcat d'anàlisi de rendiment del Cachegrind/Callgrind + Αποτύπωση προφίλ Cachegrind/Callgrind + Cachegrind/Callgrind profile dump + Volcado de análisis de rendimiento de Cachegrind/Callgrind + Cachegrind/Callgrind profil iraulketa + Vidage de profil pour Cachegrind / Callgrind + Cachegrind/Callgrind-képfájl + Cachegrind/Callgrind : Discargatorio de profilo + Dump di profilatura di Cachegrind/Callgrind + Cachegrind/Callgrind profilio kopija + Cachegrind/Callgrind profieldump + Cachegrind/Callgrind-profildump + Resultado da análise do Cachegrind/Callgrind + Resultado da análise do Cachegrind/Callgrind + Izpis profila Cachegrind/Callgrind + Cachegrind/Callgrind-profileringsutskrift + дамп профілювання Cachegrind/Callgrind + phần xổ tiểu sử Cachegrind/Callgrind + + + + + Umbrello UML Modeller file + Fitxer del modelador UML Umbrello + Αρχείο μοντελοποίησης Umbrello UML + Umbrello UML Modeller file + Archivo del modelador UML Umbrello + Umbrello UML Modelatzailerako fitxategia + Fichier de modélisation « UML » pour Umbrello + Umbrello UML-fájl + Umbrello: file Modellator UML + File del modellatore UML Umbrello + Umbrello UML modeliuotojo failas + Umbrello UML Modeller-bestand + Umbrello UML Modeller-fil + Ficheiro de modelação em UML do Umbrello + Arquivo de modelador UML do Umbrello + Datoteka UML modelirnika Umbrello + Umbrello UML-modelleringsfil + файл програми для моделювання UML Umbrello + tệp Trình tạo mô hình UML Umbrello + + + + + + + Windows link + Enllaç de Windows + Αρχείο δεσμού Windows + Windows link + Enlace de Windows + Windows-eko esteka + Lien pour Windows + Windows-link + Ligamine de Windows + Collegamento di Windows + Windows nuoroda + Windows-koppeling + Windows-lenkje + Hiperligação do Windows + Link do Windows + Povezava v Windows + Windows-länk + посилання Windows + liên kết Windows + + + + + + + KGet download list + Llista de baixades del KGet + Λίστα λήψης KGet + KGet download list + Lista de descarga de KGet + KGet-en zama-jaisteko zerrenda + Liste de téléchargements pour KGet + KGet letöltési lista + KGet : Lista de discargar + Elenco di scaricamenti di KGet + KGet atsiuntimų sąrašas + KGet-downloadlijst + KGet-nedlastingsliste + Lista de transferências do KGet + Lista de downloads do KGet + Seznam prejemov KGet + Kget-nerladdningslista + список отримань KGet + danh sách tải về KGet + + + + Kopete emoticons archive + Arxiu d'emoticones del Kopete + Αρχειοθήκη εικονιδίων διάθεσης Kopete + Kopete emoticons archive + Archivo de emoticonos de Kopete + Kopete-ko aurpegiera-artxiboa + Archive d'émoticônes pour Kopete + Kopete-emotikoncsomag + Archivo de emoticones de Kopete + Archivio di faccine di Kopete + Kopete jaustukų archyvas + Kopete emoticon-archief + Fjesingtema for Kopete + Pacote de ícones emotivos do Kopete + Arquivo de emoticons do Kopete + Arhiv z izraznimi ikonami za Kopete + Kopete-smilisarkiv + архів емоційок Kopete + kho trữ hình biểu cảm Kopete + + + + ICQ contact + Contacte de l'ICQ + Επαφή ICQ + ICQ contact + Contacto de ICQ + ICQ-ko kontaktua + Contact « ICQ » + ICQ-névjegy + ICQ : Contacto + Contatto ICQ + ICQ adresatas + ICQ-contact + ICQ-kontakt + Contacto de ICQ + Contato do ICQ + Stik ICQ + ICQ-kontakt + контакт ICQ + liên hệ ICQ + + + + + + Microsoft Media Format + Format de suports de Microsoft + Τύπος μέσων της Microsoft + Microsoft Media Format + Formato multimedia de Microsoft + Microsoft Media formatua + Format « Microsoft Média » + Microsoft-médiafájl + Formato de Media de Microsoft + Formato multimediale di Microsoft + Microsoft medijos formatas + Microsoft mediaformaat + Microsoft Media-format + Formato Multimédia da Microsoft + Formato de mídia da Microsoft + Microsoft Media Format + Microsoft mediaformat + Microsoft Media Format + định dạng phương tiện Microsoft + + + + + + + + + + Turtle RDF document + Document RDF del Turtle + Έγγραφο Turtle RDF + Turtle RDF document + Documento RDF de Turtle + Turtle RDF dokumentua + Document « RDF » pour Turtle + Turtle RDF-dokumentum + documento RDF de Turtle + Documento Turtle RDF + Turtle RDF dokumentas + Turtle RDF-document + Turtle RDF-dokument + Documento RDF do Turtle + Documento RDF do Turtle + Dokument Turtle RDF + Turtle RDF-dokument + документ RDF Turtle + tài liệu RDF Turtle + + + + Softimage PIC image + Imatge PIC de Softimage + Εικόνα Softimage PIC + Softimage PIC image + Imagen PIC de Softimage + Softimage PIC irudia + Image « PIC » de Softimage + Softimage PIC-kép + Imagine PIC de SoftImage + Immagine Softimage PIC + Softimage PIC paveikslas + Softimage PIC-image + Softimage PIC-bilete + Imagem PIC da Softimage + Imagem PIC da Softimage + Slika Softimage PIC + Softimage PIC-bild + зображення PIC Softimage + ảnh PIC Softimage + + + + + + + Qt Markup Language file + Fitxer de llenguatge de marques de les Qt + Αρχείο γλώσσας σήμανσης Qt + Qt Markup Language file + Archivo de lenguaje de marcas de Qt + Qt markatzeko lengoaiako fitxategia + Fichier de langage à balises pour Qt + Qt Markup Language fájl + Le de linguage de marcation de QT + File di linguaggio di contrassegno di Qt + Qt ženklinimo kalbos failas + Qt Markup Language-bestand + Qt-oppmerkingsspråk + Ficheiro na Qt Markup Language + Arquivo da Qt Markup Language + Datoteka označevalnega jezika Qt (QML) + Qt-taggspråkfil + файл мови розмітки Qt + tệp Ngôn ngữ Đánh dấu Qt + + + + + + + + + KConfigXT Configuration Options + Opcions de configuració del KConfigXT + Επιλογές διαμόρφωσης KConfigXT + KConfigXT Configuration Options + Opciones de configuración de KConfigXT + KConfigXT konfiguratzeko aukerak + Options de configuration pour KConfigXT + KConfigXT konfigurációs beállítások + KConfigXT Optiones de Configuration + Opzioni di configurazione KConfigXT + KConfigXT konfigūravimo parinktys + Configuratie-opties van KConfigXT + KConfigXT-oppsett + Opções de Configuração do KConfigXT + Opções de configuração do KConfigXT + Nastavitvene možnosti KConfigXT + KConfigXT-inställningsalternativ + параметри налаштування KConfigXT + các lựa chọn cấu hình KConfigXT + + + + + + + + + + KConfigXT Code Generation Options + Opcions de generació de codi del KConfigXT + Επιλογές γεννήτρια κώδικα KConfigXT + KConfigXT Code Generation Options + Opciones de generación de código de KConfigXT + KConfigXT kodea sortzeko aukerak + Options de génération de code pour KConfigXT + KConfigXT kódgenerálási beállítások + KConfigXT Optiones de Generation de Codice + Opzioni di generazione codice KConfigXT + KConfigXT kodo generavimo parinktys + Codegeneratie-opties van KConfigXT + KConfigXT-kodegeneringsval + Opções de Geração de Código do KConfigXT + Opções de geração de código do KConfigXT + Možnosti ustvarjanja kode KConfigXT + KConfigXT-kodgenereringsalternativ + параметри створення коду KConfigXT + các lựa chọn tạo mã KConfigXT + + + + + + KXMLGUI UI Declaration + Declaració d'IU del KXMLGUI + Δήλωση KXMLGUI UI + KXMLGUI UI Declaration + Declaración de interfaz de usuario de KXMLGUI + KXMLGUI UI deklarazioa + Déclaration d'interface « KXMLGUI » + KXMLGUI UI deklaráció + KXMLGUI UI Declaration + Dichiarazione UI KXMLGUI + KXMLGUI naudotojo sąsajos deklaracija + Declaratie van KXMLGUI UI + KXMLGUI UI-deklarasjon + Declaração de UI do KXMLGUI + Declaração UI do KXMLGUI + Deklaracija uporabniškega vmesnika KXMLGUI + KXMLGUI-användargränssnittsdeklaration + оголошення інтерфейсу KXMLGUI + khai báo UI KXMLGUI + + + + + + + + + + + KNotification Declaration + Declaració del KNotification + Δήλωση KNotification + KNotification Declaration + Declaración de KNotification + KNotification-eko deklarazioa + Déclaration de KNotification + KNotification deklaráció + KNotification Declaration + Dichiarazione KNotification + KNotification deklaracija + Declaratie van KNotification + KNotification-deklarasjon + Declaração do KNotification + Declaração do KNotification + Deklaracija KNotification + KNotification-deklaration + оголошення KNotification + khai báo KNotification + + + + + + KCrash Report + Informe del KCrash + Αναφορά KCrash + KCrash Report + Informe de KCrash + KCrash-eko txostena + Rapport de KCrash + KCrash jelentés + KCrash Reporto + Rapporto di KCrash + KCrash pranešimas + Rapport van KCrash + KCrash-rapport + Relatório do KCrash + Relatório do KCrash + Poročilo KCrash + KCrash-rapport + звіт KCrash + báo cáo KCrash + + + + + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e68ce93 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +remove_definitions(-DQT_NO_CAST_FROM_ASCII) + +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS Widgets) +if(NOT Qt5Widgets_FOUND) + message(STATUS "Qt5Widgets not found, examples will not be built.") + return() +endif() + +add_executable(kdirwatchtest_gui kdirwatchtest_gui.cpp) +target_link_libraries(kdirwatchtest_gui Qt5::Widgets KF5::CoreAddons) + +add_executable(faceicontest faceicontest.cpp) +target_link_libraries(faceicontest Qt5::Widgets KF5::CoreAddons) diff --git a/tests/faceicontest.cpp b/tests/faceicontest.cpp new file mode 100644 index 0000000..720918e --- /dev/null +++ b/tests/faceicontest.cpp @@ -0,0 +1,41 @@ +/* + 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 new file mode 100644 index 0000000..f112adb --- /dev/null +++ b/tests/faceicontest.h @@ -0,0 +1,22 @@ +/* + 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 new file mode 100644 index 0000000..6b9984d --- /dev/null +++ b/tests/kdirwatchtest.cpp @@ -0,0 +1,76 @@ +/* + 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 new file mode 100644 index 0000000..88a2ed4 --- /dev/null +++ b/tests/kdirwatchtest.h @@ -0,0 +1,38 @@ +/* + This file is part of the KDE libraries + + SPDX-FileCopyrightText: 1998 Sven Radej + + 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 new file mode 100644 index 0000000..2fcb7fd --- /dev/null +++ b/tests/kdirwatchtest_gui.cpp @@ -0,0 +1,128 @@ +/* + 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 new file mode 100644 index 0000000..fbe487b --- /dev/null +++ b/tests/kdirwatchtest_gui.h @@ -0,0 +1,40 @@ +// krazy:excludeall=qclasses +/* + 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 new file mode 100644 index 0000000..09fcefe --- /dev/null +++ b/tests/krandomsequencetest.cpp @@ -0,0 +1,87 @@ +/* + 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"); +} -- 2.30.2