diff --git a/about-config/custom_enchant_mapping.prop b/about-config/custom_enchant_mapping.prop new file mode 100644 index 0000000000..ccbb243d8b --- /dev/null +++ b/about-config/custom_enchant_mapping.prop @@ -0,0 +1 @@ +com_github_penfeizhou_android_animation__apng:APNG4Android \ No newline at end of file diff --git a/about-config/custom_license_mappings.prop b/about-config/custom_license_mappings.prop new file mode 100644 index 0000000000..59f9dbc1ca --- /dev/null +++ b/about-config/custom_license_mappings.prop @@ -0,0 +1,10 @@ +org_bouncycastle__bcprov_jdk15on:MIT +com_github_omicronapps__7_Zip_JBinding_4Android:LGPL_2_1_or_later +org_eclipse_jgit__org_eclipse_jgit:BSD_3_Clause +com_github_penfeizhou_android_animation__apng:Apache_2_0 +com_github_penfeizhou_android_animation__frameanimation:Apache_2_0 +org_apache_commons__commons_lang3:Apache_2_0 +commons_io__commons_io:Apache_2_0 +org_apache_commons__commons_text:Apache_2_0 +com_google_auto_value__auto_value_annotations:Apache_2_0 +net.jcip:jcip-annotations \ No newline at end of file diff --git a/about-config/custom_name_mappings.prop b/about-config/custom_name_mappings.prop new file mode 100644 index 0000000000..2cc4684460 --- /dev/null +++ b/about-config/custom_name_mappings.prop @@ -0,0 +1,6 @@ +info_debatty__java_string_similarity:Java String Similarity +com_github_penfeizhou_android_animation__apng:APNG4Android +com_github_penfeizhou_android_animation__frameanimation:APNG4Android Animation +me_zhanghai_android_fastscroll__library:AndroidFastScroll +com_squareup_retrofit2__adapter_rxjava2:RxJava2 adapter for Retrofit +net_jcip__jcip_annotations:JCIP Annotations \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 527d1495ca..cfeee41d3c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,12 +3,22 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'org.sonarqube' apply plugin: 'com.google.firebase.crashlytics' +apply plugin: 'com.mikepenz.aboutlibraries.plugin' // apply signing configuration if config file exists if (file('signing.gradle').exists()) { apply from: 'signing.gradle' } +aboutLibraries { + exclusionPatterns = [~"com_google_android.*",~"android.*",~"com_mikepenz__fastadapter_extensions.*"] + configPath = "about-config" + additionalLicenses { + LGPL_2_1_or_later + BSD_3_Clause + } +} + android { compileSdkVersion 30 compileOptions { @@ -24,7 +34,7 @@ android { //noinspection ExpiringTargetSdkVersion targetSdkVersion 30 versionCode 130 // is updated automatically by BitRise; only used when building locally - versionName '1.14.5' + versionName '1.14.6' def fkToken = '\"' + (System.getenv("FK_TOKEN") ?: "") + '\"' def includeObjectBoxBrowser = System.getenv("INCLUDE_OBJECTBOX_BROWSER") ?: "false" @@ -179,6 +189,11 @@ dependencies { // Specific UI layout for tag mosaic : github.com/google/flexbox-layout implementation 'com.google.android:flexbox:2.0.1' + def auto_about_version = '8.9.0' + implementation "com.mikepenz:aboutlibraries-core:$auto_about_version" + implementation "com.mikepenz:aboutlibraries:$auto_about_version" + + /** * NETWORKING */ diff --git a/app/src/main/assets/licenses.html b/app/src/main/assets/licenses.html deleted file mode 100644 index aa53f8e38c..0000000000 --- a/app/src/main/assets/licenses.html +++ /dev/null @@ -1,2891 +0,0 @@ - - - - Licenses - - - - -

Notices for Additional Libraries

- -

This app contains several libraries that are released under the terms of the following - licenses.

- -

Notices for files:

- - -
Copyright (C) 2013 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -

Notices for files:

- - -
The MIT License (MIT)
-
-Copyright (c) 2014-2016 Aidan Michael Follestad
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- -

Notices for files:

- - -
License for everything not in third_party and not otherwise marked:
-
-Copyright 2014 Google, Inc. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
-   1. Redistributions of source code must retain the above copyright notice, this list of
-         conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright notice, this list
-         of conditions and the following disclaimer in the documentation and/or other materials
-         provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Google, Inc.
----------------------------------------------------------------------------------------------
-License for third_party/disklrucache:
-
-Copyright 2012 Jake Wharton
-Copyright 2011 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
----------------------------------------------------------------------------------------------
-License for third_party/gif_decoder:
-
-Copyright (c) 2013 Xcellent Creations, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------------------------------
-License for third_party/gif_encoder/AnimatedGifEncoder.java and
-third_party/gif_encoder/LZWEncoder.java:
-
-No copyright asserted on the source code of this class. May be used for any
-purpose, however, refer to the Unisys LZW patent for restrictions on use of
-the associated LZWEncoder class. Please forward any corrections to
-kweiner@fmsware.com.
-
------------------------------------------------------------------------------
-License for third_party/gif_encoder/NeuQuant.java
-
-Copyright (c) 1994 Anthony Dekker
-
-NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
-"Kohonen neural networks for optimal colour quantization" in "Network:
-Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
-the algorithm.
-
-Any party obtaining a copy of these files from the author, directly or
-indirectly, is granted, free of charge, a full and unrestricted irrevocable,
-world-wide, paid up, royalty-free, nonexclusive right and license to deal in
-this software and documentation files (the "Software"), including without
-limitation the rights to use, copy, modify, merge, publish, distribute,
-sublicense, and/or sell copies of the Software, and to permit persons who
-receive copies from any such party to do so, with the only requirement being
-that this copyright notice remain intact.
- -

Notices for files:

- - -
                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   Copyright 2013 Mani Selvaraj
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2015-2018 Victor Melnik
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2018 Google LLC
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
-
-"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of this License; and
-You must cause any modified files to carry prominent notices stating that You changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 
-
-You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
-5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   Copyright 2016 Bumsoo Kim
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
- -

Notices for files:

- - -
MIT License
-
-Copyright (c) 2017 Droids On Roids
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- -

Notices for files:

- - -
The MIT License
-
-Copyright (c) 2009-2018 Jonathan Hedley jonathan@hedley.net
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- -

Notices for files:

- - -
License for everything not in third_party and not otherwise marked:
-
-Copyright 2014 Google, Inc. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
-   1. Redistributions of source code must retain the above copyright notice, this list of
-         conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright notice, this list
-         of conditions and the following disclaimer in the documentation and/or other materials
-         provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Google, Inc.
----------------------------------------------------------------------------------------------
-License for third_party/disklrucache:
-
-Copyright 2012 Jake Wharton
-Copyright 2011 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
----------------------------------------------------------------------------------------------
-License for third_party/gif_decoder:
-
-Copyright (c) 2013 Xcellent Creations, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------------------------------
-License for third_party/gif_encoder/AnimatedGifEncoder.java and
-third_party/gif_encoder/LZWEncoder.java:
-
-No copyright asserted on the source code of this class. May be used for any
-purpose, however, refer to the Unisys LZW patent for restrictions on use of
-the associated LZWEncoder class. Please forward any corrections to
-kweiner@fmsware.com.
-
------------------------------------------------------------------------------
-License for third_party/gif_encoder/NeuQuant.java
-
-Copyright (c) 1994 Anthony Dekker
-
-NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
-"Kohonen neural networks for optimal colour quantization" in "Network:
-Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
-the algorithm.
-
-Any party obtaining a copy of these files from the author, directly or
-indirectly, is granted, free of charge, a full and unrestricted irrevocable,
-world-wide, paid up, royalty-free, nonexclusive right and license to deal in
-this software and documentation files (the "Software"), including without
-limitation the rights to use, copy, modify, merge, publish, distribute,
-sublicense, and/or sell copies of the Software, and to permit persons who
-receive copies from any such party to do so, with the only requirement being
-that this copyright notice remain intact.
- -

Notices for files:

- - -
BSD License
-
-For Stetho software
-
-Copyright (c) 2015, Facebook, Inc. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
-   list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
-
- * Neither the name Facebook nor the names of its contributors may be used to
-   endorse or promote products derived from this software without specific
-   prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- -

Notices for files:

- - -
https://firebase.google.com/terms/
- - - \ No newline at end of file diff --git a/app/src/main/java/me/devsaki/hentoid/activities/AboutActivity.kt b/app/src/main/java/me/devsaki/hentoid/activities/AboutActivity.kt index a12e1b416e..64b480c738 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/AboutActivity.kt +++ b/app/src/main/java/me/devsaki/hentoid/activities/AboutActivity.kt @@ -3,13 +3,13 @@ package me.devsaki.hentoid.activities import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.commit +import com.mikepenz.aboutlibraries.LibsBuilder import me.devsaki.hentoid.BuildConfig import me.devsaki.hentoid.R +import me.devsaki.hentoid.core.Consts import me.devsaki.hentoid.databinding.ActivityAboutBinding import me.devsaki.hentoid.events.UpdateEvent import me.devsaki.hentoid.fragments.about.ChangelogFragment -import me.devsaki.hentoid.fragments.about.LicensesFragment -import me.devsaki.hentoid.core.Consts import me.devsaki.hentoid.util.ThemeHelper import me.devsaki.hentoid.util.network.HttpHelper import me.devsaki.hentoid.util.startBrowserActivity @@ -37,12 +37,22 @@ class AboutActivity : BaseActivity() { it.discordText.setOnClickListener { startBrowserActivity(Consts.URL_DISCORD) } it.redditText.setOnClickListener { startBrowserActivity(Consts.URL_REDDIT) } - it.tvVersionName.text = getString(R.string.about_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) - it.tvChromeVersionName.text = getString(R.string.about_chrome_version, HttpHelper.getChromeVersion()) + it.tvVersionName.text = getString( + R.string.about_app_version, + BuildConfig.VERSION_NAME, + BuildConfig.VERSION_CODE + ) + it.tvChromeVersionName.text = + getString(R.string.about_chrome_version, HttpHelper.getChromeVersion()) it.changelogButton.setOnClickListener { showFragment(ChangelogFragment()) } - it.licensesButton.setOnClickListener { showFragment(LicensesFragment()) } + it.licensesButton.setOnClickListener { + LibsBuilder() + .withLicenseShown(true) + .withSearchEnabled(true) + .start(this) + } } if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this) diff --git a/app/src/main/java/me/devsaki/hentoid/activities/Api29MigrationActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/Api29MigrationActivity.java index ea415c7a74..8cd1d71d77 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/Api29MigrationActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/Api29MigrationActivity.java @@ -176,6 +176,8 @@ private void scanLibrary(@NonNull final DocumentFile root) { @Subscribe(threadMode = ThreadMode.MAIN) public void onMigrationEvent(ProcessEvent event) { + if (event.processId != R.id.migrate_api29) return; + ProgressBar progressBar = (ImportWorker.STEP_2_BOOK_FOLDERS == event.step) ? step2progress : step3progress; if (ProcessEvent.EventType.PROGRESS == event.eventType) { progressBar.setMax(event.elementsTotal); diff --git a/app/src/main/java/me/devsaki/hentoid/activities/ImageViewerActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/ImageViewerActivity.java index 75c5b1b1fb..f6b8b26c03 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/ImageViewerActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/ImageViewerActivity.java @@ -23,6 +23,8 @@ public class ImageViewerActivity extends BaseActivity { + public static boolean isRunning = false; + private VolumeKeyListener volumeKeyListener = null; private ImageViewerViewModel viewModel = null; @@ -40,6 +42,7 @@ protected void onCreate(Bundle savedInstanceState) { ImageViewerActivityBundle.Parser parser = new ImageViewerActivityBundle.Parser(intent.getExtras()); long contentId = parser.getContentId(); if (0 == contentId) throw new IllegalArgumentException("Incorrect ContentId"); + int pageNumber = parser.getPageNumber(); ViewModelFactory vmFactory = new ViewModelFactory(getApplication()); @@ -49,8 +52,8 @@ protected void onCreate(Bundle savedInstanceState) { if (null == viewModel.getContent().getValue()) { // ViewModel hasn't loaded anything yet (fresh start) Bundle searchParams = parser.getSearchParams(); - if (searchParams != null) viewModel.loadFromSearchParams(contentId, searchParams); - else viewModel.loadFromContent(contentId); + if (searchParams != null) viewModel.loadFromSearchParams(contentId, pageNumber, searchParams); + else viewModel.loadFromContent(contentId, pageNumber); } if (!PermissionHelper.requestExternalStorageReadPermission(this, RQST_STORAGE_PERMISSION)) { @@ -75,6 +78,8 @@ protected void onCreate(Bundle savedInstanceState) { if (!Preferences.getRecentVisibility()) getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + + isRunning = true; } @Override @@ -85,9 +90,12 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { @Override protected void onStop() { - if (isFinishing()) { + if (isFinishing()) { // i.e. the activity is closing for good; not being paused / backgrounded if (viewModel != null) viewModel.emptyCacheFolder(); Preferences.setViewerDeleteAskMode(Preferences.Constant.VIEWER_DELETE_ASK_AGAIN); + Preferences.setViewerCurrentPageNum(-1); + Preferences.setViewerCurrentContent(-1); + isRunning = false; } super.onStop(); } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/LibraryActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/LibraryActivity.java index 54815e7de0..ce2c7987b2 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/LibraryActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/LibraryActivity.java @@ -48,6 +48,8 @@ import me.devsaki.hentoid.BuildConfig; import me.devsaki.hentoid.R; +import me.devsaki.hentoid.database.CollectionDAO; +import me.devsaki.hentoid.database.ObjectBoxDAO; import me.devsaki.hentoid.database.domains.Attribute; import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.enums.Grouping; @@ -64,6 +66,7 @@ import me.devsaki.hentoid.notification.delete.DeleteNotificationChannel; import me.devsaki.hentoid.notification.delete.DeleteProgressNotification; import me.devsaki.hentoid.notification.delete.DeleteStartNotification; +import me.devsaki.hentoid.util.ContentHelper; import me.devsaki.hentoid.util.Debouncer; import me.devsaki.hentoid.util.FileHelper; import me.devsaki.hentoid.util.PermissionHelper; @@ -101,7 +104,7 @@ public class LibraryActivity extends BaseActivity { // ======== UI // Action view associated with search menu button - private SearchView mainSearchView; + private SearchView actionSearchView; // ==== Advanced search / sort bar // Grey background of the advanced search / sort bar @@ -360,6 +363,28 @@ private void onCreated() { } } + @Override + protected void onStart() { + super.onStart(); + final long previouslyViewedContent = Preferences.getViewerCurrentContent(); + final int previouslyViewedPage = Preferences.getViewerCurrentPageNum(); + if (previouslyViewedContent > -1 && previouslyViewedPage > -1 && !ImageViewerActivity.isRunning) { + Snackbar snackbar = Snackbar.make(viewPager, R.string.resume_closed, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.resume, v -> { + Timber.i("Reopening books %d from page %d", previouslyViewedContent, previouslyViewedPage); + CollectionDAO dao = new ObjectBoxDAO(this); + try { + Content c = dao.selectContent(previouslyViewedContent); + if (c != null) + ContentHelper.openHentoidViewer(this, c, previouslyViewedPage, null); + } finally { + dao.cleanup(); + } + }); + snackbar.show(); + } + } + /** * Initialize the UI components */ @@ -390,7 +415,7 @@ private void initUI() { searchClearButton.setOnClickListener(v -> { query = ""; metadata.clear(); - mainSearchView.setQuery("", false); + actionSearchView.setQuery("", false); hideSearchSortBar(false); signalCurrentFragment(EV_SEARCH, ""); }); @@ -440,7 +465,7 @@ public boolean onMenuItemActionExpand(MenuItem item) { // Without that handler the view displays with an empty value new Handler(Looper.getMainLooper()).postDelayed(() -> { invalidateNextQueryTextChange = true; - mainSearchView.setQuery(query, false); + actionSearchView.setQuery(query, false); }, 100); return true; @@ -464,16 +489,16 @@ public boolean onMenuItemActionCollapse(MenuItem item) { newGroupMenu = toolbar.getMenu().findItem(R.id.action_group_new); sortMenu = toolbar.getMenu().findItem(R.id.action_order); - mainSearchView = (SearchView) searchMenu.getActionView(); - mainSearchView.setIconifiedByDefault(true); - mainSearchView.setQueryHint(getString(R.string.search_hint)); + actionSearchView = (SearchView) searchMenu.getActionView(); + actionSearchView.setIconifiedByDefault(true); + actionSearchView.setQueryHint(getString(R.string.search_hint)); // Change display when text query is typed - mainSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + actionSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { query = s; signalCurrentFragment(EV_SEARCH, query); - mainSearchView.clearFocus(); + actionSearchView.clearFocus(); return true; } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/PrefsActivity.kt b/app/src/main/java/me/devsaki/hentoid/activities/PrefsActivity.kt index 72c0f67f47..17f856187d 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/PrefsActivity.kt +++ b/app/src/main/java/me/devsaki/hentoid/activities/PrefsActivity.kt @@ -65,9 +65,13 @@ class PrefsActivity : BaseActivity() { @Subscribe(threadMode = ThreadMode.MAIN) fun onImportEventComplete(event: ProcessEvent) { - if (ProcessEvent.EventType.COMPLETE == event.eventType && event.logFile != null) { + if (ProcessEvent.EventType.COMPLETE == event.eventType + && event.logFile != null + && (event.processId == R.id.import_external || event.processId == R.id.import_primary) + ) { val contentView = findViewById(android.R.id.content) - val snackbar = Snackbar.make(contentView, R.string.task_done, BaseTransientBottomBar.LENGTH_LONG) + val snackbar = + Snackbar.make(contentView, R.string.task_done, BaseTransientBottomBar.LENGTH_LONG) snackbar.setAction("READ LOG") { FileHelper.openFile(this, event.logFile) } snackbar.show() } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/ToolsActivity.kt b/app/src/main/java/me/devsaki/hentoid/activities/ToolsActivity.kt index 89042cf132..98c51c3e4c 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/ToolsActivity.kt +++ b/app/src/main/java/me/devsaki/hentoid/activities/ToolsActivity.kt @@ -47,9 +47,13 @@ class ToolsActivity : BaseActivity() { @Subscribe(threadMode = ThreadMode.MAIN) fun onImportEventComplete(event: ProcessEvent) { - if (ProcessEvent.EventType.COMPLETE == event.eventType && event.logFile != null) { + if (ProcessEvent.EventType.COMPLETE == event.eventType + && event.logFile != null + && (event.processId == R.id.import_external || event.processId == R.id.import_primary) + ) { val contentView = findViewById(android.R.id.content) - val snackbar = Snackbar.make(contentView, R.string.task_done, BaseTransientBottomBar.LENGTH_LONG) + val snackbar = + Snackbar.make(contentView, R.string.task_done, BaseTransientBottomBar.LENGTH_LONG) snackbar.setAction("READ LOG") { FileHelper.openFile(this, event.logFile) } snackbar.show() } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java index 7466866c3b..3719e7383f 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java @@ -13,6 +13,7 @@ */ public class DuplicateItemBundle { private static final String KEY_KEEP = "keep"; + private static final String KEY_IS_BEING_DELETED = "isBeingDeleted"; private DuplicateItemBundle() { throw new UnsupportedOperationException(); @@ -27,6 +28,11 @@ public void setKeep(Boolean value) { bundle.putBoolean(KEY_KEEP, value); } + public void setIsBeingDeleted(Boolean value) { + if (value != null) + bundle.putBoolean(KEY_IS_BEING_DELETED, value); + } + public boolean isEmpty() { return bundle.isEmpty(); } @@ -46,8 +52,14 @@ public Parser(@Nonnull Bundle bundle) { @Nullable public Boolean getKeep() { - if (bundle.containsKey(KEY_KEEP)) - return bundle.getBoolean(KEY_KEEP); + if (bundle.containsKey(KEY_KEEP)) return bundle.getBoolean(KEY_KEEP); + else return null; + } + + @Nullable + public Boolean isBeingDeleted() { + if (bundle.containsKey(KEY_IS_BEING_DELETED)) + return bundle.getBoolean(KEY_IS_BEING_DELETED); else return null; } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java index 14dbb63508..60feae9aa4 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java @@ -14,6 +14,7 @@ public class ImageViewerActivityBundle { private static final String KEY_CONTENT_ID = "contentId"; private static final String KEY_SEARCH_PARAMS = "searchParams"; private static final String KEY_IMAGE_INDEX = "imageIndex"; + private static final String KEY_IMAGE_NUMBER = "imageNumber"; private static final String KEY_SCALE = "scale"; private ImageViewerActivityBundle() { @@ -36,6 +37,10 @@ public void setImageIndex(int imageIndex) { bundle.putInt(KEY_IMAGE_INDEX, imageIndex); } + public void setPageNumber(int imageNumber) { + bundle.putInt(KEY_IMAGE_NUMBER, imageNumber); + } + public void setScale(float scale) { bundle.putFloat(KEY_SCALE, scale); } @@ -65,6 +70,10 @@ public int getImageIndex() { return bundle.getInt(KEY_IMAGE_INDEX, -1); } + public int getPageNumber() { + return bundle.getInt(KEY_IMAGE_NUMBER, -1); + } + public float getScale() { return bundle.getFloat(KEY_SCALE, -1); } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/ASMHentaiActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/ASMHentaiActivity.java index dcde78b46d..637aad6232 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/ASMHentaiActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/ASMHentaiActivity.java @@ -21,7 +21,7 @@ Site getStartSite() { protected CustomWebViewClient getWebClient() { CustomWebViewClient client = new CustomWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); - client.addToUrlBlacklist(blockedContent); + client.adBlocker.addToUrlBlacklist(blockedContent); return client; } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/AllPornComicActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/AllPornComicActivity.java index c418a77713..c218920ee6 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/AllPornComicActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/AllPornComicActivity.java @@ -1,18 +1,6 @@ package me.devsaki.hentoid.activities.sources; -import android.os.Looper; - -import androidx.annotation.NonNull; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import me.devsaki.hentoid.enums.Site; -import me.devsaki.hentoid.util.network.HttpHelper; -import okhttp3.Response; -import okhttp3.ResponseBody; -import timber.log.Timber; public class AllPornComicActivity extends BaseWebActivity { @@ -22,8 +10,6 @@ public class AllPornComicActivity extends BaseWebActivity { private static final String[] JS_CONTENT_BLACKLIST = {"var exoloader;", "popunder"}; private static final String[] DIRTY_ELEMENTS = {"iframe"}; - private static final List jsBlacklistCache = new ArrayList<>(); - Site getStartSite() { return Site.ALLPORNCOMIC; @@ -31,58 +17,12 @@ Site getStartSite() { @Override protected CustomWebViewClient getWebClient() { - CustomWebViewClient client = new AllPornComicWebViewClient(getStartSite(), GALLERY_FILTER, this); + CustomWebViewClient client = new CustomWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); - client.addUrlWhitelist(JS_WHITELIST); client.addDirtyElements(DIRTY_ELEMENTS); + client.adBlocker.addUrlWhitelist(JS_WHITELIST); + for (String s : JS_WHITELIST) client.adBlocker.addJsWhitelistUrlPattern(s); + for (String s : JS_CONTENT_BLACKLIST) client.adBlocker.addJsContentBlacklist(s); return client; } - - private static class AllPornComicWebViewClient extends CustomWebViewClient { - - AllPornComicWebViewClient(Site site, String[] filter, CustomWebActivity activity) { - super(site, filter, activity); - } - - /** - * Specific implementation to get rid of ad js files that have random names - */ - @Override - protected boolean isUrlBlacklisted(@NonNull String url) { - // 1- Process usual blacklist and cached dynamic blacklist - if (super.isUrlBlacklisted(url)) return true; - if (jsBlacklistCache.contains(url)) return true; - - // 2- Accept non-JS files - String extension = HttpHelper.getExtensionFromUri(url); - if (!extension.equals("js") && !extension.isEmpty()) return false; // obvious JS and hidden JS - - // 3- Accept JS files defined in the whitelist - if (super.isUrlWhitelisted(url)) return false; - - // 4- For the others (gray list), block them if they _contain_ keywords - if (Looper.getMainLooper().getThread() != Thread.currentThread()) { // No call on UI thread - Timber.d(">> examining grey file %s", url); - try { - Response response = HttpHelper.getOnlineResource(url, null, site.useMobileAgent(), site.useHentoidAgent(), site.useWebviewAgent()); - ResponseBody body = response.body(); - if (null == body) throw new IOException("Empty body"); - - String jsBody = body.string().toLowerCase(); - for (String s : JS_CONTENT_BLACKLIST) - if (jsBody.contains(s)) { - Timber.d(">> grey file %s BLOCKED", url); - jsBlacklistCache.add(url); - return true; - } - } catch (IOException e) { - Timber.e(e); - } - Timber.d(">> grey file %s ALLOWED", url); - } - - // Accept non-blocked (=grey) JS files - return false; - } - } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java index b9778c811f..ed075a7714 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java @@ -390,7 +390,7 @@ protected void onResume() { if (currentContent != null && url != null && getWebClient().isGalleryPage(url)) { if (processContentDisposable != null) processContentDisposable.dispose(); // Cancel whichever process was happening before - processContentDisposable = Single.fromCallable(() -> processContent(currentContent)) + processContentDisposable = Single.fromCallable(() -> processContent(currentContent, false)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -759,7 +759,7 @@ protected void onActionClick() { if (currentContent != null && (StatusContent.DOWNLOADED == currentContent.getStatus() || StatusContent.ERROR == currentContent.getStatus() || StatusContent.MIGRATED == currentContent.getStatus())) - ContentHelper.openHentoidViewer(this, currentContent, null); + ContentHelper.openHentoidViewer(this, currentContent, -1, null); else actionMenu.setEnabled(false); } break; @@ -933,7 +933,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { * @return The status of the Content after being processed */ private @ContentStatus - int processContent(@NonNull Content content) { + int processContent(@NonNull Content content, boolean quickDownload) { Helper.assertNonUiThread(); if (content.getUrl().isEmpty()) return ContentStatus.UNKNOWN; @@ -983,7 +983,7 @@ int processContent(@NonNull Content content) { // Content ID of the duplicate candidate of the currently viewed Content boolean duplicateSameSite = duplicateResult.left.getSite().equals(content.getSite()); // Same site and similar => download by default, but look for extra pics just in case - if (duplicateSameSite && Preferences.isDownloadPlusDuplicateTry()) + if (duplicateSameSite && Preferences.isDownloadPlusDuplicateTry() && !quickDownload) searchForExtraImages(duplicateResult.left); } @@ -1000,7 +1000,7 @@ int processContent(@NonNull Content content) { currentContent = content; if (isInCollection) { - searchForExtraImages(currentContent); + if (!quickDownload) searchForExtraImages(currentContent); return ContentStatus.IN_COLLECTION; } if (isInQueue) return ContentStatus.IN_QUEUE; @@ -1010,7 +1010,7 @@ int processContent(@NonNull Content content) { public void onResultReady(@NonNull Content result, boolean quickDownload) { if (processContentDisposable != null) processContentDisposable.dispose(); // Cancel whichever process was happening before - processContentDisposable = Single.fromCallable(() -> processContent(result)) + processContentDisposable = Single.fromCallable(() -> processContent(result, quickDownload)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java index 5bedc4f964..fa57c6f8b7 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java @@ -32,11 +32,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,13 +46,12 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import me.devsaki.hentoid.R; -import me.devsaki.hentoid.core.HentoidApp; import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.enums.Site; import me.devsaki.hentoid.enums.StatusContent; import me.devsaki.hentoid.parsers.ContentParserFactory; import me.devsaki.hentoid.parsers.content.ContentParser; +import me.devsaki.hentoid.util.AdBlocker; import me.devsaki.hentoid.util.FileHelper; import me.devsaki.hentoid.util.Helper; import me.devsaki.hentoid.util.JsonHelper; @@ -103,29 +100,16 @@ class CustomWebViewClient extends WebViewClient { // Loading state of the HTML code of the current webpage (used to trigger the action button) boolean isHtmlLoaded = false; + protected final AdBlocker adBlocker; + // Disposable to be used for punctual search private Disposable disposable; - // List of blocked URLs (ads or annoying images) -- will be replaced by a blank stream - // Universal lists (applied to all sites) - private static final Set universalUrlBlacklist = new HashSet<>(); - private static final Set universalUrlWhitelist = new HashSet<>(); - // Local lists (applied to current site) - private List localUrlBlacklist; - private List localUrlWhitelist; // List of "dirty" elements (CSS selector) to be cleaned before displaying the page private List dirtyElements; - static { - String[] appUrlBlacklist = HentoidApp.getInstance().getResources().getStringArray(R.array.blocked_domains); - universalUrlBlacklist.addAll(Arrays.asList(appUrlBlacklist)); - String[] appUrlWhitelist = HentoidApp.getInstance().getResources().getStringArray(R.array.allowed_domains); - universalUrlWhitelist.addAll(Arrays.asList(appUrlWhitelist)); - } - - CustomWebViewClient(Site site, String[] galleryUrl, CustomWebActivity activity) { this.site = site; this.activity = activity; @@ -134,6 +118,8 @@ class CustomWebViewClient extends WebViewClient { final Jspoon jspoon = Jspoon.create(); htmlAdapter = jspoon.adapter(c); // Unchecked but alright + adBlocker = new AdBlocker(site); + for (String s : galleryUrl) galleryUrlPattern.add(Pattern.compile(s)); } @@ -142,62 +128,6 @@ void destroy() { compositeDisposable.clear(); } - /** - * Indicates if the given URL is blacklisted by the current content filters - * - * @param url URL to be examinated - * @return True if URL is blacklisted according to current filters; false if not - */ - protected boolean isUrlBlacklisted(@NonNull String url) { - String comparisonUrl = url.toLowerCase(); - for (String s : universalUrlBlacklist) { - if (comparisonUrl.contains(s)) return true; - } - if (localUrlBlacklist != null) - for (String s : localUrlBlacklist) { - if (comparisonUrl.contains(s)) return true; - } - return false; - } - - /** - * Indicates if the given URL is whitelisted by the current content filters - * - * @param url URL to be examinated - * @return True if URL is whitelisted according to current filters; false if not - */ - protected boolean isUrlWhitelisted(@NonNull String url) { - String comparisonUrl = url.toLowerCase(); - for (String s : universalUrlWhitelist) { - if (comparisonUrl.contains(s)) return true; - } - if (localUrlWhitelist != null) - for (String s : localUrlWhitelist) { - if (comparisonUrl.contains(s)) return true; - } - return false; - } - - /** - * Add an element to current URL blacklist - * - * @param filter Filter to addAll to local blacklist - */ - protected void addToUrlBlacklist(String... filter) { - if (null == localUrlBlacklist) localUrlBlacklist = new ArrayList<>(); - Collections.addAll(localUrlBlacklist, filter); - } - - /** - * Add an element to current URL whitelist - * - * @param filter Filter to addAll to local whitelist - */ - protected void addUrlWhitelist(String... filter) { - if (null == localUrlWhitelist) localUrlWhitelist = new ArrayList<>(); - Collections.addAll(localUrlWhitelist, filter); - } - /** * Add an element filter to current site * @@ -320,7 +250,7 @@ protected boolean shouldOverrideUrlLoadingInternal( @NonNull final WebView view, @NonNull final String url, @Nullable final Map requestHeaders) { - if (isUrlBlacklisted(url) || !url.startsWith("http")) return true; + if (adBlocker.isBlocked(url) || !url.startsWith("http")) return true; // Download and open the torrent file // NB : Opening the URL itself won't work when the tracker is private @@ -425,7 +355,7 @@ public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @Nullable private WebResourceResponse shouldInterceptRequestInternal(@NonNull final String url, @Nullable final Map headers) { - if (isUrlBlacklisted(url) || !url.startsWith("http")) { + if (adBlocker.isBlocked(url) || !url.startsWith("http")) { return new WebResourceResponse("text/plain", "utf-8", NOTHING); } else { if (isGalleryPage(url)) return parseResponse(url, headers, true, false); diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java index d7b85243e6..164cbf6c44 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java @@ -4,18 +4,10 @@ import androidx.annotation.NonNull; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import me.devsaki.hentoid.enums.Site; import me.devsaki.hentoid.util.network.HttpHelper; -import okhttp3.Response; -import okhttp3.ResponseBody; -import timber.log.Timber; /** * Created by Shiro on 1/20/2016. @@ -30,24 +22,19 @@ public class HitomiActivity extends BaseWebActivity { private static final String[] JS_WHITELIST = {"galleries/[\\w%\\-]+.js$", "jquery", "filesaver", "common", "date", "download", "gallery", "jquery", "cookie", "jszip", "limitlists", "moment-with-locales", "moveimage", "pagination", "search", "searchlib", "yall", "reader", "decode_webp", "bootstrap"}; private static final String[] JS_CONTENT_BLACKLIST = {"exoloader", "popunder"}; - private static final List whitelistUrlPattern = new ArrayList<>(); - private static final List jsBlacklistCache = new ArrayList<>(); - - static { - for (String s : JS_WHITELIST) whitelistUrlPattern.add(Pattern.compile(s)); - } - Site getStartSite() { return Site.HITOMI; } @Override protected CustomWebViewClient getWebClient() { - CustomWebViewClient client = new HitomiWebViewClient(getStartSite(), GALLERY_FILTER, this); + CustomWebViewClient client = new CustomWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); client.setResultsUrlPatterns(RESULTS_FILTER); client.setResultUrlRewriter(this::rewriteResultsUrl); - client.addToUrlBlacklist(BLOCKED_CONTENT); + client.adBlocker.addToUrlBlacklist(BLOCKED_CONTENT); + for (String s : JS_WHITELIST) client.adBlocker.addJsWhitelistUrlPattern(s); + for (String s : JS_CONTENT_BLACKLIST) client.adBlocker.addJsContentBlacklist(s); return client; } @@ -67,52 +54,4 @@ private String rewriteResultsUrl(@NonNull Uri resultsUri, int page) { return builder.toString(); } - - private static class HitomiWebViewClient extends CustomWebViewClient { - - HitomiWebViewClient(Site site, String[] filter, CustomWebActivity activity) { - super(site, filter, activity); - } - - /** - * Specific implementation to get rid of ad js files that have random names - */ - @Override - protected boolean isUrlBlacklisted(@NonNull String url) { - // 1- Process usual blacklist and cached dynamic blacklist - if (super.isUrlBlacklisted(url)) return true; - if (jsBlacklistCache.contains(url)) return true; - - // 2- Accept non-JS files - if (!HttpHelper.getExtensionFromUri(url).equals("js")) return false; - - // 3- Accept JS files defined in the whitelist - for (Pattern p : whitelistUrlPattern) { - Matcher matcher = p.matcher(url.toLowerCase()); - if (matcher.find()) return false; - } - - // 4- For the others (gray list), block them if they _contain_ keywords - Timber.d(">> examining grey file %s", url); - try { - Response response = HttpHelper.getOnlineResource(url, null, site.useMobileAgent(), site.useHentoidAgent(), site.useWebviewAgent()); - ResponseBody body = response.body(); - if (null == body) throw new IOException("Empty body"); - - String jsBody = body.string().toLowerCase(); - for (String s : JS_CONTENT_BLACKLIST) - if (jsBody.contains(s)) { - Timber.d(">> grey file %s BLOCKED", url); - jsBlacklistCache.add(url); - return true; - } - } catch (IOException e) { - Timber.e(e); - } - Timber.d(">> grey file %s ALLOWED", url); - - // Accept non-blocked (=grey) JS files - return false; - } - } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/LusciousActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/LusciousActivity.java index b970f5f7a0..2fae58a302 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/LusciousActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/LusciousActivity.java @@ -16,7 +16,6 @@ import me.devsaki.hentoid.parsers.content.ContentParser; import me.devsaki.hentoid.parsers.content.LusciousContent; import me.devsaki.hentoid.util.JsonHelper; -import me.devsaki.hentoid.util.network.HttpHelper; import timber.log.Timber; public class LusciousActivity extends BaseWebActivity { @@ -37,7 +36,7 @@ Site getStartSite() { protected CustomWebViewClient getWebClient() { LusciousWebClient client = new LusciousWebClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); - client.addUrlWhitelist(DOMAIN_FILTER); + client.adBlocker.addUrlWhitelist(DOMAIN_FILTER); //client.addDirtyElements(DIRTY_ELEMENTS); // Init fetch handler here for convenience @@ -63,21 +62,6 @@ public void onFetchCall(String url, String body) { } } - /** - * Specific implementation to get rid of ad js files that have random names - */ - @Override - protected boolean isUrlBlacklisted(@NonNull String url) { - // 1- Process usual blacklist - if (super.isUrlBlacklisted(url)) return true; - - // 2- Accept non-JS files - if (!HttpHelper.getExtensionFromUri(url).equals("js")) return false; - - // 3- Accept JS files defined in the whitelist; block others - return !super.isUrlWhitelisted(url); - } - // Call the API without using BaseWebActivity.parseResponse @Override protected WebResourceResponse parseResponse(@NonNull String urlStr, @Nullable Map requestHeaders, boolean analyzeForDownload, boolean quickDownload) { diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/ManhwaActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/ManhwaActivity.java index b6a714415d..2aa6b4b4a1 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/ManhwaActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/ManhwaActivity.java @@ -1,15 +1,13 @@ package me.devsaki.hentoid.activities.sources; -import androidx.annotation.NonNull; - import me.devsaki.hentoid.enums.Site; -import me.devsaki.hentoid.util.network.HttpHelper; public class ManhwaActivity extends BaseWebActivity { private static final String DOMAIN_FILTER = "manhwahentai.me"; private static final String[] GALLERY_FILTER = {"//manhwahentai.me/[\\w\\-]+/[\\w\\-]+/$"}; private static final String[] DIRTY_ELEMENTS = {".c-ads"}; + private static final String[] JS_CONTENT_BLACKLIST = {"'iframe'"}; private static final String[] BLOCKED_CONTENT = {".cloudfront.net"}; @@ -19,34 +17,12 @@ Site getStartSite() { @Override protected CustomWebViewClient getWebClient() { - CustomWebViewClient client = new ManhwaWebViewClient(getStartSite(), GALLERY_FILTER, this); + CustomWebViewClient client = new CustomWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); - client.addToUrlBlacklist(BLOCKED_CONTENT); - client.addUrlWhitelist(DOMAIN_FILTER); + client.adBlocker.addToUrlBlacklist(BLOCKED_CONTENT); + client.adBlocker.addUrlWhitelist(DOMAIN_FILTER); + for (String s : JS_CONTENT_BLACKLIST) client.adBlocker.addJsContentBlacklist(s); client.addDirtyElements(DIRTY_ELEMENTS); return client; } - - private static class ManhwaWebViewClient extends CustomWebViewClient { - - ManhwaWebViewClient(Site site, String[] filter, CustomWebActivity activity) { - super(site, filter, activity); - } - - /** - * Specific implementation to get rid of ad js files that have random names - */ - @Override - protected boolean isUrlBlacklisted(@NonNull String url) { - // 1- Process usual blacklist - if (super.isUrlBlacklisted(url)) return true; - - // 2- Accept non-JS files - if (!HttpHelper.getExtensionFromUri(url).equals("js")) return false; - - // 3- Accept JS files defined in the whitelist; block others - return !super.isUrlWhitelisted(url); - } - } - } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java index 71690324b4..55aa73ed96 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java @@ -33,7 +33,7 @@ protected CustomWebViewClient getWebClient() { client.setResultsUrlPatterns(RESULTS_FILTER); client.setResultUrlRewriter(this::rewriteResultsUrl); client.addDirtyElements(DIRTY_ELEMENTS); - client.addToUrlBlacklist(BLOCKED_CONTENT); + client.adBlocker.addToUrlBlacklist(BLOCKED_CONTENT); return client; } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/ToonilyActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/ToonilyActivity.java index e594f882cf..6b2de16809 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/ToonilyActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/ToonilyActivity.java @@ -1,15 +1,13 @@ package me.devsaki.hentoid.activities.sources; -import androidx.annotation.NonNull; - import me.devsaki.hentoid.enums.Site; -import me.devsaki.hentoid.util.network.HttpHelper; public class ToonilyActivity extends BaseWebActivity { private static final String DOMAIN_FILTER = "toonily.com"; private static final String[] GALLERY_FILTER = {"//toonily.com/[\\w\\-]+/[\\w\\-]+/$"}; private static final String[] DIRTY_ELEMENTS = {".c-ads"}; + private static final String[] JS_CONTENT_BLACKLIST = {"'iframe'"}; private static final String[] BLOCKED_CONTENT = {".cloudfront.net"}; Site getStartSite() { @@ -18,33 +16,12 @@ Site getStartSite() { @Override protected CustomWebViewClient getWebClient() { - CustomWebViewClient client = new ToonilyWebViewClient(getStartSite(), GALLERY_FILTER, this); + CustomWebViewClient client = new CustomWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); - client.addToUrlBlacklist(BLOCKED_CONTENT); - client.addUrlWhitelist(DOMAIN_FILTER); + client.adBlocker.addToUrlBlacklist(BLOCKED_CONTENT); + client.adBlocker.addUrlWhitelist(DOMAIN_FILTER); + for (String s : JS_CONTENT_BLACKLIST) client.adBlocker.addJsContentBlacklist(s); client.addDirtyElements(DIRTY_ELEMENTS); return client; } - - private static class ToonilyWebViewClient extends CustomWebViewClient { - - ToonilyWebViewClient(Site site, String[] filter, CustomWebActivity activity) { - super(site, filter, activity); - } - - /** - * Specific implementation to get rid of ad js files that have random names - */ - @Override - protected boolean isUrlBlacklisted(@NonNull String url) { - // 1- Process usual blacklist - if (super.isUrlBlacklisted(url)) return true; - - // 2- Accept non-JS files - if (!HttpHelper.getExtensionFromUri(url).equals("js")) return false; - - // 3- Accept JS files defined in the whitelist; block others - return !super.isUrlWhitelisted(url); - } - } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/TsuminoActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/TsuminoActivity.java index dbaa18f69a..360fdb5fd1 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/TsuminoActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/TsuminoActivity.java @@ -27,7 +27,7 @@ protected CustomWebViewClient getWebClient() { CustomWebViewClient client = new TsuminoWebViewClient(getStartSite(), GALLERY_FILTER, this); client.restrictTo(DOMAIN_FILTER); client.addDirtyElements(DIRTY_ELEMENTS); - client.addToUrlBlacklist(blockedContent); + client.adBlocker.addToUrlBlacklist(blockedContent); return client; } diff --git a/app/src/main/java/me/devsaki/hentoid/core/EmergencyRestartHandler.java b/app/src/main/java/me/devsaki/hentoid/core/EmergencyRestartHandler.java index 913a1f7247..b41b1eb6b9 100644 --- a/app/src/main/java/me/devsaki/hentoid/core/EmergencyRestartHandler.java +++ b/app/src/main/java/me/devsaki/hentoid/core/EmergencyRestartHandler.java @@ -6,6 +6,13 @@ import androidx.annotation.NonNull; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import me.devsaki.hentoid.util.LogHelper; +import me.devsaki.hentoid.util.StringHelper; import timber.log.Timber; public class EmergencyRestartHandler implements @@ -20,10 +27,37 @@ public EmergencyRestartHandler(Context context, Class c) { public void uncaughtException(@NonNull Thread thread, @NonNull Throwable exception) { Timber.e(exception); - Intent intent = new Intent(myContext, myActivityClass); - myContext.startActivity(intent); - // Restart the Activity + + // Log the exception + Timber.i("Logging crash exception"); + try { + List log = new ArrayList<>(); + log.add(new LogHelper.LogEntry(StringHelper.protect(exception.getMessage()))); + log.add(new LogHelper.LogEntry(getStackTraceString(exception))); + + LogHelper.LogInfo logInfo = new LogHelper.LogInfo(); + logInfo.setEntries(log); + logInfo.setLogName("latest-crash"); + LogHelper.writeLog(HentoidApp.getInstance(), logInfo); + } finally { + // Restart the Activity + Timber.i("Restart %s", myActivityClass.getSimpleName()); + Intent intent = new Intent(myContext, myActivityClass); + myContext.startActivity(intent); + } + + Timber.i("Kill current process"); Process.killProcess(Process.myPid()); System.exit(0); } + + private String getStackTraceString(Throwable t) { + // Don't replace this with Log.getStackTraceString() - it hides + // UnknownHostException, which is not what we want. + StringWriter sw = new StringWriter(256); + PrintWriter pw = new PrintWriter(sw, false); + t.printStackTrace(pw); + pw.flush(); + return sw.toString(); + } } diff --git a/app/src/main/java/me/devsaki/hentoid/database/CollectionDAO.java b/app/src/main/java/me/devsaki/hentoid/database/CollectionDAO.java index d31dfff906..05a9083996 100644 --- a/app/src/main/java/me/devsaki/hentoid/database/CollectionDAO.java +++ b/app/src/main/java/me/devsaki/hentoid/database/CollectionDAO.java @@ -137,10 +137,20 @@ public interface CollectionDAO { long countStoredContent(boolean nonFavouriteOnly, boolean includeQueued); + + // TODO tidy up once tested + Observable streamContentWithUnhashedCovers(); + List selectContentWithUnhashedCovers(); + + void streamContentWithUnhashedCovers(Consumer consumer); + long countContentWithUnhashedCovers(); + + + void streamStoredContent(boolean nonFavouritesOnly, boolean includeQueued, int orderField, boolean orderDesc, Consumer consumer); diff --git a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java index 1da49da1d0..18e9e2854c 100644 --- a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java +++ b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java @@ -25,7 +25,6 @@ import io.objectbox.android.ObjectBoxDataSource; import io.objectbox.android.ObjectBoxLiveData; import io.objectbox.query.Query; -import io.objectbox.query.QueryBuilder; import io.objectbox.query.QueryConsumer; import io.objectbox.relation.ToOne; import io.reactivex.Emitter; @@ -90,24 +89,30 @@ public long countStoredContent(boolean nonFavouritesOnly, boolean includeQueued) @Override public long countContentWithUnhashedCovers() { - return db.selectNonHashedContent2().count(); + return db.selectNonHashedContent().count(); } @Override public Observable streamContentWithUnhashedCovers() { - Query query = db.selectNonHashedContent2(); + Query query = db.selectNonHashedContent(); return Observable.create(emitter -> query.forEach(new DatabaseConsumer<>(emitter))); } - // TODO make that observable fire onComplete event - public Observable streamStoredContent(boolean nonFavouritesOnly, boolean includeQueued, int orderField, boolean orderDesc) { - QueryBuilder query = db.selectStoredContentQ(nonFavouritesOnly, includeQueued, orderField, orderDesc); - return Observable.create(emitter -> query.build().forEach(new DatabaseConsumer<>(emitter))); + @Override + public List selectContentWithUnhashedCovers() { + return db.selectNonHashedContent().find(); } + @Override + public void streamContentWithUnhashedCovers(Consumer consumer) { + Query query = db.selectNonHashedContent(); + query.forEach(consumer::accept); + } + + @Override public void streamStoredContent(boolean nonFavouritesOnly, boolean includeQueued, int orderField, boolean orderDesc, Consumer consumer) { - QueryBuilder query = db.selectStoredContentQ(nonFavouritesOnly, includeQueued, orderField, orderDesc); - query.build().forEach(consumer::accept); + Query query = db.selectStoredContentQ(nonFavouritesOnly, includeQueued, orderField, orderDesc).build(); + query.forEach(consumer::accept); } @Override diff --git a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java index ff3e642544..783cba76d0 100644 --- a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java +++ b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java @@ -1399,17 +1399,7 @@ QueryBuilder selectStoredContentQ(boolean nonFavouritesOnly, boolean in return query; } - Query selectNonHashedContent1() { - return selectNonHashedCovers() - .link(ImageFile_.content) - .in(Content_.status, new int[]{ - StatusContent.DOWNLOADED.getCode(), - StatusContent.MIGRATED.getCode()}) - .notNull(Content_.storageUri) - .notEqual(Content_.storageUri, "").build(); - } - - Query selectNonHashedContent2() { + Query selectNonHashedContent() { QueryBuilder query = store.boxFor(Content.class).query() .in(Content_.status, new int[]{ StatusContent.DOWNLOADED.getCode(), diff --git a/app/src/main/java/me/devsaki/hentoid/database/domains/DuplicateEntry.kt b/app/src/main/java/me/devsaki/hentoid/database/domains/DuplicateEntry.kt index 3922d71a7c..691d94110e 100644 --- a/app/src/main/java/me/devsaki/hentoid/database/domains/DuplicateEntry.kt +++ b/app/src/main/java/me/devsaki/hentoid/database/domains/DuplicateEntry.kt @@ -30,7 +30,10 @@ data class DuplicateEntry( var duplicateContent: Content? = null @Transient - var keep: Boolean? = null + var keep: Boolean = true + + @Transient + var isBeingDeleted: Boolean = false fun calcTotalScore(): Float { diff --git a/app/src/main/java/me/devsaki/hentoid/events/ProcessEvent.java b/app/src/main/java/me/devsaki/hentoid/events/ProcessEvent.java index 73fba9773a..10e6fed4b7 100644 --- a/app/src/main/java/me/devsaki/hentoid/events/ProcessEvent.java +++ b/app/src/main/java/me/devsaki/hentoid/events/ProcessEvent.java @@ -1,6 +1,8 @@ package me.devsaki.hentoid.events; +import androidx.annotation.IdRes; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; import java.lang.annotation.Retention; @@ -21,6 +23,7 @@ public class ProcessEvent { public final @EventType int eventType; // Event type + public final int processId; // Identifier of the process the event is used for public final int step; // Step of the process public final String elementName; // Name of processed element public final int elementsOK; // Number of elements that have been correctly processed @@ -34,8 +37,9 @@ public class ProcessEvent { * @param eventType event type code * @param step step of the process */ - public ProcessEvent(@EventType int eventType, int step, String elementName) { + public ProcessEvent(@EventType int eventType, @IdRes int processId, int step, String elementName) { this.eventType = eventType; + this.processId = processId; this.step = step; this.elementName = elementName; this.logFile = null; @@ -53,8 +57,9 @@ public ProcessEvent(@EventType int eventType, int step, String elementName) { * @param elementsKO elements whose processing has failed so far * @param elementsTotal total elements to process */ - public ProcessEvent(@EventType int eventType, int step, int elementsOK, int elementsKO, int elementsTotal) { + public ProcessEvent(@EventType int eventType, @IdRes int processId, int step, int elementsOK, int elementsKO, int elementsTotal) { this.eventType = eventType; + this.processId = processId; this.step = step; this.elementsOK = elementsOK; this.elementsKO = elementsKO; @@ -72,8 +77,9 @@ public ProcessEvent(@EventType int eventType, int step, int elementsOK, int elem * @param elementsKO elements whose processing has failed so far * @param elementsTotal total elements to process */ - public ProcessEvent(@EventType int eventType, int step, int elementsOK, int elementsKO, int elementsTotal, DocumentFile logFile) { + public ProcessEvent(@EventType int eventType, @IdRes int processId, int step, int elementsOK, int elementsKO, int elementsTotal, DocumentFile logFile) { this.eventType = eventType; + this.processId = processId; this.step = step; this.elementsOK = elementsOK; this.elementsKO = elementsKO; diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/DeleteProgressDialogFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/DeleteProgressDialogFragment.java index d9b2e7bccd..1979bc0e48 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/DeleteProgressDialogFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/DeleteProgressDialogFragment.java @@ -72,6 +72,8 @@ public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstance @Subscribe(threadMode = ThreadMode.MAIN) public void onProcessEvent(ProcessEvent event) { + if (event.processId != R.id.generic_delete) return; + binding.deleteBar.setMax(event.elementsTotal); if (ProcessEvent.EventType.PROGRESS == event.eventType) { binding.deleteProgress.setText(getString(R.string.book_progress, event.elementsOK + event.elementsKO, event.elementsTotal)); diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/about/LicensesFragment.kt b/app/src/main/java/me/devsaki/hentoid/fragments/about/LicensesFragment.kt deleted file mode 100644 index f689cfa50f..0000000000 --- a/app/src/main/java/me/devsaki/hentoid/fragments/about/LicensesFragment.kt +++ /dev/null @@ -1,30 +0,0 @@ -package me.devsaki.hentoid.fragments.about - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import me.devsaki.hentoid.R -import me.devsaki.hentoid.databinding.FragmentLicensesBinding - -class LicensesFragment : Fragment(R.layout.fragment_licenses) { - - private var _binding: FragmentLicensesBinding? = null - private val binding get() = _binding!! - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FragmentLicensesBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.licensesToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - binding.licensesWebView.loadUrl("file:///android_asset/licenses.html") - } -} \ No newline at end of file diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java index 9a280d5248..facf97d5d3 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java @@ -1226,7 +1226,7 @@ private boolean onItemClick(int position, @NonNull ContentItem item) { if (selectExtension.getSelections().isEmpty()) { if (item.getContent() != null && !item.getContent().isBeingDeleted()) { topItemPosition = position; - ContentHelper.openHentoidViewer(requireContext(), item.getContent(), viewModel.getSearchManagerBundle()); + ContentHelper.openHentoidViewer(requireContext(), item.getContent(), -1, viewModel.getSearchManagerBundle()); } return true; } diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/preferences/LibRefreshDialogFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/preferences/LibRefreshDialogFragment.java index b3718873fb..5f5ad68c7c 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/preferences/LibRefreshDialogFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/preferences/LibRefreshDialogFragment.java @@ -323,6 +323,9 @@ private void updateOnSelectFolder() { @Subscribe(threadMode = ThreadMode.MAIN) public void onImportEvent(ProcessEvent event) { + if (event.processId != R.id.import_external && event.processId != R.id.import_primary) + return; + ProgressBar progressBar; switch (event.step) { case (ImportWorker.STEP_2_BOOK_FOLDERS): diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/queue/ErrorsFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/queue/ErrorsFragment.java index 21e03ce336..0c885ed342 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/queue/ErrorsFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/queue/ErrorsFragment.java @@ -390,7 +390,7 @@ private void onContentHashToShowFirstChanged(Long contentHash) { private boolean onItemClick(ContentItem item) { if (null == selectExtension || selectExtension.getSelections().isEmpty()) { Content c = item.getContent(); - if (c != null && !ContentHelper.openHentoidViewer(requireContext(), c, null)) + if (c != null && !ContentHelper.openHentoidViewer(requireContext(), c, -1, null)) ToastHelper.toast(R.string.err_no_content); return true; diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java index 553587641e..eea21334ea 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java @@ -792,7 +792,7 @@ private boolean onItemClick(ContentItem item) { c = new ObjectBoxDAO(requireContext()).selectContent(c.getId()); if (c != null) { - if (!ContentHelper.openHentoidViewer(requireContext(), c, null)) + if (!ContentHelper.openHentoidViewer(requireContext(), c, -1, null)) ToastHelper.toast(R.string.err_no_content); return true; } else return false; diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt index 37917129e8..cd1aaeb478 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt +++ b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt @@ -12,7 +12,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.annimon.stream.Objects +import com.google.android.material.switchmaterial.SwitchMaterial import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.diff.DiffCallback @@ -65,7 +65,8 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { oldItem: DuplicateItem, newItem: DuplicateItem ): Boolean { - return Objects.equals(oldItem.keep, newItem.keep) + return (oldItem.keep == newItem.keep) + && (oldItem.isBeingDeleted == newItem.isBeingDeleted) } override fun getChangePayload( @@ -75,9 +76,12 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { newItemPosition: Int ): Any? { val diffBundleBuilder = DuplicateItemBundle.Builder() - if (!Objects.equals(oldItem.keep, newItem.keep)) { + if (oldItem.keep != newItem.keep) { diffBundleBuilder.setKeep(newItem.keep) } + if (oldItem.isBeingDeleted != newItem.isBeingDeleted) { + diffBundleBuilder.setIsBeingDeleted(newItem.isBeingDeleted) + } return if (diffBundleBuilder.isEmpty) null else diffBundleBuilder.bundle } } @@ -133,7 +137,7 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { false } - // "Keep" button click listener + // "Keep/delete" switch click listener fastAdapter.addEventHook(object : ClickEventHook() { override fun onClick( v: View, @@ -141,30 +145,12 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { fastAdapter: FastAdapter, item: DuplicateItem ) { - onBookChoice(item.content, true) + onBookChoice(item.content, (v as SwitchMaterial).isChecked) } override fun onBind(viewHolder: RecyclerView.ViewHolder): View? { return if (viewHolder is DuplicateItem.ContentViewHolder) { - viewHolder.keepButton - } else super.onBind(viewHolder) - } - }) - - // "Delete" button click listener - fastAdapter.addEventHook(object : ClickEventHook() { - override fun onClick( - v: View, - position: Int, - fastAdapter: FastAdapter, - item: DuplicateItem - ) { - onBookChoice(item.content, false) - } - - override fun onBind(viewHolder: RecyclerView.ViewHolder): View? { - return if (viewHolder is DuplicateItem.ContentViewHolder) { - viewHolder.deleteButton + viewHolder.keepDeleteSwitch } else super.onBind(viewHolder) } }) @@ -200,11 +186,11 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { return } - if (!ContentHelper.openHentoidViewer(requireContext(), c, null)) + if (!ContentHelper.openHentoidViewer(requireContext(), c, -1, null)) ToastHelper.toast(R.string.err_no_content) } - private fun onBookChoice(item: Content?, choice: Boolean?) { + private fun onBookChoice(item: Content?, choice: Boolean) { if (item != null) viewModel.setBookChoice(item, choice) } @@ -221,17 +207,6 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details) { val items = duplicates.sortedByDescending { it.calcTotalScore() } .map { DuplicateItem(it, DuplicateItem.ViewType.DETAILS) }.toMutableList() set(itemAdapter, items, ITEM_DIFF_CALLBACK) - - // Update bottom bar - val nbChoicesRemaining = items.size - duplicates.filter { it.keep != null }.count() - if (nbChoicesRemaining > 0) { - binding.applyBtn.text = - resources.getString(R.string.apply_processing, nbChoicesRemaining) - binding.applyBtn.isEnabled = false - } else { - binding.applyBtn.text = resources.getString(R.string.apply_final) - binding.applyBtn.isEnabled = true - } } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateMainFragment.kt b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateMainFragment.kt index 4a1f848309..263a2245f4 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateMainFragment.kt +++ b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateMainFragment.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ProgressBar +import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider @@ -23,6 +24,7 @@ import me.devsaki.hentoid.databinding.FragmentDuplicateMainBinding import me.devsaki.hentoid.events.CommunicationEvent import me.devsaki.hentoid.events.ProcessEvent import me.devsaki.hentoid.events.ServiceDestroyedEvent +import me.devsaki.hentoid.ui.BlinkAnimation import me.devsaki.hentoid.util.Preferences import me.devsaki.hentoid.viewholders.DuplicateItem import me.devsaki.hentoid.viewmodels.DuplicateViewModel @@ -163,15 +165,19 @@ class DuplicateMainFragment : Fragment(R.layout.fragment_duplicate_main) { if (binding.controls.useCover.isChecked) View.VISIBLE else View.GONE binding.controls.indexPicturesTxt.visibility = coverControlsVisibility binding.controls.indexPicturesPb.visibility = coverControlsVisibility + binding.controls.indexPicturesPbTxt.visibility = View.GONE binding.controls.detectBooksTxt.visibility = View.VISIBLE binding.controls.detectBooksPb.visibility = View.VISIBLE + binding.controls.detectBooksPbTxt.visibility = View.GONE } else { binding.controls.scanFab.visibility = View.VISIBLE binding.controls.stopFab.visibility = View.INVISIBLE binding.controls.indexPicturesTxt.visibility = View.GONE binding.controls.indexPicturesPb.visibility = View.GONE + binding.controls.indexPicturesPbTxt.visibility = View.GONE binding.controls.detectBooksTxt.visibility = View.GONE binding.controls.detectBooksPb.visibility = View.GONE + binding.controls.detectBooksPbTxt.visibility = View.GONE } } binding.controls.root.visibility = result @@ -234,8 +240,10 @@ class DuplicateMainFragment : Fragment(R.layout.fragment_duplicate_main) { binding.controls.indexPicturesTxt.visibility = View.GONE binding.controls.indexPicturesPb.visibility = View.GONE + binding.controls.indexPicturesPbTxt.visibility = View.GONE binding.controls.detectBooksTxt.visibility = View.GONE binding.controls.detectBooksPb.visibility = View.GONE + binding.controls.detectBooksPbTxt.visibility = View.GONE } private fun onMainCriteriaChanged() { @@ -292,17 +300,37 @@ class DuplicateMainFragment : Fragment(R.layout.fragment_duplicate_main) { @Subscribe(threadMode = ThreadMode.MAIN) fun onProcessEvent(event: ProcessEvent) { + if (event.processId != R.id.duplicate_index && event.processId != R.id.duplicate_detect) return + val progressBar: ProgressBar = if (STEP_COVER_INDEX == event.step) binding.controls.indexPicturesPb else binding.controls.detectBooksPb + val progressBarTxt: TextView = + if (STEP_COVER_INDEX == event.step) binding.controls.indexPicturesPbTxt else binding.controls.detectBooksPbTxt + + if (STEP_COVER_INDEX == event.step) { + if (null == binding.controls.detectBooksPbTxt.animation) { + binding.controls.detectBooksPbTxt.startAnimation(BlinkAnimation(750, 20)) + binding.controls.detectBooksPbTxt.text = + resources.getText(R.string.duplicate_wait_index) + binding.controls.detectBooksPbTxt.visibility = View.VISIBLE + } + } else { + binding.controls.detectBooksPbTxt.clearAnimation() + } + progressBar.max = event.elementsTotal progressBar.progress = event.elementsOK + event.elementsKO + progressBarTxt.text = String.format("%d / %d", progressBar.progress, progressBar.max) + progressBarTxt.visibility = View.VISIBLE if (ProcessEvent.EventType.COMPLETE == event.eventType && STEP_DUPLICATES == event.step) { setSettingsPanelVisibility(false) disableScanUi() } else if (binding.controls.scanFab.visibility == View.VISIBLE && DuplicateDetectorWorker.isRunning( requireContext() ) - ) activateScanUi() + ) { + activateScanUi() + } } /** diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPagerFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPagerFragment.java index 77d2df8ade..0561f53985 100644 --- a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPagerFragment.java +++ b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPagerFragment.java @@ -281,6 +281,8 @@ public void onDestroy() { @Subscribe(threadMode = ThreadMode.MAIN) public void onProcessEvent(ProcessEvent event) { if (null == binding) return; + if (event.processId != R.id.viewer_load) return; + if (ProcessEvent.EventType.PROGRESS == event.eventType) { // Empty display until loading is complete if (adapter.getItemCount() > 0) adapter.submitList(Collections.emptyList()); @@ -356,7 +358,7 @@ private void initControlsOverlay() { Slider slider = binding.controlsOverlay.slideshowDelaySlider; slider.setValueFrom(0); int nbEntries = getResources().getStringArray(R.array.pref_viewer_slideshow_delay_entries).length; - slider.setValueTo(nbEntries - 1f); + slider.setValueTo(Math.max(1, nbEntries - 1f)); slider.setValue(convertPrefsDelayToSliderPosition(Preferences.getViewerSlideshowDelay())); slider.setLabelFormatter(value -> { String[] entries = getResources().getStringArray(R.array.pref_viewer_slideshow_delay_entries); @@ -388,22 +390,22 @@ public void onStopTrackingTouch(@NonNull Slider slider) { // Page slider and preview binding.controlsOverlay.pageSlider.addOnSliderTouchListener(new Slider.OnSliderTouchListener() { - @Override - public void onStartTrackingTouch(@NonNull Slider slider) { - binding.controlsOverlay.imagePreviewLeft.setVisibility(View.VISIBLE); - binding.controlsOverlay.imagePreviewCenter.setVisibility(View.VISIBLE); - binding.controlsOverlay.imagePreviewRight.setVisibility(View.VISIBLE); - binding.recyclerView.setVisibility(View.INVISIBLE); - } - - @Override - public void onStopTrackingTouch(@NonNull Slider slider) { - binding.controlsOverlay.imagePreviewLeft.setVisibility(View.INVISIBLE); - binding.controlsOverlay.imagePreviewCenter.setVisibility(View.INVISIBLE); - binding.controlsOverlay.imagePreviewRight.setVisibility(View.INVISIBLE); - binding.recyclerView.setVisibility(View.VISIBLE); - } - } + @Override + public void onStartTrackingTouch(@NonNull Slider slider) { + binding.controlsOverlay.imagePreviewLeft.setVisibility(View.VISIBLE); + binding.controlsOverlay.imagePreviewCenter.setVisibility(View.VISIBLE); + binding.controlsOverlay.imagePreviewRight.setVisibility(View.VISIBLE); + binding.recyclerView.setVisibility(View.INVISIBLE); + } + + @Override + public void onStopTrackingTouch(@NonNull Slider slider) { + binding.controlsOverlay.imagePreviewLeft.setVisibility(View.INVISIBLE); + binding.controlsOverlay.imagePreviewCenter.setVisibility(View.INVISIBLE); + binding.controlsOverlay.imagePreviewRight.setVisibility(View.INVISIBLE); + binding.recyclerView.setVisibility(View.VISIBLE); + } + } ); binding.controlsOverlay.pageSlider.addOnChangeListener((slider1, value, fromUser) -> { @@ -590,7 +592,7 @@ private void onImagesChanged(List images) { private void differEndCallback() { if (null == binding) return; - maxPosition = adapter.getItemCount() - 1; + maxPosition = Math.max(1, adapter.getItemCount() - 1); binding.controlsOverlay.pageSlider.setValueTo(maxPosition); // Can't access the gallery when there's no page to display @@ -710,6 +712,7 @@ else if (Constant.VIEWER_DIRECTION_RTL == direction && imageIndex < scrollPositi imageIndex = scrollPosition; ImageFile currentImage = adapter.getImageAt(imageIndex); if (currentImage != null) { + Preferences.setViewerCurrentPageNum(currentImage.getOrder()); viewModel.markPageAsRead(currentImage.getOrder()); isPageFavourite = currentImage.isFavourite(); } diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/HentaifoxContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/HentaifoxContent.java index 4507291407..843c579831 100644 --- a/app/src/main/java/me/devsaki/hentoid/parsers/content/HentaifoxContent.java +++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/HentaifoxContent.java @@ -8,19 +8,21 @@ import javax.annotation.Nonnull; +import me.devsaki.hentoid.database.domains.AttributeMap; import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.enums.AttributeType; import me.devsaki.hentoid.enums.Site; import me.devsaki.hentoid.enums.StatusContent; import me.devsaki.hentoid.parsers.ParseHelper; import me.devsaki.hentoid.parsers.images.HentaifoxParser; -import me.devsaki.hentoid.database.domains.AttributeMap; import me.devsaki.hentoid.util.StringHelper; import pl.droidsonroids.jspoon.annotation.Selector; public class HentaifoxContent extends BaseContentParser { @Selector(value = ".cover img", attr = "src", defValue = "") private String coverUrl; + @Selector(value = ".cover img", attr = "data-cfsrc", defValue = "") + private String coverUrl2; @Selector(value = ".info h1", defValue = "") private String title; @Selector(".info") @@ -38,7 +40,7 @@ public Content update(@NonNull final Content content, @Nonnull String url) { content.setUrl(url.replace(Site.HENTAIFOX.getUrl(), "").replace("/gallery", "")); content.populateUniqueSiteId(); - content.setCoverImageUrl(coverUrl); + content.setCoverImageUrl(coverUrl.isEmpty() ? coverUrl2 : coverUrl); content.setTitle(StringHelper.removeNonPrintableChars(title)); if (null == information || information.children().isEmpty()) return content; diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/ImhentaiContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/ImhentaiContent.java index 72c1e1bb8b..983fbf0b3d 100644 --- a/app/src/main/java/me/devsaki/hentoid/parsers/content/ImhentaiContent.java +++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/ImhentaiContent.java @@ -21,7 +21,7 @@ public class ImhentaiContent extends BaseContentParser { private Element cover; @Selector(value = "div.right_details h1", defValue = "") private String title; - @Selector("li.pages") + @Selector(value = "li.pages", defValue = "") private String pages; @Selector(value = "ul.galleries_info a[href*='/artist']") private List artists; @@ -48,8 +48,10 @@ public Content update(@NonNull final Content content, @Nonnull String url) { String str = !title.isEmpty() ? StringHelper.removeNonPrintableChars(title) : ""; str = ParseHelper.removeTextualTags(str); content.setTitle(str); - str = pages.replace("Pages", "").replace("pages", "").replace(":", "").trim(); - content.setQtyPages(Integer.parseInt(str)); + if (!pages.isEmpty()) { + str = pages.replace("Pages", "").replace("pages", "").replace(":", "").trim(); + content.setQtyPages(Integer.parseInt(str)); + } AttributeMap attributes = new AttributeMap(); ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, artists, false, Site.IMHENTAI); diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java index 35340668d6..faceca26a1 100644 --- a/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java +++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java @@ -46,6 +46,8 @@ public class EHentaiParser implements ImageListParser { + public static final String MPV_LINK_CSS = "#gmid a[href*='/mpv/']"; + private final ParseProgress progress = new ParseProgress(); private boolean processHalted = false; @@ -90,10 +92,14 @@ public List parseImageList(@NonNull Content content) throws Exception if (galleryDoc != null) { // Detect if multipage viewer is on // result = loadMpv("https://e-hentai.org/mpv/530350/8b3c7e4a21/", headers, useHentoidAgent); - Elements elements = galleryDoc.select(".gm a[href*='/mpv/']"); + Elements elements = galleryDoc.select(MPV_LINK_CSS); if (!elements.isEmpty()) { String mpvUrl = elements.get(0).attr("href"); - result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent); + try { + result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent); + } catch (EmptyResultException e) { + result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent); + } } else { result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent); } diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java index f591a923d0..97f73be632 100644 --- a/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java +++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java @@ -36,6 +36,7 @@ import okhttp3.ResponseBody; import timber.log.Timber; +import static me.devsaki.hentoid.parsers.images.EHentaiParser.MPV_LINK_CSS; import static me.devsaki.hentoid.parsers.images.EHentaiParser.getCookieStr; import static me.devsaki.hentoid.util.network.HttpHelper.getOnlineDocument; @@ -77,10 +78,14 @@ public List parseImageList(@NonNull Content content) throws Exception Document galleryDoc = getOnlineDocument(content.getGalleryUrl(), headers, useHentoidAgent, useWebviewAgent); if (galleryDoc != null) { // Detect if multipage viewer is on - Elements elements = galleryDoc.select(".gm a[href*='/mpv/']"); + Elements elements = galleryDoc.select(MPV_LINK_CSS); if (!elements.isEmpty()) { String mpvUrl = elements.get(0).attr("href"); - result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent); + try { + result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent); + } catch (EmptyResultException e) { + result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent); + } } else { result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent); } diff --git a/app/src/main/java/me/devsaki/hentoid/services/API29MigrationService.java b/app/src/main/java/me/devsaki/hentoid/services/API29MigrationService.java index 7325d3a7f3..2ff455cfc0 100644 --- a/app/src/main/java/me/devsaki/hentoid/services/API29MigrationService.java +++ b/app/src/main/java/me/devsaki/hentoid/services/API29MigrationService.java @@ -26,6 +26,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; import io.reactivex.schedulers.Schedulers; +import me.devsaki.hentoid.R; import me.devsaki.hentoid.core.Consts; import me.devsaki.hentoid.database.CollectionDAO; import me.devsaki.hentoid.database.ObjectBoxDAO; @@ -100,11 +101,11 @@ protected void onHandleIntent(@Nullable Intent intent) { } private void eventProgress(int step, int nbBooks, int booksOK, int booksKO) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, step, booksOK, booksKO, nbBooks)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.migrate_api29, step, booksOK, booksKO, nbBooks)); } private void eventComplete(int step, int nbBooks, int booksOK, int booksKO, DocumentFile cleanupLogFile) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, step, booksOK, booksKO, nbBooks, cleanupLogFile)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.migrate_api29, step, booksOK, booksKO, nbBooks, cleanupLogFile)); } private void trace(int priority, int chapter, List memoryLog, String s, String... t) { diff --git a/app/src/main/java/me/devsaki/hentoid/services/ExternalImportService.java b/app/src/main/java/me/devsaki/hentoid/services/ExternalImportService.java index 92849ca10e..ead2118a2f 100644 --- a/app/src/main/java/me/devsaki/hentoid/services/ExternalImportService.java +++ b/app/src/main/java/me/devsaki/hentoid/services/ExternalImportService.java @@ -100,15 +100,15 @@ protected void onHandleIntent(@Nullable Intent intent) { } private void eventProgress(int step, int nbBooks, int booksOK, int booksKO) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, step, booksOK, booksKO, nbBooks)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.import_external, step, booksOK, booksKO, nbBooks)); } private void eventProcessed(int step, String name) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, step, name)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.import_external, step, name)); } private void eventComplete(int step, int nbBooks, int booksOK, int booksKO, DocumentFile cleanupLogFile) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, step, booksOK, booksKO, nbBooks, cleanupLogFile)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.import_external, step, booksOK, booksKO, nbBooks, cleanupLogFile)); } private void trace(int priority, int chapter, List memoryLog, String s, String... t) { diff --git a/app/src/main/java/me/devsaki/hentoid/util/AdBlocker.java b/app/src/main/java/me/devsaki/hentoid/util/AdBlocker.java new file mode 100644 index 0000000000..60b7910d59 --- /dev/null +++ b/app/src/main/java/me/devsaki/hentoid/util/AdBlocker.java @@ -0,0 +1,151 @@ +package me.devsaki.hentoid.util; + +import android.os.Looper; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import me.devsaki.hentoid.R; +import me.devsaki.hentoid.core.HentoidApp; +import me.devsaki.hentoid.enums.Site; +import me.devsaki.hentoid.util.network.HttpHelper; +import okhttp3.Response; +import okhttp3.ResponseBody; +import timber.log.Timber; + +public class AdBlocker { + private final Site site; + + // List of blocked URLs (ads or annoying images) -- will be replaced by a blank stream + // Universal lists (applied to all sites) + private static final Set universalUrlBlacklist = new HashSet<>(); + private static final Set universalUrlWhitelist = new HashSet<>(); + // Local lists (applied to current site) + private List localUrlBlacklist; + private final List jsWhitelistUrlPatternList = new ArrayList<>(); + private final List jsContentBlacklist = new ArrayList<>(); + + private final List jsBlacklistCache = new ArrayList<>(); + + + static { + String[] appUrlBlacklist = HentoidApp.getInstance().getResources().getStringArray(R.array.blocked_domains); + universalUrlBlacklist.addAll(Arrays.asList(appUrlBlacklist)); + String[] appUrlWhitelist = HentoidApp.getInstance().getResources().getStringArray(R.array.allowed_domains); + universalUrlWhitelist.addAll(Arrays.asList(appUrlWhitelist)); + } + + + public AdBlocker(Site site) { + this.site = site; + for (String s : universalUrlWhitelist) addUrlWhitelist(s); + } + + /** + * Indicates if the given URL is blacklisted by the current content filters + * + * @param url URL to be examinated + * @return True if URL is blacklisted according to current filters; false if not + */ + private boolean isUrlBlacklisted(@NonNull String url) { + String comparisonUrl = url.toLowerCase(); + for (String s : universalUrlBlacklist) { + if (comparisonUrl.contains(s)) return true; + } + if (localUrlBlacklist != null) + for (String s : localUrlBlacklist) { + if (comparisonUrl.contains(s)) return true; + } + return false; + } + + /** + * Add an element to current URL blacklist + * + * @param filter Filter to addAll to local blacklist + */ + public void addToUrlBlacklist(String... filter) { + if (null == localUrlBlacklist) localUrlBlacklist = new ArrayList<>(); + Collections.addAll(localUrlBlacklist, filter); + } + + /** + * Add an element to current URL whitelist + * + * @param filter Filter to addAll to local whitelist + */ + public void addUrlWhitelist(String... filter) { + for (String s : filter) + addJsWhitelistUrlPattern("^.*" + s.replace(".", "\\.") + ".*$"); + } + + // TODO doc + public void addJsWhitelistUrlPattern(String pattern) { + jsWhitelistUrlPatternList.add(Pattern.compile(pattern)); + } + + // TODO doc + public void addJsContentBlacklist(String sequence) { + jsContentBlacklist.add(sequence); + } + + // TODO doc + public boolean isBlocked(@NonNull String url) { + // 1- Process usual blacklist and cached dynamic blacklist + if (isUrlBlacklisted(url)) return true; + if (jsBlacklistCache.contains(url)) return true; + + // If no specific whitelist has been defined, stop there + if (jsWhitelistUrlPatternList.size() == universalUrlWhitelist.size()) return false; + + + // 2- Accept non-JS files + String extension = HttpHelper.getExtensionFromUri(url); + if (!extension.equals("js") && !extension.isEmpty()) + return false; // obvious JS and hidden JS + + // 3- Accept JS files defined in the whitelist + for (Pattern p : jsWhitelistUrlPatternList) { + Matcher matcher = p.matcher(url.toLowerCase()); + if (matcher.find()) return false; + } + + // If no grey list has been defined, block url as it has not been whitelisted + if (jsContentBlacklist.isEmpty()) return true; + + + // 4- If a grey list has been defined, block them if they _contain_ keywords + if (Looper.getMainLooper().getThread() != Thread.currentThread()) { // No network call on UI thread + Timber.d(">> examining grey file %s", url); + try { + Response response = HttpHelper.getOnlineResource(url, null, site.useMobileAgent(), site.useHentoidAgent(), site.useWebviewAgent()); + ResponseBody body = response.body(); + if (null == body) throw new IOException("Empty body"); + + String jsBody = body.string().toLowerCase(); + for (String s : jsContentBlacklist) + if (jsBody.contains(s)) { + Timber.d(">> grey file %s BLOCKED", url); + jsBlacklistCache.add(url); + return true; + } + } catch (IOException e) { + Timber.e(e); + } + addJsWhitelistUrlPattern("^" + url.replace(".", "\\.") + "$"); + Timber.d(">> grey file %s ALLOWED", url); + } + + // Accept non-blocked (=grey) JS files + return false; + } +} diff --git a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java index c6db59617e..81acda2e0b 100644 --- a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java +++ b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java @@ -225,10 +225,11 @@ public static boolean updateQueueJson(@NonNull Context context, @NonNull Collect * * @param context Context to use for the action * @param content Content to view + * @param pageNumber Page number to view * @param searchParams Current search parameters (so that the next/previous book feature * is faithful to the library screen's order) */ - public static boolean openHentoidViewer(@NonNull Context context, @NonNull Content content, Bundle searchParams) { + public static boolean openHentoidViewer(@NonNull Context context, @NonNull Content content, int pageNumber, Bundle searchParams) { // Check if the book has at least its own folder if (content.getStorageUri().isEmpty()) return false; @@ -237,6 +238,7 @@ public static boolean openHentoidViewer(@NonNull Context context, @NonNull Conte ImageViewerActivityBundle.Builder builder = new ImageViewerActivityBundle.Builder(); builder.setContentId(content.getId()); if (searchParams != null) builder.setSearchParams(searchParams); + if (pageNumber > -1) builder.setPageNumber(pageNumber); Intent viewer = new Intent(context, ImageViewerActivity.class); viewer.putExtras(builder.getBundle()); @@ -1080,7 +1082,7 @@ public static ImmutablePair findDuplicate(@NonNull final Collect DuplicateHelper.DuplicateCandidate reference = new DuplicateHelper.DuplicateCandidate(content, true, true, false, pHash); List candidates = Stream.of(roughCandidates).map(c -> new DuplicateHelper.DuplicateCandidate(c, true, true, false, Long.MIN_VALUE)).toList(); for (DuplicateHelper.DuplicateCandidate candidate : candidates) { - DuplicateEntry entry = DuplicateHelper.Companion.processContent(reference, candidate, null, true, true, true, false, true, 2, cosine); + DuplicateEntry entry = DuplicateHelper.Companion.processContent(reference, candidate, true, true, true, false, true, 2, cosine); if (entry != null) entries.add(entry); } // Sort by similarity and size (unfortunately, Comparator.comparing is API24...) diff --git a/app/src/main/java/me/devsaki/hentoid/util/DuplicateHelper.kt b/app/src/main/java/me/devsaki/hentoid/util/DuplicateHelper.kt index cafa1ea525..3019ebfe6c 100644 --- a/app/src/main/java/me/devsaki/hentoid/util/DuplicateHelper.kt +++ b/app/src/main/java/me/devsaki/hentoid/util/DuplicateHelper.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.net.Uri import androidx.core.util.Consumer +import com.annimon.stream.function.BiConsumer import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers @@ -16,6 +17,7 @@ import timber.log.Timber import java.io.IOException import java.io.InputStream import java.util.* +import java.util.concurrent.atomic.AtomicBoolean class DuplicateHelper { @@ -59,7 +61,13 @@ class DuplicateHelper { "kouhen", "後編", "ex", - // Roman numerals (yes they can be present) + // Circled numerals (yes, some books do use them) + "①", + "②", + "③", + "④", + "⑤", + // Roman numerals (yes, some books do use them) "i", "v", "x", @@ -74,10 +82,50 @@ class DuplicateHelper { return ImagePHash(resolution, 8) } + fun indexCovers( + context: Context, + dao: CollectionDAO, + stopped: AtomicBoolean, + info: Consumer, + progress: BiConsumer, + error: Consumer + ) { + val hashEngine = getHashEngine() + val contentToIndex = dao.selectContentWithUnhashedCovers() + val nbContent = contentToIndex.size + + for ((index, c) in contentToIndex.withIndex()) { + try { + info.accept(c) + indexContent(context, dao, c, hashEngine) + progress.accept(index + 1, nbContent) + } catch (t: Throwable) { + // Don't break the loop + error.accept(t) + } + if (stopped.get()) break + } + progress.accept(nbContent, nbContent) + } + + private fun indexContent( + context: Context, + dao: CollectionDAO, + content: Content, + hashEngine: ImagePHash, + ) { + val bitmap = getCoverBitmapFromContent(context, content) + val pHash = calcPhash(hashEngine, bitmap) + bitmap?.recycle() + savePhash(context, dao, content, pHash) + } + fun indexCoversRx( context: Context, dao: CollectionDAO, - progress: Consumer + progress: Consumer, + info: Consumer, + error: Consumer ): Disposable { val hash = getHashEngine() @@ -87,7 +135,10 @@ class DuplicateHelper { return dao.streamContentWithUnhashedCovers() .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .map { content -> Pair(content, getCoverBitmapFromContent(context, content)) } + .map { + info.accept(it) + Pair(it, getCoverBitmapFromContent(context, it)) + } .observeOn(Schedulers.computation()) .map { val pHash = calcPhash(hash, it.second) @@ -105,7 +156,7 @@ class DuplicateHelper { } .subscribeBy( onNext = { progress.accept(++index * 1f / nbContent) }, - onError = { t -> Timber.w(t) }, + onError = { t -> error.accept(t) }, onComplete = { progress.accept(1f) } ) @@ -142,6 +193,7 @@ class DuplicateHelper { content.cover.imageHash = pHash // Update the picture in DB dao.insertImageFile(content.cover) + // The following block has to be abandoned if the cost of retaining all Content in memory is too high try { // Update the book JSON if the book folder still exists if (content.storageUri.isNotEmpty()) { @@ -162,7 +214,6 @@ class DuplicateHelper { fun processContent( reference: DuplicateCandidate, candidate: DuplicateCandidate, - ignoredIds: HashSet>?, useTitle: Boolean, useCover: Boolean, useSameArtist: Boolean, @@ -186,15 +237,8 @@ class DuplicateHelper { reference.coverHash, candidate.coverHash, sensitivity ) - val key = Pair(reference.id, candidate.id) - if (ignoredIds != null) { - if (coverScore == -2f) { // Ignored cover - ignoredIds.add(key) - return null - } else { - ignoredIds.remove(key) - } - } + // Ignored cover + if (coverScore == -2f) return null } if (useTitle) titleScore = computeTitleScore( textComparator, @@ -266,7 +310,7 @@ class DuplicateHelper { textComparator.similarity(referenceTitleNoDigits, candidateTitleNoDigits) // Cleaned up versions are identical // => most probably a chapter variant -> set to 0% - if (similarity2 > similarity1 && similarity2 > 0.995) return 0f; + if (similarity2 > similarity1 && similarity2 > 0.995) return 0f // Very little difference between cleaned up and original version // => not a chapter variant if (similarity2 - similarity1 < 0.01) { diff --git a/app/src/main/java/me/devsaki/hentoid/util/LogHelper.java b/app/src/main/java/me/devsaki/hentoid/util/LogHelper.java index 41f7079be8..5621e9e00e 100644 --- a/app/src/main/java/me/devsaki/hentoid/util/LogHelper.java +++ b/app/src/main/java/me/devsaki/hentoid/util/LogHelper.java @@ -1,6 +1,7 @@ package me.devsaki.hentoid.util; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -95,6 +96,7 @@ public static class LogInfo { */ public void setFileName(@NonNull String fileName) { this.fileName = fileName; + if (this.logName.isEmpty()) logName = fileName; } /** @@ -104,6 +106,7 @@ public void setFileName(@NonNull String fileName) { */ public void setLogName(@NonNull String logName) { this.logName = logName; + if (this.fileName.isEmpty()) this.fileName = logName; } /** @@ -145,6 +148,8 @@ private static String buildLog(@Nonnull LogInfo info) { StringBuilder logStr = new StringBuilder(); logStr.append(info.logName).append(" log : begin").append(LINE_SEPARATOR); logStr.append(String.format("Hentoid ver: %s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)).append(LINE_SEPARATOR); + logStr.append(String.format("API: %s", Build.VERSION.SDK_INT)).append(LINE_SEPARATOR); + logStr.append(String.format("Device: %s / %s", Build.PRODUCT, Build.MODEL)).append(LINE_SEPARATOR); if (info.entries.isEmpty()) logStr.append("No activity to report - ").append(info.noDataMessage).append(LINE_SEPARATOR); else { diff --git a/app/src/main/java/me/devsaki/hentoid/util/Preferences.java b/app/src/main/java/me/devsaki/hentoid/util/Preferences.java index 869fad72ec..2edb3c04cb 100644 --- a/app/src/main/java/me/devsaki/hentoid/util/Preferences.java +++ b/app/src/main/java/me/devsaki/hentoid/util/Preferences.java @@ -738,6 +738,26 @@ public static void setDownloadDuplicateTry(boolean value) { .apply(); } + public static long getViewerCurrentContent() { + return Long.parseLong(sharedPreferences.getString(Key.VIEWER_CURRENT_CONTENT, "-1") + ""); + } + + public static void setViewerCurrentContent(long value) { + sharedPreferences.edit() + .putString(Key.VIEWER_CURRENT_CONTENT, Long.toString(value)) + .apply(); + } + + public static int getViewerCurrentPageNum() { + return Integer.parseInt(sharedPreferences.getString(Key.VIEWER_CURRENT_PAGENUM, "-1") + ""); + } + + public static void setViewerCurrentPageNum(int value) { + sharedPreferences.edit() + .putString(Key.VIEWER_CURRENT_PAGENUM, Integer.toString(value)) + .apply(); + } + public static final class Key { @@ -823,6 +843,8 @@ private Key() { public static final String ARTIST_GROUP_VISIBILITY = "artist_group_visibility"; public static final String VIEWER_DELETE_ASK_MODE = "viewer_delete_ask"; public static final String VIEWER_DELETE_TARGET = "viewer_delete_target"; + public static final String VIEWER_CURRENT_CONTENT = "viewer_current_content"; + public static final String VIEWER_CURRENT_PAGENUM = "viewer_current_pagenum"; public static final String DUPLICATE_SENSITIVITY = "duplicate_sensitivity"; public static final String DUPLICATE_USE_TITLE = "duplicate_use_title"; public static final String DUPLICATE_USE_COVER = "duplicate_use_cover"; diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java index d9c28b2fb2..47a9f8f6b9 100644 --- a/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java +++ b/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java @@ -23,11 +23,11 @@ import androidx.constraintlayout.widget.Group; import androidx.core.content.ContextCompat; -import com.annimon.stream.Stream; import com.bumptech.glide.Glide; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.load.model.LazyHeaders; import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.switchmaterial.SwitchMaterial; import com.mikepenz.fastadapter.FastAdapter; import com.mikepenz.fastadapter.items.AbstractItem; @@ -40,7 +40,7 @@ import java.util.Map; import me.devsaki.hentoid.R; -import me.devsaki.hentoid.activities.bundles.ContentItemBundle; +import me.devsaki.hentoid.activities.bundles.DuplicateItemBundle; import me.devsaki.hentoid.core.HentoidApp; import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.database.domains.DuplicateEntry; @@ -54,7 +54,6 @@ import me.devsaki.hentoid.util.Preferences; import me.devsaki.hentoid.util.ThemeHelper; import me.devsaki.hentoid.util.network.HttpHelper; -import me.devsaki.hentoid.views.CircularProgressView; import timber.log.Timber; import static androidx.core.view.ViewCompat.requireViewById; @@ -83,7 +82,8 @@ public class DuplicateItem extends AbstractItem private Float coverScore = -1f; private Float artistScore = -1f; private Float totalScore = -1f; - private Boolean keep = null; + private boolean keep = true; + private boolean isBeingDeleted = false; static { Context context = HentoidApp.getInstance(); @@ -106,10 +106,9 @@ public class DuplicateItem extends AbstractItem public DuplicateItem(DuplicateEntry result, @ViewType int viewType) { this.viewType = viewType; isEmpty = (null == result); - if (result != null) setIdentifier(result.uniqueHash()); - else setIdentifier(Helper.generateIdForPlaceholder()); if (result != null) { + setIdentifier(result.uniqueHash()); if (viewType == ViewType.MAIN) { content = result.getReferenceContent(); } else { @@ -119,9 +118,11 @@ public DuplicateItem(DuplicateEntry result, @ViewType int viewType) { artistScore = result.getArtistScore(); totalScore = result.calcTotalScore(); keep = result.getKeep(); + isBeingDeleted = result.isBeingDeleted(); } nbDuplicates = result.getNbDuplicates(); } else { + setIdentifier(Helper.generateIdForPlaceholder()); content = null; } isReferenceItem = (titleScore > 1f); @@ -150,11 +151,13 @@ public int getType() { return R.id.duplicate; } - @Nullable - public Boolean getKeep() { + public boolean getKeep() { return keep; } + public boolean isBeingDeleted() { + return isBeingDeleted; + } public static class ContentViewHolder extends FastAdapter.ViewHolder { @@ -168,7 +171,6 @@ public static class ContentViewHolder extends FastAdapter.ViewHolder payloads) { // Payloads are set when the content stays the same but some properties alone change if (!payloads.isEmpty()) { Bundle bundle = (Bundle) payloads.get(0); - ContentItemBundle.Parser bundleParser = new ContentItemBundle.Parser(bundle); - - Boolean boolValue = bundleParser.isBeingDeleted(); - if (boolValue != null) item.content.setIsBeingDeleted(boolValue); - boolValue = bundleParser.isFavourite(); - if (boolValue != null) item.content.setFavourite(boolValue); - Long longValue = bundleParser.getReads(); - if (longValue != null) item.content.setReads(longValue); - longValue = bundleParser.getReadPagesCount(); - if (longValue != null) item.content.setReadPagesCount(longValue.intValue()); - String stringValue = bundleParser.getCoverUri(); - if (stringValue != null) item.content.getCover().setFileUri(stringValue); + DuplicateItemBundle.Parser bundleParser = new DuplicateItemBundle.Parser(bundle); + + Boolean boolValue = bundleParser.getKeep(); + if (boolValue != null) item.keep = boolValue; + boolValue = bundleParser.isBeingDeleted(); + if (boolValue != null) item.isBeingDeleted = boolValue; } updateLayoutVisibility(item); attachCover(item.content); attachFlag(item.content); attachTitle(item.content); - if (readingProgress != null) attachReadingProgress(item.content); if (tvLaunchCode != null) attachLaunchCode(item.content); if (tvArtist != null) attachArtist(item.content); if (tvPages != null) attachPages(item.content); @@ -245,6 +242,11 @@ public void bindView(@NotNull DuplicateItem item, @NotNull List payloads) { private void updateLayoutVisibility(@NonNull final DuplicateItem item) { baseLayout.setVisibility(item.isEmpty ? View.GONE : View.VISIBLE); + if (item.isBeingDeleted) + baseLayout.startAnimation(new BlinkAnimation(500, 250)); + else + baseLayout.clearAnimation(); + if (Preferences.Constant.LIBRARY_DISPLAY_GRID == Preferences.getLibraryDisplay()) { ViewGroup.LayoutParams layoutParams = baseLayout.getLayoutParams(); if (layoutParams instanceof ViewGroup.MarginLayoutParams) { @@ -253,11 +255,6 @@ private void updateLayoutVisibility(@NonNull final DuplicateItem item) { } baseLayout.setLayoutParams(layoutParams); } - - if (item.getContent() != null && item.getContent().isBeingDeleted()) - baseLayout.startAnimation(new BlinkAnimation(500, 250)); - else - baseLayout.clearAnimation(); } private void attachCover(@NonNull final Content content) { @@ -338,16 +335,6 @@ private void attachLaunchCode(@NonNull final Content content) { tvLaunchCode.setText(res.getString(R.string.book_launchcode, content.getUniqueSiteId())); } - private void attachReadingProgress(@NonNull final Content content) { - List imgs = content.getImageFiles(); - if (imgs != null) { - readingProgress.setVisibility(View.VISIBLE); - readingProgress.setTotalColor(readingProgress.getContext(), R.color.transparent); - readingProgress.setTotal(Stream.of(content.getImageFiles()).withoutNulls().filter(ImageFile::isReadable).count()); - readingProgress.setProgress1(content.getReadPagesCount()); - } - } - private void attachArtist(@NonNull final Content content) { tvArtist.setText(ContentHelper.formatArtistForDisplay(tvArtist.getContext(), content)); } @@ -420,37 +407,42 @@ private void attachButtons(@NonNull final DuplicateItem item) { // Keep and delete buttons if (keepButton != null) { - @ColorInt int targetColor = (item.keep == null || item.keep) ? + @ColorInt int targetColor = (item.keep) ? ThemeHelper.getColor(context, R.color.secondary_light) : ContextCompat.getColor(context, R.color.medium_gray); keepButton.setTextColor(targetColor); - Drawable[] drawables = keepButton.getCompoundDrawables(); + Drawable[] drawables = keepButton.getCompoundDrawablesRelative(); if (drawables[0] != null) { drawables[0].setColorFilter(targetColor, PorterDuff.Mode.SRC_IN); } + keepButton.setOnClickListener(v -> { + keepDeleteSwitch.setChecked(true); + keepDeleteSwitch.callOnClick(); + }); } if (deleteButton != null) { - @ColorInt int targetColor = (item.keep == null || !item.keep) ? + @ColorInt int targetColor = (!item.keep) ? ThemeHelper.getColor(context, R.color.secondary_light) : ContextCompat.getColor(context, R.color.medium_gray); deleteButton.setTextColor(targetColor); - Drawable[] drawables = deleteButton.getCompoundDrawables(); + Drawable[] drawables = deleteButton.getCompoundDrawablesRelative(); if (drawables[0] != null) { drawables[0].setColorFilter(targetColor, PorterDuff.Mode.SRC_IN); } + deleteButton.setOnClickListener(v -> { + keepDeleteSwitch.setChecked(false); + keepDeleteSwitch.callOnClick(); + }); } + if (keepDeleteSwitch != null) keepDeleteSwitch.setChecked(item.keep); } public View getViewDetailsButton() { return viewDetails; } - public View getKeepButton() { - return keepButton; - } - - public View getDeleteButton() { - return deleteButton; + public SwitchMaterial getKeepDeleteSwitch() { + return keepDeleteSwitch; } @Override diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/DuplicateViewModel.kt b/app/src/main/java/me/devsaki/hentoid/viewmodels/DuplicateViewModel.kt index 8349edfa06..c235e77c8f 100644 --- a/app/src/main/java/me/devsaki/hentoid/viewmodels/DuplicateViewModel.kt +++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/DuplicateViewModel.kt @@ -56,7 +56,7 @@ class DuplicateViewModel( useCover: Boolean, useArtist: Boolean, sameLanguageOnly: Boolean, - ignoreChapters : Boolean, + ignoreChapters: Boolean, sensitivity: Int ) { val builder = DuplicateData.Builder() @@ -101,7 +101,7 @@ class DuplicateViewModel( selectedDuplicates.postValue(selectedDupes) } - fun setBookChoice(content: Content, choice: Boolean?) { + fun setBookChoice(content: Content, choice: Boolean) { val selectedDupes = ArrayList(selectedDuplicates.value) for (dupe in selectedDupes) { if (dupe.duplicateId == content.id) dupe.keep = choice @@ -112,16 +112,24 @@ class DuplicateViewModel( fun applyChoices(onComplete: Runnable) { val selectedDupes = ArrayList(selectedDuplicates.value) + // Mark as "is being deleted" to trigger blink animation + val toRemove = selectedDupes.toMutableList() + for (entry in toRemove) { + if (!entry.keep) entry.isBeingDeleted = true + } + selectedDuplicates.postValue(toRemove) + + // Actually delete compositeDisposable.add( Observable.fromIterable(selectedDupes) .observeOn(Schedulers.io()) .map { - if (it.keep == false) doRemove(it.duplicateId) + if (!it.keep) doRemove(it.duplicateId) it } .doOnNext { // Update UI - if (it.keep == false) { + if (!it.keep) { val newList = selectedDupes.toMutableList() newList.remove(it) selectedDuplicates.postValue(newList) // Post a copy so that we don't modify the collection we're looping on diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/ImageViewerViewModel.java b/app/src/main/java/me/devsaki/hentoid/viewmodels/ImageViewerViewModel.java index a3206e7c1f..f6318bbe3e 100644 --- a/app/src/main/java/me/devsaki/hentoid/viewmodels/ImageViewerViewModel.java +++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/ImageViewerViewModel.java @@ -41,6 +41,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; import io.reactivex.schedulers.Schedulers; +import me.devsaki.hentoid.R; import me.devsaki.hentoid.core.Consts; import me.devsaki.hentoid.database.CollectionDAO; import me.devsaki.hentoid.database.ObjectBoxDAO; @@ -144,25 +145,25 @@ public void observeDbImages(AppCompatActivity activity) { }); } - public void loadFromContent(long contentId) { + public void loadFromContent(long contentId, int pageNumber) { if (contentId > 0) { Content loadedContent = collectionDao.selectContent(contentId); if (loadedContent != null) - processContent(loadedContent); + processContent(loadedContent, pageNumber); } } - public void loadFromSearchParams(long contentId, @NonNull Bundle bundle) { + public void loadFromSearchParams(long contentId, int pageNumber, @NonNull Bundle bundle) { searchManager.loadFromBundle(bundle); - applySearchParams(contentId); + applySearchParams(contentId, pageNumber); } - private void applySearchParams(long contentId) { + private void applySearchParams(long contentId, int pageNumber) { searchDisposable.dispose(); searchDisposable = searchManager.searchLibraryForId().subscribe( list -> { contentIds = list; - loadFromContent(contentId); + loadFromContent(contentId, pageNumber); }, throwable -> { Timber.w(throwable); @@ -175,7 +176,7 @@ public void setReaderStartingIndex(int index) { startingIndex.setValue(index); } - private void setImages(@NonNull Content theContent, @NonNull List newImages) { + private void setImages(@NonNull Content theContent, int pageNumber, @NonNull List newImages) { Observable observable; databaseImages.postValue(newImages); @@ -200,17 +201,17 @@ private void setImages(@NonNull Content theContent, @NonNull List new .subscribe( imageFile -> { nbProcessed.getAndIncrement(); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, 0, nbProcessed.get(), 0, newImages.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.viewer_load, 0, nbProcessed.get(), 0, newImages.size())); }, t -> { Timber.e(t); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbProcessed.get(), 0, newImages.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.viewer_load, 0, nbProcessed.get(), 0, newImages.size())); }, () -> { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbProcessed.get(), 0, newImages.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.viewer_load, 0, nbProcessed.get(), 0, newImages.size())); for (ImageFile img : newImages) imageLocations.put(img.getOrder(), img.getFileUri()); - initViewer(theContent, newImages); + initViewer(theContent, -1, newImages); imageLoadDisposable.dispose(); } ); @@ -222,7 +223,7 @@ private void setImages(@NonNull Content theContent, @NonNull List new newImg.setFileUri(location); } - initViewer(theContent, newImages); + initViewer(theContent, pageNumber, newImages); } } @@ -346,16 +347,28 @@ private ImageFile mapUriToImageFile(@NonNull final List imageFiles, @ return new ImageFile(); } - private void initViewer(@NonNull Content theContent, @NonNull List imageFiles) { + private void initViewer(@NonNull Content theContent, int pageNumber, @NonNull List imageFiles) { Boolean shuffledVal = getShuffled().getValue(); - sortAndSetViewerImages(imageFiles, (null == shuffledVal) ? false : shuffledVal); + sortAndSetViewerImages(imageFiles, null != shuffledVal && shuffledVal); if (theContent.getId() != loadedContentId) { // To be done once per book only - int collectionStartingIndex = 0; + int startingIndex = 0; // Auto-restart at last read position if asked to if (Preferences.isViewerResumeLastLeft() && theContent.getLastReadPageIndex() > -1) - collectionStartingIndex = theContent.getLastReadPageIndex(); + startingIndex = theContent.getLastReadPageIndex(); + + // Start at the given page number, if any + if (pageNumber > -1) { + int index = 0; + for (ImageFile img : imageFiles) { + if (img.getOrder() == pageNumber) { + startingIndex = index +1; + break; + } + index++; + } + } // Correct offset with the thumb index thumbIndex = -1; @@ -365,10 +378,10 @@ private void initViewer(@NonNull Content theContent, @NonNull List im break; } - if (thumbIndex == collectionStartingIndex) collectionStartingIndex += 1; - + if (thumbIndex == startingIndex) startingIndex += 1; + else if (thumbIndex > startingIndex) thumbIndex = 0; // Ignore if it doesn't intervene - setReaderStartingIndex(collectionStartingIndex - thumbIndex - 1); + setReaderStartingIndex(startingIndex - thumbIndex - 1); // Init the read pages write cache readPageNumbers.clear(); @@ -383,8 +396,8 @@ private void initViewer(@NonNull Content theContent, @NonNull List im } // Mark initial page as read - if (collectionStartingIndex < imageFiles.size()) - markPageAsRead(imageFiles.get(collectionStartingIndex).getOrder()); + if (startingIndex < imageFiles.size()) + markPageAsRead(imageFiles.get(startingIndex).getOrder()); } loadedContentId = theContent.getId(); @@ -392,7 +405,7 @@ private void initViewer(@NonNull Content theContent, @NonNull List im public void toggleShuffle() { Boolean shuffledVal = getShuffled().getValue(); - boolean isShuffled = (null == shuffledVal) ? false : shuffledVal; + boolean isShuffled = null != shuffledVal && shuffledVal; isShuffled = !isShuffled; if (isShuffled) RandomSeedSingleton.getInstance().renewSeed(Consts.SEED_PAGES); shuffled.postValue(isShuffled); @@ -496,7 +509,7 @@ public void filterFavouriteImages(boolean targetState) { if (loadedContentId > -1) { showFavouritesOnly.postValue(targetState); if (searchManager != null) searchManager.setFilterPageFavourites(targetState); - applySearchParams(loadedContentId); + applySearchParams(loadedContentId, -1); } } @@ -611,7 +624,7 @@ public void deleteBook(Consumer onError) { if (currentContentIndex >= contentIds.size() && currentContentIndex > 0) currentContentIndex--; if (contentIds.size() > currentContentIndex) - loadFromContent(contentIds.get(currentContentIndex)); + loadFromContent(contentIds.get(currentContentIndex), -1); } else { // Close the viewer if the list is empty (single book) content.postValue(null); } @@ -619,7 +632,7 @@ public void deleteBook(Consumer onError) { e -> { onError.accept(e); // Restore image source listener on error - databaseImages.addSource(currentImageSource, imgs -> setImages(targetContent, imgs)); + databaseImages.addSource(currentImageSource, imgs -> setImages(targetContent, -1, imgs)); } ) ); @@ -680,7 +693,7 @@ public void loadNextContent(int readerIndex) { currentContentIndex++; if (!contentIds.isEmpty()) { onLeaveBook(readerIndex); - loadFromContent(contentIds.get(currentContentIndex)); + loadFromContent(contentIds.get(currentContentIndex), -1); } } } @@ -690,12 +703,13 @@ public void loadPreviousContent(int readerIndex) { currentContentIndex--; if (!contentIds.isEmpty()) { onLeaveBook(readerIndex); - loadFromContent(contentIds.get(currentContentIndex)); + loadFromContent(contentIds.get(currentContentIndex), -1); } } } - private void processContent(@NonNull Content theContent) { + private void processContent(@NonNull Content theContent, int pageNumber) { + Preferences.setViewerCurrentContent(theContent.getId()); currentContentIndex = contentIds.indexOf(theContent.getId()); if (-1 == currentContentIndex) currentContentIndex = 0; @@ -709,7 +723,7 @@ private void processContent(@NonNull Content theContent) { // NB : It has to be dynamic to be updated when viewing a book from the queue screen if (currentImageSource != null) databaseImages.removeSource(currentImageSource); currentImageSource = collectionDao.selectDownloadedImagesFromContent(theContent.getId()); - databaseImages.addSource(currentImageSource, imgs -> setImages(theContent, imgs)); + databaseImages.addSource(currentImageSource, imgs -> setImages(theContent, pageNumber, imgs)); } private void postLoadProcessing(@NonNull Context context, @NonNull Content content) { diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/PreferencesViewModel.kt b/app/src/main/java/me/devsaki/hentoid/viewmodels/PreferencesViewModel.kt index fc68003169..9f96ea508e 100644 --- a/app/src/main/java/me/devsaki/hentoid/viewmodels/PreferencesViewModel.kt +++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/PreferencesViewModel.kt @@ -7,6 +7,7 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposables import io.reactivex.schedulers.Schedulers +import me.devsaki.hentoid.R import me.devsaki.hentoid.database.CollectionDAO import me.devsaki.hentoid.database.domains.Content import me.devsaki.hentoid.events.ProcessEvent @@ -18,7 +19,8 @@ import org.greenrobot.eventbus.EventBus import timber.log.Timber import java.io.File -class PreferencesViewModel(application: Application, val dao: CollectionDAO) : AndroidViewModel(application) { +class PreferencesViewModel(application: Application, val dao: CollectionDAO) : + AndroidViewModel(application) { private var deleteDisposable = Disposables.empty() @@ -37,7 +39,8 @@ class PreferencesViewModel(application: Application, val dao: CollectionDAO) : A // Remove all images stored in the app's persistent folder (archive covers) val appFolder: File = context.filesDir - val images = appFolder.listFiles { _: File?, name: String? -> ImageHelper.isSupportedImage(name!!) } + val images = + appFolder.listFiles { _: File?, name: String? -> ImageHelper.isSupportedImage(name!!) } if (images != null) for (f in images) FileHelper.removeFile(f) } @@ -46,17 +49,17 @@ class PreferencesViewModel(application: Application, val dao: CollectionDAO) : A var nbDeleted = 0 deleteDisposable = Observable.fromIterable(items) - .observeOn(Schedulers.io()) - .map { c: Content -> this.deleteItem(context, c) } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - nbDeleted++ - this.onDeleteProgress(nbDeleted, items.size) - }, - { t: Throwable? -> Timber.w(t) }, - { this.onDeleteComplete(nbDeleted, items.size) } - ) + .observeOn(Schedulers.io()) + .map { c: Content -> this.deleteItem(context, c) } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + nbDeleted++ + this.onDeleteProgress(nbDeleted, items.size) + }, + { t: Throwable? -> Timber.w(t) }, + { this.onDeleteComplete(nbDeleted, items.size) } + ) } @Throws(ContentNotRemovedException::class) @@ -65,11 +68,13 @@ class PreferencesViewModel(application: Application, val dao: CollectionDAO) : A } private fun onDeleteProgress(num: Int, max: Int) { - EventBus.getDefault().post(ProcessEvent(ProcessEvent.EventType.PROGRESS, 0, num, 0, max)) + EventBus.getDefault() + .post(ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.generic_delete, 0, num, 0, max)) } private fun onDeleteComplete(num: Int, max: Int) { deleteDisposable.dispose() - EventBus.getDefault().post(ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, num, 0, max)) + EventBus.getDefault() + .post(ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_delete, 0, num, 0, max)) } } \ No newline at end of file diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java b/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java index 54813804a2..c190218d90 100644 --- a/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java +++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java @@ -20,6 +20,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import me.devsaki.hentoid.R; import me.devsaki.hentoid.database.CollectionDAO; import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.database.domains.QueueRecord; @@ -244,14 +245,14 @@ public void remove(@NonNull List content, Consumer onError, .subscribe( v -> { nbDeleted.getAndIncrement(); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, 0, nbDeleted.get(), 0, content.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.generic_delete, 0, nbDeleted.get(), 0, content.size())); }, t -> { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbDeleted.get(), 0, content.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_delete, 0, nbDeleted.get(), 0, content.size())); onError.accept(t); }, () -> { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbDeleted.get(), 0, content.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_delete, 0, nbDeleted.get(), 0, content.size())); onComplete.run(); } ) @@ -275,14 +276,14 @@ public void cancelAll(Consumer onError, Runnable onComplete) { .subscribe( v -> { nbDeleted.getAndIncrement(); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, 0, nbDeleted.get(), 0, localQueue.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.generic_delete, 0, nbDeleted.get(), 0, localQueue.size())); }, t -> { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbDeleted.get(), 0, localQueue.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_delete, 0, nbDeleted.get(), 0, localQueue.size())); onError.accept(t); }, () -> { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, 0, nbDeleted.get(), 0, localQueue.size())); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_delete, 0, nbDeleted.get(), 0, localQueue.size())); onComplete.run(); } ) diff --git a/app/src/main/java/me/devsaki/hentoid/workers/BaseWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/BaseWorker.java index 20e54d91d1..9139c4d958 100644 --- a/app/src/main/java/me/devsaki/hentoid/workers/BaseWorker.java +++ b/app/src/main/java/me/devsaki/hentoid/workers/BaseWorker.java @@ -16,9 +16,12 @@ import org.greenrobot.eventbus.EventBus; +import java.util.ArrayList; import java.util.List; +import me.devsaki.hentoid.core.HentoidApp; import me.devsaki.hentoid.events.ServiceDestroyedEvent; +import me.devsaki.hentoid.util.LogHelper; import me.devsaki.hentoid.util.notification.Notification; import me.devsaki.hentoid.util.notification.NotificationManager; import timber.log.Timber; @@ -35,7 +38,8 @@ public abstract class BaseWorker extends Worker { int serviceId; private boolean isComplete = true; -// protected final List logs = new ArrayList<>(); + protected final String logName; + private final List logs; protected static boolean isRunning(@NonNull Context context, @IdRes int serviceId) { @@ -54,15 +58,22 @@ protected static boolean isRunning(@NonNull Context context, @IdRes int serviceI public BaseWorker( @NonNull Context context, @NonNull WorkerParameters parameters, - @IdRes int serviceId) { + @IdRes int serviceId, + String logName) { super(context, parameters); this.serviceId = serviceId; initNotifications(context); Timber.w("%s worker created", this.getClass().getSimpleName()); - // TEMP -// logs.add(new LogHelper.LogEntry("worker created")); + if (logName != null && !logName.isEmpty()) { + this.logName = logName; + logs = new ArrayList<>(); + logs.add(new LogHelper.LogEntry("worker created")); + } else { + this.logName = ""; + logs = null; + } } @Override @@ -89,18 +100,23 @@ boolean isComplete() { return isComplete; } + protected void recordLog(LogHelper.LogEntry entry) { + if (logs != null) logs.add(entry); + } + private void clear() { onClear(); -/* - logs.add(new LogHelper.LogEntry("Worker destroyed / stopped=%s / complete=%s", isStopped(), isComplete)); + if (logs != null) { + logs.add(new LogHelper.LogEntry("Worker destroyed / stopped=%s / complete=%s", isStopped(), isComplete)); + + LogHelper.LogInfo logInfo = new LogHelper.LogInfo(); + logInfo.setFileName(logName); + logInfo.setLogName(logName); + logInfo.setEntries(logs); + LogHelper.writeLog(HentoidApp.getInstance(), logInfo); + } - LogHelper.LogInfo logInfo = new LogHelper.LogInfo(); - logInfo.setFileName(Integer.toString(serviceId)); - logInfo.setLogName(Integer.toString(serviceId)); - logInfo.setEntries(logs); - LogHelper.writeLog(HentoidApp.getInstance(), logInfo); -*/ // Tell everyone the worker is shutting down EventBus.getDefault().post(new ServiceDestroyedEvent(serviceId)); @@ -116,7 +132,8 @@ public Result doWork() { try { getToWork(getInputData()); } catch (Exception e) { -// logs.add(new LogHelper.LogEntry("Exception caught ! %s : %s", e.getMessage(), e.getStackTrace())); + if (logs != null) + logs.add(new LogHelper.LogEntry("Exception caught ! %s : %s", e.getMessage(), e.getStackTrace())); Timber.e(e); } finally { clear(); diff --git a/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java index 66b8f78a88..c4b6bdb8eb 100644 --- a/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java +++ b/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java @@ -108,7 +108,7 @@ private enum QueuingResult { public ContentDownloadWorker( @NonNull Context context, @NonNull WorkerParameters parameters) { - super(context, parameters, R.id.download_service); + super(context, parameters, R.id.download_service, null); EventBus.getDefault().register(this); dao = new ObjectBoxDAO(context); diff --git a/app/src/main/java/me/devsaki/hentoid/workers/DuplicateDetectorWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/DuplicateDetectorWorker.java index 4deb583719..058d8bdc15 100644 --- a/app/src/main/java/me/devsaki/hentoid/workers/DuplicateDetectorWorker.java +++ b/app/src/main/java/me/devsaki/hentoid/workers/DuplicateDetectorWorker.java @@ -10,26 +10,29 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.Completable; import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import kotlin.Pair; +import me.devsaki.hentoid.BuildConfig; import me.devsaki.hentoid.R; import me.devsaki.hentoid.database.CollectionDAO; import me.devsaki.hentoid.database.DuplicatesDAO; import me.devsaki.hentoid.database.ObjectBoxDAO; +import me.devsaki.hentoid.database.domains.Content; import me.devsaki.hentoid.database.domains.DuplicateEntry; import me.devsaki.hentoid.events.ProcessEvent; import me.devsaki.hentoid.notification.duplicates.DuplicateCompleteNotification; import me.devsaki.hentoid.notification.duplicates.DuplicateProgressNotification; import me.devsaki.hentoid.notification.duplicates.DuplicateStartNotification; +import me.devsaki.hentoid.util.ContentHelper; import me.devsaki.hentoid.util.DuplicateHelper; +import me.devsaki.hentoid.util.LogHelper; import me.devsaki.hentoid.util.Preferences; import me.devsaki.hentoid.util.notification.Notification; import me.devsaki.hentoid.util.string_similarity.Cosine; @@ -50,16 +53,16 @@ public class DuplicateDetectorWorker extends BaseWorker { private final CollectionDAO dao; private final DuplicatesDAO duplicatesDAO; - private Disposable indexDisposable = null; private final CompositeDisposable notificationDisposables = new CompositeDisposable(); private final AtomicInteger currentIndex = new AtomicInteger(0); + private final AtomicBoolean stopped = new AtomicBoolean(false); public DuplicateDetectorWorker( @NonNull Context context, @NonNull WorkerParameters parameters) { - super(context, parameters, R.id.duplicate_detector_service); + super(context, parameters, R.id.duplicate_detector_service, "duplicate_detector"); dao = new ObjectBoxDAO(getApplicationContext()); duplicatesDAO = new DuplicatesDAO(getApplicationContext()); @@ -76,7 +79,6 @@ Notification getStartNotification() { @Override void onInterrupt() { - if (indexDisposable != null) indexDisposable.dispose(); notificationDisposables.clear(); } @@ -86,7 +88,7 @@ void onClear() { Preferences.setDuplicateLastIndex(currentIndex.get()); else Preferences.setDuplicateLastIndex(-1); - if (indexDisposable != null) indexDisposable.dispose(); + stopped.set(true); notificationDisposables.clear(); dao.cleanup(); duplicatesDAO.cleanup(); @@ -96,8 +98,18 @@ void onClear() { void getToWork(@NonNull Data input) { DuplicateData.Parser inputData = new DuplicateData.Parser(input); - // Run cover indexing in the background - indexDisposable = DuplicateHelper.Companion.indexCoversRx(getApplicationContext(), dao, this::notifyIndexProgress); + if (inputData.getUseCover()) { + // Run cover indexing in the background + recordLog(new LogHelper.LogEntry("Covers to index : " + dao.countContentWithUnhashedCovers())); + + DuplicateHelper.Companion.indexCovers(getApplicationContext(), dao, stopped, + this::indexContentInfo, this::notifyIndexProgress, this::indexError); + + recordLog(new LogHelper.LogEntry("Indexing done")); + } + + // No need to continue if the process has already been stopped + if (isStopped()) return; // Initialize duplicate detection detectDuplicates(inputData.getUseTitle(), inputData.getUseCover(), inputData.getUseArtist(), inputData.getUseSameLanguage(), inputData.getIgnoreChapters(), inputData.getSensitivity()); @@ -110,12 +122,10 @@ private void detectDuplicates( boolean useSameLanguage, boolean ignoreChapters, int sensitivity) { - // Mark process as incomplete until all combinations are searched // to support abort and retry setComplete(false); - HashSet> ignoredIds = new HashSet<>(); Map> matchedIds = new HashMap<>(); Map> reverseMatchedIds = new HashMap<>(); @@ -123,66 +133,44 @@ private void detectDuplicates( int startIndex = Preferences.getDuplicateLastIndex() + 1; if (0 == startIndex) duplicatesDAO.clearEntries(); else { + recordLog(new LogHelper.LogEntry("Resuming from index %d", startIndex)); // Pre-populate matchedIds and reverseMatchedIds using existing duplicates List entries = duplicatesDAO.getEntries(); for (DuplicateEntry entry : entries) processEntry(entry.getReferenceId(), entry.getDuplicateId(), matchedIds, reverseMatchedIds); } - boolean isReRun = false; - do { -// logs.add(new LogHelper.LogEntry("Preparation started")); - // Pre-compute all book entries as DuplicateCandidates - List candidates = new ArrayList<>(); - dao.streamStoredContent(false, false, Preferences.Constant.ORDER_FIELD_SIZE, true, - content -> candidates.add(new DuplicateHelper.DuplicateCandidate(content, useTitle, useArtist, useSameLanguage, Long.MIN_VALUE))); - -// logs.add(new LogHelper.LogEntry("Detection started")); - processAll( - duplicatesDAO, - candidates, - ignoredIds, - matchedIds, - reverseMatchedIds, - startIndex, - isReRun, - useTitle, - useCover, - useArtist, - useSameLanguage, - ignoreChapters, - sensitivity); - Timber.d(" >> PROCESS End reached"); -// logs.add(new LogHelper.LogEntry("Setection End")); - if (isStopped()) break; - if (!ignoredIds.isEmpty()) { - try { - //noinspection BusyWait - Thread.sleep(3000); // Don't rush in another loop - } catch (InterruptedException e) { - Timber.w(e); - // Restore interrupted state - Thread.currentThread().interrupt(); - } - } - if (isStopped()) break; - isReRun = true; - } while (!ignoredIds.isEmpty()); - - setComplete(ignoredIds.isEmpty()); -// logs.add(new LogHelper.LogEntry("Final End reached (complete=%s)", isComplete())); - - ignoredIds.clear(); + recordLog(new LogHelper.LogEntry("Preparation started")); + // Pre-compute all book entries as DuplicateCandidates + List candidates = new ArrayList<>(); + dao.streamStoredContent(false, false, Preferences.Constant.ORDER_FIELD_SIZE, true, + content -> candidates.add(new DuplicateHelper.DuplicateCandidate(content, useTitle, useArtist, useSameLanguage, Long.MIN_VALUE))); + + recordLog(new LogHelper.LogEntry("Detection started for %d books", candidates.size())); + processAll( + duplicatesDAO, + candidates, + matchedIds, + reverseMatchedIds, + startIndex, + useTitle, + useCover, + useArtist, + useSameLanguage, + ignoreChapters, + sensitivity); + + recordLog(new LogHelper.LogEntry(String.format(Locale.ENGLISH, "Final End reached (currentIndex=%d, complete=%s)", currentIndex.get(), isComplete()))); + + setComplete(true); matchedIds.clear(); } private void processAll(DuplicatesDAO duplicatesDao, List library, - HashSet> ignoredIds, Map> matchedIds, Map> reverseMatchedIds, int startIndex, - boolean isReRun, boolean useTitle, boolean useCover, boolean useSameArtist, @@ -192,8 +180,6 @@ private void processAll(DuplicatesDAO duplicatesDao, List tempResults = new ArrayList<>(); StringSimilarity cosine = new Cosine(); for (int i = startIndex; i < library.size(); i++) { - if (ignoredIds.size() > 1e6) - break; // Better to wait rather than saturating the ignored IDs map if (isStopped()) return; DuplicateHelper.DuplicateCandidate reference = library.get(i); @@ -202,13 +188,8 @@ private void processAll(DuplicatesDAO duplicatesDao, if (isStopped()) return; DuplicateHelper.DuplicateCandidate candidate = library.get(j); - // For re-runs, check if that combination has been ignored in the past and has to be matched - if (isReRun && !ignoredIds.contains(new Pair<>(reference.getId(), candidate.getId()))) - continue; - DuplicateEntry entry = DuplicateHelper.Companion.processContent( reference, candidate, - ignoredIds, useTitle, useCover, useSameArtist, useSameLanguage, ignoreChapters, sensitivity, cosine); if (entry != null && processEntry(entry.getReferenceId(), entry.getDuplicateId(), matchedIds, reverseMatchedIds)) tempResults.add(entry); @@ -222,20 +203,22 @@ private void processAll(DuplicatesDAO duplicatesDao, currentIndex.set(i); - if (0 == i % 10) { - float progress = i * 1f / (library.size() - 1); - notifyProcessProgress(progress); // Only update every 10 iterations for performance - } + if (0 == i % 10) + notifyProcessProgress(i, (library.size() - 1)); // Only update every 10 iterations for performance } } - private void notifyIndexProgress(Float progress) { - int progressPc = Math.round(progress * 10000); - if (progressPc < 10000) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, STEP_COVER_INDEX, progressPc, 0, 10000)); - } else { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, STEP_COVER_INDEX, progressPc, 0, 10000)); - } + private void indexContentInfo(Content c) { + // No need for that unless we're debugging + if (BuildConfig.DEBUG) + recordLog(new LogHelper.LogEntry("Indexing " + c.getSite().name() + "/" + ContentHelper.formatBookFolderName(c).left)); + } + + private void indexError(Throwable t) { + Timber.w(t); + String message = t.getMessage(); + if (message != null) + recordLog(new LogHelper.LogEntry("Indexing error : " + message)); } private boolean processEntry( @@ -277,8 +260,17 @@ private boolean processEntry( return false; } - private void notifyProcessProgress(Float progress) { - notificationDisposables.add(Completable.fromRunnable(() -> doNotifyProcessProgress(progress)) + private void notifyIndexProgress(int progress, int max) { + Timber.i(">> indexing progress %s", progress * 1f / max); + if (progress < max) { + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.duplicate_index, STEP_COVER_INDEX, progress, 0, max)); + } else { + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.duplicate_index, STEP_COVER_INDEX, progress, 0, max)); + } + } + + private void notifyProcessProgress(int progress, int max) { + notificationDisposables.add(Completable.fromRunnable(() -> doNotifyProcessProgress(progress, max)) .subscribeOn(Schedulers.computation()) .subscribe( notificationDisposables::clear @@ -286,14 +278,13 @@ private void notifyProcessProgress(Float progress) { ); } - private void doNotifyProcessProgress(Float progress) { - int progressPc = Math.round(progress * 10000); - if (progressPc < 10000) { - setForegroundAsync(notificationManager.buildForegroundInfo(new DuplicateProgressNotification(progressPc, 10000))); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, STEP_DUPLICATES, progressPc, 0, 10000)); + private void doNotifyProcessProgress(int progress, int max) { + if (progress < max) { + setForegroundAsync(notificationManager.buildForegroundInfo(new DuplicateProgressNotification(progress, max))); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.duplicate_detect, STEP_DUPLICATES, progress, 0, max)); } else { setForegroundAsync(notificationManager.buildForegroundInfo(new DuplicateCompleteNotification(0))); - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, STEP_DUPLICATES, progressPc, 0, 10000)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.duplicate_detect, STEP_DUPLICATES, progress, 0, max)); } } } diff --git a/app/src/main/java/me/devsaki/hentoid/workers/ImportWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/ImportWorker.java index ce0ca0139d..5f7a308300 100644 --- a/app/src/main/java/me/devsaki/hentoid/workers/ImportWorker.java +++ b/app/src/main/java/me/devsaki/hentoid/workers/ImportWorker.java @@ -76,7 +76,7 @@ public class ImportWorker extends BaseWorker { public ImportWorker( @NonNull Context context, @NonNull WorkerParameters parameters) { - super(context, parameters, R.id.import_service); + super(context, parameters, R.id.import_service, null); } public static boolean isRunning(@NonNull Context context) { @@ -109,11 +109,11 @@ void getToWork(@NonNull Data input) { } private void eventProgress(int step, int nbBooks, int booksOK, int booksKO) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, step, booksOK, booksKO, nbBooks)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.import_primary, step, booksOK, booksKO, nbBooks)); } private void eventComplete(int step, int nbBooks, int booksOK, int booksKO, DocumentFile cleanupLogFile) { - EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, step, booksOK, booksKO, nbBooks, cleanupLogFile)); + EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.import_primary, step, booksOK, booksKO, nbBooks, cleanupLogFile)); } private void trace(int priority, int chapter, List memoryLog, String s, Object... t) { diff --git a/app/src/main/java/me/devsaki/hentoid/workers/StartupWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/StartupWorker.java index 01da94645c..3570cfd055 100644 --- a/app/src/main/java/me/devsaki/hentoid/workers/StartupWorker.java +++ b/app/src/main/java/me/devsaki/hentoid/workers/StartupWorker.java @@ -31,7 +31,7 @@ public class StartupWorker extends BaseWorker { public StartupWorker( @NonNull Context context, @NonNull WorkerParameters parameters) { - super(context, parameters, R.id.startup_service); + super(context, parameters, R.id.startup_service, null); } @Override diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index 06ec06b1c8..af54f810d5 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -8,8 +8,8 @@ - - - - - - diff --git a/app/src/main/res/layout/include_duplicate_controls.xml b/app/src/main/res/layout/include_duplicate_controls.xml index 10aa56d63a..154197c22c 100644 --- a/app/src/main/res/layout/include_duplicate_controls.xml +++ b/app/src/main/res/layout/include_duplicate_controls.xml @@ -5,6 +5,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/dark_gray" + android:clickable="true" + android:focusable="true" android:padding="16dp" android:visibility="gone" app:layout_constraintTop_toTopOf="parent" @@ -144,6 +146,20 @@ app:layout_constraintTop_toTopOf="@id/index_pictures_txt" tools:visibility="visible" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_viewer_controls_overlay.xml b/app/src/main/res/layout/include_viewer_controls_overlay.xml index f15b2c1eed..0362a5a1fe 100644 --- a/app/src/main/res/layout/include_viewer_controls_overlay.xml +++ b/app/src/main/res/layout/include_viewer_controls_overlay.xml @@ -32,10 +32,10 @@ android:focusable="true" android:scaleType="fitCenter" android:src="@drawable/ic_info" - android:tint="?colorOnPrimary" android:tooltipText="@string/information_help" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" + app:tint="?colorOnPrimary" tools:ignore="ContentDescription" /> @@ -65,11 +65,11 @@ android:focusable="true" android:scaleType="fitCenter" android:src="@drawable/ic_fav_toggle" - android:tint="?colorOnPrimary" android:tooltipText="@string/fav_help" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/viewer_next_book_btn" app:layout_constraintStart_toEndOf="@id/viewer_prev_book_btn" + app:tint="?colorOnPrimary" tools:ignore="ContentDescription" /> @@ -100,10 +100,10 @@ android:focusable="true" android:scaleType="fitCenter" android:src="@drawable/ic_action_gallery" - android:tint="?colorOnPrimary" android:tooltipText="@string/gallery_help" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:tint="?colorOnPrimary" tools:ignore="ContentDescription" /> @@ -127,18 +127,22 @@ android:id="@+id/page_slider" android:layout_width="0dp" android:layout_height="0dp" + android:layout_marginBottom="34dp" android:colorControlActivated="?colorSecondary" android:padding="8dp" android:progressBackgroundTint="?colorOnPrimary" android:progressTint="?colorSecondary" + android:value="2" + android:valueFrom="0" + android:valueTo="10" app:labelBehavior="gone" - android:layout_marginBottom="34dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/viewer_pager_right_txt" app:layout_constraintStart_toEndOf="@id/viewer_pager_left_txt" app:thumbColor="?colorSecondary" app:tickVisible="false" - app:trackColor="@color/white_opacity_25" + app:trackColorActive="@color/accent_red" + app:trackColorInactive="@color/white_opacity_25" tools:targetApi="lollipop" /> + + + app:layout_constraintStart_toEndOf="@id/keep_delete" /> cumpedia.fun bounceme.net o333o.com + /ads-iframe/ diff --git a/app/src/main/res/values/event_process_ids.xml b/app/src/main/res/values/event_process_ids.xml new file mode 100644 index 0000000000..e8b91b3801 --- /dev/null +++ b/app/src/main/res/values/event_process_ids.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfb3cdcbde..de3f07b562 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,6 +244,7 @@ Generating log file… + Resume reading where you left? Open drawer Close drawer How would you like to filter completed books? @@ -550,7 +551,7 @@ Artist : no data %.0f%% Apply (%d remaining choices) - Apply + Apply choices Starting duplicate detection Duplicate detection complete %d duplicates detected successfully @@ -571,6 +572,7 @@ Similarity : %.0f%% Download book Download extra pages + Please wait while indexing Startup in progress… diff --git a/build.gradle b/build.gradle index 42424875da..389898c9e5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { google() mavenCentral() maven { url "https://jcenter.bintray.com" } + maven { url "https://plugins.gradle.org/m2/" } // About library plugin } dependencies { classpath 'com.android.tools.build:gradle:4.2.1' @@ -13,6 +14,7 @@ buildscript { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.6.0' + classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:8.9.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files