From 286be34e5d3f17dff83e64c199b684bc2d1a89bc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aur=C3=A9lien=20COUDERC?= Date: Sun, 5 Jan 2025 11:22:31 +0100 Subject: [PATCH 1/1] Import plasma-activities-stats_6.2.5.orig.tar.xz [dgit import orig plasma-activities-stats_6.2.5.orig.tar.xz] --- .git-blame-ignore-revs | 5 + .gitignore | 25 + .gitlab-ci.yml | 8 + .kde-ci.yml | 9 + .vim-template:cpp | 16 + .vim-template:h | 19 + CMakeLists.txt | 108 ++ LICENSES/CC0-1.0.txt | 121 ++ 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-or-later.txt | 446 +++++++ LICENSES/LGPL-2.1-only.txt | 467 ++++++++ LICENSES/LGPL-3.0-only.txt | 163 +++ LICENSES/LicenseRef-KDE-Accepted-GPL.txt | 12 + LICENSES/LicenseRef-KDE-Accepted-LGPL.txt | 12 + MAINTAINER | 3 + PlasmaActivitiesStatsConfig.cmake.in | 7 + README.developers | 29 + README.md | 29 + TODO | 25 + autotests/CMakeLists.txt | 45 + autotests/QueryTest.cpp | 310 +++++ autotests/QueryTest.h | 46 + autotests/ResultSetQuickCheckTest.cpp | 592 +++++++++ autotests/ResultSetQuickCheckTest.h | 72 ++ autotests/ResultSetTest.cpp | 264 +++++ autotests/ResultSetTest.h | 31 + autotests/ResultWatcherTest.cpp | 92 ++ autotests/ResultWatcherTest.h | 29 + autotests/common/test.cpp | 39 + autotests/common/test.h | 143 +++ autotests/main.cpp | 101 ++ autotests/quickcheck/tables/ResourceInfo.h | 33 + autotests/quickcheck/tables/ResourceLink.h | 42 + .../quickcheck/tables/ResourceScoreCache.h | 59 + autotests/quickcheck/tables/common.h | 171 +++ logo.png | Bin 0 -> 13831 bytes src/CMakeLists.txt | 144 +++ src/activitiessync_p.cpp | 51 + src/activitiessync_p.h | 23 + src/cleaning.cpp | 81 ++ src/cleaning.h | 47 + src/common/database/Database.cpp | 247 ++++ src/common/database/Database.h | 133 +++ .../schema/ResourcesDatabaseSchema.cpp | 178 +++ .../database/schema/ResourcesDatabaseSchema.h | 29 + src/common/dbus/common.h | 25 + ...g.kde.ActivityManager.ResourcesLinking.xml | 49 + ...g.kde.ActivityManager.ResourcesScoring.xml | 45 + src/common/specialvalues.h | 24 + src/libKActivitiesStats.pc.cmake | 12 + src/query.cpp | 244 ++++ src/query.h | 209 ++++ src/resultmodel.cpp | 1055 +++++++++++++++++ src/resultmodel.h | 124 ++ src/resultset.cpp | 575 +++++++++ src/resultset.h | 250 ++++ src/resultset_iterator.cpp | 241 ++++ src/resultwatcher.cpp | 358 ++++++ src/resultwatcher.h | 107 ++ src/terms.cpp | 159 +++ src/terms.h | 271 +++++ src/utils/debug_and_return.h | 31 + src/utils/lazy_val.h | 50 + src/utils/member_matcher.h | 169 +++ src/utils/qsqlquery_iterator.cpp | 17 + src/utils/qsqlquery_iterator.h | 66 ++ src/utils/slide.h | 55 + tests/CMakeLists.txt | 1 + tests/model/CMakeLists.txt | 42 + tests/model/main.cpp | 18 + tests/model/main.qml | 110 ++ tests/model/main.qrc | 6 + tests/model/window.cpp | 301 +++++ tests/model/window.h | 53 + tests/model/window.ui | 475 ++++++++ vim-extrarc | 12 + 78 files changed, 10923 insertions(+) create mode 100644 .git-blame-ignore-revs create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .kde-ci.yml create mode 100644 .vim-template:cpp create mode 100644 .vim-template:h create mode 100644 CMakeLists.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-or-later.txt create mode 100644 LICENSES/LGPL-2.1-only.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 MAINTAINER create mode 100644 PlasmaActivitiesStatsConfig.cmake.in create mode 100644 README.developers create mode 100644 README.md create mode 100644 TODO create mode 100644 autotests/CMakeLists.txt create mode 100644 autotests/QueryTest.cpp create mode 100644 autotests/QueryTest.h create mode 100644 autotests/ResultSetQuickCheckTest.cpp create mode 100644 autotests/ResultSetQuickCheckTest.h create mode 100644 autotests/ResultSetTest.cpp create mode 100644 autotests/ResultSetTest.h create mode 100644 autotests/ResultWatcherTest.cpp create mode 100644 autotests/ResultWatcherTest.h create mode 100644 autotests/common/test.cpp create mode 100644 autotests/common/test.h create mode 100644 autotests/main.cpp create mode 100644 autotests/quickcheck/tables/ResourceInfo.h create mode 100644 autotests/quickcheck/tables/ResourceLink.h create mode 100644 autotests/quickcheck/tables/ResourceScoreCache.h create mode 100644 autotests/quickcheck/tables/common.h create mode 100644 logo.png create mode 100644 src/CMakeLists.txt create mode 100644 src/activitiessync_p.cpp create mode 100644 src/activitiessync_p.h create mode 100644 src/cleaning.cpp create mode 100644 src/cleaning.h create mode 100644 src/common/database/Database.cpp create mode 100644 src/common/database/Database.h create mode 100644 src/common/database/schema/ResourcesDatabaseSchema.cpp create mode 100644 src/common/database/schema/ResourcesDatabaseSchema.h create mode 100644 src/common/dbus/common.h create mode 100644 src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml create mode 100644 src/common/specialvalues.h create mode 100644 src/libKActivitiesStats.pc.cmake create mode 100644 src/query.cpp create mode 100644 src/query.h create mode 100644 src/resultmodel.cpp create mode 100644 src/resultmodel.h create mode 100644 src/resultset.cpp create mode 100644 src/resultset.h create mode 100644 src/resultset_iterator.cpp create mode 100644 src/resultwatcher.cpp create mode 100644 src/resultwatcher.h create mode 100644 src/terms.cpp create mode 100644 src/terms.h create mode 100644 src/utils/debug_and_return.h create mode 100644 src/utils/lazy_val.h create mode 100644 src/utils/member_matcher.h create mode 100644 src/utils/qsqlquery_iterator.cpp create mode 100644 src/utils/qsqlquery_iterator.h create mode 100644 src/utils/slide.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/model/CMakeLists.txt create mode 100644 tests/model/main.cpp create mode 100644 tests/model/main.qml create mode 100644 tests/model/main.qrc create mode 100644 tests/model/window.cpp create mode 100644 tests/model/window.h create mode 100644 tests/model/window.ui create mode 100644 vim-extrarc diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..fdfb2a2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +#clang-format/tidy +d56958d57537355c2c75c9504c1c650d429e0b2a +49283f7eb8f3c66f8710f33c6e464ffefa44b621 +4c5aeeac5428f687764db0ec68833dba800d4469 +d81ba5ab0ad2097da92eb9e64815465a932b16c8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05baf8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.debug +tags +_tests +.videproject/ctags +*swp +*~ +.kdev4 +.cmake-params +.ycm_extra_conf.py +.ycm_extra_conf.pyc +.clang_complete +kactivities.kdev4 +compile_commands.json +/apidocs +GPATH +GRTAGS +GSYMS +GTAGS +.cmake/ +/.clang-format +.clangd +.idea +/cmake-build* +.cache +/build*/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..202fd25 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2020 Volker Krause +# SPDX-License-Identifier: CC0-1.0 + +include: + - project: sysadmin/ci-utilities + file: + - /gitlab-templates/linux-qt6.yml + - /gitlab-templates/freebsd-qt6.yml diff --git a/.kde-ci.yml b/.kde-ci.yml new file mode 100644 index 0000000..ad4064a --- /dev/null +++ b/.kde-ci.yml @@ -0,0 +1,9 @@ +Dependencies: +- 'on': ['Linux', 'FreeBSD'] + 'require': + 'frameworks/extra-cmake-modules': '@latest-kf6' + 'plasma/plasma-activities' : '@same' + +Options: + test-before-installing: True + require-passing-tests-on: [ 'Linux', 'FreeBSD' ] diff --git a/.vim-template:cpp b/.vim-template:cpp new file mode 100644 index 0000000..63f1c20 --- /dev/null +++ b/.vim-template:cpp @@ -0,0 +1,16 @@ +/* + SPDX-FileCopyrightText: %YEAR% %USER% <%MAIL%> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "%FILE%.h" + +namespace KActivities { +namespace Stats { + +%HERE% + +} // namespace Stats +} // namespace KActivities + diff --git a/.vim-template:h b/.vim-template:h new file mode 100644 index 0000000..d5529c3 --- /dev/null +++ b/.vim-template:h @@ -0,0 +1,19 @@ +/* + SPDX-FileCopyrightText: %YEAR% %USER% <%MAIL%> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_%GUARD% +#define KACTIVITIES_STATS_%GUARD% + +namespace KActivities { +namespace Stats { + +%HERE% + +} // namespace Stats +} // namespace KActivities + +#endif // KACTIVITIES_STATS_%GUARD% + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2418369 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,108 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +cmake_minimum_required(VERSION 3.16) + +set(PROJECT_VERSION "6.2.5") +set(PROJECT_VERSION_MAJOR 6) +project(PlasmaActivitiesStats VERSION ${PROJECT_VERSION}) + +set(PROJECT_DEP_VERSION "6.2.5") +set(QT_MIN_VERSION "6.7.0") +set(KF6_MIN_VERSION "6.5.0") +set(KDE_COMPILERSETTINGS_LEVEL "5.82") + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) + +set(KASTATS_CURRENT_ROOT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +include(FeatureSummary) +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDEGitCommitHooks) +include(KDECompilerSettings NO_POLICY_SCOPE) +include(ECMGenerateExportHeader) +include(ECMGenerateHeaders) +include(ECMGeneratePkgConfigFile) +include(ECMAddQch) +include(ECMQtDeclareLoggingCategory) +include(ECMDeprecationSettings) + +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)") + +find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core DBus Sql) + +find_package(KF6Config ${KF6_MIN_VERSION} CONFIG REQUIRED) +find_package(PlasmaActivities ${PROJECT_DEP_VERSION} CONFIG REQUIRED) +find_package(Threads REQUIRED) + +include(CMakePackageConfigHelpers) +include(ECMSetupVersion) + +set(plasmaactivitiesstats_version_header "${CMAKE_CURRENT_BINARY_DIR}/src/plasmaactivitiesstats_version.h") +ecm_setup_version ( + PROJECT + VARIABLE_PREFIX PLASMAACTIVITIESSTATS + VERSION_HEADER ${plasmaactivitiesstats_version_header} + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/PlasmaActivitiesStatsConfigVersion.cmake" + SOVERSION 1 + ) +ecm_set_disabled_deprecation_versions( + QT 6.7.0 + KF 6.4.0 +) + +add_subdirectory(src) +if(BUILD_TESTING) + set(Boost_NO_BOOST_CMAKE ON) + find_package(Boost 1.49) + if (Boost_FOUND) + add_subdirectory(autotests) + endif() + add_subdirectory(tests) +endif(BUILD_TESTING) + +set (CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/PlasmaActivitiesStats") + +if(BUILD_QCH) + ecm_install_qch_export( + TARGETS PlasmaActivitiesStats_QCH + FILE PlasmaActivitiesStatsLibraryQchTargets.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) + set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/PlasmaActivitiesStatsLibraryQchTargets.cmake\")") +endif() + +install( + EXPORT PlasmaActivitiesStatsLibraryTargets + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + FILE PlasmaActivitiesStatsLibraryTargets.cmake + NAMESPACE Plasma:: + ) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/PlasmaActivitiesStatsConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PlasmaActivitiesStatsConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} + ) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/PlasmaActivitiesStatsConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/PlasmaActivitiesStatsConfigVersion.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) + +install( + FILES ${plasmaactivitiesstats_version_header} + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/PlasmaActivitiesStats COMPONENT Devel + ) + +# Write out the features +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) + +kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +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-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-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/MAINTAINER b/MAINTAINER new file mode 100644 index 0000000..728c270 --- /dev/null +++ b/MAINTAINER @@ -0,0 +1,3 @@ + +Current kactivities-stats maintainer is: Ivan Čukić + diff --git a/PlasmaActivitiesStatsConfig.cmake.in b/PlasmaActivitiesStatsConfig.cmake.in new file mode 100644 index 0000000..aec4bf0 --- /dev/null +++ b/PlasmaActivitiesStatsConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt6Core @QT_MIN_VERSION@) + +include("${CMAKE_CURRENT_LIST_DIR}/PlasmaActivitiesStatsLibraryTargets.cmake") +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/README.developers b/README.developers new file mode 100644 index 0000000..8c4c33c --- /dev/null +++ b/README.developers @@ -0,0 +1,29 @@ + +# Commit policy + +Every non-trivial patch must go through the review before it goes into the +master branch. + + https://phabricator.kde.org/ + project: KActivities + +If you don't have an account for identity.kde.org, you can send smaller +patches to the plasma-devel@kde.org mailing list, or (please don't) directly +to the repository maintainer (see MAINTAINER file). + + +# Code policy + +The code needs to follow KDElibs coding style. You can find more information +about the style here: + + http://techbase.kde.org/Policies/Kdelibs_Coding_Style + +Macros in CMakeLists.txt should be lowercase throughout the project, +and indentation should be 3, with the closing parenthesis in the same level +as the content. + +Check out the KF5 policies: + + https://community.kde.org/Frameworks/Policies + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7d3ec9 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# KActivitiesStats + +Library to access the usage statistics data collected by the KDE activity manager. + +## Introduction + +The KActivitiesStats library provides a querying mechanism for the data +that the activity manager collects - which documents hae been opened by +which applications, and what documents have been linked to which activity. +The activity manager also keeps the score for each document which gets +higher when a particular document has been often accessed or kept open +for longer periods of time. This score is also available through the +querying mechanism. + +## Usage + +The library provides the following important classes: + +- `KActivities::Stats::ResultSet` is a low level class that provides a forward iterator to the + list of results that match the specified query +- `KActivities::Stats::ResultWatcher` provides signals when a new resource that matches a query + arrives, or when an existing one is gone (usage statistics cleared or some + for other reason) +- `KActivities::Stats::ResultModel` provides a Qt data model that shows the resources that + match the specified query. This model should be subclassed to teach it + to handle the different resource types that you want to show as the results. + +Queries are defined by the `KActivities::Stats::Query` class using a simple range-like syntax. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..5e5e25b --- /dev/null +++ b/TODO @@ -0,0 +1,25 @@ +src/common/database/schema/ResourcesDatabaseSchema.cpp:109: + TODO: This will require some refactoring after we introduce more databases + +src/utils/member_matcher.h:88: + TODO: Make this work if the arguments are reversed, + or even if both arhuments need to be checked + for the specified member + +src/resultmodel.cpp:841: + TODO: This can add or remove items from the model + +src/resultwatcher.cpp:213: + TODO: See whether it makes sense to have + lastUpdate/firstUpdate here as well + +src/resultset.cpp:259: + TODO: We need to correct the scores based on the time that passed + since the cache was last updated, although, for this query, + scores are not that important. +src/resultset.cpp:300: + TODO: We need to correct the scores based on the time that passed + since the cache was last updated +src/resultset.cpp:335: + TODO: Implement counting of the linked items + diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 index 0000000..b89c0d1 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,45 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (PlasmaActivitiesStatsAutotests) + +find_package (Qt6 REQUIRED NO_MODULE COMPONENTS Test Core DBus Sql) + +if (NOT WIN32) + +add_executable(PlasmaActivitiesStatsTest) + +target_include_directories(PlasmaActivitiesStatsTest PRIVATE + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/autotests + ${CMAKE_BINARY_DIR}/src +) + +target_sources(PlasmaActivitiesStatsTest PRIVATE + main.cpp + QueryTest.cpp + ResultSetTest.cpp + ResultSetQuickCheckTest.cpp + ResultWatcherTest.cpp + + # Generated by macro ecm_qt_declare_logging_category in src/CMakeLists.txt + ${CMAKE_BINARY_DIR}/src/plasma-activities-stats-logsettings.cpp + + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/utils/qsqlquery_iterator.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/Database.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/schema/ResourcesDatabaseSchema.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/autotests/common/test.cpp +) + +target_link_libraries(PlasmaActivitiesStatsTest + PRIVATE + Boost::boost + + Qt6::Core + Qt6::Test + Qt6::DBus + Qt6::Sql + + Plasma::Activities + Plasma::ActivitiesStats +) + +endif () diff --git a/autotests/QueryTest.cpp b/autotests/QueryTest.cpp new file mode 100644 index 0000000..0c09558 --- /dev/null +++ b/autotests/QueryTest.cpp @@ -0,0 +1,310 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "QueryTest.h" + +#include +#include +#include +#include +#include + +#include + +namespace KAStats = KActivities::Stats; +using namespace KAStats; +using namespace KAStats::Terms; + +QueryTest::QueryTest(QObject *parent) + : Test(parent) +{ +} + +void QueryTest::testDefaults() +{ + TEST_CHUNK(QStringLiteral("Testing the term defaults")); + + Query query; + + QCOMPARE(query.selection(), AllResources); + QCOMPARE(query.types(), {QStringLiteral(":any")}); + QCOMPARE(query.agents(), {QStringLiteral(":current")}); + QCOMPARE(query.activities(), {QStringLiteral(":current")}); + QCOMPARE(query.ordering(), HighScoredFirst); +} + +void QueryTest::testDebuggingOutput() +{ + TEST_CHUNK(QStringLiteral("Debugging output for a query")); + + Query query; + + // Testing whether qDebug can be called (compilation check) + qDebug() << "Writing out a query:" << query; +} + +void QueryTest::testDerivationFromDefault() +{ + TEST_CHUNK(QStringLiteral("Testing query derivation from default")) + + Query queryDefault; + auto queryDerived = queryDefault | LinkedResources; + + // queryDefault should not have been modified + QCOMPARE(queryDefault.selection(), AllResources); + QCOMPARE(queryDerived.selection(), LinkedResources); + + // Changing queryDerived back to AllResources, should be == to queryDefault + queryDerived.setSelection(AllResources); + QCOMPARE(queryDefault, queryDerived); +} + +void QueryTest::testDerivationFromCustom() +{ + TEST_CHUNK(QStringLiteral("Testing query derivation from custom")) + + Query queryCustom; + auto queryDerived = queryCustom | LinkedResources; + + // q1 should not have been modified + QCOMPARE(queryCustom.selection(), AllResources); + QCOMPARE(queryDerived.selection(), LinkedResources); + + // Changing queryDerived back to AllResources, should be == to queryDefault + queryDerived.setSelection(AllResources); + QCOMPARE(queryCustom, queryDerived); +} + +void QueryTest::testNormalSyntaxAgentManipulation() +{ + TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Agents")) + + Query query; + query.addAgents(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + + QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + + query.addAgents(QStringList() << QStringLiteral("kwrite")); + + QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate") << QStringLiteral("kwrite")); + + query.clearAgents(); + + QCOMPARE(query.agents(), QStringList() << QStringLiteral(":current")); +} + +void QueryTest::testNormalSyntaxTypeManipulation() +{ + TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Types")) + + Query query; + query.addTypes(QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain")); + + QCOMPARE(query.types(), QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain")); + + query.addTypes(QStringList() << QStringLiteral("text/xml")); + + QCOMPARE(query.types(), QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain") << QStringLiteral("text/xml")); + + query.clearTypes(); + + QCOMPARE(query.types(), QStringList() << QStringLiteral(":any")); +} + +void QueryTest::testNormalSyntaxActivityManipulation() +{ + TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Activities")) + + Query query; + query.addActivities(QStringList() << QStringLiteral("a1") << QStringLiteral("a2")); + + QCOMPARE(query.activities(), QStringList() << QStringLiteral("a1") << QStringLiteral("a2")); + + query.addActivities(QStringList() << QStringLiteral("a3")); + + QCOMPARE(query.activities(), QStringList() << QStringLiteral("a1") << QStringLiteral("a2") << QStringLiteral("a3")); + + query.clearActivities(); + + QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); +} + +void QueryTest::testNormalSyntaxOrderingManipulation() +{ + TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Activities")) + + Query query; + + QCOMPARE(query.ordering(), HighScoredFirst); + + query.setOrdering(RecentlyCreatedFirst); + + QCOMPARE(query.ordering(), RecentlyCreatedFirst); + + query.setOrdering(OrderByUrl); + + QCOMPARE(query.ordering(), OrderByUrl); +} + +void QueryTest::testFancySyntaxBasic() +{ + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, non c++11")) + + auto query = LinkedResources | Type(QStringLiteral("text")) | Type(QStringLiteral("image")) | Agent(QStringLiteral("test")) | RecentlyCreatedFirst; + + QCOMPARE(query.selection(), LinkedResources); + QCOMPARE(query.types(), QStringList() << QStringLiteral("text") << QStringLiteral("image")); + QCOMPARE(query.agents(), QStringList() << QStringLiteral("test")); + QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); + QCOMPARE(query.ordering(), RecentlyCreatedFirst); + + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, c++11")) + + // Testing the fancy c++11 syntax + auto queryCXX11 = LinkedResources | Type{QStringLiteral("text"), QStringLiteral("image")} | Agent{QStringLiteral("test")} | RecentlyCreatedFirst; + + QCOMPARE(query, queryCXX11); +} + +void QueryTest::testFancySyntaxAgentDefinition() +{ + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, agent definition")) + + { + auto query = LinkedResources | OrderByUrl; + QCOMPARE(query.agents(), QStringList() << QStringLiteral(":current")); + } + + { + auto query = LinkedResources | Agent(QStringLiteral("gvim")); + QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim")); + } + + { + auto query = LinkedResources | Agent(QStringLiteral("gvim")) | Agent(QStringLiteral("kate")); + QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + } + + { + auto query = LinkedResources | Agent(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + } +} + +void QueryTest::testFancySyntaxTypeDefinition() +{ + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, type definition")) + + { + auto query = LinkedResources | OrderByUrl; + QCOMPARE(query.types(), QStringList() << QStringLiteral(":any")); + } + + { + auto query = LinkedResources | Type(QStringLiteral("text/plain")); + QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain")); + } + + { + auto query = LinkedResources | Type(QStringLiteral("text/plain")) | Type(QStringLiteral("text/html")); + QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); + } + + { + auto query = LinkedResources | Type(QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); + QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); + } +} + +void QueryTest::testFancySyntaxActivityDefinition() +{ + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, activity definition")) + + { + auto query = LinkedResources | OrderByUrl; + QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); + } + + { + auto query = LinkedResources | Activity(QStringLiteral("gvim")); + QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim")); + } + + { + auto query = LinkedResources | Activity(QStringLiteral("gvim")) | Activity(QStringLiteral("kate")); + QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + } + + { + auto query = LinkedResources | Activity(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); + } +} + +void QueryTest::testFancySyntaxOrderingDefinition() +{ + TEST_CHUNK(QStringLiteral("Testing the fancy syntax, activity definition")) + + { + auto query = LinkedResources | OrderByUrl; + QCOMPARE(query.ordering(), OrderByUrl); + } + + { + auto query = LinkedResources | HighScoredFirst; + QCOMPARE(query.ordering(), HighScoredFirst); + } + + { + auto query = LinkedResources | RecentlyCreatedFirst; + QCOMPARE(query.ordering(), RecentlyCreatedFirst); + } + + { + auto query = LinkedResources | RecentlyCreatedFirst | OrderByUrl; + QCOMPARE(query.ordering(), OrderByUrl); + } + + { + auto query = LinkedResources | RecentlyCreatedFirst | HighScoredFirst; + QCOMPARE(query.ordering(), HighScoredFirst); + } +} + +void QueryTest::testNormalSyntaxDateDefinition() +{ + TEST_CHUNK(QStringLiteral("Testing the Date definition")) + { + auto query = Date::today(); + QCOMPARE(query.start, QDate::currentDate()); + } + { + auto query = Date::yesterday(); + QDate date = QDate::currentDate(); + QCOMPARE(query.start, date.addDays(-1)); + } + { + auto query = Date(QDate::fromString(QStringLiteral("2019-07-25"))); + QCOMPARE(query.start, QDate::fromString(QStringLiteral("2019-07-25"))); + } + { + auto query = Date(QDate::fromString(QStringLiteral("2019-07-24,2019-07-25"))); + QCOMPARE(query.start, QDate::fromString(QStringLiteral("2019-07-24"))); + QCOMPARE(query.end, QDate::fromString(QStringLiteral("2019-07-25"))); + } +} + +void QueryTest::initTestCase() +{ + // CHECK_CONDITION(isActivityManagerRunning, FailIfTrue); +} + +void QueryTest::cleanupTestCase() +{ + Q_EMIT testFinished(); +} + +#include "moc_QueryTest.cpp" diff --git a/autotests/QueryTest.h b/autotests/QueryTest.h new file mode 100644 index 0000000..b0b6271 --- /dev/null +++ b/autotests/QueryTest.h @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef OFFLINETEST_H +#define OFFLINETEST_H + +#include + +#include + +class QueryTest : public Test +{ + Q_OBJECT +public: + QueryTest(QObject *parent = nullptr); + +private Q_SLOTS: + void initTestCase(); + + void testDefaults(); + void testDebuggingOutput(); + + void testDerivationFromDefault(); + void testDerivationFromCustom(); + + void testNormalSyntaxAgentManipulation(); + void testNormalSyntaxTypeManipulation(); + void testNormalSyntaxActivityManipulation(); + void testNormalSyntaxOrderingManipulation(); + void testNormalSyntaxDateDefinition(); + + void testFancySyntaxBasic(); + void testFancySyntaxAgentDefinition(); + void testFancySyntaxTypeDefinition(); + void testFancySyntaxActivityDefinition(); + void testFancySyntaxOrderingDefinition(); + + void cleanupTestCase(); + +private: +}; + +#endif /* OFFLINETEST_H */ diff --git a/autotests/ResultSetQuickCheckTest.cpp b/autotests/ResultSetQuickCheckTest.cpp new file mode 100644 index 0000000..bcf565d --- /dev/null +++ b/autotests/ResultSetQuickCheckTest.cpp @@ -0,0 +1,592 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "ResultSetQuickCheckTest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#define NUMBER_ACTIVITIES 10 +#define NUMBER_AGENTS 10 +#define NUMBER_RESOURCES 50 +#define NUMBER_CACHES 200 + +namespace KAStats = KActivities::Stats; + +static ResultSetQuickCheckTest *instance; + +ResultSetQuickCheckTest::ResultSetQuickCheckTest(QObject *parent) + : Test(parent) + , activities(std::make_unique()) +{ + instance = this; +} + +namespace +{ +QString resourceTitle(const QString &resource) +{ + // We need to find the title + ResourceInfo::Item key; + key.targettedResource = resource; + + auto &infos = instance->resourceInfos; + + auto ri = infos.lower_bound(key); + + return (ri != infos.cend() && ri->targettedResource == resource) ? ri->title : resource; +} + +QString toQString(const ResourceScoreCache::Item &item) +{ + return item.targettedResource + QLatin1Char(':') // + + resourceTitle(item.targettedResource) + QLatin1Char('(') + QString::number(item.cachedScore) + QLatin1Char(')'); +} + +QString toQString(const ResourceLink::Item &item) +{ + return item.targettedResource + QLatin1Char(':') + resourceTitle(item.targettedResource); + // + '(' + QString::number(0/* item.score */) + ')' +} + +QString toQString(const KAStats::ResultSet::Result &item) +{ + return item.resource() + QLatin1Char(':') + item.title() + QLatin1Char('(') + QString::number(item.score()) + QLatin1Char(')'); +} + +bool operator==(const ResourceScoreCache::Item &left, const KAStats::ResultSet::Result &right) +{ + return left.targettedResource == right.resource() && resourceTitle(left.targettedResource) == right.title() + && qFuzzyCompare(left.cachedScore, right.score()); +} + +bool operator==(const ResourceLink::Item &left, const KAStats::ResultSet::Result &right) +{ + return left.targettedResource == right.resource() // + && resourceTitle(left.targettedResource) == right.title(); + // && qFuzzyCompare(left.cachedScore, right.score); +} + +template +void assert_range_equal(const Left &left, const KAStats::ResultSet &right, const char *file, int line) +{ + auto leftIt = left.cbegin(); + auto rightIt = right.cbegin(); + auto leftEnd = left.cend(); + auto rightEnd = right.cend(); + + bool equal = true; + + QString leftLine; + QString rightLine; + + for (; leftIt != leftEnd && rightIt != rightEnd; ++leftIt, ++rightIt) { + auto leftString = toQString(*leftIt); + auto rightString = toQString(*rightIt); + + if (*leftIt == *rightIt) { + rightString.fill(QLatin1Char('.')); + + } else { + equal = false; + } + + int longer = qMax(leftString.length(), rightString.length()); + leftString = leftString.leftJustified(longer); + rightString = rightString.leftJustified(longer, QLatin1Char('.')); + + leftLine += QStringLiteral(" ") + leftString; + rightLine += QStringLiteral(" ") + rightString; + } + + // So far, we are equal, but do we have the same number + // of elements - did we reach the end of both ranges? + if (leftIt != leftEnd) { + for (; leftIt != leftEnd; ++leftIt) { + auto item = toQString(*leftIt); + leftLine += QStringLiteral(" ") + item; + item.fill(QLatin1Char('X')); + rightLine += QStringLiteral(" ") + item; + } + equal = false; + + } else if (rightIt != rightEnd) { + for (; rightIt != rightEnd; ++rightIt) { + auto item = toQString(*leftIt); + rightLine += QStringLiteral(" ") + item; + item.fill(QLatin1Char('X')); + leftLine += QStringLiteral(" ") + item; + } + equal = false; + } + + if (!equal) { + qDebug() << "Ranges differ:\n" + << "MEM: " << leftLine << '\n' + << "SQL: " << rightLine; + QTest::qFail("Results do not match", file, line); + } +} + +#define ASSERT_RANGE_EQUAL(L, R) assert_range_equal(L, R, __FILE__, __LINE__) +} + +//_ Data init +void ResultSetQuickCheckTest::initTestCase() +{ + QString databaseFile; + + int dbArgIndex = QCoreApplication::arguments().indexOf(QStringLiteral("--ResultSetQuickCheckDatabase")); + if (dbArgIndex > 0) { + databaseFile = QCoreApplication::arguments()[dbArgIndex + 1]; + + qDebug() << "Using an existing database: " << databaseFile; + Common::ResourcesDatabaseSchema::overridePath(databaseFile); + + pullFromDatabase(); + + } else { + QTemporaryDir dir(QDir::tempPath() + QStringLiteral("/KActivitiesStatsTest_ResultSetQuickCheckTest_XXXXXX")); + dir.setAutoRemove(false); + + if (!dir.isValid()) { + qFatal("Can not create a temporary directory"); + } + + databaseFile = dir.path() + QStringLiteral("/database"); + + qDebug() << "Creating database in " << databaseFile; + Common::ResourcesDatabaseSchema::overridePath(databaseFile); + + while (activities->serviceStatus() == KActivities::Consumer::Unknown) { + QCoreApplication::processEvents(); + } + + generateActivitiesList(); + generateAgentsList(); + generateTypesList(); + generateResourcesList(); + + generateResourceInfos(); + generateResourceScoreCaches(); + generateResourceLinks(); + + pushToDatabase(); + } + + if (QCoreApplication::arguments().contains(QLatin1String("--show-data"))) { + QString rscs; + for (const auto &rsc : resourceScoreCaches) { + rscs += QLatin1Char('(') + rsc.targettedResource // + + QLatin1Char(',') + rsc.usedActivity // + + QLatin1Char(',') + rsc.initiatingAgent // + + QLatin1Char(',') + QString::number(rsc.cachedScore) + QLatin1Char(')'); + } + + QString ris; + for (const auto &ri : resourceInfos) { + ris += QLatin1Char('(') + ri.targettedResource + QLatin1Char(',') + ri.title + QLatin1Char(',') + ri.mimetype + QLatin1Char(')'); + } + + QString rls; + for (const auto &rl : resourceLinks) { + rls += QLatin1Char('(') + rl.targettedResource // + + QLatin1Char(',') + rl.usedActivity // + + QLatin1Char(',') + rl.initiatingAgent + QLatin1Char(')'); + } + + qDebug() << "\nUsed data: -----------------------------" + << "\nActivities: " << activitiesList << "\nAgents: " << agentsList << "\nTypes: " << typesList << "\nResources: " << resourcesList + << "\n----------------------------------------"; + qDebug() << "\n RSCs: " << rscs; + qDebug() << "\n RIs: " << ris; + qDebug() << "\n RLs: " << rls << "\n----------------------------------------"; + } +} + +void ResultSetQuickCheckTest::generateActivitiesList() +{ + activitiesList = activities->activities(); + + while (activitiesList.size() < NUMBER_ACTIVITIES) { + activitiesList << QUuid::createUuid().toString().mid(1, 36); + } +} + +void ResultSetQuickCheckTest::generateAgentsList() +{ + for (int i = 0; i < NUMBER_AGENTS; ++i) { + agentsList << QStringLiteral("Agent_") + QString::number(i); + } +} + +void ResultSetQuickCheckTest::generateTypesList() +{ + typesList = { + QStringLiteral("application/postscript"), + QStringLiteral("application/pdf"), + QStringLiteral("image/x-psd"), + QStringLiteral("image/x-sgi"), + QStringLiteral("image/x-tga"), + QStringLiteral("image/x-xbitmap"), + QStringLiteral("image/x-xwindowdump"), + QStringLiteral("image/x-xcf"), + QStringLiteral("image/x-compressed-xcf"), + QStringLiteral("image/tiff"), + QStringLiteral("image/jpeg"), + QStringLiteral("image/x-psp"), + QStringLiteral("image/png"), + QStringLiteral("image/x-icon"), + QStringLiteral("image/x-xpixmap"), + QStringLiteral("image/svg+xml"), + QStringLiteral("application/pdf"), + QStringLiteral("image/x-wmf"), + QStringLiteral("image/jp2"), + QStringLiteral("image/jpeg2000"), + QStringLiteral("image/jpx"), + QStringLiteral("image/x-xcursor"), + }; +} + +void ResultSetQuickCheckTest::generateResourcesList() +{ + for (int i = 0; i < NUMBER_RESOURCES; ++i) { + resourcesList << (QStringLiteral("/r") // + + (i < 10 ? QStringLiteral("0") : QString()) + QString::number(i)); + } +} + +void ResultSetQuickCheckTest::generateResourceInfos() +{ + auto *generator = QRandomGenerator::global(); + for (const QString &resource : std::as_const(resourcesList)) { + // We want every n-th or so to be without the title + if (generator->bounded(3)) { + continue; + } + + ResourceInfo::Item ri; + ri.targettedResource = resource; + ri.title = QStringLiteral("Title_") + QString::number(generator->bounded(100)); + ri.mimetype = randItem(typesList); + + resourceInfos.insert(ri); + } +} + +void ResultSetQuickCheckTest::generateResourceScoreCaches() +{ + auto *generator = QRandomGenerator::global(); + for (int i = 0; i < NUMBER_CACHES; ++i) { + ResourceScoreCache::Item rsc; + + rsc.usedActivity = randItem(activitiesList); + rsc.initiatingAgent = randItem(agentsList); + rsc.targettedResource = randItem(resourcesList); + + rsc.cachedScore = generator->bounded(1000); + rsc.firstUpdate = generator->generate(); + rsc.lastUpdate = generator->generate(); + + resourceScoreCaches.insert(rsc); + } +} + +void ResultSetQuickCheckTest::generateResourceLinks() +{ + auto *generator = QRandomGenerator::global(); + for (const QString &resource : std::as_const(resourcesList)) { + // We don't want all the resources to be linked + // to something + if (generator->bounded(2)) { + continue; + } + + ResourceLink::Item rl; + + rl.targettedResource = resource; + rl.usedActivity = randItem(activitiesList); + rl.initiatingAgent = randItem(agentsList); + + resourceLinks.insert(rl); + } +} + +void ResultSetQuickCheckTest::pushToDatabase() +{ + auto database = Common::Database::instance(Common::Database::ResourcesDatabase, Common::Database::ReadWrite); + + Common::ResourcesDatabaseSchema::initSchema(*database); + + // Inserting activities, so that a test can be replicated + database->execQuery(QStringLiteral("CREATE TABLE Activity (activity TEXT)")); + for (const auto &activity : activitiesList) { + database->execQuery(QStringLiteral("INSERT INTO Activity VALUES ('%1')").arg(activity)); + } + + // Inserting agent, so that a test can be replicated + database->execQuery(QStringLiteral("CREATE TABLE Agent (agent TEXT)")); + for (const auto &agent : agentsList) { + database->execQuery(QStringLiteral("INSERT INTO Agent VALUES ('%1')").arg(agent)); + } + + // Inserting types, so that a test can be replicated + database->execQuery(QStringLiteral("CREATE TABLE Type (type TEXT)")); + for (const auto &type : typesList) { + database->execQuery(QStringLiteral("INSERT INTO Type VALUES ('%1')").arg(type)); + } + + // Inserting resources, so that a test can be replicated + database->execQuery(QStringLiteral("CREATE TABLE Resource (resource TEXT)")); + for (const auto &resource : resourcesList) { + database->execQuery(QStringLiteral("INSERT INTO Resource VALUES ('%1')").arg(resource)); + } + + // Inserting resource score caches + qDebug() << "Inserting" << resourceScoreCaches.size() << "items into ResourceScoreCache"; + int i = 0; + + for (const auto &rsc : std::as_const(resourceScoreCaches)) { + std::cerr << '.'; + + if (++i % 10 == 0) { + std::cerr << i; + } + + database->execQuery( // + QStringLiteral("INSERT INTO ResourceScoreCache (" + " usedActivity" + ", initiatingAgent" + ", targettedResource" + ", scoreType" + ", cachedScore" + ", firstUpdate" + ", lastUpdate" + ") VALUES (" + " '%1'" // usedActivity + ", '%2'" // initiatingAgent + ", '%3'" // targettedResource + ", 0 " // scoreType + ", %4 " // cachedScore + ", %5 " // firstUpdate + ", %6 " // lastUpdate + ")") + .arg(rsc.usedActivity, rsc.initiatingAgent, rsc.targettedResource) + .arg(rsc.cachedScore) + .arg(rsc.firstUpdate) + .arg(rsc.lastUpdate)); + } + std::cerr << std::endl; + + // Inserting resource infos + qDebug() << "Inserting" << resourceInfos.size() << "items into ResourceInfo"; + i = 0; + + for (const auto &ri : std::as_const(resourceInfos)) { + std::cerr << '.'; + + if (++i % 10 == 0) { + std::cerr << i; + } + + database->execQuery( // + QStringLiteral("INSERT INTO ResourceInfo (" + " targettedResource" + ", title" + ", mimetype" + ", autoTitle" + ", autoMimetype" + ") VALUES (" + " '%1' " // targettedResource + ", '%2' " // title + ", '%3' " // mimetype + ", 1 " // autoTitle + ", 1 " // autoMimetype + ")") + .arg(ri.targettedResource, ri.title, ri.mimetype)); + } + std::cerr << std::endl; + + // Inserting resource links + qDebug() << "Inserting" << resourceLinks.size() << "items into ResourceLink"; + i = 0; + + for (const auto &rl : std::as_const(resourceLinks)) { + std::cerr << '.'; + + if (++i % 10 == 0) { + std::cerr << i; + } + + database->execQuery( // + QStringLiteral("INSERT INTO ResourceLink (" + " targettedResource" + ", usedActivity" + ", initiatingAgent" + ") VALUES (" + " '%1' " // targettedResource + ", '%2' " // usedActivity + ", '%3' " // initiatingAgent + ")") + .arg(rl.targettedResource, rl.usedActivity, rl.initiatingAgent)); + } + std::cerr << std::endl; +} + +void ResultSetQuickCheckTest::pullFromDatabase() +{ + auto database = Common::Database::instance(Common::Database::ResourcesDatabase, Common::Database::ReadWrite); + + auto activityQuery = database->execQuery(QStringLiteral("SELECT * FROM Activity")); + for (const auto &activity : activityQuery) { + activitiesList << activity[0].toString(); + } + + auto agentQuery = database->execQuery(QStringLiteral("SELECT * FROM Agent")); + for (const auto &agent : agentQuery) { + agentsList << agent[0].toString(); + } + + auto typeQuery = database->execQuery(QStringLiteral("SELECT * FROM Type")); + for (const auto &type : typeQuery) { + typesList << type[0].toString(); + } + + auto resourceQuery = database->execQuery(QStringLiteral("SELECT * FROM Resource")); + for (const auto &resource : resourceQuery) { + resourcesList << resource[0].toString(); + } + + auto rscQuery = database->execQuery(QStringLiteral("SELECT * FROM ResourceScoreCache")); + + for (const auto &rsc : rscQuery) { + ResourceScoreCache::Item item; + item.usedActivity = rsc[QStringLiteral("usedActivity")].toString(); + item.initiatingAgent = rsc[QStringLiteral("initiatingAgent")].toString(); + item.targettedResource = rsc[QStringLiteral("targettedResource")].toString(); + item.cachedScore = rsc[QStringLiteral("cachedScore")].toDouble(); + item.firstUpdate = rsc[QStringLiteral("firstUpdate")].toInt(); + item.lastUpdate = rsc[QStringLiteral("lastUpdate")].toInt(); + resourceScoreCaches.insert(item); + } + + auto riQuery = database->execQuery(QStringLiteral("SELECT * FROM ResourceInfo")); + + for (const auto &ri : riQuery) { + ResourceInfo::Item item; + item.targettedResource = ri[QStringLiteral("targettedResource")].toString(); + item.title = ri[QStringLiteral("title")].toString(); + item.mimetype = ri[QStringLiteral("mimetype")].toString(); + resourceInfos.insert(item); + } + + auto rlQuery = database->execQuery(QStringLiteral("SELECT * FROM ResourceLink")); + + for (const auto &rl : rlQuery) { + ResourceLink::Item item; + item.targettedResource = rl[QStringLiteral("targettedResource")].toString(); + item.usedActivity = rl[QStringLiteral("usedActivity")].toString(); + item.initiatingAgent = rl[QStringLiteral("initiatingAgent")].toString(); + resourceLinks.insert(item); + } +} + +void ResultSetQuickCheckTest::cleanupTestCase() +{ + Q_EMIT testFinished(); +} + +QString ResultSetQuickCheckTest::randItem(const QStringList &choices) const +{ + return choices[QRandomGenerator::global()->bounded(choices.size())]; +} +//^ Data init + +void ResultSetQuickCheckTest::testUsedResourcesForAgents() +{ + using namespace KAStats; + using namespace KAStats::Terms; + using boost::sort; + using boost::adaptors::filtered; + + for (const auto &agent : std::as_const(agentsList)) { + auto memItems = ResourceScoreCache::groupByResource(resourceScoreCaches | filtered(ResourceScoreCache::initiatingAgent() == agent)); + + auto baseTerm = UsedResources | Agent{agent} | Activity::any(); + +#define ORDERING_TEST(Column, Dir, OrderFlag) \ + { \ + sort(memItems, ResourceScoreCache::Column().Dir() | ResourceScoreCache::targettedResource().asc()); \ + ResultSet dbItems = baseTerm | OrderFlag | Limit(100); \ + ASSERT_RANGE_EQUAL(memItems, dbItems); \ + } + + ORDERING_TEST(targettedResource, asc, OrderByUrl) + ORDERING_TEST(cachedScore, desc, HighScoredFirst); + ORDERING_TEST(lastUpdate, desc, RecentlyUsedFirst); + ORDERING_TEST(firstUpdate, desc, RecentlyCreatedFirst); + +#undef ORDERING_TEST + } +} + +void ResultSetQuickCheckTest::testUsedResourcesForActivities() +{ +} + +void ResultSetQuickCheckTest::testLinkedResourcesForAgents() +{ + using namespace KAStats; + using namespace KAStats::Terms; + using boost::sort; + using boost::adaptors::filtered; + + for (const auto &agent : std::as_const(agentsList)) { + auto memItems = ResourceLink::groupByResource(resourceLinks | filtered(ResourceLink::initiatingAgent() == agent)); + + auto baseTerm = LinkedResources | Agent{agent} | Activity::any(); + +#define ORDERING_TEST(Column, Dir, OrderFlag) \ + { \ + sort(memItems, ResourceLink::Column().Dir() | ResourceLink::targettedResource().asc()); \ + ResultSet dbItems = baseTerm | OrderFlag; \ + ASSERT_RANGE_EQUAL(memItems, dbItems); \ + } + + ORDERING_TEST(targettedResource, asc, OrderByUrl) + // ORDERING_TEST(cachedScore, desc, HighScoredFirst); + // ORDERING_TEST(lastUpdate, desc, RecentlyUsedFirst); + // ORDERING_TEST(firstUpdate, desc, RecentlyCreatedFirst); + +#undef ORDERING_TEST + } +} + +#include "moc_ResultSetQuickCheckTest.cpp" + +// vim: set foldmethod=marker: diff --git a/autotests/ResultSetQuickCheckTest.h b/autotests/ResultSetQuickCheckTest.h new file mode 100644 index 0000000..74a0a4a --- /dev/null +++ b/autotests/ResultSetQuickCheckTest.h @@ -0,0 +1,72 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESULTSET_QUICKCHECK_TEST_H +#define RESULTSET_QUICKCHECK_TEST_H + +#include + +#include +#include + +#include + +#include "quickcheck/tables/ResourceInfo.h" +#include "quickcheck/tables/ResourceLink.h" +#include "quickcheck/tables/ResourceScoreCache.h" + +class ResultSetQuickCheckTest : public Test +{ + Q_OBJECT +public: + ResultSetQuickCheckTest(QObject *parent = nullptr); + +private Q_SLOTS: + void initTestCase(); + + void testUsedResourcesForAgents(); + void testUsedResourcesForActivities(); + + void testLinkedResourcesForAgents(); + + void cleanupTestCase(); + +public: + std::unique_ptr activities; + + struct PrimaryKeyOrder { + template + bool operator()(const T &left, const T &right) const + { + return left.primaryKey() < right.primaryKey(); + } + }; + + TABLE(ResourceScoreCache) resourceScoreCaches; + TABLE(ResourceInfo) resourceInfos; + TABLE(ResourceLink) resourceLinks; + + QString randItem(const QStringList &choices) const; + + QStringList activitiesList; + QStringList agentsList; + QStringList typesList; + QStringList resourcesList; + + void generateActivitiesList(); + void generateAgentsList(); + void generateTypesList(); + void generateResourcesList(); + + void generateResourceInfos(); + void generateResourceScoreCaches(); + void generateResourceLinks(); + + void pushToDatabase(); + void pullFromDatabase(); +}; + +#endif /* RESULTSET_QUICKCHECK_TEST_H */ diff --git a/autotests/ResultSetTest.cpp b/autotests/ResultSetTest.cpp new file mode 100644 index 0000000..ba03556 --- /dev/null +++ b/autotests/ResultSetTest.cpp @@ -0,0 +1,264 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "ResultSetTest.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +namespace KAStats = KActivities::Stats; + +ResultSetTest::ResultSetTest(QObject *parent) + : Test(parent) +{ +} + +namespace +{ +QString getBarredUri(QString lhs, const KAStats::ResultSet::Result &result) +{ + return lhs + result.resource() + QStringLiteral("|"); +} + +QString concatenateResults(const KAStats::ResultSet &results) +{ + return std::accumulate(results.cbegin(), results.cend(), QStringLiteral("|"), getBarredUri); +} +} + +void ResultSetTest::testConcat() +{ + using namespace KAStats; + using namespace KAStats::Terms; + + TEST_CHUNK(QStringLiteral("Checking barred function")) + { + ResultSet::Result r; + r.setResource(QStringLiteral("quack")); + + QCOMPARE(getBarredUri(QString(), KAStats::ResultSet::Result{}), QStringLiteral("|")); + QCOMPARE(getBarredUri(QString(), r), QStringLiteral("quack|")); + r.setResource(QStringLiteral("http://www.kde.org")); + QVERIFY(getBarredUri(QString(), r).startsWith(QStringLiteral("http://"))); + QVERIFY(getBarredUri(QString(), r).endsWith(QStringLiteral("org|"))); + } + + TEST_CHUNK(QStringLiteral("Checking empty concatenation")) + { + ResultSet rs(KActivities::Stats::Terms::LinkedResources); + // There is no "count" on a resultset + unsigned int rs_count = 0; + for (const auto &r : rs) { + Q_UNUSED(r); + rs_count++; + } + QCOMPARE(rs_count, 0); // It's empty + QCOMPARE(concatenateResults(rs), QStringLiteral("|")); // 0 results pasted after a "|" to start + } + + TEST_CHUNK(QStringLiteral("Checking non-empty concatenation")) + { + ResultSet rs(UsedResources | HighScoredFirst | Agent{QStringLiteral("gvim")}); + // There is no "count" on a resultset + unsigned int rs_count = 0; + for (const auto &r : rs) { + Q_UNUSED(r); + rs_count++; + } + QCOMPARE(rs_count, 5); // Not empty (see data inserted into ResourceLink, in initTestCase() + + const QString cat = concatenateResults(rs); + QCOMPARE(cat.count(QStringLiteral("|")), 6); // 5 items, plus 1 to start + } +} + +void ResultSetTest::testLinkedResources() +{ + using namespace KAStats; + using namespace KAStats::Terms; + + TEST_CHUNK(QStringLiteral("Getting the linked resources alphabetically")) + { + ResultSet result(LinkedResources | Agent{QStringLiteral("gvim")} | Activity{QStringLiteral("activity1")}); + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/mid1_a1")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/mid2_a1")); + } +} + +void ResultSetTest::testUsedResources() +{ + using namespace KAStats; + using namespace KAStats::Terms; + + qDebug() << "Agent: " << QCoreApplication::instance()->applicationName(); + + TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, default query")) + { + ResultSet result(UsedResources); + + qDebug() << "-----------------------------"; + for (const auto &item : result) { + qDebug() << "Item: " << item.resource(); + } + qDebug() << "-----------------------------"; + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high5_act1_kast")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high7_act1_kast")); + QCOMPARE(result.at(2).resource(), QStringLiteral("/path/high8_act1_kast")); + + // END! + QCOMPARE(result.at(3).resource(), QString()); + + // Testing whether range works + QCOMPARE(QStringLiteral("|/path/high5_act1_kast|/path/high7_act1_kast|/path/high8_act1_kast|"), // + concatenateResults(result)); + } + + TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, gvim")) + { + ResultSet result(UsedResources | HighScoredFirst | Agent{QStringLiteral("gvim")}); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high4_act1_gvim")); + } + + TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, global agent")) + { + ResultSet result(UsedResources | HighScoredFirst | Agent::global()); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/mid6_act1_glob")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/mid7_act1_glob")); + QCOMPARE(result.at(2).resource(), QStringLiteral("/path/mid8_act1_glob")); + } + + TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, any agent")) + { + ResultSet result(UsedResources | HighScoredFirst | Agent::any() | Activity::any()); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high2_act2_kate")); + QCOMPARE(result.at(2).resource(), QStringLiteral("/path/high3_act1_kate")); + } + + TEST_CHUNK(QStringLiteral("Getting the used resources filter by Date")) + { + ResultSet result(UsedResources | HighScoredFirst | Agent::any() | Activity::any() | Date::fromString(QStringLiteral("2015-01-15"))); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); + } + + TEST_CHUNK(QStringLiteral("Getting the used resources filter by Date range")) + { + ResultSet result(UsedResources | HighScoredFirst | Agent::any() | Activity::any() | Date::fromString(QStringLiteral("2015-01-14,2015-01-15"))); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high2_act2_kate")); + } +} + +void ResultSetTest::initTestCase() +{ + QTemporaryDir dir(QDir::tempPath() + QStringLiteral("/KActivitiesStatsTest_ResultSetTest_XXXXXX")); + dir.setAutoRemove(false); + + if (!dir.isValid()) { + qFatal("Can not create a temporary directory"); + } + + const QString databaseFile = dir.path() + QStringLiteral("/database"); + + Common::ResourcesDatabaseSchema::overridePath(databaseFile); + qDebug() << "Creating database in " << databaseFile; + + // Creating the database, and pushing some dummy data into it + auto database = Common::Database::instance(Common::Database::ResourcesDatabase, Common::Database::ReadWrite); + + Common::ResourcesDatabaseSchema::initSchema(*database); + + database->execQuery(QStringLiteral( + "INSERT INTO ResourceScoreCache (usedActivity, initiatingAgent, targettedResource, scoreType, cachedScore, firstUpdate, lastUpdate) VALUES " + + " ('activity1' , 'gvim' , '/path/high1_act1_gvim' , '0' , '800' , '-1' , '1421446599')" + " , ('activity2' , 'kate' , '/path/high2_act2_kate' , '0' , '700' , '-1' , '1421439442')" + " , ('activity1' , 'kate' , '/path/high3_act1_kate' , '0' , '600' , '-1' , '1421439442')" + " , ('activity1' , 'gvim' , '/path/high4_act1_gvim' , '0' , '500' , '-1' , '1421446488')" + " , ('activity1' , 'KActivitiesStatsTest' , '/path/high5_act1_kast' , '0' , '400' , '-1' , '1421446599')" + " , ('activity2' , 'KActivitiesStatsTest' , '/path/high6_act2_kast' , '0' , '300' , '-1' , '1421439442')" + " , ('activity1' , 'KActivitiesStatsTest' , '/path/high7_act1_kast' , '0' , '200' , '-1' , '1421439442')" + " , ('activity1' , 'KActivitiesStatsTest' , '/path/high8_act1_kast' , '0' , '100' , '-1' , '1421446488')" + + " , ('activity1' , 'gvim' , '/path/mid1_act1_gvim' , '0' , '17' , '-1' , '1421433419')" + " , ('activity1' , 'gvim' , '/path/mid2_act1_gvim' , '0' , '54' , '-1' , '1421431630')" + " , ('activity2' , 'gvim' , '/path/mid3_act2_gvim' , '0' , '8' , '-1' , '1421433172')" + " , ('activity2' , 'gvim' , '/path/mid4_act2_gvim' , '0' , '8' , '-1' , '1421432545')" + " , ('activity2' , 'gvim' , '/path/mid5_act2_gvim' , '0' , '79' , '-1' , '1421439118')" + " , ('activity1' , ':global' , '/path/mid6_act1_glob' , '0' , '20' , '-1' , '1421439331')" + " , ('activity1' , ':global' , '/path/mid7_act1_glob' , '0' , '8' , '-1' , '0')" + " , ('activity1' , ':global' , '/path/mid8_act1_glob' , '0' , '7' , '-1' , '1421432617')" + + " , ('activity1' , 'gvim' , '/path/low3_act1_gvim' , '0' , '6' , '-1' , '1421434704')" + " , ('activity1' , 'kate' , '/path/low2_act1_kate' , '0' , '3' , '-1' , '1421433266')" + " , ('activity1' , 'kate' , '/path/low1_act1_kate' , '0' , '2' , '-1' , '1421433254')")); + + database->execQuery( + QStringLiteral("INSERT INTO ResourceEvent (usedActivity, initiatingAgent, targettedResource, start, end ) VALUES" + // 15 january 2015 + " ('activity1' , 'gvim' , '/path/high1_act1_gvim' , '1421345799', '1421345799')" + // 14 january 2015 + " , ('activity2' , 'kate' , '/path/high2_act2_kate' , '1421259377', '1421259377')")); + + database->execQuery( + QStringLiteral("INSERT INTO ResourceInfo (targettedResource, title, mimetype, autoTitle, autoMimetype) VALUES" + "('/path/high1_act1_gvim', 'high1_act1_gvim', 'text/plain', 1, 1 ) ," + "('/path/high2_act2_kate', 'high2_act2_kate', 'text/plain', 1, 1 )")); + + // Renaming the activity1 to the current acitivty + KActivities::Consumer kamd; + + while (kamd.serviceStatus() == KActivities::Consumer::Unknown) { + QCoreApplication::processEvents(); + } + + database->execQuery(QStringLiteral("UPDATE ResourceScoreCache SET usedActivity = '") + kamd.currentActivity() + + QStringLiteral("' WHERE usedActivity = 'activity1'")); + + database->execQuery(QStringLiteral("UPDATE ResourceEvent SET usedActivity = '") + kamd.currentActivity() + + QStringLiteral("' WHERE usedActivity = 'activity1'")); + + database->execQuery( + QStringLiteral("INSERT INTO ResourceLink (usedActivity, initiatingAgent, targettedResource) VALUES " + + "('activity1' , 'gvim' , '/path/mid1_a1')" + ", ('activity1' , 'gvim' , '/path/mid2_a1')" + ", ('activity2' , 'gvim' , '/path/mid3_a2')" + ", ('activity2' , 'gvim' , '/path/mid4_a2')" + ", ('activity2' , 'gvim' , '/path/link5_a2')" + ", ('activity1' , 'kate' , '/path/link6_a1')" + ", ('activity1' , 'kate' , '/path/link7_a1')" + ", ('activity1' , 'kate' , '/path/link8_a1')") + + ); +} + +void ResultSetTest::cleanupTestCase() +{ + Q_EMIT testFinished(); +} + +#include "moc_ResultSetTest.cpp" diff --git a/autotests/ResultSetTest.h b/autotests/ResultSetTest.h new file mode 100644 index 0000000..80c58ca --- /dev/null +++ b/autotests/ResultSetTest.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESULTSETTEST_H +#define RESULTSETTEST_H + +#include + +#include +#include + +class ResultSetTest : public Test +{ + Q_OBJECT +public: + ResultSetTest(QObject *parent = nullptr); + +private Q_SLOTS: + void initTestCase(); + + void testConcat(); ///< Tests test-implementation-details + void testLinkedResources(); + void testUsedResources(); + + void cleanupTestCase(); +}; + +#endif /* RESULTSETTEST_H */ diff --git a/autotests/ResultWatcherTest.cpp b/autotests/ResultWatcherTest.cpp new file mode 100644 index 0000000..ce6069a --- /dev/null +++ b/autotests/ResultWatcherTest.cpp @@ -0,0 +1,92 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "ResultWatcherTest.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace KAStats = KActivities::Stats; + +ResultWatcherTest::ResultWatcherTest(QObject *parent) + : Test(parent) +{ +} + +namespace +{ +inline void liveSleep(int seconds) +{ + qDebug() << "Sleeping for " << seconds << " seconds"; + auto start = QTime::currentTime(); + while (start.secsTo(QTime::currentTime()) < seconds) { + QCoreApplication::processEvents(); + } +} + +#define CHECK_SIGNAL_RESULT(OBJ, SIGN, SECS, TESTARGS, TESTBODY) \ + { \ + QObject context; \ + bool executed = false; \ + \ + QObject::connect(OBJ, SIGN, &context, [&] TESTARGS { \ + TESTBODY; \ + executed = true; \ + qDebug() << "Signal processed"; \ + }); \ + \ + qDebug() << "Waiting for the signal at most " << SECS << " seconds"; \ + auto start = QTime::currentTime(); \ + while (start.secsTo(QTime::currentTime()) < SECS && !executed) { \ + QCoreApplication::processEvents(); \ + } \ + QCOMPARE(executed, true); \ + } +} + +void ResultWatcherTest::testLinkedResources() +{ + using namespace KAStats; + using namespace KAStats::Terms; + + KAStats::ResultWatcher watcher(LinkedResources | Agent::global() | Activity::any()); + + watcher.linkToActivity(QUrl(QStringLiteral("test://link1")), Activity::current()); + + // A signal should arrive soon, waiting for 5 seconds at most + CHECK_SIGNAL_RESULT(&watcher, &KAStats::ResultWatcher::resultLinked, 5, (const QString &uri), QCOMPARE(QStringLiteral("test://link1"), uri)); + + watcher.unlinkFromActivity(QUrl(QStringLiteral("test://link1")), Activity::current()); + + // A signal should arrive soon, waiting for 5 seconds at most + CHECK_SIGNAL_RESULT(&watcher, &KAStats::ResultWatcher::resultUnlinked, 5, (const QString &uri), QCOMPARE(QStringLiteral("test://link1"), uri)); +} + +void ResultWatcherTest::initTestCase() +{ +} + +void ResultWatcherTest::cleanupTestCase() +{ + Q_EMIT testFinished(); +} + +#include "moc_ResultWatcherTest.cpp" diff --git a/autotests/ResultWatcherTest.h b/autotests/ResultWatcherTest.h new file mode 100644 index 0000000..a5517c5 --- /dev/null +++ b/autotests/ResultWatcherTest.h @@ -0,0 +1,29 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESULTWATCHERTEST_H +#define RESULTWATCHERTEST_H + +#include + +#include +#include + +class ResultWatcherTest : public Test +{ + Q_OBJECT +public: + ResultWatcherTest(QObject *parent = nullptr); + +private Q_SLOTS: + void initTestCase(); + + void testLinkedResources(); + + void cleanupTestCase(); +}; + +#endif /* RESULTWATCHERTEST_H */ diff --git a/autotests/common/test.cpp b/autotests/common/test.cpp new file mode 100644 index 0000000..5d54d5e --- /dev/null +++ b/autotests/common/test.cpp @@ -0,0 +1,39 @@ +/* + SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "test.h" + +#include +#include + +#include "common/dbus/common.h" + +Test::Test(QObject *parent) + : QObject(parent) +{ +} + +bool Test::inEmptySession() +{ + const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames(); + + for (const QString &service : services) { + bool kdeServiceAndNotKAMD = service.startsWith(QStringLiteral("org.kde")) && service != KAMD_DBUS_SERVICE; + + if (kdeServiceAndNotKAMD) { + return false; + } + } + + return true; +} + +bool Test::isActivityManagerRunning() +{ + return QDBusConnection::sessionBus().interface()->isServiceRegistered(KAMD_DBUS_SERVICE); +} + +#include "moc_test.cpp" diff --git a/autotests/common/test.h b/autotests/common/test.h new file mode 100644 index 0000000..5cb8394 --- /dev/null +++ b/autotests/common/test.h @@ -0,0 +1,143 @@ +/* + SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef COMMON_TEST_H +#define COMMON_TEST_H + +#include +#include +#include +#include +#include +#include + +class Test : public QObject +{ + Q_OBJECT +public: + Test(QObject *parent = nullptr); + +protected: + enum WhenToFail { + DontFail = 0, + FailIfTrue = 1, + FailIfFalse = 2, + }; + + template + void continue_future(const QFuture<_ReturnType> &future, _Continuation &&continuation) + { + if (!future.isFinished()) { + auto watcher = new QFutureWatcher(); + QObject::connect( + watcher, + &QFutureWatcherBase::finished, + watcher, + [=] { + continuation(watcher->result()); + watcher->deleteLater(); + }, + Qt::QueuedConnection); + + watcher->setFuture(future); + + } else { + continuation(future.result()); + } + } + + template + void continue_future(const QFuture &future, _Continuation &&continuation) + { + if (!future.isFinished()) { + auto watcher = new QFutureWatcher(); + QObject::connect( + watcher, + &QFutureWatcherBase::finished, + watcher, + [=] { + continuation(); + watcher->deleteLater(); + }, + Qt::QueuedConnection); + + watcher->setFuture(future); + + } else { + continuation(); + } + } + + template + static inline void wait_until(T condition, const char *msg, int msecs = 300) + { + auto start = QTime::currentTime(); + + while (!condition()) { + QCoreApplication::processEvents(); + + auto now = QTime::currentTime(); + QVERIFY2(start.msecsTo(now) < msecs, msg); + if (start.msecsTo(now) >= msecs) + break; + } + } + +#define TEST_WAIT_UNTIL(C) \ + wait_until( \ + [&]() -> bool { \ + return C; \ + }, \ + "Timeout waiting for: " #C); +#define TEST_WAIT_UNTIL_WITH_TIMEOUT(C, T) \ + wait_until( \ + [&]() -> bool { \ + return C; \ + }, \ + "Timeout waiting for: " #C, \ + T); + + template + static bool check(T what, WhenToFail wtf = DontFail, const char *msg = nullptr) + { + bool result = what(); + + if ((wtf == FailIfTrue && result) || (wtf == FailIfFalse && !result)) { + qFatal( + "\n" + "\n" + "!!! > \n" + "!!! > %s\n" + "!!! > \n", + msg); + } + + return result; + } + + static bool inEmptySession(); + static bool isActivityManagerRunning(); + +Q_SIGNALS: + void testFinished(); +}; + +#define CHECK_CONDITION(A, B) check(A, B, #A " raised " #B) + +// Pretty print +#include + +#if defined(Q_NO_DEBUG) || !defined(Q_OS_LINUX) +#define TEST_CHUNK(Name) +#else +inline void _test_chunk(const QString &message) +{ + std::cerr << '\n' << message.toStdString() << "\n" << std::string(message.length(), '-') << '\n'; +} +#define TEST_CHUNK(Name) _test_chunk(Name); +#endif + +#endif /* TEST_H */ diff --git a/autotests/main.cpp b/autotests/main.cpp new file mode 100644 index 0000000..653efb9 --- /dev/null +++ b/autotests/main.cpp @@ -0,0 +1,101 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include +#include + +#include + +#include "QueryTest.h" +#include "ResultSetQuickCheckTest.h" +#include "ResultSetTest.h" +#include "ResultWatcherTest.h" + +class TestRunner : public QObject +{ +public: + TestRunner() + : m_nextToStart(0) + { + } + + TestRunner &addTest(Test *test) + { + if (m_nextToStart == 0) { + m_tests << test; + } + return *this; + } + + TestRunner &operator<<(Test *test) + { + addTest(test); + return *this; + } + + void start() + { + if (m_nextToStart) { + return; + } + + if (m_tests.size() == 0) { + // We do not have a QCoreApplication here, calling system exit + ::exit(0); + return; + } + + next(); + } + +private: + void next() + { + if (m_nextToStart >= m_tests.size()) { + QCoreApplication::exit(0); + return; + } + + Test *test = m_tests[m_nextToStart++]; + + QObject::connect(test, &Test::testFinished, this, &TestRunner::next, Qt::QueuedConnection); + + QTest::qExec(test); + } + +private: + QList m_tests; + int m_nextToStart; +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + TestRunner &runner = *(new TestRunner()); + + qDebug() << app.arguments(); + + bool all = (app.arguments().size() <= 1); + +#define ADD_TEST(TestName) \ + qDebug() << "Test " << #TestName << " is enabled " << (all || app.arguments().contains(QLatin1String(#TestName))); \ + if (all || app.arguments().contains(QLatin1String(#TestName))) { \ + runner << new TestName##Test(); \ + } + + ADD_TEST(Query) + ADD_TEST(ResultSet) + ADD_TEST(ResultSetQuickCheck) + ADD_TEST(ResultWatcher) + + runner.start(); + +#undef ADD_TEST + return app.exec(); + // QTest::qExec(&tc, argc, argv); +} diff --git a/autotests/quickcheck/tables/ResourceInfo.h b/autotests/quickcheck/tables/ResourceInfo.h new file mode 100644 index 0000000..0986981 --- /dev/null +++ b/autotests/quickcheck/tables/ResourceInfo.h @@ -0,0 +1,33 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESOURCEINFO_TABLE_H +#define RESOURCEINFO_TABLE_H + +#include + +#include "common.h" + +namespace ResourceInfo +{ +struct Item { + QString targettedResource; + QString title; + QString mimetype; + + const QString &primaryKey() const + { + return targettedResource; + } +}; + +DECL_COLUMN(QString, targettedResource) +DECL_COLUMN(QString, title) +DECL_COLUMN(QString, mimetype) + +} + +#endif // RESOURCEINFO_TABLE_H diff --git a/autotests/quickcheck/tables/ResourceLink.h b/autotests/quickcheck/tables/ResourceLink.h new file mode 100644 index 0000000..38cbdba --- /dev/null +++ b/autotests/quickcheck/tables/ResourceLink.h @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESOURCELINK_TABLE_H +#define RESOURCELINK_TABLE_H + +#include + +#include "common.h" + +namespace ResourceLink +{ +struct Item { + QString usedActivity; + QString initiatingAgent; + QString targettedResource; + + inline std::tuple primaryKey() const + { + return std::tie(targettedResource, usedActivity, initiatingAgent); + } +}; + +DECL_COLUMN(QString, usedActivity) +DECL_COLUMN(QString, initiatingAgent) +DECL_COLUMN(QString, targettedResource) + +template +inline std::vector groupByResource(const Range &range) +{ + return groupBy(range, &Item::targettedResource, [](Item &acc, const Item &item) { + acc.usedActivity += item.usedActivity + QLatin1Char(' '); + acc.initiatingAgent += item.initiatingAgent + QLatin1Char(' '); + }); +} + +} // namespace ResourceLink + +#endif // RESOURCELINK_TABLE_H diff --git a/autotests/quickcheck/tables/ResourceScoreCache.h b/autotests/quickcheck/tables/ResourceScoreCache.h new file mode 100644 index 0000000..00275a0 --- /dev/null +++ b/autotests/quickcheck/tables/ResourceScoreCache.h @@ -0,0 +1,59 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESOURCESCORECACHE_TABLE_H +#define RESOURCESCORECACHE_TABLE_H + +#include + +#include + +#include "common.h" + +namespace ResourceScoreCache +{ +struct Item { + QString usedActivity; + QString initiatingAgent; + QString targettedResource; + + double cachedScore; + int firstUpdate; + int lastUpdate; + + // defining the primary key + + inline std::tuple primaryKey() const + { + return std::tie(targettedResource, usedActivity, initiatingAgent); + } +}; + +DECL_COLUMN(QString, usedActivity) +DECL_COLUMN(QString, initiatingAgent) +DECL_COLUMN(QString, targettedResource) + +DECL_COLUMN(double, cachedScore) +DECL_COLUMN(int, lastUpdate) +DECL_COLUMN(int, firstUpdate) + +template +inline std::vector groupByResource(const Range &range) +{ + return groupBy(range, &Item::targettedResource, [](Item &acc, const Item &item) { + acc.cachedScore += item.cachedScore; + if (acc.lastUpdate < item.lastUpdate) { + acc.lastUpdate = item.lastUpdate; + } + if (acc.firstUpdate > item.firstUpdate) { + acc.firstUpdate = item.firstUpdate; + } + }); +} + +} + +#endif // RESOURCESCORECACHE_TABLE_H diff --git a/autotests/quickcheck/tables/common.h b/autotests/quickcheck/tables/common.h new file mode 100644 index 0000000..54682a1 --- /dev/null +++ b/autotests/quickcheck/tables/common.h @@ -0,0 +1,171 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef QUICKCHECK_DATABASE_COMMON_H +#define QUICKCHECK_DATABASE_COMMON_H + +struct PrimaryKeyOrdering { + template + bool operator()(const T &left, const T &right) const + { + return left.primaryKey() < right.primaryKey(); + } +}; + +#define TABLE(Table) std::set + +#define DECL_COLUMN(ColumnType, ColumnName) \ + inline Column ColumnName() \ + { \ + return Column(&Item::ColumnName); \ + } + +template +class Column +{ + typedef Column ThisType; + +public: + Column(const ColumnType Type::*memberptr) + : memberptr(memberptr) + { + } + + //_ Column comparator functor + template + class CompositeComparator + { + public: + CompositeComparator(Comp1 comp1, Comp2 comp2) + : comp1(comp1) + , comp2(comp2) + { + } + + inline bool operator()(const Type &left, const Type &right) const + { + return comp1(left, right) ? true : comp1(right, left) ? false : comp2(left, right); + } + + private: + const Comp1 comp1; + const Comp2 comp2; + }; + + class Comparator + { + public: + Comparator(const ColumnType Type::*memberptr, bool invert) + : memberptr(memberptr) + , invert(invert) + { + } + + inline bool operator()(const Type &left, const Type &right) const + { + return (invert) ? right.*memberptr < left.*memberptr // + : left.*memberptr < right.*memberptr; + } + + template + CompositeComparator operator|(const Comp2 &comp2) + { + return CompositeComparator(*this, comp2); + } + + private: + const ColumnType Type::*memberptr; + const bool invert; + }; + //^ + + inline Comparator asc() const + { + return Comparator(memberptr, false); + } + + inline Comparator desc() const + { + return Comparator(memberptr, true); + } + + //_ Column filtering functor + enum ComparisonOperation { + Less, + LessOrEqual, + Equal, + GreaterOrEqual, + Greater, + }; + + template + class Filterer + { + public: + Filterer(const ColumnType Type::*memberptr, ComparisonOperation comparison, const T &value) + : memberptr(memberptr) + , comparison(comparison) + , value(value) + { + } + + inline bool operator()(const Type &item) const + { + return comparison == Less ? item.*memberptr < value + : comparison == LessOrEqual ? item.*memberptr <= value + : comparison == Equal ? item.*memberptr == value + : comparison == GreaterOrEqual ? item.*memberptr >= value + : comparison == Greater ? item.*memberptr > value + : false; + } + + private: + const ColumnType Type::*memberptr; + const ComparisonOperation comparison; + const T value; + }; + +#define IMPLEMENT_COMPARISON_OPERATOR(OPERATOR, NAME) \ + template \ + inline Filterer operator OPERATOR(const T &value) const \ + { \ + return Filterer(memberptr, NAME, value); \ + } + //^ + + IMPLEMENT_COMPARISON_OPERATOR(<, Less) + IMPLEMENT_COMPARISON_OPERATOR(<=, LessOrEqual) + IMPLEMENT_COMPARISON_OPERATOR(==, Equal) + IMPLEMENT_COMPARISON_OPERATOR(>=, GreaterOrEqual) + IMPLEMENT_COMPARISON_OPERATOR(>, Greater) + +#undef IMPLEMENT_COMPARISON_OPERATOR + + // Column stuff + +private: + const ColumnType Type::*memberptr; +}; + +template +inline auto groupBy(const Range &range, const ColumnMemberPointer &memberptr, const MergeFunction &merge) -> std::vector +{ + std::vector result; + + for (const auto &item : range) { + if (result.size() == 0 || result.back().*memberptr != item.*memberptr) { + result.push_back(item); + } else { + merge(result.back(), item); + } + } + + return result; +} + +#endif // QUICKCHECK_DATABASE_COMMON_H + +// vim: set foldmethod=marker: diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..72343d8f1a35d5d7c75804c5a16ee550f334ae22 GIT binary patch literal 13831 zcmaKTXH=6>vvojv?;tH8UApudswh=N1*Ay_ktSUrp`+44DJs27limpdq&MkEN$4Og z)C34{!+Y;t>-+xPtd$k=B%J4*IWv3a%-;F*OkazfgqZ{Y0FXa@qVXI6z`_2B10W*6 zzL>u+alpP1dpt4q0ss^!?w&Zq4Y}dii}$@XO}vfV9lZUl-`fNH{QN|m-CVqEtv&2T z+}}HXKTu=_05||oHB?{t|JZML4q<&c4-J0#5_P)gk~;91fu`mjXQB#6NJv;%UczU@ zv0@cO1JBSy3t#hvh9MP|#$uwLJvALJcYkt3#9pA9T55*wBb`SsytRIQnW%2NJ)lr%2(eTQT+>9Uin{;|kDG|LI z}K3Y2AF;mNV)< z27wIuRp&tusZu<%!+*tl|1-0B_XG+0Ms>?Ybt08Ad?3NRYC<1asdak8^?5a@qq@An zVCZik33Mo)W5Z1_N*x^;u49O7jkUJ=qzCXL3f{ah=Zt8Qv-?bnvlK(|5QXy)ZLg+% zUl46^`O&{v@#Bi_r>6ilCkgoa+t4N>BHo3Q`heFk(ZfoFA60jS!EqNQ@e_bV>Swt& z9(Fp9N>@jGXd4~I^4nui?_G(0q|V$f#ZPLU^zkAnj~lPhD^5Dh1OW+{@SFr(wWoA} zFSg@jNae4im=BAl@$@7Cy8!W_E|dK6Zb2HSMp?g+qsuG_C8>l}pN zn<@nA@A6CB^cIa+1~nlq+vG5KRA?AHv^@;&6==>KGEns~sGE-gd+Al~-My;WG^ap& zJsxW*KafLoD|?&>HZ_#vXUEVb48#_!{EJ7@uBwu@u9SV)MGi>dprP6NnKA6E>sxwsf8b15ln~i z{QMHBCC6=?Q-LRTB3ucx%Vh&*{QsX>}7UQO4D@4o?P&U2{dRMBZpiP5!qkL#H%#c%5CDR=bt6x@bncW%J`@nJ$v?tWS1GQ z>>plh0tsXGOQ9?7Fpxgz6c3z`tf`CUvFX3XrCQycXrO`7V`k2YL!sHhPXWqu`ddFe znQd$Hs~D5M;^)^$7HxGG=C|xW{EKIhN}L6HGePtIb@7dIGLmva=3Pq{WwIb{=5Z9$ znmTJ@O&ZZ2Gni6AZ7OfI_lXi(M`9(llprl(2HE<9V1_vibRcyxX_@k7P8Ej)Y^+>^ zJZyg`!CU)$sgo**1RwG)oI46M6jGI$ck(wE&MCbZlIsjNUp#Gc%+!Qbcd% z@#@)@I7zR?AUE4p`Czxe90>;>n`W=8*)5JH1*2nO-s{Fl#Cv(ckU5|PGI8ptH z4AVCj2VZTaK0hMsXqkCH%#XIX;Kl_fHsO!so#bV`!0vGfLUbg8Z}b!S?F=t$3r>FW zd)nHv>@)r?F}|LE>2u_Oy>&;UxUNxJ{n7=OBp5WtG2ZGZa5oMKI5B{LLwDEjHmg(A zh7oHk83r*PgSF<9pyB?;s9PtZjz)pumx5j8?o{zJdyJhig!slHgSE{k4K zf3BxYV26WzNyzXiQGlyWkLnnr(I_M$N(>`GDw!p_@W!O`?_1Y)#GpOrjm5lDV&8rg zn-m+encmv+XPwCQgduaI?j#qREqw(d!LvUQt@;3mD6WH81twaB6Lwpt7S}f_ou^z7 z*fzX=YsOae^Ey3Ei>+lc;?Rxk1cS3%47SS1JE^X&ZcPt^|1EZ}pt%R?Zzm9{qm?y9 zBm{URJw_Ug%%ay6!xdxtnmKVm%WvP$QlucOYucB84$*lmIHWLAzGhht_y${7+%==Y zht?YmzYZV*^)KS8#J@w>g~0=2r$TQFSmQt=68(+oKiDK!SXjC+wW~diUU}y`68IPo zuXMlN2W&ND==}@=)9YT2yKp9;0Vg)&hrGjwl9N+mx^a=S7xLZ02Nw!m$ge)umQ_2` znKX;J>b#3>BEvT`Px=G`m>WkGlW^!TNy@>H>JNC(h=d-q5OfTLAI-H-OAIN(q0Dv` z;}twE_R(Uhv|@44-0y_J*SrdJJSP8&u`zhvQ0<_t>wHZ_Pb@^V9EaY{WS4aL^q_Z)>PrfUnl)> z%%8AX{h18=qut*r@|tI#?m_v)_vZQVz=SoKfCtI3_A^IJH~bkRMraE#>67NapZ_r4 z$Z0Y|6_)Url;rGq5Emc>O9?Vk*2)=RhnB5`h@K-W`9X^aIf5!u8 zrWC_LE(rG$V|+L8z^#$MEBLmM|F&*8SSvaUDL!$laFlLej+5P+WW0`9ykZ-ubQwP;i*W~Wh@6Fn%O5+#yMbCU&m z(Ll^TM;{=VxF7DWe>i(hhpDuYO&V6=_|!=P&PaUT+a9DW{%Y%I%7gFxLWten?mfAO z$;>zG*RE9q@mxtAKwz;%>sDw^82lw!aZR&C;;HJ+WX-uc`Az8sGbqb`NfEm2TvH<= zg@`_g@YzcB&>(IhXdICqSHv;OK{Se9;N>9F{QQU^9YFRUF6~ipmT0_$eF<7p$S+d0 z%q*;3$jQTIg?b5w1!mCOvfoAXZ7@PFkqaq{9RLz=fL|KYrPHFA`G;M#B_yB>5%|4} zNEE?nUH9$%Y0LF?oRpK^^!Nw7irRZUp8|c8lJMs*KIT2wuoiIG8A$Ku3#rs%U@Ly^ z=PuFgQNS)PzO5kPV_EgcInC749Clc0fcc-I)%`$&l|?P=>vvFnB{kd08A@A_;P#I>Blf>yb`NL;h(d z!~5_#e)NX#q-FZ3bWoWk_Zl4f7~aaRr(sqCS8zTf>^H#>-S}nifuWP(*6uuq8oa%1vs<3rUvZFb~q+26nqJMhBUjH^ZEJ&>mKvJJmZ9e zL|Ai=3RAeg!|0Zf#tlQ7OJnbJ|L zS{)e#u`p^$T}(?)zGtaE9G$otVbE1Y3$tsXIdvBo|b*1Ajuh9!R_&wJM z(=Q~@?643#stO_~d#TO7Y~SwDKU>T~XU`1)w`$v>VFTtN8r?vdC=|++I2`o3k~Lp5 zbKUFYQq0G*cWax_7ZG@&TDSQ@;Vbit`++m7R#!GXN1GgqcH)4NPD+`vZT8|KL}Nq3 zAv3&d!yMqKEWXlc_U1DTRI9Q!YE&`%`)Wi##fMz{M`Y*RG4IXn?Szt==x@6{^HBGb z=VD{$jRpJYnC?!;bEF&0aPjFx* zts~`#3TzQ>H73sFB0f-Du8CaIOFH}=SWqvWbTkppv|+aQg5P-q{)@;KeyQB|ik+d& zlr{q}CiJK2-Eg(rPXvk%jC^Ai;&jAANlAk74K9dXn}Y{^9_?BY=e;ly`i{Rud4&s~?$criQROBKx3snA;uJ+7KaZMGA)auFu#U*~*q0x2f;ogZwiAbePl@|AO8$ zJ}vMRZj;YR_19pbf|rLo;JMkqPQ_}aoYcIw^J{(VcD_~+%`!x_Qc@#seoc!b>0sJ^ zf$|XrIXmV()MUS#(((@M3wgKRF0$Fn9fA&}OwC;_LM%HnVtF9i1JS_~|K9Pe?Sp{O z$2f|0g2OBE{bTEwr38Fh^Y%>%6I9@YxU2r}Sj=mj`UNm-Jg~G7ZSpHVQwP~zy~nsK z>W7_>CU@&Vp7V8ii4Jsj4)KTv5wjGpo$) zaJfZ+)iA2F@gIIrnN^)x?w3@Z<3TahAQ%IEo0vykv{1kVd|v?AF`unnD5hY@?3aujzW@{q|n)x~>Ph%k>^e0f=xK z-HE7ce()h;77aK!k$sJRH0r(J90Xy)7H&`Z5LGC!r5I%?{g4KXb5}XJUZ?d__^d*% z$Cd=o$*k?m1E|{@a6>Fc!nEKCZS?&o-D9I4rR+`ANN(1S--(2TLCt``GSBwkw2%;Z zn`2GMPb<#-5j3Mxgk|%PzWs!eLt<x4-&Cg8+mqodSOP^^dMLMP${sdIf93Ptoj5SxZCDPfI)$Xm~<6zAjia2y9GS!d0 zJaQ($XOIxEeYh!nJsu8JoMYl{Ujdz8b6A7qjMs9TXIT6zOBpdvE>`HXZu+HrLGjZ4 zuN?ZHR4o|v4njhx-o2$#v|tDj6jL->i!L6lcuQG8h!F?=l1nKYP}!k`{s?wvne?IEDHyev1S|l8$+U!_dQ^Na{rP{8iVk7lvs>x?az9?QV@4}UkAN@i7+@J zFm#hsSxpA|M86ltrFQ(b?Rc;ta ztrCj^l25GBqAZ0+bC_-9t!LGM0lcEy|H6-nC5<0V__X8tJ9_`{wpWLKh6~?aX}V7; zg$y4;7f>hu@uTDuL$nkkYCs;9X`SdSUTpvX!QI{;maz<=$AY)(ul8T43E)f!HhA<^ zu{(GLukVLEMtQ6V3j5l8{wWy>x8Z9b3cyxkgp{V27i0{W0A+uVO4x{z&rtnV81G_B zM@lge!hHKZf$~y%QJash{BQRx_>w9xL4y|uo2y1nyw12keT|H!!XQ4_CZ2}guA9d} zezXR z?rb46)Cxfc<~Ua-1x#9!_I22$F{X*3^ZDVF@T{Vfos@FmDNdbVBzmA5vh~$|cMtEC zwi^BB782=dWj(W=A~e>Bx6f=ThkH+Dq9oEsAIobjx3BhmJ8zCDHalz1y z0HaROd1SM@S$eO2(gafd0Zm9uOp31$F*ag@>}y6}^6jJ!d81}l=~*r=^2a8;r!GHL zKfiRwF24!YI{RF{TBb7o6Mn1yf7QgGp-1y~< zZsbb^c~k(>;#`uQVEHUC*!eWJ|rl5ezv&_`a#iSM5`60tAc-)ye&fJzakFoN;E z1TV+nB}O2(t`Ieb^2Q0X>D2a>V2H3qYn;{f89v3-)I4#~mrdqZ>0_?5R=+h*9Q*2( z#vh`GD#dQ+fkmZ)%`1eWG@WG(lsG)NsH@^S10j5G6R~m@jE+uy6X+zc&T~r{j5>WV z$M##P8!5>RWf5_o$^t9u_LU+J1gd^nZf_Kd;d2qtDdKdKyp4A0p+`Qxj%liEnk zfrCsK2*dA|WFVVtdK2-uf%_C-wk)~@D`u?zFm!dSudV(%D%b1Gp0s&IA}uNVyYPlr ziN!B;&cMV!u`x(L7{uHcrF1>d!%V7;$G0(OUaLX$%I)s}t!Vqm zpq_&3#Y2I!5;4)}F$+(e*&zR1?+1WwGSZnTlGK;q%gdzZ=j|r!AMqo*Rd@j3ku1z) zV!x3p7rpOjg8&r^-DH{cEdBO%iHKg_c`tJG8B z96Hft>cwJ{Z#3LO4Dsj5hQ=Nzq$fCAsS!0CERW^c@A~iT*VomV8sJ<*1w|hSj{9K_ zCw$BT+~KOTK2P|9*D(@urQZJ8whO3Nzk;sMReu^D^LHPveI^}UUt`Q1ah~LTjQjIx z=ai0`iiMY;l%DGg8NZ1s_B=tlBBk;_>|~Snz6qNCKb?PQSX1B1R{9fn&d*k8wq-$X zwpUXu=mqkzfof9oo;D`6B0sN1y7KxKy!SwxHCl82f5g9l{!`!D;=;k z!a?r_0&qRvj~zbCgmHXd7POsCwGc9chA&-}(Xbjm6=;b!iDlm3CP`-d#cugStT_Vs zgEs`P8-Abc7;H-K(s<}Fy@{G{@&aaByiH>(T%WG2(e8l2;j(_C-lLfknMTF&jwZR+ zjsz3O0I)~1Xowj)l4?qd&Hga-t#9hfnzHibOnX9sDtTSm^3?fP+QKiVrICBd^op%^ z_yDfYr0L*eSqEL_q1dNuvi7S?j~N(qc12#T-itOMuCN!ydD023`Wb&-$x$TWw7=b@ zF}_S)Aoy;=zb@`F-*t-3xp2ivkX&nlIP$7Pa)e1d%II-yb-?}VjoS1Jska#aqOmx0 z#fj(A%DWnH3ZCb37fEoF$nIzf*U?4Qk`WHU$LguELUFRGUsJm4Cb>Yx>Z-DJWvPw6 z)3_{R-f?+tLQxxA&z>i$KLu0pT+E|`3FdJ?--?pHXM|QdW8aNe=_y74_uB9iF`IDD zjJPCuZEbBsdXmWW8EkQ9fyVz5%z17GSh&J1IF|X5{Csc_2lJ?=UNj`k>Zvo;7Y;P6 z_h;vG`=4aF1?v?wME~6jOP+WmP;HR2)`9+z*7Rv6~_H-qNHe7@}PouOFS+tmx@&sY1$fn$NZqGwhl ze|uYB_ElMDat|^orxvsJqDM@XHz^OVhPNZHd7<-e;_bq2WTfGt=<;{XBm(Ms>FX~l zi~Ui%c;6_FOAT%)jmax>GGdyS2=6UL^M5v1QKjX*;gZ5*9vmnU z-)`DL5f|rl1?TeHfjeb=;M9{)))jMKKbkE19qlDaRNr1q@q|V=?}n4p z0KxdjpgH!BJ;^1M=^v50YxJ+W1h7*gB4N+6I&O{W?Ft9J{Sedn@(|jL;txq3E*o2t zm%dsq6?t47pZ099H(h*jz7sB+Casg`Hw{OaNd%|(YccO~u;*hOR0m!0-Jz<^^R}a4 z$D2OBsLVrBS6;f~H4$zcVSnYK+6=71Zn`a+p)PBC((HpVOQe}q?wj4Rx;Riulw5Y6 z?VJY7tho1zpKZ1;^1Jot^A6ZAw%gv>8K&-ERbg`7PQprY`S}Lm*+Yuki=Ck<$yPE3o`0=#| z-oX8QhY97ky!E6VPkc9Cy+>7YGzO7>FgA;Qgx73eCMF-RR`i=@9LqJG@8Ch98&riX z*$+@}F#CuI_D^wQk3tTT3I}kZy3U?jxcb4rM>+rpD+xTptlaK!ZIZ?g-5fCFMJuUj zTZjq=z^N`SOLZ3=Q0{tKU8UJmvG^pDLi0F5lU9fjWr?#4!%()MnYac6~bHic;VBvW9Xe zK?br^5Pe8Z7o{b4h^Fs3zQ-~B!REc#DwSZl?pI2WeSP-gKxPKthH|~2y8Nx&&1RV-6_SN#6Q1BU5N8TE91 z@VfU;k(LaPS4oX$&yfl>*b#1SzQzWiP#op7;Gu1a62+~@y3oZw8e;E)KX-S{zmoM{K1;GCxT75U|0+a6?Y0DC=gt7!KXm(?69*d0${lhV3f%&(sIY$pwta1p0L6@#gOa=0 zs-9nN25)l?e2w4=GX=gtYW*E*Hf3S_BAoAi1{78>7jtB~61}0$)Lh$tDWJDPxIoNt zjR1-0<*!T$mR^omMBEE75vU51%1^^QILG52dNV^vk4ZXrLyUobMH!X>>)W;tg_wqJ zbiiuH%lv^a?;5SIaG%(s1515|onHj)RUVuX3w^Pu4X>`crpxCV4dW3YhW@5JqzeVr z=EfS9ffINi$b8uu6f9i52jXBljMKm6ccuV6;S0&dfh2?laLIaH7&r@NItM7%f`Mz6 z7vdcaQL?adN?uXSr%#-=hXi1Vi||_N&ET;TM65XCGbu!+|E;`#IlnppocMv1s!onE z$E=hD;_ke{!DWmI#V8F4j}e|c_CQW7T@&|D-S}eMPY=CHL6;XqjD|qHHtSSIeO1hz^{RJPT)i?XUiY`&r){52$-~ zxhJRF{SWnzE-Simbaykjx98@8;jxM)P8`TQ5!MqllCMkyr81yV=~@9T=h6~E?atq2 z1>r=GUVXNw>36EM9!}1-^=>dRhN(uKcP)=7-G)0}F{R}rfc*4!PM8%807v2byH<3n zXBwaf02AHWCIPd&&3exnJ=VVbh!~Gqkm+ouornq7A*rxGee$+fL-*ZMwc!bnXB-d$cwP!{U2YJt2a&T0y5FZfLg6@`t6z!? zy+-Tn;uW~MUefaBrz&aW9ECKVX>(uz-8C<8ncl_i-Ts@lMXO3;p4Lf!dB+z@&V>M0KO2Wxb?jkz~*mc>%-t}EJ^FZkv&KX4Hd&sZygW5 zde(GO-y}Xk59W!!JGF@4@6WiV3b*=!++mWA?Dm4bah86+L~FeeTifvZhZl4}KAS!y-7zx1^ zwFvEjKe zBx(q-62*rM@>3(jcbl;sYq2tQ{e)xmo#NBrou7R(*xO=~)NN$x)Dy3lQpbCzfA;X)cFCs?CGyUq)vK3kZjs89XK;5{x)B6myh) zxdk7dlL33$lpWbVTs$GLz_)cuKesOZz$1tO7(?tRZ&=5 zl6_4)ud3qe z(Hq+GqoWg9>vGHR5_OS$B2h|#RV#-MwSg1Q0tePVPJZ~6S*>#joAoS6NC@J`hrI5$ zW|7y&k`4aRZCH;kN>bgpfOCJLm-8BjKkrMo?<=Ad#CofFU2Z-WuoC?G&Cr2#gCeVl zNO;5I6Yer!nf$aR!UyFDp!WOus0P|jL>FXLA5wxeIeRJT^6V1RVFRInMwFy znaPCU8M3%zwZR(mxIv1}AGwlXxJVTO)za!4$v-$PW9rz+#zF~2x%JdPtD(PdqutniiLWMj#wM1qhaUf0G8*-*3X=4T;nC?9$dotTPEMu*-VC> z-d+oI#6QM|$oGq25h5!+PW3;f?IjUl1cB&sAAht-rZZW7sif<_@B4^lkQMK_HZuP~&z__0223>2B!7tl-Sa74U5cva}9=YwT0FBDn zZBkCHY*9U%ml^l2`7MtNa7eda$a2K6_$AM~TdB?d=i5XeSqqwB?f)L!rkO;q_`{8l z_r}*pL6e|yq4L_daL2Ay-~={U{yCaIb-Z!%JF9u-5S5KteCJN0<7w~cz+JMAzJ69D z$EB5WI{AgMbu~@m#R?E1|5PR-q%C}xsr?^n8+N|E`R(z-$NQ3cCQlanGw^D4;O z=Hv8k_UyB36VDX%mA?v4jfSrNT}+SLzF`dEgM-<-52@2jNkE-{svT<%YpldJm!b!L z27*4va9oZ1)=;_<<_J3CnsPIp!&muaTfmyqSxjZun_`V zq~3$BYfX*bNOmFngn9DM#OCW22C7UV8UEE7nHAzBHDZzKfB!=v@)m8XSL`;!Hy8N3 zeaW3z@~l=_YC{Qwki1J7cV{V5h&z6*PKo^0<3W%&-^d!H$$@6cmvU_~48k94`g5%5 z&n>&)X8W3aMggOGOpcO9&l+Oee(KpxAI8sp)DSTJB8!H(VF3*5BQDwe=)i^Odl!H12b@jrzQRzzib6&eCs{jyT*)8qZ-^2l~2 zwqH|=rtm8ZJin#EvvalC5bP!mH21hf;59Bt@mzk43&wf(1m?bAIXV)ERuc28fI?e- zZ0t5tdc1JF%hm|)5WG&xd(zn40UT1^K>)YOa_G#6!+->LSqGHb9%dy_beDvJL{w_J zfw#nElh~6K3Hb19=6yt4vqZXN5uN(%oR!58wgixyu#qZ|u3wkJAYNiuxX@sX46G6q z{Wd0ZgTi)J-(Rx2?{2UE*<}QL zAkitDealq%TXXSxYm1=C;ovJjnv>HDCz%o)ie<_Jm^(rC7$>)zMS9Qu%A0<*QMus1 zfT?~39vUzbJJ}NkLt*Vpi7kvBb7mN%?K$fP`04ePdnkhfFQGBW+{rQwIxG$OWNvKnT zYN`Mpyk$$Ye4fUBU;T~aysF#f1(&yPA)Io-lJEj_eM%(7V$1fY)bWY3u4BI!w4ACtg+6V$ZIi z47u-10xgPf`j2pW{O7jBeucro*g0?#-6p1W7p3?Um0(q4`3Pg?7tK!cJ7#yn!Gth) zdyzh?y0MsKiBID6Hnk=;+jhL4_kMmS^VrWvpdQOucwM|P=9Jn{o&4R7HI3O7Kl&@d zOLnQu>-i88Fg|nwG1y&n@hd8eqNh&;)AgOnbDx$R ziiIH}G_^;8j(rI1m2GqinCF9EUlVcXg0PK}HiPF1iX*B#SZ$@zMOg)TZ>qiwPi#(p z$^m7XybWjjz8c`)DE=`FZiux54)$y_M*S5)V0QIQmt}?;$u&N>8W50zwPU=^lkb#b z7=ZhWBE)dBaiBzex5fqm|$CcCM!!uqSlWxdf zXO{mm@f>4kM?x6r^92Wtl8OO|n3-bA&W^u4nc>iqU#qtzL*wM`9QMYn9d!WCv6_6t zVM6K~cBMv{p3%n&s9bH!lA@Q$?HT_nq-{54-4>&RdpzTi>Hpk03lDiO0?!;;9uq~u z7ni%Pk!js@G(zz@tXlb`3CV?6+BbH#I9etDYaU`3YW1XJvgW8BWHoBsAS zzOm@rt@;|1F^eA0o3*-#(u-5bK#vcWp9ltGIr9^4EHc}VgbgwJAiJkk|4j>rI=-cZ zaeWUD+P2z%_cQKxMD%2U)c>lpUhd_iBSLSHkVJ)@hf%RuFzV{*Qg-SB`9`A+Og~2E zlZ)K96>B%K^-t~nrK{H=qHaRY7WUT{cJMgmmYwM2JoMM;BzS&ih8i_6!*_R#TZp$F zQq)8oRy0rvRT9-Up;W#SWy?nP$g-V%+1jUuipmQ-s4NK9KUFtn)caMICx1vkPqa*q ze~^R~`6=SBq{ZYS{0?n_V1OjJhwa;GtC>4($ZtYyXxLOD(m;iXv}9iY#?_WZ$Nd)r zt@9z`AS?<2PrO6m6U(+}+6gV26cPGIxxqavGSD0yiLZdLg1QO&yYIlSiQbFuKt9!k_B%g?)Q9IjdsgP>^ zgF(=r^#OCN1cc5DAL{sfKF-=X(Guw{;j W!|8MaJnWCS08cgbH7eEKg#8y7*^5{J literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..8bfa041 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,144 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: + +add_library(PlasmaActivitiesStats) +add_library(Plasma::ActivitiesStats ALIAS PlasmaActivitiesStats) + +set_target_properties(PlasmaActivitiesStats PROPERTIES + VERSION ${PLASMAACTIVITIESSTATS_VERSION} + SOVERSION ${PLASMAACTIVITIESSTATS_SOVERSION} + EXPORT_NAME ActivitiesStats +) + +target_sources(PlasmaActivitiesStats PRIVATE + query.cpp + terms.cpp + resultset.cpp + resultwatcher.cpp + resultmodel.cpp + activitiessync_p.cpp + cleaning.cpp + + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/Database.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/schema/ResourcesDatabaseSchema.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/utils/qsqlquery_iterator.cpp +) + +ecm_qt_declare_logging_category(PlasmaActivitiesStats + HEADER plasma-activities-stats-logsettings.h + IDENTIFIER PLASMA_ACTIVITIES_STATS_LOG + CATEGORY_NAME kde.plasma.activitiesstats + OLD_CATEGORY_NAMES kf5.kactivity.stat kf.activitiesstats + DESCRIPTION "Plasma Activities Stats" + EXPORT PLASMA_ACTIVITIES_STATS +) + +set(PlasmaActivitiesStats_DBus_SRCS) +qt_add_dbus_interface(PlasmaActivitiesStats_DBus_SRCS + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml + resourcesscoring_interface +) +qt_add_dbus_interface(PlasmaActivitiesStats_DBus_SRCS + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml + resourceslinking_interface +) + +target_sources(PlasmaActivitiesStats PRIVATE + ${PlasmaActivitiesStats_DBus_SRCS} +) + +target_link_libraries(PlasmaActivitiesStats + PUBLIC + Qt6::Core + PRIVATE + Qt6::DBus + Qt6::Sql + Plasma::Activities + KF6::ConfigCore + Threads::Threads +) + +target_include_directories(PlasmaActivitiesStats + INTERFACE "$" +) + +# install +ecm_generate_export_header (PlasmaActivitiesStats + BASE_NAME PlasmaActivitiesStats + VERSION ${PROJECT_VERSION} + USE_VERSION_HEADER plasmaactivitiesstats_version.h + DEPRECATED_BASE_VERSION 0 +) + +ecm_generate_headers ( + PlasmaActivitiesStats_CamelCase_HEADERS + HEADER_NAMES + Query + Terms + ResultSet + ResultWatcher + ResultModel + Cleaning + + PREFIX PlasmaActivitiesStats + REQUIRED_HEADERS PlasmaActivitiesStats_HEADERS +) + +install ( + TARGETS PlasmaActivitiesStats + EXPORT PlasmaActivitiesStatsLibraryTargets + ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} + ) + +install ( + FILES ${PlasmaActivitiesStats_CamelCase_HEADERS} + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/PlasmaActivitiesStats/PlasmaActivities/Stats + COMPONENT Devel + ) + +install ( + FILES ${PlasmaActivitiesStats_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/plasmaactivitiesstats_export.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/PlasmaActivitiesStats/plasmaactivitiesstats + COMPONENT Devel + ) + +ecm_qt_install_logging_categories( + EXPORT PLASMA_ACTIVITIES_STATS + FILE plasma-activities-stats.categories + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) + +if(BUILD_QCH) + ecm_add_qch( + PlasmaActivitiesStats_QCH + NAME PlasmaActivitiesStats + BASE_NAME PlasmaActivitiesStats + VERSION ${PROJECT_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + ${PlasmaActivitiesStats_HEADERS} + MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" + LINK_QCHS + Qt6Core_QCH + PlasmaActivities_QCH # while not in API symbols, some classes are referenced in comments + INCLUDE_DIRS + ${CMAKE_CURRENT_BINARY_DIR} + BLANK_MACROS + PLASMAACTIVITIESSTATS_EXPORT + PLASMAACTIVITIESSTATS_DEPRECATED + PLASMAACTIVITIESSTATS_DEPRECATED_EXPORT + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() + +if (NOT WIN32) + ecm_generate_pkgconfig_file(BASE_NAME PlasmaActivitiesStats + LIB_NAME PlasmaActivitiesStats + INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR}/PlasmaActivitiesStats + DEPS Qt6Core + DESCRIPTION "libPlasmaActivitiesStats is a C++ library for using Plasma activities" + INSTALL + ) +endif () + diff --git a/src/activitiessync_p.cpp b/src/activitiessync_p.cpp new file mode 100644 index 0000000..6205742 --- /dev/null +++ b/src/activitiessync_p.cpp @@ -0,0 +1,51 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "activitiessync_p.h" + +#include + +namespace ActivitiesSync +{ +typedef std::shared_ptr ConsumerPtr; + +ConsumerPtr instance() +{ + static std::mutex s_instanceMutex; + static std::weak_ptr s_instance; + + std::unique_lock locker{s_instanceMutex}; + + auto ptr = s_instance.lock(); + + if (!ptr) { + ptr = std::make_shared(); + s_instance = ptr; + } + + return ptr; +} + +QString currentActivity(ConsumerPtr &activities) +{ + // We need to get the current activity synchonously, + // this means waiting for the service to be available. + // It should not introduce blockages since there usually + // is a global activity cache in applications that care + // about activities. + + if (!activities) { + activities = instance(); + } + + while (activities->serviceStatus() == KActivities::Consumer::Unknown) { + QCoreApplication::instance()->processEvents(); + } + + return activities->currentActivity(); +} + +} // namespace ActivitiesSync diff --git a/src/activitiessync_p.h b/src/activitiessync_p.h new file mode 100644 index 0000000..a7afc3a --- /dev/null +++ b/src/activitiessync_p.h @@ -0,0 +1,23 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef ACTIVITIES_SYNC_P_H +#define ACTIVITIES_SYNC_P_H + +#include +#include + +#include + +namespace ActivitiesSync +{ +typedef std::shared_ptr ConsumerPtr; + +QString currentActivity(ConsumerPtr &activities); + +} // namespace ActivitiesSync + +#endif // ACTIVITIES_SYNC_P_H diff --git a/src/cleaning.cpp b/src/cleaning.cpp new file mode 100644 index 0000000..12ebeaa --- /dev/null +++ b/src/cleaning.cpp @@ -0,0 +1,81 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include +#include + +#include "cleaning.h" +#include "common/dbus/common.h" + +namespace KActivities +{ +namespace Stats +{ +void forgetResource(Terms::Activity activities, Terms::Agent agents, const QString &resource) +{ + QDBusMessage message = QDBusMessage::createMethodCall(KAMD_DBUS_SERVICE, + KAMD_DBUS_OBJECT_PATH("Resources/Scoring"), + KAMD_DBUS_OBJECT("ResourcesScoring"), + QStringLiteral("DeleteStatsForResource")); + + for (const auto& activity: activities.values) { + for (const auto& agent: agents.values) { + message.setArguments({activity, agent, resource}); + QDBusConnection::sessionBus().asyncCall(message); + } + } +} + +void forgetResources(const Query &query) +{ + QDBusMessage message = QDBusMessage::createMethodCall(KAMD_DBUS_SERVICE, + KAMD_DBUS_OBJECT_PATH("Resources/Scoring"), + KAMD_DBUS_OBJECT("ResourcesScoring"), + QStringLiteral("DeleteStatsForResource")); + + for (const auto& activity: query.activities()) { + for (const auto& agent: query.agents()) { + for (const auto& urlFilter: query.urlFilters()) { + message.setArguments({activity, agent, urlFilter}); + QDBusConnection::sessionBus().asyncCall(message); + } + } + } +} + +void forgetRecentStats(Terms::Activity activities, int count, TimeUnit what) +{ + QDBusMessage message = QDBusMessage::createMethodCall(KAMD_DBUS_SERVICE, + KAMD_DBUS_OBJECT_PATH("Resources/Scoring"), + KAMD_DBUS_OBJECT("ResourcesScoring"), + QStringLiteral("DeleteRecentStats")); + + for (const auto& activity: activities.values) { + message.setArguments({QStringLiteral("DeleteRecentStats"), + activity, + count, + what == Hours ? QStringLiteral("h") + : what == Days ? QStringLiteral("d") + : QStringLiteral("m")}); + QDBusConnection::sessionBus().asyncCall(message); + } +} + +void forgetEarlierStats(Terms::Activity activities, int months) +{ + QDBusMessage message = QDBusMessage::createMethodCall(KAMD_DBUS_SERVICE, + KAMD_DBUS_OBJECT_PATH("Resources/Scoring"), + KAMD_DBUS_OBJECT("ResourcesScoring"), + QStringLiteral("DeleteEarlierStats")); + + for (const auto& activity: activities.values) { + message.setArguments({QStringLiteral("DeleteEarlierStats"), activity, months}); + QDBusConnection::sessionBus().asyncCall(message); + } +} + +} // namespace Stats +} // namespace KActivities diff --git a/src/cleaning.h b/src/cleaning.h new file mode 100644 index 0000000..4481500 --- /dev/null +++ b/src/cleaning.h @@ -0,0 +1,47 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef CLEANING_H +#define CLEANING_H + +#include "query.h" +#include "terms.h" +#include + +namespace KActivities +{ +namespace Stats +{ +/** + * Forget the resource(s) for the specified activity and agent + */ +void PLASMAACTIVITIESSTATS_EXPORT forgetResource(Terms::Activity activity, Terms::Agent agent, const QString &resource); + +enum PLASMAACTIVITIESSTATS_EXPORT TimeUnit { + Hours, + Days, + Months, +}; + +/** + * Forget recent stats for the specified activity and time + */ +void PLASMAACTIVITIESSTATS_EXPORT forgetRecentStats(Terms::Activity activity, int count, TimeUnit what); + +/** + * Forget events that are older than the specified number of months + */ +void PLASMAACTIVITIESSTATS_EXPORT forgetEarlierStats(Terms::Activity activity, int months); + +/** + * Forget resources that match the specified query + */ +void PLASMAACTIVITIESSTATS_EXPORT forgetResources(const Query &query); + +} // namespace Stats +} // namespace KActivities + +#endif // CLEANING_H diff --git a/src/common/database/Database.cpp b/src/common/database/Database.cpp new file mode 100644 index 0000000..4b42e72 --- /dev/null +++ b/src/common/database/Database.cpp @@ -0,0 +1,247 @@ +/* + SPDX-FileCopyrightText: 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "Database.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "plasma-activities-stats-logsettings.h" + +namespace Common +{ +namespace +{ +std::mutex databases_mutex; + +struct DatabaseInfo { + Qt::HANDLE thread; + Database::OpenMode openMode; +}; + +bool operator<(const DatabaseInfo &left, const DatabaseInfo &right) +{ + return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode; +} + +std::map> databases; +} + +class QSqlDatabaseWrapper +{ +private: + QSqlDatabase m_database; + bool m_open; + QString m_connectionName; + +public: + QSqlDatabaseWrapper(const DatabaseInfo &info) + : m_open(false) + { + m_connectionName = QStringLiteral("kactivities_db_resources_") + // Adding the thread number to the database name + + QString::number((quintptr)info.thread) + // And whether it is read-only or read-write + + (info.openMode == Database::ReadOnly ? QStringLiteral("_readonly") : QStringLiteral("_readwrite")); + + m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName) + : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName); + + if (info.openMode == Database::ReadOnly) { + m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY")); + } + + // We are allowing the database file to be overridden mostly for testing purposes + m_database.setDatabaseName(ResourcesDatabaseSchema::path()); + + m_open = m_database.open(); + + if (!m_open) { + qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName() + << m_database.lastError(); + + if (info.openMode == Database::ReadWrite) { + qFatal("PlasmaActivities: Opening the database in RW mode should always succeed"); + } + } + } + + ~QSqlDatabaseWrapper() + { + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Closing SQL connection: " << m_connectionName; + } + + QSqlDatabase &get() + { + return m_database; + } + + bool isOpen() const + { + return m_open; + } + + QString connectionName() const + { + return m_connectionName; + } +}; + +class Database::Private +{ +public: + Private() + { + } + + QSqlQuery query(const QString &query) + { + return database ? QSqlQuery(query, database->get()) : QSqlQuery(); + } + + QSqlQuery query() + { + return database ? QSqlQuery(database->get()) : QSqlQuery(); + } + + std::unique_ptr database; +}; + +Database::Locker::Locker(Database &database) + : m_database(database.d->database->get()) +{ + m_database.transaction(); +} + +Database::Locker::~Locker() +{ + m_database.commit(); +} + +Database::Ptr Database::instance(Source source, OpenMode openMode) +{ + Q_UNUSED(source) // for the time being + + std::lock_guard lock(databases_mutex); + + // We are saving instances per thread and per read/write mode + DatabaseInfo info; + info.thread = QThread::currentThreadId(); + info.openMode = openMode; + + // Do we have an instance matching the request? + auto search = databases.find(info); + if (search != databases.end()) { + auto ptr = search->second.lock(); + + if (ptr) { + return ptr; + } + } + + // Creating a new database instance + auto ptr = std::make_shared(); + + ptr->d->database = std::make_unique(info); + + if (!ptr->d->database->isOpen()) { + return nullptr; + } + + databases[info] = ptr; + + if (info.openMode == ReadOnly) { + // From now on, only SELECT queries will work + ptr->setPragma(QStringLiteral("query_only = 1")); + + // These should not make any difference + ptr->setPragma(QStringLiteral("synchronous = 0")); + + } else { + // Using the write-ahead log and sync = NORMAL for faster writes + ptr->setPragma(QStringLiteral("synchronous = 1")); + } + + // Maybe we should use the write-ahead log + auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL")); + + if (walResult != QLatin1String("wal")) { + qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database can not be opened in WAL mode. Check the " + "SQLite version (required >3.7.0). And whether your filesystem " + "supports shared memory"; + + return nullptr; + } + + // We don't have a big database, lets flush the WAL when + // it reaches 400k, not 4M as is default + ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100")); + + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivities: Database connection: " << ptr->d->database->connectionName() + << "\n query_only: " << ptr->pragma(QStringLiteral("query_only")) + << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode")) + << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint")) + << "\n synchronous: " << ptr->pragma(QStringLiteral("synchronous")); + + return ptr; +} + +Database::Database() + : d(new Database::Private()) +{ +} + +Database::~Database() +{ +} + +QSqlQuery Database::createQuery() const +{ + return d->query(); +} + +QSqlQuery Database::execQuery(const QString &query) const +{ + return d->query(query); +} + +QSqlQuery Database::execQueries(const QStringList &queries) const +{ + QSqlQuery result; + + for (const auto &query : queries) { + result = execQuery(query); + } + + return result; +} + +void Database::setPragma(const QString &pragma) +{ + execQuery(QStringLiteral("PRAGMA ") + pragma); +} + +QVariant Database::pragma(const QString &pragma) const +{ + return value(QStringLiteral("PRAGMA ") + pragma); +} + +QVariant Database::value(const QString &query) const +{ + auto result = execQuery(query); + return result.next() ? result.value(0) : QVariant(); +} + +} // namespace Common diff --git a/src/common/database/Database.h b/src/common/database/Database.h new file mode 100644 index 0000000..99e26c3 --- /dev/null +++ b/src/common/database/Database.h @@ -0,0 +1,133 @@ +/* + SPDX-FileCopyrightText: 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#ifndef COMMON_DATABASE_H +#define COMMON_DATABASE_H + +#include +#include +#include + +namespace Common +{ +class Database +{ +public: + typedef std::shared_ptr Ptr; + + enum Source { + ResourcesDatabase, + }; + + enum OpenMode { + ReadWrite, + ReadOnly, + }; + + static Ptr instance(Source source, OpenMode openMode); + + QSqlQuery execQueries(const QStringList &queries) const; + QSqlQuery execQuery(const QString &query) const; + QSqlQuery createQuery() const; + + void setPragma(const QString &pragma); + QVariant pragma(const QString &pragma) const; + QVariant value(const QString &query) const; + + ~Database(); + Database(); + + friend class Locker; + class Locker + { + public: + Locker(Database &database); + ~Locker(); + + private: + QSqlDatabase &m_database; + }; + +#define DATABASE_TRANSACTION(A) \ + /* enable this for debugging only: qDebug() << "Location:" << __FILE__ << __LINE__; */ \ + Common::Database::Locker lock(A) + +private: + class Private; + std::unique_ptr d; +}; + +template +QString parseStarPattern(const QString &pattern, const QString &joker, EscapeFunction escape) +{ + const auto begin = pattern.constBegin(); + const auto end = pattern.constEnd(); + + auto currentStart = pattern.constBegin(); + auto currentPosition = pattern.constBegin(); + + bool isEscaped = false; + + // This should be available in the QString class... + auto stringFromIterators = [&](const QString::const_iterator ¤tStart, const QString::const_iterator ¤tPosition) { + return pattern.mid(std::distance(begin, currentStart), std::distance(currentStart, currentPosition)); + }; + + // Escaping % and _ for sql like + // auto escape = [] (QString str) { + // return str.replace("%", "\\%").replace("_", "\\_"); + // }; + + QString resultPattern; + resultPattern.reserve(pattern.size() * 1.5); + + for (; currentPosition != end; ++currentPosition) { + if (isEscaped) { + // Just skip the current character + isEscaped = false; + + } else if (*currentPosition == QLatin1Char('\\')) { + // Skip two characters + isEscaped = true; + + } else if (*currentPosition == QLatin1Char('*')) { + // Replacing the star with the sql like joker - % + resultPattern.append(escape(stringFromIterators(currentStart, currentPosition)) + joker); + currentStart = currentPosition + 1; + + } else { + // This one is boring, nothing to do + } + } + + if (currentStart != currentPosition) { + resultPattern.append(escape(stringFromIterators(currentStart, currentPosition))); + } + + return resultPattern; +} + +inline QString escapeSqliteLikePattern(QString pattern) +{ + return pattern.replace(QLatin1String("%"), QLatin1String("\\%")) + .replace(QLatin1String("_"), QLatin1String("\\_")) + .replace(QLatin1String("'"), QLatin1String("\\'")); +} + +inline QString starPatternToLike(const QString &pattern) +{ + return parseStarPattern(pattern, QStringLiteral("%"), escapeSqliteLikePattern); +} + +inline QRegularExpression starPatternToRegex(const QString &pattern) +{ + const QString parsed = parseStarPattern(pattern, QStringLiteral(".*"), QOverload::of(&QRegularExpression::escape)); + return QRegularExpression(QRegularExpression::anchoredPattern(parsed)); +} + +} // namespace Common + +#endif // COMMON_DATABASE_H diff --git a/src/common/database/schema/ResourcesDatabaseSchema.cpp b/src/common/database/schema/ResourcesDatabaseSchema.cpp new file mode 100644 index 0000000..f2b9935 --- /dev/null +++ b/src/common/database/schema/ResourcesDatabaseSchema.cpp @@ -0,0 +1,178 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "ResourcesDatabaseSchema.h" + +#include +#include +#include + +namespace Common +{ +namespace ResourcesDatabaseSchema +{ +const QString name = QStringLiteral("Resources"); + +QLatin1String version() +{ + return QLatin1String("2015.02.09"); +} + +QStringList schema() +{ + return QStringList{ + + // Schema information table, used for versioning + QStringLiteral("CREATE TABLE IF NOT EXISTS SchemaInfo (" + "key text PRIMARY KEY, value text" + ")"), + + QStringLiteral("INSERT OR IGNORE INTO schemaInfo VALUES ('version', '%1')").arg(version()), + QStringLiteral("UPDATE schemaInfo SET value = '%1' WHERE key = 'version'").arg(version()), + + // The ResourceEvent table saves the Opened/Closed event pairs for + // a resource. The Accessed event is mapped to those. + // Focussing events are not stored in order not to get a + // huge database file and to lessen writes to the disk. + QStringLiteral("CREATE TABLE IF NOT EXISTS ResourceEvent (" + "usedActivity TEXT, " + "initiatingAgent TEXT, " + "targettedResource TEXT, " + "start INTEGER, " + "end INTEGER " + ")"), + + // The ResourceScoreCache table stores the calculated scores + // for resources based on the recorded events. + QStringLiteral("CREATE TABLE IF NOT EXISTS ResourceScoreCache (" + "usedActivity TEXT, " + "initiatingAgent TEXT, " + "targettedResource TEXT, " + "scoreType INTEGER, " + "cachedScore FLOAT, " + "firstUpdate INTEGER, " + "lastUpdate INTEGER, " + "PRIMARY KEY(usedActivity, initiatingAgent, targettedResource)" + ")"), + + // @since 2014.05.05 + // The ResourceLink table stores the information, formerly kept by + // Nepomuk, of which resources are linked to which activities. + // The additional features compared to the old days are + // the ability to limit the link to specific applications, and + // to create global links. + QStringLiteral("CREATE TABLE IF NOT EXISTS ResourceLink (" + "usedActivity TEXT, " + "initiatingAgent TEXT, " + "targettedResource TEXT, " + "PRIMARY KEY(usedActivity, initiatingAgent, targettedResource)" + ")"), + + // @since 2015.01.18 + // The ResourceInfo table stores the collected information about a + // resource that is not agent nor activity related like the + // title and the mime type. + // If these are automatically retrieved (works for files), the + // flag is set to true. This is done for the agents to be able to + // override these. + QStringLiteral("CREATE TABLE IF NOT EXISTS ResourceInfo (" + "targettedResource TEXT, " + "title TEXT, " + "mimetype TEXT, " + "autoTitle INTEGER, " + "autoMimetype INTEGER, " + "PRIMARY KEY(targettedResource)" + ")")} + + ; +} + +// TODO: This will require some refactoring after we introduce more databases +QString defaultPath() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/database"); +} + +const char *overrideFlagProperty = "org.kde.KActivities.ResourcesDatabase.overrideDatabase"; +const char *overrideFileProperty = "org.kde.KActivities.ResourcesDatabase.overrideDatabaseFile"; + +QString path() +{ + auto app = QCoreApplication::instance(); + + return app->property(overrideFlagProperty).toBool() ? app->property(overrideFileProperty).toString() : defaultPath(); +} + +void overridePath(const QString &path) +{ + auto app = QCoreApplication::instance(); + + app->setProperty(overrideFlagProperty, true); + app->setProperty(overrideFileProperty, path); +} + +void initSchema(Database &database) +{ + QString dbSchemaVersion; + + auto query = database.execQuery( // + QStringLiteral("SELECT value FROM SchemaInfo WHERE key = 'version'")); + + if (query.next()) { + dbSchemaVersion = query.value(0).toString(); + } + + // Early bail-out if the schema is up-to-date + if (dbSchemaVersion == version()) { + return; + } + + // Transition to KF5: + // We left the world of Nepomuk, and all the ontologies went + // across the sea to the Undying Lands. + // This needs to be done before executing the schema() queries + // so that we do not create new (empty) tables and block these + // queries from being executed. + if (dbSchemaVersion < QStringLiteral("2014.04.14")) { + database.execQuery( // + QStringLiteral("ALTER TABLE nuao_DesktopEvent RENAME TO ResourceEvent")); + database.execQuery( // + QStringLiteral("ALTER TABLE kext_ResourceScoreCache RENAME TO ResourceScoreCache")); + } + + database.execQueries(ResourcesDatabaseSchema::schema()); + + // We can not allow empty fields for activity and agent, they need to + // be at least magic values. These do not change the structure + // of the database, but the old data. + if (dbSchemaVersion < QStringLiteral("2015.02.09")) { + const QString updateActivity = QStringLiteral( + "SET usedActivity=':global' " + "WHERE usedActivity IS NULL OR usedActivity = ''"); + + const QString updateAgent = QStringLiteral( + "SET initiatingAgent=':global' " + "WHERE initiatingAgent IS NULL OR initiatingAgent = ''"); + + // When the activity field was empty, it meant the file was + // linked to all activities (aka :global) + database.execQuery(QStringLiteral("UPDATE ResourceLink ") + updateActivity); + + // When the agent field was empty, it meant the file was not + // linked to a specified agent (aka :global) + database.execQuery(QStringLiteral("UPDATE ResourceLink ") + updateAgent); + + // These were not supposed to be empty, but in the case they were, + // deal with them as well + database.execQuery(QStringLiteral("UPDATE ResourceEvent ") + updateActivity); + database.execQuery(QStringLiteral("UPDATE ResourceEvent ") + updateAgent); + database.execQuery(QStringLiteral("UPDATE ResourceScoreCache ") + updateActivity); + database.execQuery(QStringLiteral("UPDATE ResourceScoreCache ") + updateAgent); + } +} + +} // namespace Common +} // namespace ResourcesDatabaseSchema diff --git a/src/common/database/schema/ResourcesDatabaseSchema.h b/src/common/database/schema/ResourcesDatabaseSchema.h new file mode 100644 index 0000000..5fb8d4a --- /dev/null +++ b/src/common/database/schema/ResourcesDatabaseSchema.h @@ -0,0 +1,29 @@ +/* + SPDX-FileCopyrightText: 2011, 2012, 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef RESOURCESDATABASESCHEMA_H +#define RESOURCESDATABASESCHEMA_H + +#include "../Database.h" +#include + +namespace Common +{ +namespace ResourcesDatabaseSchema +{ +QLatin1String version(); + +QStringList schema(); + +QString path(); +void overridePath(const QString &path); + +void initSchema(Database &database); + +} // namespace ResourcesDatabase +} // namespace Common + +#endif // RESOURCESDATABASESCHEMA_H diff --git a/src/common/dbus/common.h b/src/common/dbus/common.h new file mode 100644 index 0000000..e4f6890 --- /dev/null +++ b/src/common/dbus/common.h @@ -0,0 +1,25 @@ +/* + SPDX-FileCopyrightText: 2010, 2011, 2012, 2013, 2014 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef DBUS_COMMON_H +#define DBUS_COMMON_H + +#include +#include + +#define KAMD_DBUS_SERVICE QStringLiteral("org.kde.ActivityManager") + +#define KAMD_DBUS_OBJECT_PATH(A) (sizeof(A) > 2 ? QLatin1String("/ActivityManager/" A) : QLatin1String("/ActivityManager")) + +#define KAMD_DBUS_OBJECT(A) QLatin1String("org.kde.ActivityManager." A) + +#define KAMD_DBUS_INTERFACE(OBJECT_PATH, OBJECT, PARENT) \ + QDBusInterface(KAMD_DBUS_SERVICE, KAMD_DBUS_OBJECT_PATH(OBJECT_PATH), KAMD_DBUS_OBJECT(OBJECT), QDBusConnection::sessionBus(), PARENT) + +#define KAMD_DBUS_DECL_INTERFACE(VAR, OBJECT_PATH, OBJECT) \ + QDBusInterface VAR(KAMD_DBUS_SERVICE, KAMD_DBUS_OBJECT_PATH(OBJECT_PATH), KAMD_DBUS_OBJECT(OBJECT), QDBusConnection::sessionBus(), nullptr) + +#endif // DBUS_COMMON_H diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml new file mode 100644 index 0000000..9d04cdd --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml new file mode 100644 index 0000000..0190ff7 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/specialvalues.h b/src/common/specialvalues.h new file mode 100644 index 0000000..8db6e19 --- /dev/null +++ b/src/common/specialvalues.h @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef SPECIALVALUES_H +#define SPECIALVALUES_H + +#include + +#define GLOBAL_ACTIVITY_TAG QStringLiteral(":global") +#define ANY_ACTIVITY_TAG QStringLiteral(":any") +#define CURRENT_ACTIVITY_TAG QStringLiteral(":current") + +#define GLOBAL_AGENT_TAG QStringLiteral(":global") +#define ANY_AGENT_TAG QStringLiteral(":any") +#define CURRENT_AGENT_TAG QStringLiteral(":current") + +#define ANY_TYPE_TAG QStringLiteral(":any") +#define FILES_TYPE_TAG QStringLiteral(":files") +#define DIRECTORIES_TYPE_TAG QStringLiteral(":directories") + +#endif // SPECIALVALUES_H diff --git a/src/libKActivitiesStats.pc.cmake b/src/libKActivitiesStats.pc.cmake new file mode 100644 index 0000000..e64337f --- /dev/null +++ b/src/libKActivitiesStats.pc.cmake @@ -0,0 +1,12 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${KDE_INSTALL_BINDIR} +libdir=${KDE_INSTALL_LIBDIR} +includedir=${KDE_INSTALL_INCLUDEDIR} + +Name: libKActivitiesStats +Description: libKActivitiesStats is a C++ library for using KDE activities +URL: http://www.kde.org +Requires: Qt6Core +Version: ${KACTIVITIESSTATS_VERSION} +Libs: -L${KDE_INSTALL_LIBDIR} -lKF6ActivitiesStats +Cflags: -I${KDE_INSTALL_INCLUDEDIR} diff --git a/src/query.cpp b/src/query.cpp new file mode 100644 index 0000000..1d4a8ae --- /dev/null +++ b/src/query.cpp @@ -0,0 +1,244 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "query.h" +#include "common/specialvalues.h" +#include +#include + +constexpr int s_defaultCacheSize = 50; + +namespace KActivities +{ +namespace Stats +{ +namespace details +{ +inline void validateTypes(QStringList &types) +{ + // Nothing at the moment + Q_UNUSED(types); +} + +inline void validateAgents(QStringList &agents) +{ + // Nothing at the moment + Q_UNUSED(agents); +} + +inline void validateActivities(QStringList &activities) +{ + // Nothing at the moment + Q_UNUSED(activities); +} + +inline void validateFilters(QStringList &filters) +{ + for (auto it = filters.begin(), end = filters.end(); it != end; ++it) { + it->replace(QLatin1String("'"), QLatin1String("")); + } +} +inline void validateUrlFilters(QStringList &urlFilters) +{ + validateFilters(urlFilters); +} + +inline void validateTitleFilters(QStringList &titleFilters) +{ + validateFilters(titleFilters); +} + +} // namespace details + +class QueryPrivate +{ +public: + QueryPrivate() + : ordering(Terms::HighScoredFirst) + , limit(s_defaultCacheSize) + , offset(0) + { + } + + Terms::Select selection; + QStringList types; + QStringList agents; + QStringList activities; + QStringList urlFilters; + QStringList titleFilters; + Terms::Order ordering; + QDate start, end; + int limit; + int offset; +}; + +Query::Query(Terms::Select selection) + : d(new QueryPrivate()) +{ + d->selection = selection; +} + +Query::Query(Query &&source) + : d(nullptr) +{ + std::swap(d, source.d); +} + +Query::Query(const Query &source) + : d(new QueryPrivate(*source.d)) +{ +} + +Query &Query::operator=(Query source) +{ + std::swap(d, source.d); + return *this; +} + +Query::~Query() +{ + delete d; +} + +bool Query::operator==(const Query &right) const +{ + return selection() == right.selection() // + && types() == right.types() // + && agents() == right.agents() // + && activities() == right.activities() // + && selection() == right.selection() // + && urlFilters() == right.urlFilters() // + && dateStart() == right.dateStart() // + && dateEnd() == right.dateEnd(); +} + +bool Query::operator!=(const Query &right) const +{ + return !(*this == right); +} + +#define IMPLEMENT_QUERY_LIST_FIELD(WHAT, What, Term, Default) \ + void Query::add##WHAT(const QStringList &What) \ + { \ + d->What << What; \ + details::validate##WHAT(d->What); \ + } \ + \ + void Query::set##WHAT(const Terms::Term &What) \ + { \ + d->What = What.values; \ + details::validate##WHAT(d->What); \ + } \ + \ + QStringList Query::What() const \ + { \ + return d->What.size() ? d->What : Default; \ + } \ + \ + void Query::clear##WHAT() \ + { \ + d->What.clear(); \ + } + +IMPLEMENT_QUERY_LIST_FIELD(Types, types, Type, QStringList(ANY_TYPE_TAG)) +IMPLEMENT_QUERY_LIST_FIELD(Agents, agents, Agent, QStringList(CURRENT_AGENT_TAG)) +IMPLEMENT_QUERY_LIST_FIELD(Activities, activities, Activity, QStringList(CURRENT_ACTIVITY_TAG)) +IMPLEMENT_QUERY_LIST_FIELD(UrlFilters, urlFilters, Url, QStringList(QStringLiteral("*"))) +IMPLEMENT_QUERY_LIST_FIELD(TitleFilters, titleFilters, Title, QStringList(QStringLiteral("*"))) + +#undef IMPLEMENT_QUERY_LIST_FIELD + +void Query::setOrdering(Terms::Order ordering) +{ + d->ordering = ordering; +} + +void Query::setSelection(Terms::Select selection) +{ + d->selection = selection; +} + +void Query::setLimit(int limit) +{ + d->limit = limit; +} + +void Query::setOffset(int offset) +{ + d->offset = offset; +} + +void Query::setDate(const Terms::Date &date) +{ + d->start = date.start; + d->end = date.end; +} + +void Query::setDateStart(QDate start) +{ + d->start = start; +} + +void Query::setDateEnd(QDate end) +{ + d->end = end; +} + +Terms::Order Query::ordering() const +{ + return d->ordering; +} + +Terms::Select Query::selection() const +{ + return d->selection; +} + +int Query::limit() const +{ + return d->limit; +} + +int Query::offset() const +{ + Q_ASSERT_X(d->limit > 0, "Query::offset", "Offset can only be specified if limit is set"); + return d->offset; +} + +QDate Query::dateStart() const +{ + return d->start; +} + +QDate Query::dateEnd() const +{ + return d->end; +} +} // namespace Stats +} // namespace KActivities + +namespace KAStats = KActivities::Stats; + +QDebug operator<<(QDebug dbg, const KAStats::Query &query) +{ + using namespace KAStats::Terms; + + // clang-format off + dbg.nospace() + << "Query { " + << query.selection() + << ", " << Type(query.types()) + << ", " << Agent(query.agents()) + << ", " << Activity(query.activities()) + << ", " << Url(query.urlFilters()) + << ", " << Date(query.dateStart(), query.dateEnd()) + << ", " << query.ordering() + << ", Limit: " << query.limit() + << " }"; + // clang-format on + + return dbg; +} diff --git a/src/query.h b/src/query.h new file mode 100644 index 0000000..3de3f26 --- /dev/null +++ b/src/query.h @@ -0,0 +1,209 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_QUERY_H +#define KACTIVITIES_STATS_QUERY_H + +#include + +#include +#include + +#include "plasmaactivitiesstats_export.h" + +#include "terms.h" + +namespace KActivities +{ +namespace Stats +{ +class QueryPrivate; + +/** + * @class KActivities::Stats::Query query.h + * + * The activities system tracks resources (documents, contacts, etc.) + * that the user has used. It also allows linking resources to + * specific activities (like bookmarks, favorites, etc.). + * + * The Query class specifies which resources to return - + * the previously used ones, the linked ones, or to + * combine these two. + * + * It allows filtering the results depending on the resource type, + * the agent (application that reported the usage event, + * see KActivities::ResourceInstance) and the activity the resource + * has been used in, or linked to. It also allows filtering + * on the URL of the resource. + * + * While it can be explicitly instantiated, a preferred approach + * is to use the pipe syntax like this: + * + * @code + * auto query = UsedResources + * | RecentlyUsedFirst + * | Agent::any() + * | Type::any() + * | Activity::current(); + * @endcode + */ +class PLASMAACTIVITIESSTATS_EXPORT Query +{ +public: + Query(Terms::Select selection = Terms::AllResources); + + // The damned rule of five minus one :) + Query(Query &&source); + Query(const Query &source); + Query &operator=(Query source); + ~Query(); + + // Not all are born equal + bool operator==(const Query &right) const; + bool operator!=(const Query &right) const; + + Terms::Select selection() const; + QStringList types() const; + QStringList agents() const; + QStringList activities() const; + + QStringList urlFilters() const; + Terms::Order ordering() const; + QStringList titleFilters() const; + int offset() const; + int limit() const; + QDate dateStart() const; + QDate dateEnd() const; + + void setSelection(Terms::Select selection); + + void addTypes(const QStringList &types); + void addAgents(const QStringList &agents); + void addActivities(const QStringList &activities); + void addUrlFilters(const QStringList &urlFilters); + void addTitleFilters(const QStringList &urlFilters); + /** + * @since 5.62 + */ + void setTypes(const Terms::Type &types); + /** + * @since 5.62 + */ + void setAgents(const Terms::Agent &agents); + /** + * @since 5.62 + */ + void setActivities(const Terms::Activity &activities); + /** + * @since 5.62 + */ + void setUrlFilters(const Terms::Url &urlFilters); + void setOrdering(Terms::Order ordering); + void setOffset(int offset); + void setLimit(int limit); + void setTitleFilters(const Terms::Title &title); + /** + * @since 5.62 + */ + void setDate(const Terms::Date &date); + void setDateStart(QDate date); + void setDateEnd(QDate date); + + void clearTypes(); + void clearAgents(); + void clearActivities(); + void clearUrlFilters(); + void clearTitleFilters(); + + void removeTypes(const QStringList &types); + void removeAgents(const QStringList &agents); + void removeActivities(const QStringList &activities); + void removeUrlFilters(const QStringList &urlFilters); + +private: + inline void addTerm(const Terms::Type &term) + { + addTypes(term.values); + } + + inline void addTerm(const Terms::Agent &term) + { + addAgents(term.values); + } + + inline void addTerm(const Terms::Activity &term) + { + addActivities(term.values); + } + + inline void addTerm(const Terms::Url &term) + { + addUrlFilters(term.values); + } + + inline void addTerm(Terms::Order ordering) + { + setOrdering(ordering); + } + + inline void addTerm(Terms::Select selection) + { + setSelection(selection); + } + + inline void addTerm(Terms::Limit limit) + { + setLimit(limit.value); + } + + inline void addTerm(Terms::Offset offset) + { + setOffset(offset.value); + } + + inline void addTerm(Terms::Date date) + { + setDateStart(date.start); + setDateEnd(date.end); + } + inline void addTerm(Terms::Title title) + { + setTitleFilters(title); + } + +public: + template + friend inline Query operator|(const Query &query, Term &&term) + { + Query result(query); + result.addTerm(term); + return result; + } + + template + friend inline Query operator|(Query &&query, Term &&term) + { + query.addTerm(term); + return std::move(query); + } + +private: + QueryPrivate *d; +}; + +template +inline Query operator|(Terms::Select selection, Term &&term) +{ + return Query(selection) | term; +} + +} // namespace Stats +} // namespace KActivities + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Query &query); + +#endif // KACTIVITIES_STATS_QUERY_H diff --git a/src/resultmodel.cpp b/src/resultmodel.cpp new file mode 100644 index 0000000..422baa6 --- /dev/null +++ b/src/resultmodel.cpp @@ -0,0 +1,1055 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +// Self +#include "resultmodel.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// STL +#include +#include + +// KDE +#include +#include + +// Local +#include "cleaning.h" +#include "plasma-activities-stats-logsettings.h" +#include "plasmaactivities/consumer.h" +#include "resultset.h" +#include "resultwatcher.h" +#include +#include +#include +#include + +#include + +constexpr int s_defaultCacheSize = 50; + +#define QDBG qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivitiesStats(" << (void *)this << ")" + +namespace KActivities +{ +namespace Stats +{ +using Common::Database; + +class ResultModelPrivate +{ +public: + ResultModelPrivate(Query query, const QString &clientId, ResultModel *parent) + : cache(this, clientId, query.limit()) + , query(query) + , watcher(query) + , hasMore(true) + , database(Database::instance(Database::ResourcesDatabase, Database::ReadOnly)) + , q(parent) + { + s_privates << this; + } + + ~ResultModelPrivate() + { + s_privates.removeAll(this); + } + + enum Fetch { + FetchReset, // Remove old data and reload + FetchReload, // Update all data + FetchMore, // Load more data if there is any + }; + + class Cache + { //_ + public: + typedef QList Items; + + Cache(ResultModelPrivate *d, const QString &clientId, int limit) + : d(d) + , m_countLimit(limit) + , m_clientId(clientId) + { + if (!m_clientId.isEmpty()) { + m_configFile = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); + } + } + + ~Cache() + { + } + + inline int size() const + { + return m_items.size(); + } + + inline void setLinkedResultPosition(const QString &resourcePath, int position) + { + if (!m_orderingConfig.isValid()) { + qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "We can not reorder the results, no clientId was specified"; + return; + } + + // Preconditions: + // - cache is ordered properly, first on the user's desired order, + // then on the query specified order + // - the resource that needs to be moved is a linked resource, not + // one that comes from the stats (there are overly many + // corner-cases that need to be covered in order to support + // reordering of the statistics-based resources) + // - the new position for the resource is not outside of the cache + + auto resourcePosition = find(resourcePath); + + if (resourcePosition) { + if (resourcePosition.index == position) { + return; + } + if (resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { + return; + } + } + + // Lets make a list of linked items - we can only reorder them, + // not others + QStringList linkedItems; + + for (const ResultSet::Result &item : std::as_const(m_items)) { + if (item.linkStatus() == ResultSet::Result::NotLinked) { + break; + } + linkedItems << item.resource(); + } + + // We have two options: + // - we are planning to add an item to the desired position, + // but the item is not yet in the model + // - we want to move an existing item + if (!resourcePosition || resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { + linkedItems.insert(position, resourcePath); + + m_fixedOrderedItems = linkedItems; + + } else { + // We can not accept the new position to be outside + // of the linked items area + if (position >= linkedItems.size()) { + position = linkedItems.size() - 1; + } + + Q_ASSERT(resourcePosition.index == linkedItems.indexOf(resourcePath)); + auto oldPosition = linkedItems.indexOf(resourcePath); + + kamd::utils::move_one(linkedItems.begin() + oldPosition, linkedItems.begin() + position); + + // When we change this, the cache is not valid anymore, + // destinationFor will fail and we can not use it + m_fixedOrderedItems = linkedItems; + + // We are prepared to reorder the cache + d->repositionResult(resourcePosition, d->destinationFor(*resourcePosition)); + } + + m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); + m_orderingConfig.sync(); + + // We need to notify others to reload + for (const auto &other : std::as_const(s_privates)) { + if (other != d && other->cache.m_clientId == m_clientId) { + other->fetch(FetchReset); + } + } + } + + inline void debug() const + { + for (const auto &item : m_items) { + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Item: " << item; + } + } + + void loadOrderingConfig(const QString &activityTag) + { + if (!m_configFile) { + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Nothing to load - the client id is empty"; + return; + } + + m_orderingConfig = KConfigGroup(m_configFile, QStringLiteral("ResultModel-OrderingFor-") + m_clientId + activityTag); + + if (m_orderingConfig.hasKey("kactivitiesLinkedItemsOrder")) { + // If we have the ordering defined, use it + m_fixedOrderedItems = m_orderingConfig.readEntry("kactivitiesLinkedItemsOrder", QStringList()); + } else { + // Otherwise, copy the order from the previous activity to this one + m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); + m_orderingConfig.sync(); + } + } + + private: + ResultModelPrivate *const d; + + QList m_items; + int m_countLimit; + + QString m_clientId; + KSharedConfig::Ptr m_configFile; + KConfigGroup m_orderingConfig; + QStringList m_fixedOrderedItems; + + friend QDebug operator<<(QDebug out, const Cache &cache) + { + for (const auto &item : cache.m_items) { + out << "Cache item: " << item << "\n"; + } + + return out; + } + + public: + inline const QStringList &fixedOrderedItems() const + { + return m_fixedOrderedItems; + } + + //_ Fancy iterator, find, lowerBound + struct FindCacheResult { + Cache *const cache; + Items::iterator iterator; + int index; + + FindCacheResult(Cache *cache, Items::iterator iterator) + : cache(cache) + , iterator(iterator) + , index(std::distance(cache->m_items.begin(), iterator)) + { + } + + operator bool() const + { + return iterator != cache->m_items.end(); + } + + ResultSet::Result &operator*() const + { + return *iterator; + } + + ResultSet::Result *operator->() const + { + return &(*iterator); + } + }; + + inline FindCacheResult find(const QString &resource) + { + using namespace kamd::utils::member_matcher; + + // Non-const iterator because the result is constructed from it + return FindCacheResult(this, std::find_if(m_items.begin(), m_items.end(), member(&ResultSet::Result::resource) == resource)); + } + + template + inline FindCacheResult lowerBoundWithSkippedResource(Predicate &&lessThanPredicate) + { + using namespace kamd::utils::member_matcher; + const int count = std::count_if(m_items.cbegin(), m_items.cend(), [&](const ResultSet::Result &result) { + return lessThanPredicate(result, _); + }); + + return FindCacheResult(this, m_items.begin() + count); + + // using namespace kamd::utils::member_matcher; + // + // const auto position = + // std::lower_bound(m_items.begin(), m_items.end(), + // _, std::forward(lessThanPredicate)); + // + // // We seem to have found the position for the item. + // // The problem is that we might have found the same position + // // we were previously at. Since this function is usually used + // // to reposition the result, we might not be in a completely + // // sorted collection, so the next item(s) could be less than us. + // // We could do this with count_if, but it would be slower + // + // if (position >= m_items.cend() - 1) { + // return FindCacheResult(this, position); + // + // } else if (lessThanPredicate(_, *(position + 1))) { + // return FindCacheResult(this, position); + // + // } else { + // return FindCacheResult( + // this, std::lower_bound(position + 1, m_items.end(), + // _, std::forward(lessThanPredicate))); + // } + } + //^ + + inline void insertAt(const FindCacheResult &at, const ResultSet::Result &result) + { + m_items.insert(at.iterator, result); + } + + inline void removeAt(const FindCacheResult &at) + { + m_items.removeAt(at.index); + } + + inline const ResultSet::Result &operator[](int index) const + { + return m_items[index]; + } + + inline void clear() + { + if (m_items.size() == 0) { + return; + } + + d->q->beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); + m_items.clear(); + d->q->endRemoveRows(); + } + + // Algorithm to calculate the edit operations to allow + //_ replaceing items without model reset + inline void replace(const Items &newItems, int from = 0) + { + using namespace kamd::utils::member_matcher; + +#if 0 + QDBG << "======"; + QDBG << "Old items {"; + for (const auto &item : m_items) { + QDBG << item; + } + QDBG << "}"; + + QDBG << "New items to be added at " << from << " {"; + for (const auto &item : newItems) { + QDBG << item; + } + QDBG << "}"; +#endif + + // Based on 'The string to string correction problem + // with block moves' paper by Walter F. Tichy + // + // In essence, it goes like this: + // + // Take the first element from the new list, and try to find + // it in the old one. If you can not find it, it is a new item + // item - send the 'inserted' event. + // If you did find it, test whether the following items also + // match. This detects blocks of items that have moved. + // + // In this example, we find 'b', and then detect the rest of the + // moved block 'b' 'c' 'd' + // + // Old items: a[b c d]e f g + // ^ + // / + // New items: [b c d]a f g + // + // After processing one block, just repeat until the end of the + // new list is reached. + // + // Then remove all remaining elements from the old list. + // + // The main addition here compared to the original papers is that + // our 'strings' can not hold two instances of the same element, + // and that we support updating from arbitrary position. + + auto newBlockStart = newItems.cbegin(); + + // How many items should we add? + // This should remove the need for post-replace-trimming + // in the case where somebody called this with too much new items. + const int maxToReplace = m_countLimit - from; + + if (maxToReplace <= 0) { + return; + } + + const auto newItemsEnd = newItems.size() <= maxToReplace ? newItems.cend() : newItems.cbegin() + maxToReplace; + + // Finding the blocks until we reach the end of the newItems list + // + // from = 4 + // Old items: X Y Z U a b c d e f g + // ^ oldBlockStart points to the first element + // of the currently processed block in the old list + // + // New items: _ _ _ _ b c d a f g + // ^ newBlockStartIndex is the index of the first + // element of the block that is currently being + // processed (with 'from' offset) + + while (newBlockStart != newItemsEnd) { + const int newBlockStartIndex = from + std::distance(newItems.cbegin(), newBlockStart); + + const auto oldBlockStart = + std::find_if(m_items.begin() + from, m_items.end(), member(&ResultSet::Result::resource) == newBlockStart->resource()); + + if (oldBlockStart == m_items.end()) { + // This item was not found in the old cache, so we are + // inserting a new item at the same position it had in + // the newItems array + d->q->beginInsertRows(QModelIndex(), newBlockStartIndex, newBlockStartIndex); + + m_items.insert(newBlockStartIndex, *newBlockStart); + d->q->endInsertRows(); + + // This block contained only one item, move on to find + // the next block - it starts from the next item + ++newBlockStart; + + } else { + // We are searching for a block of matching items. + // This is a reimplementation of std::mismatch that + // accepts two complete ranges that is available only + // since C++14, so we can not use it. + auto newBlockEnd = newBlockStart; + auto oldBlockEnd = oldBlockStart; + + while (newBlockEnd != newItemsEnd && oldBlockEnd != m_items.end() && newBlockEnd->resource() == oldBlockEnd->resource()) { + ++newBlockEnd; + ++oldBlockEnd; + } + + // We have found matching blocks + // [newBlockStart, newBlockEnd) and [oldBlockStart, newBlockEnd) + const int oldBlockStartIndex = std::distance(m_items.begin() + from, oldBlockStart); + + const int blockSize = std::distance(oldBlockStart, oldBlockEnd); + + if (oldBlockStartIndex != newBlockStartIndex) { + // If these blocks do not have the same start, + // we need to send the move event. + + // Note: If there is a crash here, it means we + // are getting a bad query which has duplicate + // results + + d->q->beginMoveRows(QModelIndex(), oldBlockStartIndex, oldBlockStartIndex + blockSize - 1, QModelIndex(), newBlockStartIndex); + + // Moving the items from the old location to the new one + kamd::utils::slide(oldBlockStart, oldBlockEnd, m_items.begin() + newBlockStartIndex); + + d->q->endMoveRows(); + } + + // Skip all the items in this block, and continue with + // the search + newBlockStart = newBlockEnd; + } + } + + // We have avoided the need for trimming for the most part, + // but if the newItems list was shorter than needed, we still + // need to trim the rest. + trim(from + newItems.size()); + + // Check whether we got an item representing a non-existent file, + // if so, schedule its removal from the database + // we want to do this async so that we don't block + QPointer model{d->q}; + std::thread([model, newItems] { + QList missingResources; + for (const auto &item : newItems) { + // QFile.exists() can be incredibly slow (eg. if resource is on remote filesystem) + if (item.resource().startsWith(QLatin1Char('/')) && !QFile(item.resource()).exists()) { + missingResources << item.resource(); + } + } + + if (missingResources.empty()) { + return; + } + + QTimer::singleShot(0, model, [missingResources, model] { + if (model) { + model->forgetResources(missingResources); + } + }); + }).detach(); + } + //^ + + inline void trim() + { + trim(m_countLimit); + } + + inline void trim(int limit) + { + if (m_items.size() <= limit) { + return; + } + + // Example: + // limit is 5, + // current cache (0, 1, 2, 3, 4, 5, 6, 7), size = 8 + // We need to delete from 5 to 7 + + d->q->beginRemoveRows(QModelIndex(), limit, m_items.size() - 1); + m_items.erase(m_items.begin() + limit, m_items.end()); + d->q->endRemoveRows(); + } + + } cache; //^ + + struct FixedItemsLessThan { + //_ Compartor that orders the linked items by user-specified order + typedef kamd::utils::member_matcher::placeholder placeholder; + + enum Ordering { + PartialOrdering, + FullOrdering, + }; + + FixedItemsLessThan(Ordering ordering, const Cache &cache, const QString &matchResource = QString()) + : cache(cache) + , matchResource(matchResource) + , ordering(ordering) + { + } + + bool lessThan(const QString &leftResource, const QString &rightResource) const + { + const auto fixedOrderedItems = cache.fixedOrderedItems(); + + const auto indexLeft = fixedOrderedItems.indexOf(leftResource); + const auto indexRight = fixedOrderedItems.indexOf(rightResource); + + const bool hasLeft = indexLeft != -1; + const bool hasRight = indexRight != -1; + + return (hasLeft && !hasRight) ? true + : (!hasLeft && hasRight) ? false + : (hasLeft && hasRight) ? indexLeft < indexRight + : (ordering == PartialOrdering ? false : leftResource < rightResource); + } + + template + bool operator()(const T &left, placeholder) const + { + return lessThan(left.resource(), matchResource); + } + + template + bool operator()(placeholder, const T &right) const + { + return lessThan(matchResource, right.resource()); + } + + template + bool operator()(const T &left, const V &right) const + { + return lessThan(left.resource(), right.resource()); + } + + const Cache &cache; + const QString matchResource; + Ordering ordering; + //^ + }; + + inline Cache::FindCacheResult destinationFor(const ResultSet::Result &result) + { + using namespace kamd::utils::member_matcher; + using namespace Terms; + + const auto resource = result.resource(); + const auto score = result.score(); + const auto firstUpdate = result.firstUpdate(); + const auto lastUpdate = result.lastUpdate(); + const auto linkStatus = result.linkStatus(); + +#define FIXED_ITEMS_LESS_THAN FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache, resource) +#define ORDER_BY(Field) member(&ResultSet::Result::Field) > Field +#define ORDER_BY_FULL(Field) \ + (query.selection() == Terms::AllResources \ + ? cache.lowerBoundWithSkippedResource(FIXED_ITEMS_LESS_THAN && ORDER_BY(linkStatus) && ORDER_BY(Field) && ORDER_BY(resource)) \ + : cache.lowerBoundWithSkippedResource(FIXED_ITEMS_LESS_THAN && ORDER_BY(Field) && ORDER_BY(resource))) + + const auto destination = query.ordering() == HighScoredFirst ? ORDER_BY_FULL(score) + : query.ordering() == RecentlyUsedFirst ? ORDER_BY_FULL(lastUpdate) + : query.ordering() == RecentlyCreatedFirst ? ORDER_BY_FULL(firstUpdate) + : + /* otherwise */ ORDER_BY_FULL(resource); +#undef ORDER_BY +#undef ORDER_BY_FULL +#undef FIXED_ITEMS_LESS_THAN + + return destination; + } + + inline void removeResult(const Cache::FindCacheResult &result) + { + q->beginRemoveRows(QModelIndex(), result.index, result.index); + cache.removeAt(result); + q->endRemoveRows(); + + if (query.selection() != Terms::LinkedResources) { + fetch(cache.size(), 1); + } + } + + inline void repositionResult(const Cache::FindCacheResult &result, const Cache::FindCacheResult &destination) + { + // We already have the resource in the cache + // So, it is the time for a reshuffle + const int oldPosition = result.index; + int position = destination.index; + + Q_EMIT q->dataChanged(q->index(oldPosition), q->index(oldPosition)); + + if (oldPosition == position) { + return; + } + + if (position > oldPosition) { + position++; + } + + bool moving = q->beginMoveRows(QModelIndex(), oldPosition, oldPosition, QModelIndex(), position); + + kamd::utils::move_one(result.iterator, destination.iterator); + + if (moving) { + q->endMoveRows(); + } + } + + void reload() + { + fetch(FetchReload); + } + + void init() + { + using namespace std::placeholders; + + QObject::connect(&watcher, &ResultWatcher::resultScoreUpdated, q, std::bind(&ResultModelPrivate::onResultScoreUpdated, this, _1, _2, _3, _4)); + QObject::connect(&watcher, &ResultWatcher::resultRemoved, q, std::bind(&ResultModelPrivate::onResultRemoved, this, _1)); + QObject::connect(&watcher, &ResultWatcher::resultLinked, q, std::bind(&ResultModelPrivate::onResultLinked, this, _1)); + QObject::connect(&watcher, &ResultWatcher::resultUnlinked, q, std::bind(&ResultModelPrivate::onResultUnlinked, this, _1)); + + QObject::connect(&watcher, &ResultWatcher::resourceTitleChanged, q, std::bind(&ResultModelPrivate::onResourceTitleChanged, this, _1, _2)); + QObject::connect(&watcher, &ResultWatcher::resourceMimetypeChanged, q, std::bind(&ResultModelPrivate::onResourceMimetypeChanged, this, _1, _2)); + + QObject::connect(&watcher, &ResultWatcher::resultsInvalidated, q, std::bind(&ResultModelPrivate::reload, this)); + + if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { + QObject::connect(&activities, + &KActivities::Consumer::currentActivityChanged, + q, + std::bind(&ResultModelPrivate::onCurrentActivityChanged, this, _1)); + } + + fetch(FetchReset); + } + + void fetch(const int from, int count) + { + using namespace Terms; + + if (from + count > query.limit()) { + count = query.limit() - from; + } + + if (count <= 0) { + return; + } + + // In order to see whether there are more results, we need to pass + // the count increased by one + ResultSet results(query | Offset(from) | Limit(count + 1)); + + auto it = results.begin(); + + Cache::Items newItems; + + while (count-- > 0 && it != results.end()) { + newItems << *it; + ++it; + } + + hasMore = (it != results.end()); + + // We need to sort the new items for the linked resources + // user-defined reordering. This needs only to be a partial sort, + // the main sorting is done by sqlite + if (query.selection() != Terms::UsedResources) { + std::stable_sort(newItems.begin(), newItems.end(), FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache)); + } + + cache.replace(newItems, from); + } + + void fetch(Fetch mode) + { + if (mode == FetchReset) { + // Removing the previously cached data + // and loading all from scratch + cache.clear(); + + const QString activityTag = query.activities().contains(CURRENT_ACTIVITY_TAG) // + ? (QStringLiteral("-ForActivity-") + activities.currentActivity()) + : QStringLiteral("-ForAllActivities"); + + cache.loadOrderingConfig(activityTag); + + // If the user has requested less than 50 entries, only fetch those. If more, they should be fetched in subsequent batches + fetch(0, qMin(s_defaultCacheSize, query.limit())); + + } else if (mode == FetchReload) { + if (cache.size() > s_defaultCacheSize) { + // If the cache is big, we are pretending + // we were asked to reset the model + fetch(FetchReset); + + } else { + // We are only updating the currently + // cached items, nothing more + fetch(0, cache.size()); + } + + } else { // FetchMore + // Load a new batch of data + fetch(cache.size(), s_defaultCacheSize); + } + } + + void onResultScoreUpdated(const QString &resource, double score, uint lastUpdate, uint firstUpdate) + { + QDBG << "ResultModelPrivate::onResultScoreUpdated " + << "result added:" << resource << "score:" << score << "last:" << lastUpdate << "first:" << firstUpdate; + + // This can also be called when the resource score + // has been updated, so we need to check whether + // we already have it in the cache + const auto result = cache.find(resource); + + ResultSet::Result::LinkStatus linkStatus = result ? result->linkStatus() + : query.selection() != Terms::UsedResources ? ResultSet::Result::Unknown + : query.selection() != Terms::LinkedResources ? ResultSet::Result::Linked + : ResultSet::Result::NotLinked; + + if (result) { + // We are only updating a result we already had, + // lets fill out the data and send the update signal. + // Move it if necessary. + + auto &item = *result.iterator; + + item.setScore(score); + item.setLinkStatus(linkStatus); + item.setLastUpdate(lastUpdate); + item.setFirstUpdate(firstUpdate); + + repositionResult(result, destinationFor(item)); + + } else { + // We do not have the resource in the cache, + // lets fill out the data and insert it + // at the desired position + + ResultSet::Result result; + result.setResource(resource); + + result.setTitle(QStringLiteral(" ")); + result.setMimetype(QStringLiteral(" ")); + fillTitleAndMimetype(result); + + result.setScore(score); + result.setLinkStatus(linkStatus); + result.setLastUpdate(lastUpdate); + result.setFirstUpdate(firstUpdate); + + const auto destination = destinationFor(result); + + q->beginInsertRows(QModelIndex(), destination.index, destination.index); + + cache.insertAt(destination, result); + + q->endInsertRows(); + + cache.trim(); + } + } + + void onResultRemoved(const QString &resource) + { + const auto result = cache.find(resource); + + if (!result) { + return; + } + + if (query.selection() == Terms::UsedResources || result->linkStatus() != ResultSet::Result::Linked) { + removeResult(result); + } + } + + void onResultLinked(const QString &resource) + { + if (query.selection() != Terms::UsedResources) { + onResultScoreUpdated(resource, 0, 0, 0); + } + } + + void onResultUnlinked(const QString &resource) + { + const auto result = cache.find(resource); + + if (!result) { + return; + } + + if (query.selection() == Terms::LinkedResources) { + removeResult(result); + + } else if (query.selection() == Terms::AllResources) { + // When the result is unlinked, it might go away or not + // depending on its previous usage + reload(); + } + } + + Query query; + ResultWatcher watcher; + bool hasMore; + + KActivities::Consumer activities; + Common::Database::Ptr database; + + //_ Title and mimetype functions + void fillTitleAndMimetype(ResultSet::Result &result) + { + if (!database) { + return; + } + + auto query = database->execQuery(QStringLiteral("SELECT " + "title, mimetype " + "FROM " + "ResourceInfo " + "WHERE " + "targettedResource = '") + + result.resource() + QStringLiteral("'")); + + // Only one item at most + for (const auto &item : query) { + result.setTitle(item[QStringLiteral("title")].toString()); + result.setMimetype(item[QStringLiteral("mimetype")].toString()); + } + } + + void onResourceTitleChanged(const QString &resource, const QString &title) + { + const auto result = cache.find(resource); + + if (!result) { + return; + } + + result->setTitle(title); + + Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index)); + } + + void onResourceMimetypeChanged(const QString &resource, const QString &mimetype) + { + // TODO: This can add or remove items from the model + + const auto result = cache.find(resource); + + if (!result) { + return; + } + + result->setMimetype(mimetype); + + Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index)); + } + //^ + + void onCurrentActivityChanged(const QString &activity) + { + Q_UNUSED(activity); + // If the current activity has changed, and + // the query lists items for the ':current' one, + // reset the model (not a simple refresh this time) + if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { + fetch(FetchReset); + } + } + +private: + ResultModel *const q; + static QList s_privates; +}; + +QList ResultModelPrivate::s_privates; + +ResultModel::ResultModel(Query query, QObject *parent) + : QAbstractListModel(parent) + , d(new ResultModelPrivate(query, QString(), this)) +{ + d->init(); +} + +ResultModel::ResultModel(Query query, const QString &clientId, QObject *parent) + : QAbstractListModel(parent) + , d(new ResultModelPrivate(query, clientId, this)) +{ + d->init(); +} + +ResultModel::~ResultModel() +{ + delete d; +} + +QHash ResultModel::roleNames() const +{ + return { + {ResourceRole, "resource"}, + {TitleRole, "title"}, + {ScoreRole, "score"}, + {FirstUpdateRole, "created"}, + {LastUpdateRole, "modified"}, + {LinkStatusRole, "linkStatus"}, + {LinkedActivitiesRole, "linkedActivities"}, + {MimeType, "mimeType"}, + }; +} + +QVariant ResultModel::data(const QModelIndex &item, int role) const +{ + const auto row = item.row(); + + if (row < 0 || row >= d->cache.size()) { + return QVariant(); + } + + const auto &result = d->cache[row]; + + return role == Qt::DisplayRole ? QString(result.title() + QStringLiteral(" ") + result.resource() + QStringLiteral(" - ") + + QString::number(result.linkStatus()) + QStringLiteral(" - ") + QString::number(result.score())) + : role == ResourceRole ? result.resource() + : role == TitleRole ? result.title() + : role == ScoreRole ? result.score() + : role == FirstUpdateRole ? result.firstUpdate() + : role == LastUpdateRole ? result.lastUpdate() + : role == LinkStatusRole ? result.linkStatus() + : role == LinkedActivitiesRole ? result.linkedActivities() + : role == MimeType ? result.mimetype() + : role == Agent ? result.agent() + : QVariant(); +} + +QVariant ResultModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section); + Q_UNUSED(orientation); + Q_UNUSED(role); + return QVariant(); +} + +int ResultModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : d->cache.size(); +} + +void ResultModel::fetchMore(const QModelIndex &parent) +{ + if (parent.isValid()) { + return; + } + d->fetch(ResultModelPrivate::FetchMore); +} + +bool ResultModel::canFetchMore(const QModelIndex &parent) const +{ + return parent.isValid() ? false : d->cache.size() >= d->query.limit() ? false : d->hasMore; +} + +void ResultModel::forgetResources(const QList &resources) +{ + const auto lstActivities = d->query.activities(); + for (const QString &activity : lstActivities) { + const auto lstAgents = d->query.agents(); + for (const QString &agent : lstAgents) { + for (const QString &resource : resources) { + Stats::forgetResource(activity, agent == CURRENT_AGENT_TAG ? QCoreApplication::applicationName() : agent, resource); + } + } + } +} + +void ResultModel::forgetResource(const QString &resource) +{ + ResultModel::forgetResources({resource}); +} + +void ResultModel::forgetResource(int row) +{ + if (row >= d->cache.size()) { + return; + } + const auto lstActivities = d->query.activities(); + for (const QString &activity : lstActivities) { + const auto lstAgents = d->query.agents(); + for (const QString &agent : lstAgents) { + Stats::forgetResource(activity, agent == CURRENT_AGENT_TAG ? QCoreApplication::applicationName() : agent, d->cache[row].resource()); + } + } +} + +void ResultModel::forgetAllResources() +{ + Stats::forgetResources(d->query); +} + +void ResultModel::setResultPosition(const QString &resource, int position) +{ + d->cache.setLinkedResultPosition(resource, position); +} + +void ResultModel::sortItems(Qt::SortOrder sortOrder) +{ + // TODO + Q_UNUSED(sortOrder); +} + +void ResultModel::linkToActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) +{ + d->watcher.linkToActivity(resource, activity, agent); +} + +void ResultModel::unlinkFromActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) +{ + d->watcher.unlinkFromActivity(resource, activity, agent); +} + +} // namespace Stats +} // namespace KActivities + +#include "moc_resultmodel.cpp" diff --git a/src/resultmodel.h b/src/resultmodel.h new file mode 100644 index 0000000..d7c0f62 --- /dev/null +++ b/src/resultmodel.h @@ -0,0 +1,124 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_RESULTMODEL_H +#define KACTIVITIES_STATS_RESULTMODEL_H + +// Qt +#include +#include + +// Local +#include "query.h" + +class QModelIndex; +class QDBusPendingCallWatcher; + +class KConfigGroup; + +namespace KActivities +{ +namespace Stats +{ +class ResultModelPrivate; + +/** + * @class KActivities::Stats::ResultModel resultmodel.h + * + * Provides a model which displays the resources matching + * the specified Query. + */ +class PLASMAACTIVITIESSTATS_EXPORT ResultModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ResultModel(Query query, QObject *parent = nullptr); + ResultModel(Query query, const QString &clientId, QObject *parent = nullptr); + ~ResultModel() override; + + enum Roles { + ResourceRole = Qt::UserRole, + TitleRole = Qt::UserRole + 1, + ScoreRole = Qt::UserRole + 2, + FirstUpdateRole = Qt::UserRole + 3, + LastUpdateRole = Qt::UserRole + 4, + LinkStatusRole = Qt::UserRole + 5, + LinkedActivitiesRole = Qt::UserRole + 6, + MimeType = Qt::UserRole + 7, // @since 5.77 + Agent = Qt::UserRole + 8, // @since 6.0 + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void fetchMore(const QModelIndex &parent) override; + bool canFetchMore(const QModelIndex &parent) const override; + + void linkToActivity(const QUrl &resource, + const Terms::Activity &activity = Terms::Activity(QStringList()), + const Terms::Agent &agent = Terms::Agent(QStringList())); + + void unlinkFromActivity(const QUrl &resource, + const Terms::Activity &activity = Terms::Activity(QStringList()), + const Terms::Agent &agent = Terms::Agent(QStringList())); + +public Q_SLOTS: + /** + * Removes the specified resource from the history + */ + void forgetResource(const QString &resource); + + /** + * Removes specified list of resources from the history + */ + void forgetResources(const QList &resources); + + /** + * Removes the specified resource from the history + */ + void forgetResource(int row); + + /** + * Clears the history of all resources that match the current + * model query + */ + void forgetAllResources(); + + /** + * Moves the resource to the specified position. + * + * Note that this only applies to the linked resources + * since the recently/frequently used ones have + * their natural order. + * + * @note This requires the clientId to be specified on construction. + */ + void setResultPosition(const QString &resource, int position); + + /** + * Sort the items by title. + * + * Note that this only affects the linked resources + * since the recently/frequently used ones have + * their natural order. + * + * @note This requires the clientId to be specified on construction. + */ + void sortItems(Qt::SortOrder sortOrder); + +private: + friend class ResultModelPrivate; + ResultModelPrivate *const d; +}; + +} // namespace Stats +} // namespace KActivities + +#endif // KACTIVITIES_STATS_RESULTMODEL_H diff --git a/src/resultset.cpp b/src/resultset.cpp new file mode 100644 index 0000000..ec22df2 --- /dev/null +++ b/src/resultset.cpp @@ -0,0 +1,575 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "resultset.h" + +// Qt +#include +#include +#include +#include +#include + +// Local +#include "plasma-activities-stats-logsettings.h" +#include +#include +#include +#include + +// STL +#include +#include +#include + +// KActivities +#include "activitiessync_p.h" + +#define DEBUG_QUERIES 0 + +namespace KActivities +{ +namespace Stats +{ +using namespace Terms; + +class ResultSet_ResultPrivate +{ +public: + QString resource; + QString title; + QString mimetype; + double score; + uint lastUpdate; + uint firstUpdate; + ResultSet::Result::LinkStatus linkStatus; + QStringList linkedActivities; + QString agent; +}; + +ResultSet::Result::Result() + : d(new ResultSet_ResultPrivate()) +{ +} + +ResultSet::Result::Result(Result &&result) + : d(result.d) +{ + result.d = nullptr; +} + +ResultSet::Result::Result(const Result &result) + : d(new ResultSet_ResultPrivate(*result.d)) +{ +} + +ResultSet::Result &ResultSet::Result::operator=(Result result) +{ + std::swap(d, result.d); + + return *this; +} + +ResultSet::Result::~Result() +{ + delete d; +} + +#define CREATE_GETTER_AND_SETTER(Type, Name, Set) \ + Type ResultSet::Result::Name() const \ + { \ + return d->Name; \ + } \ + \ + void ResultSet::Result::Set(Type Name) \ + { \ + d->Name = Name; \ + } + +CREATE_GETTER_AND_SETTER(QString, resource, setResource) +CREATE_GETTER_AND_SETTER(QString, title, setTitle) +CREATE_GETTER_AND_SETTER(QString, mimetype, setMimetype) +CREATE_GETTER_AND_SETTER(double, score, setScore) +CREATE_GETTER_AND_SETTER(uint, lastUpdate, setLastUpdate) +CREATE_GETTER_AND_SETTER(uint, firstUpdate, setFirstUpdate) +CREATE_GETTER_AND_SETTER(ResultSet::Result::LinkStatus, linkStatus, setLinkStatus) +CREATE_GETTER_AND_SETTER(QStringList, linkedActivities, setLinkedActivities) +CREATE_GETTER_AND_SETTER(QString, agent, setAgent) + +#undef CREATE_GETTER_AND_SETTER + +QUrl ResultSet::Result::url() const +{ + if (QDir::isAbsolutePath(d->resource)) { + return QUrl::fromLocalFile(d->resource); + } else { + return QUrl(d->resource); + } +} + +class ResultSetPrivate +{ +public: + Common::Database::Ptr database; + QSqlQuery query; + Query queryDefinition; + + mutable ActivitiesSync::ConsumerPtr activities; + + void initQuery() + { + if (!database || query.isActive()) { + return; + } + + auto selection = queryDefinition.selection(); + + query = database->execQuery(replaceQueryParameters( // + selection == LinkedResources ? linkedResourcesQuery() + : selection == UsedResources ? usedResourcesQuery() + : selection == AllResources ? allResourcesQuery() + : QString())); + + if (query.lastError().isValid()) { + qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "[Error at ResultSetPrivate::initQuery]: " << query.lastError(); + } + } + + QString agentClause(const QString &agent) const + { + if (agent == QLatin1String(":any")) { + return QStringLiteral("1"); + } + + return QLatin1String("agent = '") + + Common::escapeSqliteLikePattern(agent == QLatin1String(":current") ? QCoreApplication::instance()->applicationName() : agent) + + QLatin1String("'"); + } + + QString activityClause(const QString &activity) const + { + if (activity == QLatin1String(":any")) { + return QStringLiteral("1"); + } + + return QLatin1String("activity = '") + // + Common::escapeSqliteLikePattern(activity == QLatin1String(":current") ? ActivitiesSync::currentActivity(activities) : activity) + + QLatin1String("'"); + } + + inline QString starPattern(const QString &pattern) const + { + return Common::parseStarPattern(pattern, QStringLiteral("%"), [](QString str) { + return str.replace(QLatin1String("%"), QLatin1String("\\%")).replace(QLatin1String("_"), QLatin1String("\\_")); + }); + } + + QString urlFilterClause(const QString &urlFilter) const + { + if (urlFilter == QLatin1String("*")) { + return QStringLiteral("1"); + } + + return QLatin1String("resource LIKE '") + Common::starPatternToLike(urlFilter) + QLatin1String("' ESCAPE '\\'"); + } + + QString mimetypeClause(const QString &mimetype) const + { + if (mimetype == ANY_TYPE_TAG || mimetype == QLatin1String("*")) { + return QStringLiteral("1"); + + } else if (mimetype == FILES_TYPE_TAG) { + return QStringLiteral("mimetype != 'inode/directory' AND mimetype != ''"); + } else if (mimetype == DIRECTORIES_TYPE_TAG) { + return QStringLiteral("mimetype = 'inode/directory'"); + } + + return QLatin1String("mimetype LIKE '") + Common::starPatternToLike(mimetype) + QLatin1String("' ESCAPE '\\'"); + } + + QString dateClause(QDate start, QDate end) const + { + if (end.isNull()) { + // only date filtering + return QLatin1String("DATE(re.start, 'unixepoch') = '") + start.toString(Qt::ISODate) + QLatin1String("' "); + } else { + // date range filtering + return QLatin1String("DATE(re.start, 'unixepoch') >= '") + start.toString(Qt::ISODate) + QLatin1String("' AND DATE(re.start, 'unixepoch') <= '") + + end.toString(Qt::ISODate) + QLatin1String("' "); + } + } + QString titleClause(const QString titleFilter) const + { + if (titleFilter == QLatin1String("*")) { + return QStringLiteral("1"); + } + + return QLatin1String("title LIKE '") + Common::starPatternToLike(titleFilter) + QLatin1String("' ESCAPE '\\'"); + } + + QString resourceEventJoinClause() const + { + return QStringLiteral(R"( + LEFT JOIN + ResourceEvent re + ON from_table.targettedResource = re.targettedResource + AND from_table.usedActivity = re.usedActivity + AND from_table.initiatingAgent = re.initiatingAgent + )"); + } + + /** + * Transforms the input list's elements with the f member method, + * and returns the resulting list + */ + template + inline QStringList transformedList(const QStringList &input, F f) const + { + using namespace std::placeholders; + + QStringList result; + std::transform(input.cbegin(), input.cend(), std::back_inserter(result), std::bind(f, this, _1)); + + return result; + } + + QString limitOffsetSuffix() const + { + QString result; + + const int limit = queryDefinition.limit(); + if (limit > 0) { + result += QLatin1String(" LIMIT ") + QString::number(limit); + + const int offset = queryDefinition.offset(); + if (offset > 0) { + result += QLatin1String(" OFFSET ") + QString::number(offset); + } + } + + return result; + } + + inline QString replaceQueryParameters(const QString &_query) const + { + // ORDER BY column + auto ordering = queryDefinition.ordering(); + QString orderingColumn = QLatin1String("linkStatus DESC, ") + + (ordering == HighScoredFirst ? QLatin1String("score DESC,") + : ordering == RecentlyCreatedFirst ? QLatin1String("firstUpdate DESC,") + : ordering == RecentlyUsedFirst ? QLatin1String("lastUpdate DESC,") + : ordering == OrderByTitle ? QLatin1String("title ASC,") + : QLatin1String()); + + // WHERE clause for filtering on agents + QStringList agentsFilter = transformedList(queryDefinition.agents(), &ResultSetPrivate::agentClause); + + // WHERE clause for filtering on activities + QStringList activitiesFilter = transformedList(queryDefinition.activities(), &ResultSetPrivate::activityClause); + + // WHERE clause for filtering on resource URLs + QStringList urlFilter = transformedList(queryDefinition.urlFilters(), &ResultSetPrivate::urlFilterClause); + + // WHERE clause for filtering on resource mime + QStringList mimetypeFilter = transformedList(queryDefinition.types(), &ResultSetPrivate::mimetypeClause); + QStringList titleFilter = transformedList(queryDefinition.titleFilters(), &ResultSetPrivate::titleClause); + + QString dateColumn = QStringLiteral("1"); + QString resourceEventJoin; + // WHERE clause for access date filtering and ResourceEvent table Join + if (!queryDefinition.dateStart().isNull()) { + dateColumn = dateClause(queryDefinition.dateStart(), queryDefinition.dateEnd()); + + resourceEventJoin = resourceEventJoinClause(); + } + + auto queryString = _query; + + queryString.replace(QLatin1String("ORDER_BY_CLAUSE"), QLatin1String("ORDER BY $orderingColumn resource ASC")) + .replace(QLatin1String("LIMIT_CLAUSE"), limitOffsetSuffix()); + + const QString replacedQuery = + queryString.replace(QLatin1String("$orderingColumn"), orderingColumn) + .replace(QLatin1String("$agentsFilter"), agentsFilter.join(QStringLiteral(" OR "))) + .replace(QLatin1String("$activitiesFilter"), activitiesFilter.join(QStringLiteral(" OR "))) + .replace(QLatin1String("$urlFilter"), urlFilter.join(QStringLiteral(" OR "))) + .replace(QLatin1String("$mimetypeFilter"), mimetypeFilter.join(QStringLiteral(" OR "))) + .replace(QLatin1String("$resourceEventJoin"), resourceEventJoin) + .replace(QLatin1String("$dateFilter"), dateColumn) + .replace(QLatin1String("$titleFilter"), titleFilter.isEmpty() ? QStringLiteral("1") : titleFilter.join(QStringLiteral(" OR "))); + return kamd::utils::debug_and_return(DEBUG_QUERIES, "Query: ", replacedQuery); + } + + static const QString &linkedResourcesQuery() + { + // TODO: We need to correct the scores based on the time that passed + // since the cache was last updated, although, for this query, + // scores are not that important. + static const QString queryString = QStringLiteral(R"( + SELECT + from_table.targettedResource as resource + , SUM(rsc.cachedScore) as score + , MIN(rsc.firstUpdate) as firstUpdate + , MAX(rsc.lastUpdate) as lastUpdate + , from_table.usedActivity as activity + , from_table.initiatingAgent as agent + , COALESCE(ri.title, from_table.targettedResource) as title + , ri.mimetype as mimetype + , 2 as linkStatus + + FROM + ResourceLink from_table + LEFT JOIN + ResourceScoreCache rsc + ON from_table.targettedResource = rsc.targettedResource + AND from_table.usedActivity = rsc.usedActivity + AND from_table.initiatingAgent = rsc.initiatingAgent + LEFT JOIN + ResourceInfo ri + ON from_table.targettedResource = ri.targettedResource + + $resourceEventJoin + + WHERE + ($agentsFilter) + AND ($activitiesFilter) + AND ($urlFilter) + AND ($mimetypeFilter) + AND ($dateFilter) + AND ($titleFilter) + + GROUP BY resource, title + + ORDER_BY_CLAUSE + LIMIT_CLAUSE + )"); + + return queryString; + } + + static const QString &usedResourcesQuery() + { + // TODO: We need to correct the scores based on the time that passed + // since the cache was last updated + static const QString queryString = QStringLiteral(R"( + SELECT + from_table.targettedResource as resource + , SUM(from_table.cachedScore) as score + , MIN(from_table.firstUpdate) as firstUpdate + , MAX(from_table.lastUpdate) as lastUpdate + , from_table.usedActivity as activity + , from_table.initiatingAgent as agent + , COALESCE(ri.title, from_table.targettedResource) as title + , ri.mimetype as mimetype + , 1 as linkStatus + + FROM + ResourceScoreCache from_table + LEFT JOIN + ResourceInfo ri + ON from_table.targettedResource = ri.targettedResource + + $resourceEventJoin + + WHERE + ($agentsFilter) + AND ($activitiesFilter) + AND ($urlFilter) + AND ($mimetypeFilter) + AND ($dateFilter) + AND ($titleFilter) + + GROUP BY resource, title + + ORDER_BY_CLAUSE + LIMIT_CLAUSE + )"); + + return queryString; + } + + static const QString &allResourcesQuery() + { + // TODO: We need to correct the scores based on the time that passed + // since the cache was last updated, although, for this query, + // scores are not that important. + static const QString queryString = QStringLiteral(R"( + WITH + LinkedResourcesResults AS ( + SELECT from_table.targettedResource as resource + , rsc.cachedScore as score + , rsc.firstUpdate as firstUpdate + , rsc.lastUpdate as lastUpdate + , from_table.usedActivity as activity + , from_table.initiatingAgent as agent + , 2 as linkStatus + + FROM + ResourceLink from_table + + LEFT JOIN + ResourceScoreCache rsc + ON from_table.targettedResource = rsc.targettedResource + AND from_table.usedActivity = rsc.usedActivity + AND from_table.initiatingAgent = rsc.initiatingAgent + + $resourceEventJoin + + WHERE + ($agentsFilter) + AND ($activitiesFilter) + AND ($urlFilter) + AND ($mimetypeFilter) + AND ($dateFilter) + AND ($titleFilter) + ), + + UsedResourcesResults AS ( + SELECT from_table.targettedResource as resource + , from_table.cachedScore as score + , from_table.firstUpdate as firstUpdate + , from_table.lastUpdate as lastUpdate + , from_table.usedActivity as activity + , from_table.initiatingAgent as agent + , 0 as linkStatus + + FROM + ResourceScoreCache from_table + + $resourceEventJoin + + WHERE + ($agentsFilter) + AND ($activitiesFilter) + AND ($urlFilter) + AND ($mimetypeFilter) + AND ($dateFilter) + AND ($titleFilter) + ), + + CollectedResults AS ( + SELECT * + FROM LinkedResourcesResults + + UNION + + SELECT * + FROM UsedResourcesResults + WHERE resource NOT IN (SELECT resource FROM LinkedResourcesResults) + ) + + SELECT + resource + , SUM(score) as score + , MIN(firstUpdate) as firstUpdate + , MAX(lastUpdate) as lastUpdate + , activity + , agent + , COALESCE(ri.title, resource) as title + , ri.mimetype as mimetype + , linkStatus + + FROM CollectedResults cr + + LEFT JOIN + ResourceInfo ri + ON cr.resource = ri.targettedResource + + GROUP BY resource, title + + ORDER_BY_CLAUSE + LIMIT_CLAUSE + )"); + + return queryString; + } + + ResultSet::Result currentResult() const + { + ResultSet::Result result; + + if (!database || !query.isActive()) { + return result; + } + + result.setResource(query.value(QStringLiteral("resource")).toString()); + result.setTitle(query.value(QStringLiteral("title")).toString()); + result.setMimetype(query.value(QStringLiteral("mimetype")).toString()); + result.setScore(query.value(QStringLiteral("score")).toDouble()); + result.setLastUpdate(query.value(QStringLiteral("lastUpdate")).toUInt()); + result.setFirstUpdate(query.value(QStringLiteral("firstUpdate")).toUInt()); + result.setAgent(query.value(QStringLiteral("agent")).toString()); + + result.setLinkStatus(static_cast(query.value(QStringLiteral("linkStatus")).toUInt())); + + auto linkedActivitiesQuery = database->createQuery(); + + linkedActivitiesQuery.prepare(QStringLiteral(R"( + SELECT usedActivity + FROM ResourceLink + WHERE targettedResource = :resource + )")); + + linkedActivitiesQuery.bindValue(QStringLiteral(":resource"), result.resource()); + linkedActivitiesQuery.exec(); + + QStringList linkedActivities; + for (const auto &item : linkedActivitiesQuery) { + linkedActivities << item[0].toString(); + } + + result.setLinkedActivities(linkedActivities); + // qDebug(PLASMA_ACTIVITIES_STATS_LOG) << result.resource() << "linked to activities" << result.linkedActivities(); + + return result; + } +}; + +ResultSet::ResultSet(Query queryDefinition) + : d(new ResultSetPrivate()) +{ + using namespace Common; + + d->database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); + + if (!(d->database)) { + qCWarning(PLASMA_ACTIVITIES_STATS_LOG) << "Plasma Activities ERROR: There is no database. This probably means " + "that you do not have the Activity Manager running, or that " + "something else is broken on your system. Recent documents and " + "alike will not work!"; + } + + d->queryDefinition = queryDefinition; + + d->initQuery(); +} + +ResultSet::ResultSet(ResultSet &&source) + : d(nullptr) +{ + std::swap(d, source.d); +} + +ResultSet::~ResultSet() +{ + delete d; +} + +ResultSet::Result ResultSet::at(int index) const +{ + if (!d->query.isActive()) { + return Result(); + } + + d->query.seek(index); + + return d->currentResult(); +} + +} // namespace Stats +} // namespace KActivities + +#include "resultset_iterator.cpp" diff --git a/src/resultset.h b/src/resultset.h new file mode 100644 index 0000000..7a6b6d4 --- /dev/null +++ b/src/resultset.h @@ -0,0 +1,250 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_RESULTSET +#define KACTIVITIES_STATS_RESULTSET + +#include "query.h" + +#include + +namespace KActivities +{ +namespace Stats +{ +class ResultSetPrivate; +class ResultSet_ResultPrivate; +class ResultSet_IteratorPrivate; + +/** + * @class KActivities::Stats::ResultSet resultset.h + * + * Class that can query the KActivities usage tracking mechanism + * for resources. + * + * Note: It is important to note that you should not create + * long-living instances of ResultSet. It might lock the database + * and break proper updating mechanisms. If you want a list of results + * that automatically get updated, use ResultModel. + * + * ResultSet is meant to be used when you just need to fetch a few results + * like this: + * + * @code + * auto results = ResultSet(AllResources | Agent("org.kde.kate")); + * for (const auto &result: results) { + * // ... + * } + * @endcode + */ +class PLASMAACTIVITIESSTATS_EXPORT ResultSet +{ +public: + /** + * Structure containing data of one of the results + */ + class Result + { + public: + Result(); + ~Result(); + + Result(Result &&result); + Result(const Result &result); + Result &operator=(Result result); + + enum LinkStatus { + NotLinked = 0, + Unknown = 1, + Linked = 2, + }; + + // TODO: KF6 rething the function names, and maybe their signature, perhaps leverage std::variant or std::optional to add semantics to the API + QString resource() const; ///< String representation of resource (can represent an url or a path) + QUrl url() const; ///< Url representation of a resource based on internal resource, readonly, @since 5.64 + QString title() const; ///< Title of the resource, or URL if title is not known + QString mimetype() const; ///< Mimetype of the resource, or URL if title is not known + double score() const; ///< The score calculated based on the usage statistics + uint lastUpdate() const; ///< Timestamp of the last update + uint firstUpdate() const; ///< Timestamp of the first update + LinkStatus linkStatus() const; ///< Differentiates between linked and non-linked resources in mixed queries + QStringList linkedActivities() const; ///< Contains the activities this resource is linked to for the queries that care about resource linking + QString agent() const; /// Contains the initiating agent for this resource + + void setResource(QString resource); + void setTitle(QString title); + void setMimetype(QString mimetype); + void setScore(double score); + void setLastUpdate(uint lastUpdate); + void setFirstUpdate(uint firstUpdate); + void setLinkStatus(LinkStatus linkedStatus); + void setLinkedActivities(QStringList activities); + void setAgent(QString agent); + + private: + ResultSet_ResultPrivate *d; + }; + + /** + * ResultSet is a container. This notifies the generic algorithms + * from STL and others of the contained type. + */ + typedef Result value_type; + + /** + * Creates the ResultSet from the specified query + */ + ResultSet(Query query); + + ResultSet(ResultSet &&source); + ResultSet(const ResultSet &source) = delete; + ResultSet &operator=(ResultSet source) = delete; + ~ResultSet(); + + /** + * @returns a result at the specified index + * @param index of the result + * @note You should use iterators instead + */ + Result at(int index) const; + + // Iterators + + /** + * An STL-style constant forward iterator for accessing the results in a ResultSet + * TODO: Consider making this to be more than just forward iterator. + * Maybe even a random-access one. + */ + class const_iterator + { + public: + typedef std::random_access_iterator_tag iterator_category; + typedef int difference_type; + + typedef const Result value_type; + typedef const Result &reference; + typedef const Result *pointer; + + const_iterator(); + const_iterator(const const_iterator &source); + const_iterator &operator=(const const_iterator &source); + + ~const_iterator(); + + bool isSourceValid() const; + + reference operator*() const; + pointer operator->() const; + + // prefix + const_iterator &operator++(); + // postfix + const_iterator operator++(int); + + // prefix + const_iterator &operator--(); + // postfix + const_iterator operator--(int); + + const_iterator operator+(difference_type n) const; + const_iterator &operator+=(difference_type n); + + const_iterator operator-(difference_type n) const; + const_iterator &operator-=(difference_type n); + + reference operator[](difference_type n) const; + + PLASMAACTIVITIESSTATS_EXPORT friend bool operator==(const const_iterator &left, const const_iterator &right); + PLASMAACTIVITIESSTATS_EXPORT friend bool operator!=(const const_iterator &left, const const_iterator &right); + + PLASMAACTIVITIESSTATS_EXPORT friend bool operator<(const const_iterator &left, const const_iterator &right); + PLASMAACTIVITIESSTATS_EXPORT friend bool operator>(const const_iterator &left, const const_iterator &right); + + PLASMAACTIVITIESSTATS_EXPORT friend bool operator<=(const const_iterator &left, const const_iterator &right); + PLASMAACTIVITIESSTATS_EXPORT friend bool operator>=(const const_iterator &left, const const_iterator &right); + + PLASMAACTIVITIESSTATS_EXPORT friend difference_type operator-(const const_iterator &left, const const_iterator &right); + + private: + const_iterator(const ResultSet *resultSet, int currentRow); + + friend class ResultSet; + + ResultSet_IteratorPrivate *const d; + }; + + /** + * @returns a constant iterator pointing to the start of the collection + * (to the first item) + * @note as usual in C++, the range of the collection is [begin, end) + */ + const_iterator begin() const; + /** + * @returns a constant iterator pointing to the end of the collection + * (after the last item) + * @note as usual in C++, the range of the collection is [begin, end) + */ + const_iterator end() const; + + /** + * Alias for begin + */ + inline const_iterator cbegin() const + { + return begin(); + } + /** + * Alias for end + */ + inline const_iterator cend() const + { + return end(); + } + + /** + * Alias for begin + */ + inline const_iterator constBegin() const + { + return cbegin(); + } + /** + * Alias for end + */ + inline const_iterator constEnd() const + { + return cend(); + } + +private: + friend class ResultSet_IteratorPrivate; + ResultSetPrivate *d; +}; + +bool PLASMAACTIVITIESSTATS_EXPORT operator==(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); +bool PLASMAACTIVITIESSTATS_EXPORT operator!=(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); + +bool PLASMAACTIVITIESSTATS_EXPORT operator<(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); +bool PLASMAACTIVITIESSTATS_EXPORT operator>(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); + +bool PLASMAACTIVITIESSTATS_EXPORT operator<=(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); +bool PLASMAACTIVITIESSTATS_EXPORT operator>=(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); + +ResultSet::const_iterator::difference_type PLASMAACTIVITIESSTATS_EXPORT operator-(const ResultSet::const_iterator &left, const ResultSet::const_iterator &right); + +inline QDebug operator<<(QDebug out, const ResultSet::Result &result) +{ + return out << (result.linkStatus() == ResultSet::Result::Linked ? "⊤" + : result.linkStatus() == ResultSet::Result::NotLinked ? "⊥" + : "?") + << result.score() << (result.title() != result.resource() ? result.title() : QString()) << result.lastUpdate() + << QStringView(result.resource()).right(20); +} + +} // namespace Stats +} // namespace KActivities + +#endif // KACTIVITIES_STATS_RESULTSET diff --git a/src/resultset_iterator.cpp b/src/resultset_iterator.cpp new file mode 100644 index 0000000..fae3521 --- /dev/null +++ b/src/resultset_iterator.cpp @@ -0,0 +1,241 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include + +namespace KActivities +{ +namespace Stats +{ +using namespace Terms; + +typedef ResultSet::const_iterator iterator; + +// Iterator + +class ResultSet_IteratorPrivate +{ +public: + ResultSet_IteratorPrivate(const ResultSet *resultSet, int currentRow = -1) + : resultSet(resultSet) + , currentRow(currentRow) + { + updateValue(); + } + + const ResultSet *resultSet; + int currentRow; + std::optional currentValue; + + inline void moveTo(int row) + { + if (row == currentRow) + return; + currentRow = row; + updateValue(); + } + + inline void moveBy(int row) + { + moveTo(currentRow + row); + } + + void updateValue() + { + if (!resultSet || !resultSet->d->query.seek(currentRow)) { + currentValue.reset(); + + } else { + auto value = resultSet->d->currentResult(); + currentValue = std::move(value); + } + } + + friend void swap(ResultSet_IteratorPrivate &left, ResultSet_IteratorPrivate &right) + { + std::swap(left.resultSet, right.resultSet); + std::swap(left.currentRow, right.currentRow); + std::swap(left.currentValue, right.currentValue); + } + + bool operator==(const ResultSet_IteratorPrivate &other) const + { + bool thisValid = currentValue.has_value(); + bool otherValid = other.currentValue.has_value(); + + return + // If one is valid, and the other is not, + // they are not equal + thisValid != otherValid ? false : + + // If both are invalid, they are equal + !thisValid ? true + : + + // Otherwise, really compare + resultSet == other.resultSet && currentRow == other.currentRow; + } + + bool isValid() const + { + return currentValue.has_value(); + } + + static bool sameSource(const ResultSet_IteratorPrivate &left, const ResultSet_IteratorPrivate &right) + { + return left.resultSet == right.resultSet && left.resultSet != nullptr; + } +}; + +iterator::const_iterator(const ResultSet *resultSet, int currentRow) + : d(new ResultSet_IteratorPrivate(resultSet, currentRow)) +{ +} + +iterator::const_iterator() + : d(new ResultSet_IteratorPrivate(nullptr, -1)) +{ +} + +iterator::const_iterator(const const_iterator &source) + : d(new ResultSet_IteratorPrivate(source.d->resultSet, source.d->currentRow)) +{ +} + +bool iterator::isSourceValid() const +{ + return d->resultSet != nullptr; +} + +iterator &iterator::operator=(const const_iterator &source) +{ + const_iterator temp(source); + swap(*d, *temp.d); + return *this; +} + +iterator::~const_iterator() +{ + delete d; +} + +iterator::reference iterator::operator*() const +{ + return d->currentValue.value(); +} + +iterator::pointer iterator::operator->() const +{ + return &d->currentValue.value(); +} + +// prefix +iterator &iterator::operator++() +{ + d->currentRow++; + d->updateValue(); + + return *this; +} + +// postfix +iterator iterator::operator++(int) +{ + return const_iterator(d->resultSet, d->currentRow + 1); +} + +// prefix +iterator &iterator::operator--() +{ + d->currentRow--; + d->updateValue(); + + return *this; +} + +// postfix +iterator iterator::operator--(int) +{ + return const_iterator(d->resultSet, d->currentRow - 1); +} + +iterator ResultSet::begin() const +{ + return const_iterator(this, d->database ? 0 : -1); +} + +iterator ResultSet::end() const +{ + return const_iterator(this, -1); +} + +iterator iterator::operator+(iterator::difference_type n) const +{ + return const_iterator(d->resultSet, d->currentRow + n); +} + +iterator &iterator::operator+=(iterator::difference_type n) +{ + d->moveBy(n); + return *this; +} + +iterator iterator::operator-(iterator::difference_type n) const +{ + return const_iterator(d->resultSet, d->currentRow - n); +} + +iterator &iterator::operator-=(iterator::difference_type n) +{ + d->moveBy(-n); + return *this; +} + +iterator::reference iterator::operator[](iterator::difference_type n) const +{ + return *(*this + n); +} + +// bool iterator::operator==(const const_iterator &right) const +// { +// return *d == *right.d; +// } +// +// bool iterator::operator!=(const const_iterator &right) const +// { +// return !(*d == *right.d); +// } + +bool operator==(const iterator &left, const iterator &right) +{ + return *left.d == *right.d; +} + +bool operator!=(const iterator &left, const iterator &right) +{ + return !(*left.d == *right.d); +} + +#define COMPARATOR_IMPL(OP) \ + bool operator OP(const iterator &left, const iterator &right) \ + { \ + return ResultSet_IteratorPrivate::sameSource(*left.d, *right.d) ? left.d->currentRow OP right.d->currentRow : false; \ + } + +COMPARATOR_IMPL(<) +COMPARATOR_IMPL(>) +COMPARATOR_IMPL(<=) +COMPARATOR_IMPL(>=) + +#undef COMPARATOR_IMPL + +iterator::difference_type operator-(const iterator &left, const iterator &right) +{ + return ResultSet_IteratorPrivate::sameSource(*left.d, *right.d) ? left.d->currentRow - right.d->currentRow : 0; +} + +} // namespace Stats +} // namespace KActivities diff --git a/src/resultwatcher.cpp b/src/resultwatcher.cpp new file mode 100644 index 0000000..1a2d072 --- /dev/null +++ b/src/resultwatcher.cpp @@ -0,0 +1,358 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "resultwatcher.h" + +// Qt +#include +#include +#include +#include +#include + +// Local +#include "plasma-activities-stats-logsettings.h" +#include +#include + +// STL +#include +#include +#include +#include + +// PlasmaActivities +#include + +#include "activitiessync_p.h" +#include "common/dbus/common.h" +#include "common/specialvalues.h" +#include "resourceslinking_interface.h" +#include "resourcesscoring_interface.h" +#include "utils/lazy_val.h" +#include "utils/qsqlquery_iterator.h" + +#include + +#define QDBG qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivitiesStats(" << (void *)this << ")" + +namespace KActivities +{ +namespace Stats +{ +// Main class + +class ResultWatcherPrivate +{ +public: + mutable ActivitiesSync::ConsumerPtr activities; + QList urlFilters; + + ResultWatcherPrivate(ResultWatcher *parent, Query query) + : linking(KAMD_DBUS_SERVICE, QStringLiteral("/ActivityManager/Resources/Linking"), QDBusConnection::sessionBus(), nullptr) + , scoring(KAMD_DBUS_SERVICE, QStringLiteral("/ActivityManager/Resources/Scoring"), QDBusConnection::sessionBus(), nullptr) + , q(parent) + , query(query) + { + for (const auto &urlFilter : query.urlFilters()) { + urlFilters << Common::starPatternToRegex(urlFilter); + } + + m_resultInvalidationTimer.setSingleShot(true); + m_resultInvalidationTimer.setInterval(200); + QObject::connect(&m_resultInvalidationTimer, &QTimer::timeout, q, Q_EMIT & ResultWatcher::resultsInvalidated); + } + + template + inline bool any_of(const Collection &collection, Predicate &&predicate) const + { + const auto begin = collection.cbegin(); + const auto end = collection.cend(); + + return begin == end || std::any_of(begin, end, std::forward(predicate)); + } + +#define DEBUG_MATCHERS 0 + + // Processing the list of activities as specified by the query. + // If it contains :any, we are returning true, otherwise + // we want to match a specific activity (be it the current + // activity or not). The :global special value is not special here + bool activityMatches(const QString &activity) const + { +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Activity " << activity << "matching against" << query.activities(); +#endif + + return kamd::utils::debug_and_return(DEBUG_MATCHERS, + " -> returning ", + activity == ANY_ACTIVITY_TAG || any_of(query.activities(), [&](const QString &matcher) { + return matcher == ANY_ACTIVITY_TAG ? true + : matcher == CURRENT_ACTIVITY_TAG + ? (matcher == activity || activity == ActivitiesSync::currentActivity(activities)) + : activity == matcher; + })); + } + + // Same as above, but for agents + bool agentMatches(const QString &agent) const + { +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Agent " << agent << "matching against" << query.agents(); +#endif + + return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", agent == ANY_AGENT_TAG || any_of(query.agents(), [&](const QString &matcher) { + return matcher == ANY_AGENT_TAG ? true + : matcher == CURRENT_AGENT_TAG + ? (matcher == agent || agent == QCoreApplication::applicationName()) + : agent == matcher; + })); + } + + // Same as above, but for urls + bool urlMatches(const QString &url) const + { +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Url " << url << "matching against" << urlFilters; +#endif + + return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", any_of(urlFilters, [&](const QRegularExpression &matcher) { + return matcher.match(url).hasMatch(); + })); + } + + bool typeMatches(const QString &resource) const + { + // We don't necessarily need to retrieve the type from + // the database. If we do, get it only once + auto type = kamd::utils::make_lazy_val([&]() -> QString { + using Common::Database; + + auto database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); + + if (!database) { + return QString(); + } + + auto query = database->execQuery(QStringLiteral("SELECT mimetype FROM ResourceInfo WHERE " + "targettedResource = '") + + resource + QStringLiteral("'")); + + for (const auto &item : query) { + return item[0].toString(); + } + + return QString(); + }); + +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Type " + << "...type..." + << "matching against" << query.types(); + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "ANY_TYPE_TAG" << ANY_TYPE_TAG; +#endif + + return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", any_of(query.types(), [&](const QString &matcher) { + if (matcher == ANY_TYPE_TAG) { + return true; + } + + const QString _type = type; + return matcher == ANY_TYPE_TAG + || (matcher == FILES_TYPE_TAG && !_type.isEmpty() && _type != QStringLiteral("inode/directory")) + || (matcher == DIRECTORIES_TYPE_TAG && _type == QLatin1String("inode/directory")) || matcher == type; + })); + } + + bool eventMatches(const QString &agent, const QString &resource, const QString &activity) const + { + // The order of checks is not arbitrary, it is sorted + // from the cheapest, to the most expensive + return kamd::utils::debug_and_return(DEBUG_MATCHERS, + "event matches?", + agentMatches(agent) && activityMatches(activity) && urlMatches(resource) && typeMatches(resource)); + } + + void onResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity) + { +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Resource has been linked: " << agent << resource << activity; +#endif + + // The used resources do not really care about the linked ones + if (query.selection() == Terms::UsedResources) { + return; + } + + if (!eventMatches(agent, resource, activity)) { + return; + } + + // TODO: See whether it makes sense to have + // lastUpdate/firstUpdate here as well + Q_EMIT q->resultLinked(resource); + } + + void onResourceUnlinkedFromActivity(const QString &agent, const QString &resource, const QString &activity) + { +#if DEBUG_MATCHERS + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Resource unlinked: " << agent << resource << activity; +#endif + + // The used resources do not really care about the linked ones + if (query.selection() == Terms::UsedResources) { + return; + } + + if (!eventMatches(agent, resource, activity)) { + return; + } + + Q_EMIT q->resultUnlinked(resource); + } + +#undef DEBUG_MATCHERS + + void onResourceScoreUpdated(const QString &activity, const QString &agent, const QString &resource, double score, uint lastUpdate, uint firstUpdate) + { + Q_ASSERT_X(activity == QLatin1String("00000000-0000-0000-0000-000000000000") || !QUuid(activity).isNull(), + "ResultWatcher::onResourceScoreUpdated", + "The activity should be always specified here, no magic values"); + + // The linked resources do not really care about the stats + if (query.selection() == Terms::LinkedResources) { + return; + } + + if (!eventMatches(agent, resource, activity)) { + return; + } + + Q_EMIT q->resultScoreUpdated(resource, score, lastUpdate, firstUpdate); + } + + void onEarlierStatsDeleted(QString, int) + { + // The linked resources do not really care about the stats + if (query.selection() == Terms::LinkedResources) { + return; + } + + scheduleResultsInvalidation(); + } + + void onRecentStatsDeleted(QString, int, QString) + { + // The linked resources do not really care about the stats + if (query.selection() == Terms::LinkedResources) { + return; + } + + scheduleResultsInvalidation(); + } + + void onStatsForResourceDeleted(const QString &activity, const QString &agent, const QString &resource) + { + if (query.selection() == Terms::LinkedResources) { + return; + } + + if (activityMatches(activity) && agentMatches(agent)) { + if (resource.contains(QLatin1Char('*'))) { + scheduleResultsInvalidation(); + + } else if (typeMatches(resource)) { + if (!m_resultInvalidationTimer.isActive()) { + // Remove a result only if we haven't an invalidation + // request scheduled + q->resultRemoved(resource); + } + } + } + } + + // Lets not send a lot of invalidation events at once + QTimer m_resultInvalidationTimer; + void scheduleResultsInvalidation() + { + QDBG << "Scheduling invalidation"; + m_resultInvalidationTimer.start(); + } + + org::kde::ActivityManager::ResourcesLinking linking; + org::kde::ActivityManager::ResourcesScoring scoring; + + ResultWatcher *const q; + Query query; +}; + +ResultWatcher::ResultWatcher(Query query, QObject *parent) + : QObject(parent) + , d(new ResultWatcherPrivate(this, query)) +{ + using namespace org::kde::ActivityManager; + using namespace std::placeholders; + + // There is no need for private slots, when we have bind + + // Connecting the linking service + QObject::connect(&d->linking, + &ResourcesLinking::ResourceLinkedToActivity, + this, + std::bind(&ResultWatcherPrivate::onResourceLinkedToActivity, d, _1, _2, _3)); + QObject::connect(&d->linking, + &ResourcesLinking::ResourceUnlinkedFromActivity, + this, + std::bind(&ResultWatcherPrivate::onResourceUnlinkedFromActivity, d, _1, _2, _3)); + + // Connecting the scoring service + QObject::connect(&d->scoring, + &ResourcesScoring::ResourceScoreUpdated, + this, + std::bind(&ResultWatcherPrivate::onResourceScoreUpdated, d, _1, _2, _3, _4, _5, _6)); + QObject::connect(&d->scoring, &ResourcesScoring::ResourceScoreDeleted, this, std::bind(&ResultWatcherPrivate::onStatsForResourceDeleted, d, _1, _2, _3)); + QObject::connect(&d->scoring, &ResourcesScoring::RecentStatsDeleted, this, std::bind(&ResultWatcherPrivate::onRecentStatsDeleted, d, _1, _2, _3)); + QObject::connect(&d->scoring, &ResourcesScoring::EarlierStatsDeleted, this, std::bind(&ResultWatcherPrivate::onEarlierStatsDeleted, d, _1, _2)); +} + +ResultWatcher::~ResultWatcher() +{ + delete d; +} + +void ResultWatcher::linkToActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) +{ + const auto activities = (!activity.values.isEmpty()) ? activity.values + : (!d->query.activities().isEmpty()) ? d->query.activities() + : Terms::Activity::current().values; + const auto agents = (!agent.values.isEmpty()) ? agent.values : (!d->query.agents().isEmpty()) ? d->query.agents() : Terms::Agent::current().values; + + for (const auto &activity : activities) { + for (const auto &agent : agents) { + d->linking.LinkResourceToActivity(agent, resource.toString(), activity); + } + } +} + +void ResultWatcher::unlinkFromActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) +{ + const auto activities = !activity.values.isEmpty() ? activity.values + : !d->query.activities().isEmpty() ? d->query.activities() + : Terms::Activity::current().values; + const auto agents = !agent.values.isEmpty() ? agent.values : !d->query.agents().isEmpty() ? d->query.agents() : Terms::Agent::current().values; + + for (const auto &activity : activities) { + for (const auto &agent : agents) { + qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "Unlink " << agent << resource << activity; + d->linking.UnlinkResourceFromActivity(agent, resource.toString(), activity); + } + } +} + +} // namespace Stats +} // namespace KActivities + +#include "moc_resultwatcher.cpp" diff --git a/src/resultwatcher.h b/src/resultwatcher.h new file mode 100644 index 0000000..f300ca4 --- /dev/null +++ b/src/resultwatcher.h @@ -0,0 +1,107 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_RESULTWATCHER +#define KACTIVITIES_STATS_RESULTWATCHER + +#include + +#include "query.h" +#include "resultset.h" + +namespace KActivities +{ +namespace Stats +{ +class ResultWatcherPrivate; + +/** + * @class KActivities::Stats::ResultWatcher resultwatcher.h + * + * A very thin class that sends signals when new resources matching + * a predefined query are available. + */ +class PLASMAACTIVITIESSTATS_EXPORT ResultWatcher : public QObject +{ + Q_OBJECT + +public: + explicit ResultWatcher(Query query, QObject *parent = nullptr); + ~ResultWatcher() override; + +Q_SIGNALS: + /** + * Emitted when a result has been added or updated. This either means + * a new resource has appeared in the result set, or that + * a previously existing one has some of the attributes changed. + * @param result new data for the resource defined by result.resource + */ + void resultScoreUpdated(const QString &resource, double score, uint lastUpdate, uint firstUpdate); + + /** + * Emitted when a result has been added or updated. This either means + * a new resource has appeared in the result set, or that + * a previously existing one has some of the attributes changed. + * @param result new data for the resource defined by result.resource + */ + void resultRemoved(const QString &resource); + + /** + * Emitted when a result has been linked to the activity + */ + void resultLinked(const QString &resource); + + /** + * Emitted when a result has been unlinked from the activity + */ + void resultUnlinked(const QString &resource); + + /** + * Emitted when the title of a resource has been changed. + * @param resource URL of the resource that has a new title + * @param title new title of the resource + * @note This signal will be emitted even for the resources that + * do not match the specified query. This is because the class is + * lightweight, and it does not keep track of which resources match + * the query to be able to filter this signal. + */ + void resourceTitleChanged(const QString &resource, const QString &title); + + /** + * Emitted when the mimetype of a resource has been changed. + * @param resource URL of the resource that has a new mimetype + * @param mimetype new mimetype of the resource + * @note This signal will be emitted even for the resources that + * do not match the specified query. This is because the class is + * lightweight, and it does not keep track of which resources match + * the query to be able to filter this signal. + */ + void resourceMimetypeChanged(const QString &resource, const QString &mimetype); + + /** + * Emitted when the client should forget about all the results it + * knew about and reload them. This can happen when the user clears + * the history, or when there are more significant changes to the data. + */ + void resultsInvalidated(); + +public: + void linkToActivity(const QUrl &resource, + const Terms::Activity &activity = Terms::Activity(QStringList()), + const Terms::Agent &agent = Terms::Agent(QStringList())); + + void unlinkFromActivity(const QUrl &resource, + const Terms::Activity &activity = Terms::Activity(QStringList()), + const Terms::Agent &agent = Terms::Agent(QStringList())); + +private: + ResultWatcherPrivate *const d; +}; + +} // namespace Stats +} // namespace KActivities + +#endif // KACTIVITIES_STATS_RESULTWATCHER diff --git a/src/terms.cpp b/src/terms.cpp new file mode 100644 index 0000000..1ce9d2d --- /dev/null +++ b/src/terms.cpp @@ -0,0 +1,159 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "terms.h" +#include "common/specialvalues.h" + +#include + +namespace KActivities +{ +namespace Stats +{ +// Term classes +#define IMPLEMENT_TERM_CONSTRUCTORS(TYPE) \ + Terms::TYPE::TYPE(QStringList values) \ + : values(std::move(values)) \ + { \ + } \ + \ + Terms::TYPE::TYPE(QString value) \ + : values(QStringList() << std::move(value)) \ + { \ + } + +#define IMPLEMENT_SPECIAL_TERM_VALUE(TYPE, VALUE_NAME, VALUE) \ + Terms::TYPE Terms::TYPE::VALUE_NAME() \ + { \ + return Terms::TYPE(VALUE); \ + } + +IMPLEMENT_TERM_CONSTRUCTORS(Type) +IMPLEMENT_SPECIAL_TERM_VALUE(Type, any, ANY_TYPE_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Type, files, FILES_TYPE_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Type, directories, DIRECTORIES_TYPE_TAG) + +IMPLEMENT_TERM_CONSTRUCTORS(Agent) +IMPLEMENT_SPECIAL_TERM_VALUE(Agent, any, ANY_AGENT_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Agent, global, GLOBAL_AGENT_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Agent, current, CURRENT_AGENT_TAG) + +IMPLEMENT_TERM_CONSTRUCTORS(Activity) +IMPLEMENT_SPECIAL_TERM_VALUE(Activity, any, ANY_ACTIVITY_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Activity, global, GLOBAL_ACTIVITY_TAG) +IMPLEMENT_SPECIAL_TERM_VALUE(Activity, current, CURRENT_ACTIVITY_TAG) + +IMPLEMENT_TERM_CONSTRUCTORS(Url) +IMPLEMENT_SPECIAL_TERM_VALUE(Url, localFile, QStringLiteral("/*")) +IMPLEMENT_SPECIAL_TERM_VALUE(Url, + file, + QStringList() << QStringLiteral("/*") << QStringLiteral("smb:*") << QStringLiteral("fish:*") << QStringLiteral("sftp:*") + << QStringLiteral("ftp:*")) + +#undef IMPLEMENT_TERM_CONSTRUCTORS +#undef IMPLEMENT_SPECIAL_TERM_VALUE + +Terms::Limit::Limit(int value) + : value(value) +{ +} + +Terms::Offset::Offset(int value) + : value(value) +{ +} + +Terms::Date::Date(QDate value) + : start(std::move(value)) +{ +} + +Terms::Date::Date(QDate start, QDate end) + : start(std::move(start)) + , end(std::move(end)) +{ +} + +Terms::Date Terms::Date::today() +{ + return Date(QDate::currentDate()); +} + +Terms::Date Terms::Date::yesterday() +{ + auto date = QDate::currentDate(); + return Date(date.addDays(-1)); +} + +Terms::Date Terms::Date::currentWeek() +{ + auto start = QDate::currentDate(); + auto end = start.addDays(-7); + return Date(start, end); +} + +Terms::Date Terms::Date::previousWeek() +{ + auto start = QDate::currentDate().addDays(-7); + auto end = start.addDays(-7); + return Date(start, end); +} + +Terms::Date Terms::Date::fromString(QString string) +{ + auto splitted = string.split(QStringLiteral(",")); + if (splitted.count() == 2) { + // date range case + auto start = QDate::fromString(splitted[0], Qt::ISODate); + auto end = QDate::fromString(splitted[1], Qt::ISODate); + return Date(start, end); + } else { + auto date = QDate::fromString(string, Qt::ISODate); + return Date(date); + } +} + +Terms::Url Terms::Url::startsWith(const QString &prefix) +{ + return Url(prefix + QStringLiteral("*")); +} + +Terms::Url Terms::Url::contains(const QString &infix) +{ + return Url(QStringLiteral("*") + infix + QStringLiteral("*")); +} + +} // namespace Stats +} // namespace KActivities + +namespace KAStats = KActivities::Stats; + +#define QDEBUG_TERM_OUT(TYPE, OUT) \ + QDebug operator<<(QDebug dbg, const KAStats::Terms::TYPE &_) \ + { \ + using namespace KAStats::Terms; \ + dbg.nospace() << #TYPE << ": " << (OUT); \ + return dbg; \ + } + +QDEBUG_TERM_OUT(Order, + _ == HighScoredFirst ? "HighScore" + : _ == RecentlyUsedFirst ? "RecentlyUsed" + : _ == RecentlyCreatedFirst ? "RecentlyCreated" + : "Alphabetical") + +QDEBUG_TERM_OUT(Select, _ == LinkedResources ? "LinkedResources" : _ == UsedResources ? "UsedResources" : "AllResources") + +QDEBUG_TERM_OUT(Type, _.values) +QDEBUG_TERM_OUT(Agent, _.values) +QDEBUG_TERM_OUT(Activity, _.values) +QDEBUG_TERM_OUT(Url, _.values) + +QDEBUG_TERM_OUT(Limit, _.value) +QDEBUG_TERM_OUT(Offset, _.value) +QDEBUG_TERM_OUT(Date, _.end.isNull() ? _.start.toString(Qt::ISODate) : _.start.toString(Qt::ISODate) + QStringLiteral(",") + _.end.toString(Qt::ISODate)) + +#undef QDEBUG_TERM_OUT diff --git a/src/terms.h b/src/terms.h new file mode 100644 index 0000000..3ee59be --- /dev/null +++ b/src/terms.h @@ -0,0 +1,271 @@ +/* + SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef KACTIVITIES_STATS_TERMS_H +#define KACTIVITIES_STATS_TERMS_H + +#include + +#include +#include +#include + +#include "plasmaactivitiesstats_export.h" + +namespace KActivities +{ +namespace Stats +{ +/** + * @namespace KActivities::Stats::Terms + * Provides enums and strucss to use.for building queries with @c Query. + */ +namespace Terms +{ +/** + * Enumerator specifying the ordering in which the + * results of the query should be listed + */ +enum PLASMAACTIVITIESSTATS_EXPORT Order { + HighScoredFirst, ///< Resources with the highest scores first + RecentlyUsedFirst, ///< Recently used resources first + RecentlyCreatedFirst, ///< Recently created resources first + OrderByUrl, ///< Order by uri, alphabetically + OrderByTitle, ///< Order by uri, alphabetically +}; + +/** + * Which resources should be returned + */ +enum PLASMAACTIVITIESSTATS_EXPORT Select { + LinkedResources, ///< Resources linked to an activity, or globally + UsedResources, ///< Resources that have been accessed + AllResources, ///< Combined set of accessed and linked resources +}; + +/** + * @struct KActivities::Stats::Terms::Limit terms.h + * + * How many items you need. The default is 50 + */ +struct PLASMAACTIVITIESSTATS_EXPORT Limit { + Limit(int value); + int value; +}; + +/** + * @struct KActivities::Stats::Terms::Offset terms.h + * + * How many items to skip? + * This can be specified only if limit is also set to a finite value. + */ +struct PLASMAACTIVITIESSTATS_EXPORT Offset { + Offset(int value); + int value; +}; + +/** + * @struct KActivities::Stats::Terms::Type terms.h + * + * Term to filter the resources according to their types + */ +struct PLASMAACTIVITIESSTATS_EXPORT Type { + /** + * Show resources of any type + */ + static Type any(); + /** + * Show non-directory resources + */ + static Type files(); + /** + * Show directory resources aka folders + */ + static Type directories(); + + inline Type(std::initializer_list types) + : values(types) + { + } + + Type(QStringList types); + Type(QString type); + + const QStringList values; +}; + +/** + * @struct KActivities::Stats::Terms::Agent terms.h + * + * Term to filter the resources according the agent (application) which + * accessed it + */ +struct PLASMAACTIVITIESSTATS_EXPORT Agent { + /** + * Show resources accessed/linked by any application + */ + static Agent any(); + + /** + * Show resources not tied to a specific agent + */ + static Agent global(); + + /** + * Show resources accessed/linked by the current application + */ + static Agent current(); + + inline Agent(std::initializer_list agents) + : values(agents) + { + } + + Agent(QStringList agents); + Agent(QString agent); + + const QStringList values; +}; + +/** + * @struct KActivities::Stats::Terms::Activity terms.h + * + * Term to filter the resources according the activity in which they + * were accessed + */ +struct PLASMAACTIVITIESSTATS_EXPORT Activity { + /** + * Show resources accessed in / linked to any activity + */ + static Activity any(); + + /** + * Show resources linked to all activities + */ + static Activity global(); + + /** + * Show resources linked to all activities + */ + static Activity current(); + + inline Activity(std::initializer_list activities) + : values(activities) + { + } + + Activity(QStringList activities); + Activity(QString activity); + + const QStringList values; +}; + +/** + * @struct KActivities::Stats::Terms::Url terms.h + * + * Url filtering. + */ +struct PLASMAACTIVITIESSTATS_EXPORT Url { + /** + * Show only resources that start with the specified prefix + */ + static Url startsWith(const QString &prefix); + + /** + * Show resources that contain the specified infix + */ + static Url contains(const QString &infix); + + /** + * Show local files + */ + static Url localFile(); + + /** + * Show local files, smb, fish, ftp and sftp + */ + static Url file(); + + inline Url(std::initializer_list urlPatterns) + : values(urlPatterns) + { + } + + Url(QStringList urlPatterns); + Url(QString urlPattern); + + const QStringList values; +}; +/** + * @since 6.0 + */ +struct PLASMAACTIVITIESSTATS_EXPORT Title { + /** + * Show resources with title that contain the specified pattern + */ + Title(const QString &titlePattern) + : values({titlePattern}) + { + } + /// Default constructor + Title() + { + } + + const QStringList values; +}; + +/** + * @struct KActivities::Stats::Terms::Date terms.h + * + * On which start access date do you want to filter ? + */ +struct PLASMAACTIVITIESSTATS_EXPORT Date { + Date(QDate value); + Date(QDate start, QDate end); + + static Date today(); + static Date yesterday(); + static Date currentWeek(); + static Date previousWeek(); + static Date fromString(QString); + + QDate start, end; +}; + +} // namespace Terms + +} // namespace Stats +} // namespace KActivities + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Order &order); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Select &select); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Type &type); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Agent &agent); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Activity &activity); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Url &url); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Limit &limit); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Offset &offset); + +PLASMAACTIVITIESSTATS_EXPORT +QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Date &date); + +#endif // KACTIVITIES_STATS_TERMS_H diff --git a/src/utils/debug_and_return.h b/src/utils/debug_and_return.h new file mode 100644 index 0000000..b708d64 --- /dev/null +++ b/src/utils/debug_and_return.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef DEBUG_AND_RETURN_H +#define DEBUG_AND_RETURN_H + +#ifdef QT_DEBUG +#include +#endif + +namespace kamd +{ +namespace utils +{ +template +T debug_and_return(bool debug, const char *message, T &&value) +{ + if (debug) { + qDebug().noquote() << message << " " << value; + } + + return std::forward(value); +} + +} // namespace utils +} // namespace kamd + +#endif // DEBUG_AND_RETURN_H diff --git a/src/utils/lazy_val.h b/src/utils/lazy_val.h new file mode 100644 index 0000000..3e9a258 --- /dev/null +++ b/src/utils/lazy_val.h @@ -0,0 +1,50 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef UTILS_LAZY_VAL_H +#define UTILS_LAZY_VAL_H + +namespace kamd +{ +namespace utils +{ +template +class lazy_val +{ +public: + lazy_val(F f) + : _f(std::forward(f)) + , valueRetrieved(false) + { + } + +private: + F _f; + mutable decltype(_f()) value; + mutable bool valueRetrieved; + +public: + operator decltype(_f())() const + { + if (!valueRetrieved) { + valueRetrieved = true; + value = _f(); + } + + return value; + } +}; + +template +inline lazy_val make_lazy_val(F &&f) +{ + return lazy_val(std::forward(f)); +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_LAZY_VAL_H diff --git a/src/utils/member_matcher.h b/src/utils/member_matcher.h new file mode 100644 index 0000000..a8ae324 --- /dev/null +++ b/src/utils/member_matcher.h @@ -0,0 +1,169 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef UTILS_MEMBER_MATCHER_H +#define UTILS_MEMBER_MATCHER_H + +namespace kamd +{ +namespace utils +{ +namespace member_matcher +{ +struct placeholder { +} _; + +namespace detail +{ //_ +enum ComparisonOperation { + Less, + LessOrEqual, + Equal, + GreaterOrEqual, + Greater, +}; + +// Member comparison object +// Call operator returns true if: +// collection item specified item +// where can be <, >, ==, >=, <= +template +struct member_comparator { //_ + + member_comparator(ComparisonOperation comparison, Member member, Value value) + : m_comparator(comparison) + , m_member(member) + , m_value(value) + { + } + + const ComparisonOperation m_comparator; + const Member m_member; + const Value m_value; + + // When passing only a item to compare with, + // it means that we already have the value for comparison. + // For example (member(M) > 5)(2) + template + inline bool operator()(const T &collItem) const + { + return operator()(collItem, m_value); + } + + // When passing the placeholder aka 'ignore' as a value, + // it means that we already have the value for comparison. + // For example (member(M) > 5)(collItem, _) + template + inline bool operator()(const T &collItem, const placeholder &) const + { + return operator()(collItem, m_value); + } + + // Like the previous one, but with reversed argument order + template + inline bool operator()(const placeholder &, const T &collItem) const + { + return compare(m_value, (collItem.*m_member)()); + } + + // Comparing two values + // For example (member(M) > _)(item, 5) + template + inline bool operator()(const T &collItem, const V &value) const + { + // TODO: Make this work if the arguments are reversed, + // or even if both arhuments need to be checked + // for the specified member + return compare((collItem.*m_member)(), value); + } + +private: + template + inline bool compare(const Left &left, const Right &right) const + { + return m_comparator == Less ? left < right + : m_comparator == LessOrEqual ? left <= right + : m_comparator == Equal ? left == right + : m_comparator == GreaterOrEqual ? left >= right + : m_comparator == Greater ? left > right + : false; + } + +}; //^ + +// Chaining multiple comparators to achieve lexicographical +// comparison of multiple members in order. +// This would me so much nicer with variadic templates... f**ing MSVC. +template +struct member_comparator_chain { + member_comparator_chain(First first, Second second) + : first(first) + , second(second) + { + } + + // Implement if needed... + // template + // inline bool operator()(const T &item) const + // { + // return first(item) || second(item); + // } + + template + inline bool operator()(const T &item, const V &value) const + { + return first(item, value) || (!first(value, item) && second(item, value)); + } + + First first; + Second second; +}; + +template +inline member_comparator_chain operator&&(First first, Second second) +{ + return member_comparator_chain(first, second); +} + +// Provides syntax sugar for building member comparators +template +struct member_matcher { //_ + member_matcher(Member m) + : m_member(m) + { + } + +#define IMPLEMENT_COMPARISON_OPERATOR(OPERATOR, NAME) \ + template \ + inline member_comparator operator OPERATOR(const Value &value) const \ + { \ + return member_comparator(NAME, m_member, value); \ + } + + IMPLEMENT_COMPARISON_OPERATOR(<, Less) + IMPLEMENT_COMPARISON_OPERATOR(<=, LessOrEqual) + IMPLEMENT_COMPARISON_OPERATOR(==, Equal) + IMPLEMENT_COMPARISON_OPERATOR(>=, GreaterOrEqual) + IMPLEMENT_COMPARISON_OPERATOR(>, Greater) + +#undef IMPLEMENT_COMPARISON_OPERATOR + + Member m_member; +}; //^ + +} //^ namespace detail + +template +detail::member_matcher member(Member m) +{ + return detail::member_matcher(m); +} +} // namespace member + +} // namespace utils +} // namespace kamd + +#endif // UTILS_MEMBER_MATCHER_H diff --git a/src/utils/qsqlquery_iterator.cpp b/src/utils/qsqlquery_iterator.cpp new file mode 100644 index 0000000..2228ad5 --- /dev/null +++ b/src/utils/qsqlquery_iterator.cpp @@ -0,0 +1,17 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "qsqlquery_iterator.h" + +NextValueIterator begin(QSqlQuery &query) +{ + return NextValueIterator(query); +} + +NextValueIterator end(QSqlQuery &query) +{ + return NextValueIterator(query, NextValueIterator::EndIterator); +} diff --git a/src/utils/qsqlquery_iterator.h b/src/utils/qsqlquery_iterator.h new file mode 100644 index 0000000..af5904b --- /dev/null +++ b/src/utils/qsqlquery_iterator.h @@ -0,0 +1,66 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef UTILS_QSQLQUERYITERATOR_H +#define UTILS_QSQLQUERYITERATOR_H + +#include +#include + +template +class NextValueIterator +{ +public: + enum Type { + NormalIterator, + EndIterator, + }; + + NextValueIterator(ResultSet &query, Type type = NormalIterator) + : m_query(query) + , m_type(type) + { + if (type != EndIterator) { + m_query.next(); + } + } + + inline bool operator!=(const NextValueIterator &other) const + { + Q_UNUSED(other); + return m_query.isValid(); + } + + inline NextValueIterator &operator*() + { + return *this; + } + + inline QVariant operator[](int index) const + { + return m_query.value(index); + } + + inline QVariant operator[](const QString &name) const + { + return m_query.value(name); + } + + inline NextValueIterator &operator++() + { + m_query.next(); + return *this; + } + +private: + ResultSet &m_query; + Type m_type; +}; + +NextValueIterator begin(QSqlQuery &query); +NextValueIterator end(QSqlQuery &query); + +#endif /* UTILS_QSQLQUERYITERATOR_H */ diff --git a/src/utils/slide.h b/src/utils/slide.h new file mode 100644 index 0000000..687bc8e --- /dev/null +++ b/src/utils/slide.h @@ -0,0 +1,55 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef UTILS_SLIDE_H +#define UTILS_SLIDE_H + +#include + +// Inspired by C++ Seasoning talk by Sean Parent + +namespace kamd +{ +namespace utils +{ +template +void slide(Iterator f, Iterator l, Iterator p) +{ + if (p < f) { + std::rotate(p, f, l); + } else if (l < p) { + std::rotate(f, l, p); + } +} + +template +void slide_one(Iterator f, Iterator p) +{ + slide(f, f + 1, p); +} + +template +void move_one(Iterator from, Iterator to) +{ + if (from < to) { + while (from != to) { + using std::swap; + swap(*from, *(from + 1)); + ++from; + } + } else { + while (from != to) { + using std::swap; + swap(*from, *(from - 1)); + --from; + } + } +} + +} // namespace utils +} // namespace kamd + +#endif // UTILS_SLIDE_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..4800958 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(model) diff --git a/tests/model/CMakeLists.txt b/tests/model/CMakeLists.txt new file mode 100644 index 0000000..eaa4e8b --- /dev/null +++ b/tests/model/CMakeLists.txt @@ -0,0 +1,42 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (PlasmaActivitiesStatsTestApp) + +find_package (Qt6 REQUIRED NO_MODULE COMPONENTS Core Gui Widgets Test Quick QuickWidgets) + +if (NOT WIN32) + +add_executable (PlasmaActivitiesStatsTestApp) + +target_include_directories (PlasmaActivitiesStatsTestApp PRIVATE + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/autotests + ${CMAKE_BINARY_DIR}/src +) + +qt_wrap_ui(PlasmaActivitiesStatsTestApp_SRCS + window.ui +) + +qt_add_resources(PlasmaActivitiesStatsTestApp_SRCS + main.qrc +) + +target_sources(PlasmaActivitiesStatsTestApp PRIVATE + ${PlasmaActivitiesStatsTestApp_SRCS} + window.cpp + main.cpp +) + +target_link_libraries (PlasmaActivitiesStatsTestApp + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Test + Qt6::Quick + Qt6::QuickWidgets + + Plasma::Activities + Plasma::ActivitiesStats +) + +endif () diff --git a/tests/model/main.cpp b/tests/model/main.cpp new file mode 100644 index 0000000..a471c4c --- /dev/null +++ b/tests/model/main.cpp @@ -0,0 +1,18 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "window.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + Window w; + w.show(); + + return app.exec(); +} diff --git a/tests/model/main.qml b/tests/model/main.qml new file mode 100644 index 0000000..ef2dca9 --- /dev/null +++ b/tests/model/main.qml @@ -0,0 +1,110 @@ +import QtQuick 2.0 +Rectangle { + color: "black" + ListView { + anchors.fill: parent + anchors.margins: 8 + + model: kamdmodel + + delegate: Item { + height: 100-32 + width: 500 + + Text { + text: model.index + color: '#ffffff' + + width: 24 + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + } + + Rectangle { + id: titleRect + height: 32 + + color: '#202020' + anchors { + left: parent.left + leftMargin: 24 + right: parent.right + top: parent.top + } + + Text { + text: model.title + anchors { + fill: parent + margins: 6 + } + color: 'white' + } + + Text { + text: "Score: " + model.score + anchors { + fill: parent + margins: 6 + } + color: 'white' + horizontalAlignment: Text.AlignRight + } + } + + Rectangle { + anchors { + left: parent.left + leftMargin: 24 + right: parent.right + top: titleRect.bottom + } + + color: '#303030' + height: 32 + + Text { + anchors { + fill: parent + margins: 6 + } + color: 'white' + text: model.modified + "\t" + model.created + "\t" + model.resource + } + + } + } + + add: Transition { + NumberAnimation { properties: "x,y"; from: 100; duration: 1000 } + } + addDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + displaced: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + move: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + moveDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + // populate: Transition { + // NumberAnimation { properties: "x,y"; duration: 1000 } + // } + removeDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 1000 } + } + remove: Transition { + ParallelAnimation { + NumberAnimation { property: "opacity"; to: 0; duration: 1000 } + NumberAnimation { properties: "x,y"; to: 100; duration: 1000 } + } + } + } +} diff --git a/tests/model/main.qrc b/tests/model/main.qrc new file mode 100644 index 0000000..5d9f7dd --- /dev/null +++ b/tests/model/main.qrc @@ -0,0 +1,6 @@ + + + main.qml + + + diff --git a/tests/model/window.cpp b/tests/model/window.cpp new file mode 100644 index 0000000..e7d64c1 --- /dev/null +++ b/tests/model/window.cpp @@ -0,0 +1,301 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "window.h" + +#include "ui_window.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +class Delegate : public QItemDelegate +{ +public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + painter->save(); + + const QString title = index.data(ResultModel::TitleRole).toString(); + + QRect titleRect = painter->fontMetrics().boundingRect(title); + int lineHeight = titleRect.height(); + + // Header background + titleRect.moveTop(option.rect.top()); + titleRect.setWidth(option.rect.width()); + + painter->fillRect(titleRect.x(), titleRect.y(), titleRect.width(), titleRect.height() + 16, QColor(32, 32, 32)); + + // Painting the title + painter->setPen(QColor(255, 255, 255)); + + titleRect.moveTop(titleRect.top() + 8); + titleRect.setLeft(8); + titleRect.setWidth(titleRect.width() - 8); + painter->drawText(titleRect, index.data(ResultModel::TitleRole).toString()); + + // Painting the score + painter->drawText(titleRect, QStringLiteral("Score: ") + QString::number(index.data(ResultModel::ScoreRole).toDouble()), QTextOption(Qt::AlignRight)); + + // Painting the moification and creation times + titleRect.moveTop(titleRect.bottom() + 16); + + painter->fillRect(titleRect.x() - 4, titleRect.y() - 8, titleRect.width() + 8, titleRect.height() + 8 + lineHeight, QColor(64, 64, 64)); + + titleRect.moveTop(titleRect.top() - 4); + + painter->drawText(titleRect, index.data(ResultModel::ResourceRole).toString()); + + auto firstUpdate = QDateTime::fromSecsSinceEpoch(index.data(ResultModel::FirstUpdateRole).toUInt()); + auto lastUpdate = QDateTime::fromSecsSinceEpoch(index.data(ResultModel::LastUpdateRole).toUInt()); + + titleRect.moveTop(titleRect.top() + lineHeight); + + painter->drawText(titleRect, QStringLiteral("Modified: ") + lastUpdate.toString()); + painter->drawText(titleRect, QStringLiteral("Created: ") + firstUpdate.toString(), QTextOption(Qt::AlignRight)); + + painter->restore(); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + Q_UNUSED(option); + Q_UNUSED(index); + return QSize(0, 100); + } +}; + +Window::Window() + : ui(new Ui::MainWindow()) + , model(nullptr) + , activities(new KActivities::Consumer()) +{ + ui->setupUi(this); + ui->viewResults->setItemDelegate(new Delegate()); + // ui->viewResults->setUniformItemSizes(true); + // ui->viewResults->setGridSize(QSize(200, 100)); + + while (activities->serviceStatus() == KActivities::Consumer::Unknown) { + QCoreApplication::processEvents(); + } + + connect(ui->buttonUpdate, SIGNAL(clicked()), this, SLOT(updateResults())); + connect(ui->buttonReloadRowCount, SIGNAL(clicked()), this, SLOT(updateRowCount())); + + for (const auto &activity : + (QStringList() << QStringLiteral(":current") << QStringLiteral(":any") << QStringLiteral(":global")) + activities->activities()) { + ui->comboActivity->addItem(activity); + } + + const auto argumentsList = QCoreApplication::arguments(); + for (const auto &arg : argumentsList) { + if (arg == QLatin1String("--used")) { + ui->radioSelectUsedResources->setChecked(true); + + } else if (arg == QLatin1String("--linked")) { + ui->radioSelectLinkedResources->setChecked(true); + + } else if (arg == QLatin1String("--combined")) { + ui->radioSelectAllResources->setChecked(true); + + } else if (arg.startsWith(QLatin1String("--activity="))) { + ui->comboActivity->setCurrentText(arg.split(QLatin1String("="))[1]); + + } else if (arg.startsWith(QLatin1String("--agent="))) { + ui->textAgent->setText(arg.split(QLatin1String("="))[1]); + + } else if (arg.startsWith(QLatin1String("--mimetype="))) { + ui->textMimetype->setText(arg.split(QLatin1String("="))[1]); + + } else if (arg == QLatin1String("--select")) { + updateResults(); + } + } + + auto redisplayAction = new QAction(this); + addAction(redisplayAction); + redisplayAction->setShortcut(Qt::Key_F5); + connect(redisplayAction, SIGNAL(triggered()), this, SLOT(updateResults())); + + // loading the presets + + const auto recentQueryBase = UsedResources | RecentlyUsedFirst | Agent::any() | Type::any() | Activity::current(); + + const auto popularQueryBase = UsedResources | HighScoredFirst | Agent::any() | Type::any() | Activity::current(); + + presets = { + {QStringLiteral("kicker-favorites"), + LinkedResources + | Agent{QStringLiteral("org.kde.plasma.favorites.applications"), + QStringLiteral("org.kde.plasma.favorites.documents"), + QStringLiteral("org.kde.plasma.favorites.contacts")} + | Type::any() | Activity::current() | Activity::global() | Limit(15)}, + {QStringLiteral("kicker-recent-apps-n-docs"), recentQueryBase | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30)}, + {QStringLiteral("kicker-recent-apps"), recentQueryBase | Url::startsWith(QStringLiteral("applications:")) | Limit(15)}, + {QStringLiteral("kicker-recent-docs"), recentQueryBase | Url::file() | Limit(15)}, + {QStringLiteral("kicker-popular-apps-n-docs"), popularQueryBase | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30)}, + {QStringLiteral("kicker-popular-apps"), popularQueryBase | Url::startsWith(QStringLiteral("applications:")) | Limit(15)}, + {QStringLiteral("kicker-popular-docs"), popularQueryBase | Url::file() | Limit(15)}, + }; + + ui->comboPreset->addItem(QStringLiteral("Choose a preset"), QVariant()); + for (auto it = presets.cbegin(); it != presets.cend(); ++it) { + ui->comboPreset->addItem(it.key(), it.key()); + } + + connect(ui->comboPreset, SIGNAL(activated(int)), this, SLOT(selectPreset())); +} + +Window::~Window() +{ +} + +void Window::selectPreset() +{ + const auto id = ui->comboPreset->currentData().toString(); + + if (id.isEmpty()) { + return; + } + + const auto &query = presets[id]; + qDebug() << "Id: " << id; + qDebug() << "Query: " << query; + + // Selection + qDebug() << "\tSelection:" << query.selection(); + ui->radioSelectUsedResources->setChecked(query.selection() == UsedResources); + ui->radioSelectLinkedResources->setChecked(query.selection() == LinkedResources); + ui->radioSelectAllResources->setChecked(query.selection() == AllResources); + + // Ordering + qDebug() << "\tOrdering:" << query.ordering(); + ui->radioOrderHighScoredFirst->setChecked(query.ordering() == HighScoredFirst); + ui->radioOrderRecentlyUsedFirst->setChecked(query.ordering() == RecentlyUsedFirst); + ui->radioOrderRecentlyCreatedFirst->setChecked(query.ordering() == RecentlyCreatedFirst); + ui->radioOrderByUrl->setChecked(query.ordering() == OrderByUrl); + ui->radioOrderByTitle->setChecked(query.ordering() == OrderByTitle); + + // Agents + qDebug() << "\tAgents:" << query.agents(); + ui->textAgent->setText(query.agents().join(QLatin1Char(','))); + + // Types + qDebug() << "\tTypes:" << query.types(); + ui->textMimetype->setText(query.types().join(QLatin1Char(','))); + + // Activities + qDebug() << "\tActivities:" << query.activities(); + ui->comboActivity->setEditText(query.activities().join(QLatin1Char(','))); + + // Url filters + qDebug() << "\tUrl filters:" << query.urlFilters(); + ui->textUrl->setText(query.urlFilters().join(QLatin1Char(','))); + + // Limit + ui->spinLimitCount->setValue(query.limit()); + + updateResults(); +} + +void Window::updateRowCount() +{ + ui->labelRowCount->setText(QString::number(ui->viewResults->model()->rowCount())); +} + +void Window::setQuery(const KActivities::Stats::Query &query) +{ + qDebug() << "Updating the results"; + + ui->viewResults->setModel(nullptr); + + // Log results + QString buffer; + for (const ResultSet::Result &result : ResultSet(query)) { + buffer += result.title() + QStringLiteral(" (") + result.resource() + QStringLiteral(")\n"); + } + ui->textLog->setText(buffer); + + model.reset(new ResultModel(query)); + + if (QCoreApplication::arguments().contains(QLatin1String("--enable-model-test"))) { + modelTest.reset(); + modelTest.reset(new QAbstractItemModelTester(new ResultModel(query))); + } + + ui->viewResults->setModel(model.get()); + + // QML + + auto context = ui->viewResultsQML->rootContext(); + ui->viewResultsQML->setResizeMode(QQuickWidget::SizeRootObjectToView); + + context->setContextProperty(QStringLiteral("kamdmodel"), model.get()); + + ui->viewResultsQML->setSource(QUrl(QStringLiteral("qrc:/main.qml"))); +} + +void Window::updateResults() +{ + qDebug() << "Updating the results"; + + QString textDate = ui->textDate->text(); + + setQuery( + // What should we get + (ui->radioSelectUsedResources->isChecked() ? UsedResources + : ui->radioSelectLinkedResources->isChecked() ? LinkedResources + : AllResources) + | + + // How we should order it + (ui->radioOrderHighScoredFirst->isChecked() ? HighScoredFirst + : ui->radioOrderRecentlyUsedFirst->isChecked() ? RecentlyUsedFirst + : ui->radioOrderRecentlyCreatedFirst->isChecked() ? RecentlyCreatedFirst + : ui->radioOrderByUrl->isChecked() ? OrderByUrl + : OrderByTitle) + | + + // Which agents? + Agent(ui->textAgent->text().split(QLatin1Char(','))) | + + // Which mime? + Type(ui->textMimetype->text().split(QLatin1Char(','))) | + + // Which activities? + Activity(ui->comboActivity->currentText().split(QLatin1Char(','))) | + + // And URL filters + Url(ui->textUrl->text().split(QLatin1Char(','), Qt::SkipEmptyParts)) | + + // And date filter + (textDate == QStringLiteral("today") ? Date::today() + : textDate == QStringLiteral("yesterday") ? Date::yesterday() + : Date::fromString(textDate)) + | + + // And how many items + Limit(ui->spinLimitCount->value())); +} + +#include "moc_window.cpp" diff --git a/tests/model/window.h b/tests/model/window.h new file mode 100644 index 0000000..172240b --- /dev/null +++ b/tests/model/window.h @@ -0,0 +1,53 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include +#include + +#include + +class QAbstractItemModelTester; +namespace Ui +{ +class MainWindow; +} + +namespace KActivities +{ +class Consumer; +namespace Stats +{ +class ResultModel; +} +} + +class Window : public QMainWindow +{ + Q_OBJECT + +public: + Window(); + ~Window() override; + +private Q_SLOTS: + void updateResults(); + void updateRowCount(); + void selectPreset(); + +private: + void setQuery(const KActivities::Stats::Query &query); + + std::unique_ptr ui; + std::unique_ptr model; + std::unique_ptr modelTest; + std::unique_ptr activities; + + QMap presets; +}; diff --git a/tests/model/window.ui b/tests/model/window.ui new file mode 100644 index 0000000..b21af81 --- /dev/null +++ b/tests/model/window.ui @@ -0,0 +1,475 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + Qt::Horizontal + + + 2 + + + + + 0 + 0 + + + + + 200 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + true + + + + Query + + + + + + Presets + + + true + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + What + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Linked resources + + + false + + + + + + + &Used resources + + + true + + + + + + + Combined (no&t implemented) + + + + + + + + + + Ordering + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Hi&gh score + + + false + + + + + + + Recentl&y used resources + + + true + + + + + + + Rece&ntly created first + + + + + + + &By url + + + + + + + By t&itle + + + + + + + + + + Qt::Horizontal + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 8 + + + + + Activity + + + + + + + true + + + :current + + + + + + + Agent + + + + + + + :any + + + + + + + Mimetype + + + + + + + :any + + + + + + + Url + + + + + + + + + + Date + + + + + + + + + + Count + + + + + + + 10 + + + + + + + + + Update + + + + + + + Qt::Vertical + + + + 20 + 176 + + + + + + + + + Logs + + + + + + + + ... + + + + + + + ... + + + + + + + + + + + + QTextEdit::NoWrap + + + + + + + + + + 1 + 0 + + + + 1 + + + true + + + + QWidgets (QListView) + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + 0 + + + + QFrame::StyledPanel + + + + + + + + QML + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QQuickWidget::SizeRootObjectToView + + + + + + + + + + + + + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
+
+ + +
diff --git a/vim-extrarc b/vim-extrarc new file mode 100644 index 0000000..ea92437 --- /dev/null +++ b/vim-extrarc @@ -0,0 +1,12 @@ + +set makeprg=OBJ_REPLACEMENT='s=src=build-clang='\ makeobj + +imap :SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher +map :SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher + +let g:ctrlpswitcher_project_sources = expand(':p:h')."/src" +let g:ctrlpswitcher_mode = 1 + +set foldmethod=marker +set foldmarker=//_,//^ + -- 2.30.2