Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0413d502c1 |
@@ -1,5 +0,0 @@
|
|||||||
[*.{kt,kts}]
|
|
||||||
indent_size=4
|
|
||||||
insert_final_newline=true
|
|
||||||
ij_kotlin_allow_trailing_comma=true
|
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
github: inorichi
|
||||||
ko_fi: inorichi
|
ko_fi: inorichi
|
||||||
|
|||||||
@@ -2,15 +2,9 @@
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- I have updated to the latest version of the app (stable is v1.5.0)
|
||||||
- To the latest version of the app (stable is v1.8.3)
|
- I have updated all extensions
|
||||||
- All extensions
|
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
|
|
||||||
- I will fill out the title and the information in this template
|
|
||||||
|
|
||||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
@@ -30,5 +24,3 @@ Note that the issue will be automatically closed if you do not fill out the titl
|
|||||||
|
|
||||||
## Other details
|
## Other details
|
||||||
Additional details and attachments.
|
Additional details and attachments.
|
||||||
|
|
||||||
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: "🐞 Bug report"
|
||||||
|
about: Report a bug
|
||||||
|
title: "[Bug] <Write short description here>"
|
||||||
|
labels: "bug"
|
||||||
|
---
|
||||||
|
|
||||||
|
**PLEASE READ THIS**
|
||||||
|
|
||||||
|
I acknowledge that:
|
||||||
|
|
||||||
|
- I have updated to the latest version of the app (stable is v1.5.0)
|
||||||
|
- I have updated all extensions
|
||||||
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|
||||||
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Device information
|
||||||
|
* Tachiyomi version: ?
|
||||||
|
* Android version: ?
|
||||||
|
* Device: ?
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
This should happen.
|
||||||
|
|
||||||
|
### Actual behavior
|
||||||
|
This happened instead.
|
||||||
|
|
||||||
|
## Other details
|
||||||
|
Additional details and attachments.
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ⚠️ Extension/source issue
|
- name: Tachiyomi help website
|
||||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
|
||||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
|
||||||
- name: 📦 Tachiyomi extensions
|
|
||||||
url: https://tachiyomi.org/extensions
|
|
||||||
about: List of all available extensions with download links
|
|
||||||
- name: 🖥️ Tachiyomi website
|
|
||||||
url: https://tachiyomi.org/help/
|
url: https://tachiyomi.org/help/
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Common questions are answered here.
|
||||||
|
- name: Tachiyomi extensions GitHub repository
|
||||||
|
url: https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
about: Issues about an extension/source/catalogue should be opened here instead.
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: "🌟 Feature request"
|
||||||
|
about: Suggest a feature to improve Tachiyomi
|
||||||
|
title: "[Feature Request] <Write short description here>"
|
||||||
|
labels: "feature"
|
||||||
|
---
|
||||||
|
|
||||||
|
**PLEASE READ THIS**
|
||||||
|
|
||||||
|
I acknowledge that:
|
||||||
|
|
||||||
|
- I have updated to the latest version of the app (stable is v1.5.0)
|
||||||
|
- I have updated all extensions
|
||||||
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|
||||||
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why/User Benefit/User Problem
|
||||||
|
(explain why this feature should be added)
|
||||||
|
|
||||||
|
## What/Requirements
|
||||||
|
(explain how this feature would behave)
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
name: 🐞 Issue report
|
|
||||||
description: Report an issue in Tachiyomi
|
|
||||||
labels: [Bug]
|
|
||||||
body:
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduce-steps
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: Provide an example of the issue.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
3. Issue here
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: expected-behavior
|
|
||||||
attributes:
|
|
||||||
label: Expected behavior
|
|
||||||
description: Explain what you should expect to happen.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This should happen..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: actual-behavior
|
|
||||||
attributes:
|
|
||||||
label: Actual behavior
|
|
||||||
description: Explain what actually happens.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This happened instead..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: crash-logs
|
|
||||||
attributes:
|
|
||||||
label: Crash logs
|
|
||||||
description: |
|
|
||||||
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
|
||||||
placeholder: |
|
|
||||||
You can paste the crash logs in pure text or upload it as an attachment.
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: tachiyomi-version
|
|
||||||
attributes:
|
|
||||||
label: Tachiyomi version
|
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
|
||||||
placeholder: |
|
|
||||||
Example: "1.8.3"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: android-version
|
|
||||||
attributes:
|
|
||||||
label: Android version
|
|
||||||
description: You can find this somewhere in your Android settings.
|
|
||||||
placeholder: |
|
|
||||||
Example: "Android 11"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: device
|
|
||||||
attributes:
|
|
||||||
label: Device
|
|
||||||
description: List your device and model.
|
|
||||||
placeholder: |
|
|
||||||
Example: "Google Pixel 5"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
|
||||||
required: true
|
|
||||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[1.8.3](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
|
||||||
required: true
|
|
||||||
- label: I have updated all installed extensions.
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
name: ⭐ Feature request
|
|
||||||
description: Suggest a feature to improve Tachiyomi
|
|
||||||
labels: [Feature request]
|
|
||||||
body:
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: feature-description
|
|
||||||
attributes:
|
|
||||||
label: Describe your suggested feature
|
|
||||||
description: How can Tachiyomi be improved?
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"It should work like this..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[1.8.3](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: "Extension/source/catalogue issue"
|
||||||
|
about: "Do not open an issue here. See https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||||
|
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||||
|
labels: "catalog, invalid"
|
||||||
|
---
|
||||||
|
|
||||||
|
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!--
|
|
||||||
Please include a summary of the change and which issue is fixed.
|
|
||||||
Also make sure you've tested your code and also done a self-review of it.
|
|
||||||
Don't forget to check all base themes and tablet mode for relevant changes.
|
|
||||||
|
|
||||||
If your changes are visual, please provide images below:
|
|
||||||
|
|
||||||
### Images
|
|
||||||
| Image 1 | Image 2 |
|
|
||||||
| ------- | ------- |
|
|
||||||
|  |  |
|
|
||||||
-->
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 489 KiB After Width: | Height: | Size: 1.7 MiB |
@@ -2,4 +2,5 @@ org.gradle.daemon=false
|
|||||||
org.gradle.jvmargs=-Xmx5120m
|
org.gradle.jvmargs=-Xmx5120m
|
||||||
org.gradle.workers.max=2
|
org.gradle.workers.max=2
|
||||||
|
|
||||||
kotlin.incremental=false
|
kotlin.incremental=false
|
||||||
|
kotlin.compiler.execution.strategy=in-process
|
||||||
@@ -20,6 +20,7 @@ jobs:
|
|||||||
preview:
|
preview:
|
||||||
name: Build app preview
|
name: Build app preview
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
|
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build app
|
name: Build app
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
|
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -31,10 +32,10 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 1.8
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -52,9 +53,12 @@ jobs:
|
|||||||
write-mode: overwrite # optional, default is preserve
|
write-mode: overwrite # optional, default is preserve
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: eskatos/gradle-command-action@v1
|
||||||
with:
|
with:
|
||||||
arguments: assembleStandardRelease --stacktrace
|
arguments: assembleRelease --stacktrace
|
||||||
|
wrapper-cache-enabled: true
|
||||||
|
dependencies-cache-enabled: true
|
||||||
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build app
|
name: Build app
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
|
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -27,10 +28,10 @@ jobs:
|
|||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 1.8
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -38,10 +39,12 @@ jobs:
|
|||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: eskatos/gradle-command-action@v1
|
||||||
with:
|
with:
|
||||||
arguments: assembleDevDebug
|
arguments: assembleStandardDebug
|
||||||
|
wrapper-cache-enabled: true
|
||||||
|
dependencies-cache-enabled: true
|
||||||
|
configuration-cache-enabled: true
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Issue closer
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
autoclose:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Autoclose when created in wrong repo
|
||||||
|
uses: arkon/issue-closer-action@v1.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
type: title
|
||||||
|
regex: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||||
|
message: "@${issue.user.login} this issue was automatically closed because it was not opened in the correct repo, as the template mentioned."
|
||||||
|
- name: Autoclose when no short description provided
|
||||||
|
uses: arkon/issue-closer-action@v1.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
type: title
|
||||||
|
regex: ".*<Write short description here>*"
|
||||||
|
message: "@${issue.user.login} this issue was automatically closed because you did not fill out the description in the title."
|
||||||
|
- name: Autoclose when body acknowledgement section not removed
|
||||||
|
uses: arkon/issue-closer-action@v1.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
type: body
|
||||||
|
regex: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
||||||
|
message: "@${issue.user.login} this issue was automatically closed because the acknowledgment section was not removed."
|
||||||
|
- name: Autoclose when body requested information not filled out
|
||||||
|
uses: arkon/issue-closer-action@v1.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
type: body
|
||||||
|
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
|
||||||
|
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
name: Issue moderator
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
moderate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Moderate issues
|
|
||||||
uses: tachiyomiorg/issue-moderator-action@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
auto-close-rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
|
||||||
"message": "The acknowledgment section was not removed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
|
|
||||||
"message": "Requested information in the template was not filled out."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "both",
|
|
||||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
|
||||||
"ignoreCase": true,
|
|
||||||
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: Lock threads
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Daily
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
# Manual trigger
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@v3
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
issue-inactive-days: '2'
|
|
||||||
pr-inactive-days: '2'
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community moderators are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community moderators have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community moderators responsible for enforcement at
|
|
||||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community moderators are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community moderators will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community moderators, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
|
||||||
version 2.1, available at
|
|
||||||
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
|
|
||||||
at [translations](https://www.contributor-covenant.org/translations).
|
|
||||||
+1
-17
@@ -10,23 +10,7 @@ Thanks for your interest in contributing to Tachiyomi!
|
|||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
||||||
You do not need to ask for permission nor an assignment.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you.
|
|
||||||
|
|
||||||
- Basic [Android development](https://developer.android.com/)
|
|
||||||
- [Kotlin](https://kotlinlang.org/)
|
|
||||||
|
|
||||||
### Tools
|
|
||||||
|
|
||||||
- [Android Studio](https://developer.android.com/studio)
|
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
|
||||||
|
|
||||||
## Getting help
|
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
@@ -42,7 +26,7 @@ When creating a fork, remember to:
|
|||||||
- To avoid confusion with the main app:
|
- To avoid confusion with the main app:
|
||||||
- Change the app name
|
- Change the app name
|
||||||
- Change the app icon
|
- Change the app icon
|
||||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt)
|
||||||
- To avoid installation conflicts:
|
- To avoid installation conflicts:
|
||||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
||||||
- To avoid having your data polluting the main app's analytics and crash report services:
|
- To avoid having your data polluting the main app's analytics and crash report services:
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
| Preview Builds | Release Builds | Tachiyomi Support Server |
|
| Preview Builds | Release Builds | Tachiyomi Support Server |
|
||||||
|-------|----------|----------|
|
|-------|----------|----------|
|
||||||
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases/latest) | [](https://discord.gg/tachiyomi) |
|
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases/latest) | [](https://discord.gg/tachiyomi) |
|
||||||
|
|
||||||
|
|
||||||
# TachiyomiSY
|
# TachiyomiSY
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
|
Tachiyomi is a free and open source manga reader for Android 5.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Features of Tachiyomi(original) include:
|
Features of Tachiyomi(original) include:
|
||||||
* Online reading from a variety of sources
|
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/tachiyomiorg/tachiyomi-extensions)
|
||||||
* Local reading of downloaded content
|
* Local reading of downloaded manga
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/)
|
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
* Light and dark themes
|
||||||
* Schedule updating your library for new chapters
|
* Schedule updating your library for new chapters
|
||||||
@@ -84,12 +84,13 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
|
|
||||||
<details><summary>Bugs</summary>
|
<details><summary>Bugs</summary>
|
||||||
|
|
||||||
* Include version (More → About → Version)
|
* Include version (More > About > Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* If not latest, try updating, it may have already been solved
|
||||||
* Preview version is equal to the number of commits as seen in the main page
|
* Preview version is equal to the number of commits as seen in the main page
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* Include steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
* Don't group unrelated requests into one issue
|
* Don't group unrelated requests into one issue
|
||||||
|
|
||||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||||
@@ -108,12 +109,7 @@ Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-e
|
|||||||
|
|
||||||
<details><summary>Contributing</summary>
|
<details><summary>Contributing</summary>
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
See [CONTRIBUTING.md](https://github.com/tachiyomiorg/tachiyomi/blob/master/CONTRIBUTING.md).
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Code of Conduct</summary>
|
|
||||||
|
|
||||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|||||||
+213
-130
@@ -1,16 +1,23 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
id("com.mikepenz.aboutlibraries.plugin")
|
||||||
kotlin("android")
|
kotlin("android")
|
||||||
|
kotlin("kapt")
|
||||||
kotlin("plugin.parcelize")
|
kotlin("plugin.parcelize")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
id("com.github.zellius.shortcut-helper")
|
id("com.github.zellius.shortcut-helper")
|
||||||
|
// Realm (EH)
|
||||||
|
id("realm-android")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
if (!gradle.startParameter.taskRequests.toString().contains("Debug")) {
|
||||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
apply(plugin = "com.google.gms.google-services")
|
||||||
// Firebase Crashlytics
|
// Firebase Crashlytics
|
||||||
apply(plugin = "com.google.firebase.crashlytics")
|
apply(plugin = "com.google.firebase.crashlytics")
|
||||||
}
|
}
|
||||||
@@ -18,25 +25,32 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
|||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = AndroidConfig.compileSdk
|
compileSdkVersion(AndroidConfig.compileSdk)
|
||||||
|
buildToolsVersion(AndroidConfig.buildTools)
|
||||||
ndkVersion = AndroidConfig.ndk
|
ndkVersion = AndroidConfig.ndk
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
minSdk = AndroidConfig.minSdk
|
minSdkVersion(AndroidConfig.minSdk)
|
||||||
targetSdk = AndroidConfig.targetSdk
|
targetSdkVersion(AndroidConfig.targetSdk)
|
||||||
versionCode = 34
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
versionName = "1.8.3"
|
versionCode = 13
|
||||||
|
versionName = "1.5.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
|
|
||||||
|
multiDexEnabled = true
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86")
|
abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86")
|
||||||
}
|
}
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -48,16 +62,18 @@ android {
|
|||||||
applicationIdSuffix = ".rt"
|
applicationIdSuffix = ".rt"
|
||||||
//isMinifyEnabled = true
|
//isMinifyEnabled = true
|
||||||
//isShrinkResources = true
|
//isShrinkResources = true
|
||||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
isZipAlignEnabled = true
|
||||||
|
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
|
||||||
}
|
}
|
||||||
named("release") {
|
named("release") {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
|
isZipAlignEnabled = true
|
||||||
|
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions += "default"
|
flavorDimensions("default")
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
create("standard") {
|
create("standard") {
|
||||||
@@ -68,41 +84,30 @@ android {
|
|||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
create("dev") {
|
create("dev") {
|
||||||
resourceConfigurations.addAll(listOf("en", "xxhdpi"))
|
resConfigs("en", "xxhdpi")
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
resources.excludes.addAll(listOf(
|
exclude("META-INF/DEPENDENCIES")
|
||||||
"META-INF/DEPENDENCIES",
|
exclude("LICENSE.txt")
|
||||||
"LICENSE.txt",
|
exclude("META-INF/LICENSE")
|
||||||
"META-INF/LICENSE",
|
exclude("META-INF/LICENSE.txt")
|
||||||
"META-INF/LICENSE.txt",
|
exclude("META-INF/NOTICE")
|
||||||
"META-INF/README.md",
|
|
||||||
"META-INF/NOTICE",
|
// Compatibility for two RxJava versions (EXH)
|
||||||
"META-INF/*.kotlin_module",
|
exclude("META-INF/rxjava.properties")
|
||||||
"META-INF/*.version",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
includeInApk = false
|
includeInApk = false
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
lintOptions {
|
||||||
viewBinding = true
|
disable("MissingTranslation", "ExtraTranslation")
|
||||||
|
isAbortOnError = false
|
||||||
// Disable some unused things
|
isCheckReleaseBuilds = false
|
||||||
aidl = false
|
|
||||||
renderScript = false
|
|
||||||
shaders = false
|
|
||||||
}
|
|
||||||
|
|
||||||
lint {
|
|
||||||
disable.addAll(listOf("MissingTranslation", "ExtraTranslation"))
|
|
||||||
abortOnError = false
|
|
||||||
checkReleaseBuilds = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -116,145 +121,193 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlinx.reflect)
|
|
||||||
|
|
||||||
implementation(kotlinx.bundles.coroutines)
|
|
||||||
|
|
||||||
// Source models and interfaces from Tachiyomi 1.x
|
// Source models and interfaces from Tachiyomi 1.x
|
||||||
implementation(libs.tachiyomi.api)
|
implementation("tachiyomi.sourceapi:source-api:1.1")
|
||||||
|
|
||||||
// AndroidX libraries
|
// AndroidX libraries
|
||||||
implementation(androidx.annotation)
|
implementation("androidx.annotation:annotation:1.2.0-beta01")
|
||||||
implementation(androidx.appcompat)
|
implementation("androidx.appcompat:appcompat:1.3.0-beta01")
|
||||||
implementation(androidx.biometricktx)
|
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha02")
|
||||||
implementation(androidx.constraintlayout)
|
implementation("androidx.browser:browser:1.3.0")
|
||||||
implementation(androidx.coordinatorlayout)
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
implementation(androidx.corektx)
|
implementation("androidx.constraintlayout:constraintlayout:2.1.0-alpha2")
|
||||||
implementation(androidx.splashscreen)
|
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
|
||||||
implementation(androidx.recyclerview)
|
implementation("androidx.core:core-ktx:1.5.0-beta01")
|
||||||
implementation(androidx.swiperefreshlayout)
|
implementation("androidx.multidex:multidex:2.0.1")
|
||||||
implementation(androidx.viewpager)
|
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||||
|
implementation("androidx.recyclerview:recyclerview:1.2.0-beta01")
|
||||||
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||||
|
|
||||||
implementation(androidx.bundles.lifecycle)
|
val lifecycleVersion = "2.3.0-rc01"
|
||||||
|
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation(androidx.bundles.workmanager)
|
implementation("androidx.work:work-runtime-ktx:2.5.0")
|
||||||
|
|
||||||
// RX
|
// UI library
|
||||||
implementation(libs.bundles.reactivex)
|
implementation("com.google.android.material:material:1.3.0")
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
"standardImplementation"("com.google.firebase:firebase-core:18.0.2")
|
||||||
|
|
||||||
|
// ReactiveX
|
||||||
|
implementation("io.reactivex:rxandroid:1.2.1")
|
||||||
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
|
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||||
|
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
implementation(libs.bundles.okhttp)
|
val okhttpVersion = "4.10.0-RC1"
|
||||||
implementation(libs.okio)
|
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||||
|
implementation("com.squareup.okio:okio:2.10.0")
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
// TLS 1.3 support for Android < 10
|
||||||
implementation(libs.conscrypt.android)
|
implementation("org.conscrypt:conscrypt-android:2.5.1")
|
||||||
|
|
||||||
// Data serialization (JSON, protobuf)
|
// JSON
|
||||||
implementation(kotlinx.bundles.serialization)
|
val kotlinSerializationVersion = "1.0.1"
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
||||||
|
implementation("com.google.code.gson:gson:2.8.6")
|
||||||
|
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||||
|
|
||||||
// JavaScript engine
|
// JavaScript engine
|
||||||
implementation(libs.bundles.js.engine)
|
implementation("com.squareup.duktape:duktape-android:1.3.0")
|
||||||
|
|
||||||
// HTML parser
|
|
||||||
implementation(libs.jsoup)
|
|
||||||
|
|
||||||
// Disk
|
// Disk
|
||||||
implementation(libs.disklrucache)
|
implementation("com.jakewharton:disklrucache:2.0.2")
|
||||||
implementation(libs.unifile)
|
implementation("com.github.inorichi:unifile:e9ee588")
|
||||||
implementation(libs.junrar)
|
implementation("com.github.junrar:junrar:7.4.0")
|
||||||
|
|
||||||
|
// HTML parser
|
||||||
|
implementation("org.jsoup:jsoup:1.13.1")
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation(libs.bundles.sqlite)
|
implementation("androidx.sqlite:sqlite-ktx:2.1.0")
|
||||||
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
||||||
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
|
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
|
||||||
|
implementation("io.requery:sqlite-android:3.33.0")
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
implementation(libs.preferencektx)
|
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.3")
|
||||||
implementation(libs.flowpreferences)
|
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
implementation(libs.bundles.nucleus)
|
val nucleusVersion = "3.0.0"
|
||||||
|
implementation("info.android15.nucleus:nucleus:$nucleusVersion")
|
||||||
|
implementation("info.android15.nucleus:nucleus-support-v7:$nucleusVersion")
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation(libs.injekt.core)
|
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||||
|
|
||||||
// Image loading
|
// Image library
|
||||||
implementation(libs.bundles.coil)
|
val glideVersion = "4.11.0"
|
||||||
|
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
||||||
|
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
|
||||||
|
kapt("com.github.bumptech.glide:compiler:$glideVersion")
|
||||||
|
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:6caf219")
|
||||||
exclude(module = "image-decoder")
|
// TODO: switch to new decoder for stable releases
|
||||||
}
|
// implementation("com.github.tachiyomiorg:subsampling-scale-image-view:ca26317")
|
||||||
implementation(libs.image.decoder)
|
|
||||||
|
|
||||||
// Sort
|
|
||||||
implementation(libs.natural.comparator)
|
|
||||||
|
|
||||||
// UI libraries
|
|
||||||
implementation(libs.material)
|
|
||||||
implementation(libs.androidprocessbutton)
|
|
||||||
implementation(libs.flexible.adapter.core)
|
|
||||||
implementation(libs.flexible.adapter.ui)
|
|
||||||
implementation(libs.viewstatepageradapter)
|
|
||||||
implementation(libs.photoview)
|
|
||||||
implementation(libs.directionalviewpager) {
|
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
|
||||||
}
|
|
||||||
implementation(libs.insetter)
|
|
||||||
implementation(libs.markwon)
|
|
||||||
|
|
||||||
// Conductor
|
|
||||||
implementation(libs.bundles.conductor)
|
|
||||||
|
|
||||||
// FlowBinding
|
|
||||||
implementation(libs.bundles.flowbinding)
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
|
|
||||||
// Crash reports/analytics
|
// Crash reports
|
||||||
// implementation(libs.acra.http)
|
//implementation("ch.acra:acra-http:5.7.0")
|
||||||
// "standardImplementation"(libs.firebase.analytics)
|
|
||||||
|
// Sort
|
||||||
|
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
|
||||||
|
|
||||||
|
// UI
|
||||||
|
implementation("com.dmitrymalkovich.android:material-design-dimens:1.4")
|
||||||
|
implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4")
|
||||||
|
implementation("eu.davidea:flexible-adapter:5.1.0")
|
||||||
|
implementation("eu.davidea:flexible-adapter-ui:1.0.0")
|
||||||
|
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
|
||||||
|
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||||
|
implementation("com.github.tachiyomiorg:DirectionalViewPager:7d0617d")
|
||||||
|
|
||||||
|
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices
|
||||||
|
val materialDialogsVersion = "3.1.1"
|
||||||
|
implementation("com.afollestad.material-dialogs:core:$materialDialogsVersion")
|
||||||
|
implementation("com.afollestad.material-dialogs:input:$materialDialogsVersion")
|
||||||
|
implementation("com.afollestad.material-dialogs:datetime:$materialDialogsVersion")
|
||||||
|
|
||||||
|
// Conductor
|
||||||
|
implementation("com.bluelinelabs:conductor:2.1.5")
|
||||||
|
implementation("com.bluelinelabs:conductor-support:2.1.5") {
|
||||||
|
exclude(group = "com.android.support")
|
||||||
|
}
|
||||||
|
implementation("com.github.tachiyomiorg:conductor-support-preference:1.1.1")
|
||||||
|
|
||||||
|
// FlowBinding
|
||||||
|
val flowbindingVersion = "0.12.0"
|
||||||
|
implementation("io.github.reactivecircus.flowbinding:flowbinding-android:$flowbindingVersion")
|
||||||
|
implementation("io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbindingVersion")
|
||||||
|
implementation("io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbindingVersion")
|
||||||
|
implementation("io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbindingVersion")
|
||||||
|
implementation("io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbindingVersion")
|
||||||
|
|
||||||
// Licenses
|
// Licenses
|
||||||
implementation(libs.aboutlibraries.core)
|
implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
|
||||||
|
|
||||||
// Shizuku
|
|
||||||
implementation(libs.bundles.shizuku)
|
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation(libs.junit)
|
testImplementation("junit:junit:4.13.1")
|
||||||
testImplementation(libs.assertj.core)
|
testImplementation("org.assertj:assertj-core:3.16.1")
|
||||||
testImplementation(libs.mockito.core)
|
testImplementation("org.mockito:mockito-core:1.10.19")
|
||||||
|
|
||||||
testImplementation(libs.bundles.robolectric)
|
val robolectricVersion = "3.1.4"
|
||||||
|
testImplementation("org.robolectric:robolectric:$robolectricVersion")
|
||||||
|
testImplementation("org.robolectric:shadows-multidex:$robolectricVersion")
|
||||||
|
testImplementation("org.robolectric:shadows-play-services:$robolectricVersion")
|
||||||
|
|
||||||
|
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
|
||||||
|
|
||||||
|
val coroutinesVersion = "1.4.2"
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation(libs.leakcanary.android)
|
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
// [EXH] Android 7 SSL Workaround
|
||||||
|
implementation("com.google.android.gms:play-services-safetynet:17.0.0")
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
implementation(sylibs.changelog)
|
implementation("com.github.gabrielemariotti.changeloglib:changelog:2.1.0")
|
||||||
|
|
||||||
// Text distance (EH)
|
// Text distance (EH)
|
||||||
implementation (sylibs.simularity)
|
implementation ("info.debatty:java-string-similarity:2.0.0")
|
||||||
|
|
||||||
// Firebase (EH)
|
// Firebase (EH)
|
||||||
implementation(sylibs.firebase.analytics)
|
implementation("com.google.firebase:firebase-analytics-ktx:18.0.0")
|
||||||
implementation(sylibs.firebase.crashlytics.ktx)
|
implementation("com.google.firebase:firebase-crashlytics-ktx:17.3.0")
|
||||||
|
|
||||||
// Better logging (EH)
|
// Better logging (EH)
|
||||||
implementation(sylibs.xlog)
|
implementation("com.elvishew:xlog:1.7.1")
|
||||||
|
|
||||||
// Debug utils (EH)
|
// Debug utils (EH)
|
||||||
debugImplementation(sylibs.debugOverlay.standard)
|
val debugOverlayVersion = "1.1.3"
|
||||||
"releaseTestImplementation"(sylibs.debugOverlay.noop)
|
debugImplementation("com.ms-square:debugoverlay:$debugOverlayVersion")
|
||||||
releaseImplementation(sylibs.debugOverlay.noop)
|
"releaseTestImplementation"("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||||
testImplementation(sylibs.debugOverlay.noop)
|
releaseImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||||
|
testImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
|
||||||
|
|
||||||
// RatingBar (SY)
|
// RatingBar (SY)
|
||||||
implementation(sylibs.ratingbar)
|
implementation ("me.zhanghai.android.materialratingbar:library:1.4.0")
|
||||||
|
|
||||||
|
// JsonReader for similar manga
|
||||||
|
implementation("com.squareup.moshi:moshi:1.11.0")
|
||||||
|
|
||||||
|
implementation("androidx.gridlayout:gridlayout:1.0.0")
|
||||||
|
|
||||||
|
implementation("com.mikepenz:fastadapter:5.3.4")
|
||||||
|
// SY -->
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
@@ -263,13 +316,11 @@ tasks {
|
|||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-Xopt-in=kotlin.Experimental",
|
"-Xopt-in=kotlin.Experimental",
|
||||||
"-Xopt-in=kotlin.RequiresOptIn",
|
"-Xopt-in=kotlin.RequiresOptIn",
|
||||||
"-Xopt-in=kotlin.ExperimentalStdlibApi",
|
"-Xuse-experimental=kotlin.ExperimentalStdlibApi",
|
||||||
"-Xopt-in=kotlinx.coroutines.FlowPreview",
|
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
|
||||||
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi"
|
||||||
"-Xopt-in=coil.annotation.ExperimentalCoilApi",
|
|
||||||
"-Xopt-in=kotlin.time.ExperimentalTime",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,8 +336,40 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlinx.gradle)
|
classpath(kotlin("gradle-plugin", version = BuildPluginsVersion.KOTLIN))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Git is needed in your system PATH for these commands to work.
|
||||||
|
// If it's not installed, you can return a random value as a workaround
|
||||||
|
fun getCommitCount(): String {
|
||||||
|
return runCommand("git rev-list --count HEAD")
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGitSha(): String {
|
||||||
|
return runCommand("git rev-parse --short HEAD")
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBuildTime(): String {
|
||||||
|
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||||
|
df.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
return df.format(Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runCommand(command: String): String {
|
||||||
|
val byteOut = ByteArrayOutputStream()
|
||||||
|
project.exec {
|
||||||
|
commandLine = command.split(" ")
|
||||||
|
standardOutput = byteOut
|
||||||
|
}
|
||||||
|
return String(byteOut.toByteArray()).trim()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
-allowaccessmodification
|
|
||||||
-dontusemixedcaseclassnames
|
|
||||||
-verbose
|
|
||||||
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclassmembers enum * {
|
|
||||||
public static **[] values();
|
|
||||||
public static ** valueOf(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclassmembers class * implements android.os.Parcelable {
|
|
||||||
public static final ** CREATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class androidx.annotation.Keep
|
|
||||||
|
|
||||||
-keep @androidx.annotation.Keep class * {*;}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <fields>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <init>(...);
|
|
||||||
}
|
|
||||||
Vendored
+67
-16
@@ -58,7 +58,6 @@
|
|||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Filter serializer
|
|
||||||
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
|
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
|
||||||
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
|
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
|
||||||
*** Companion;
|
*** Companion;
|
||||||
@@ -67,25 +66,37 @@
|
|||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep extension's common dependencies
|
# Madokami extension username and password crash fix
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.** { public protected *; }
|
-keepclassmembers class androidx.preference.EditTextPreference {
|
||||||
-keep,allowoptimization class androidx.preference.** { *; }
|
*** mOnBindEditTextListener;
|
||||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
*** mText;
|
||||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
public *;
|
||||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
}
|
||||||
-keep,allowoptimization class okio.** { public protected *; }
|
|
||||||
-keep,allowoptimization class rx.** { public protected *; }
|
# Hitomi extension crash fix
|
||||||
-keep,allowoptimization class org.jsoup.** { public protected *; }
|
-keepclassmembers class rx.Single {
|
||||||
-keep,allowoptimization class com.google.gson.** { public protected *; }
|
*** onSubscribe;
|
||||||
-keep,allowoptimization class com.github.salomonbrys.kotson.** { public protected *; }
|
final *;
|
||||||
-keep,allowoptimization class com.squareup.duktape.** { public protected *; }
|
protected *;
|
||||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
public *;
|
||||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
}
|
||||||
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
|
||||||
|
|
||||||
# RxJava 1.1.0
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
|
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||||
|
long producerIndex;
|
||||||
|
long consumerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
|
||||||
|
rx.internal.util.atomic.LinkedQueueNode producerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
|
||||||
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
|
}
|
||||||
|
|
||||||
-dontnote rx.internal.util.PlatformDependent
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
|
|
||||||
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
||||||
@@ -107,6 +118,32 @@
|
|||||||
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# === GSON: https://raw.githubusercontent.com/google/gson/master/examples/android-proguard-example/proguard.cfg
|
||||||
|
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||||
|
# removes such information by default, so configure it to keep all of it.
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
# Gson specific classes
|
||||||
|
-dontwarn sun.misc.**
|
||||||
|
#-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
|
# Application classes that will be serialized/deserialized over Gson
|
||||||
|
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||||
|
|
||||||
|
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||||
|
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||||
|
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||||
|
-keep class * implements com.google.gson.JsonSerializer
|
||||||
|
-keep class * implements com.google.gson.JsonDeserializer
|
||||||
|
|
||||||
|
# Prevent R8 from leaving Data object members always null
|
||||||
|
-keepclassmembers,allowobfuscation class * {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
# == Nucleus
|
# == Nucleus
|
||||||
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||||
<init>();
|
<init>();
|
||||||
@@ -118,6 +155,20 @@
|
|||||||
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
||||||
-keep class uy.kohesive.injekt.** { *; }
|
-keep class uy.kohesive.injekt.** { *; }
|
||||||
|
|
||||||
|
|
||||||
|
# === Glide
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||||
|
|
||||||
|
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
|
||||||
|
-dontwarn jp.co.cyberagent.android.gpuimage.**
|
||||||
|
|
||||||
# === Conductor
|
# === Conductor
|
||||||
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
||||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
android:shortcutId="show_recently_updated"
|
android:shortcutId="show_recently_updated"
|
||||||
android:shortcutLongLabel="@string/label_recent_updates"
|
android:shortcutLongLabel="@string/label_recent_updates"
|
||||||
android:shortcutShortLabel="@string/label_recent_updates">
|
android:shortcutShortLabel="@string/short_recent_updates">
|
||||||
<intent
|
<intent
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
|||||||
@@ -2,5 +2,4 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
+192
-189
@@ -14,33 +14,30 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
|
|
||||||
<!-- For managing extensions -->
|
<!-- For managing extensions -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
|
||||||
<!-- To view extension packages in API 30+ -->
|
<!-- To view extension packages in API 30+ -->
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:theme="@style/Theme.Tachiyomi.Light"
|
||||||
android:supportsRtl="true"
|
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
android:theme="@style/Theme.Splash">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@@ -54,8 +51,7 @@
|
|||||||
android:name=".ui.main.DeepLinkActivity"
|
android:name=".ui.main.DeepLinkActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:label="@string/action_global_search"
|
android:label="@string/action_global_search">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||||
@@ -76,11 +72,9 @@
|
|||||||
android:name="android.app.searchable"
|
android:name="android.app.searchable"
|
||||||
android:resource="@xml/searchable" />
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask">
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -88,26 +82,15 @@
|
|||||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||||
android:resource="@xml/s_pen_actions"/>
|
android:resource="@xml/s_pen_actions"/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.security.UnlockActivity"
|
android:name=".ui.security.BiometricUnlockActivity"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:theme="@style/Theme.Splash" />
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.webview.WebViewActivity"
|
android:name=".ui.webview.WebViewActivity"
|
||||||
android:configChanges="uiMode|orientation|screenSize"
|
android:configChanges="uiMode|orientation|screenSize" />
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".extension.util.ExtensionInstallActivity"
|
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||||
android:label="Anilist"
|
android:label="Anilist">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -121,8 +104,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
||||||
android:label="MyAnimeList"
|
android:label="MyAnimeList">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -136,8 +118,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
||||||
android:label="Shikimori"
|
android:label="Shikimori">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -151,8 +132,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
android:name=".ui.setting.track.BangumiLoginActivity"
|
||||||
android:label="Bangumi"
|
android:label="Bangumi">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -166,9 +146,18 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.login.EhLoginActivity"
|
android:name=".extension.util.ExtensionInstallActivity"
|
||||||
android:label="EHentaiLogin"
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
android:exported="false"/>
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
@@ -183,190 +172,204 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.updater.AppUpdateService"
|
android:name=".data.updater.UpdaterService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".data.backup.BackupCreateService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.backup.BackupRestoreService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service android:name=".extension.util.ExtensionInstallService"
|
<!-- EH -->
|
||||||
|
<service
|
||||||
|
android:name="exh.md.similar.SimilarUpdateService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<service
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="exh.eh.EHentaiUpdateWorker"
|
||||||
android:authorities="${applicationId}.provider"
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
android:exported="false"
|
android:exported="true" />
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/provider_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
|
||||||
android:authorities="${applicationId}.shizuku"
|
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
|
||||||
|
|
||||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
|
||||||
android:value="false" />
|
|
||||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
|
|
||||||
android:value="true" />
|
|
||||||
|
|
||||||
<!-- EH -->
|
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.intercept.InterceptActivity"
|
android:name="exh.ui.intercept.InterceptActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:theme="@style/Theme.EHActivity">
|
||||||
android:exported="true">
|
|
||||||
<!-- E-Hentai -->
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<!-- EH -->
|
||||||
<data android:scheme="http" />
|
<data
|
||||||
|
android:host="g.e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="g.e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="e-hentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:host="e-hentai.org" />
|
<!-- EXH -->
|
||||||
<data android:host="www.e-hentai.org" />
|
<data
|
||||||
<data android:host="g.e-hentai.org" />
|
android:host="exhentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="exhentai.org"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:pathPattern="/g/..*" />
|
<!-- nhentai -->
|
||||||
</intent-filter>
|
<data
|
||||||
<!-- ExHentai -->
|
android:host="nhentai.net"
|
||||||
<intent-filter>
|
android:pathPrefix="/g/"
|
||||||
<action android:name="android.intent.action.VIEW" />
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="nhentai.net"
|
||||||
|
android:pathPrefix="/g/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<!-- Perv Eden -->
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<data
|
||||||
|
android:host="www.perveden.com"
|
||||||
|
android:pathPattern="/.*/.*-manga/.*"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.perveden.com"
|
||||||
|
android:pathPattern="/.*/.*-manga/.*"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<!-- Hentai Cafe -->
|
||||||
<data android:scheme="http" />
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:pathPrefix="/hc.fyi/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:pathPrefix="/hc.fyi/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:host="exhentai.org" />
|
<!-- Tsumino -->
|
||||||
<data android:host="www.exhentai.org" />
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Book/Info/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Book/Info/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Read/View/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:pathPrefix="/Read/View/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:pathPattern="/g/..*" />
|
<!-- Hitomi.la -->
|
||||||
</intent-filter>
|
<data
|
||||||
<!-- NHentai -->
|
android:host="hitomi.la"
|
||||||
<intent-filter>
|
android:pathPrefix="/galleries/"
|
||||||
<action android:name="android.intent.action.VIEW" />
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/galleries/"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<!-- Pururin.io -->
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<data
|
||||||
|
android:host="pururin.io"
|
||||||
|
android:pathPrefix="/gallery/"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="pururin.io"
|
||||||
|
android:pathPrefix="/gallery/"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<!-- HBrowse -->
|
||||||
<data android:scheme="http" />
|
<data
|
||||||
|
android:host="www.hbrowse.com"
|
||||||
|
android:scheme="http" />
|
||||||
|
<data
|
||||||
|
android:host="www.hbrowse.com"
|
||||||
|
android:scheme="https" />
|
||||||
|
|
||||||
<data android:host="nhentai.net" />
|
<!-- MangaDex -->
|
||||||
<data android:host="www.nhentai.net" />
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.org"
|
||||||
|
android:pathPrefix="/manga/" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="mangadex.org"
|
||||||
|
android:pathPrefix="/manga/" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.cc"
|
||||||
|
android:pathPrefix="/manga/" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.cc"
|
||||||
|
android:pathPrefix="/manga/" />
|
||||||
|
|
||||||
<data android:pathPattern="/g/..*" />
|
<data
|
||||||
</intent-filter>
|
android:scheme="https"
|
||||||
<!-- Perv Eden -->
|
android:host="www.mangadex.org"
|
||||||
<intent-filter>
|
android:pathPrefix="/title/" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="mangadex.org"
|
||||||
|
android:pathPrefix="/title/" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.cc"
|
||||||
|
android:pathPrefix="/title/" />
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.cc"
|
||||||
|
android:pathPrefix="/title/" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<data
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.org"
|
||||||
<data android:scheme="https" />
|
android:pathPrefix="/chapter/" />
|
||||||
<data android:scheme="http" />
|
<data
|
||||||
|
android:scheme="https"
|
||||||
<data android:host="perveden.com" />
|
android:host="mangadex.org"
|
||||||
<data android:host="www.perveden.com" />
|
android:pathPrefix="/chapter/" />
|
||||||
|
<data
|
||||||
<data android:pathPattern="/.*/.*-manga/.*" />
|
android:scheme="https"
|
||||||
</intent-filter>
|
android:host="www.mangadex.cc"
|
||||||
<!-- Tsumino -->
|
android:pathPrefix="/chapter/" />
|
||||||
<intent-filter>
|
<data
|
||||||
<action android:name="android.intent.action.VIEW" />
|
android:scheme="https"
|
||||||
|
android:host="www.mangadex.cc"
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
android:pathPrefix="/chapter/" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
<data android:scheme="http" />
|
|
||||||
|
|
||||||
<data android:host="tsumino.com" />
|
|
||||||
<data android:host="www.tsumino.com" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/Read/View/..*" />
|
|
||||||
<data android:pathPattern="/Book/Info/..*" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Hitomi.la -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
<data android:scheme="http" />
|
|
||||||
|
|
||||||
<data android:host="hitomi.la" />
|
|
||||||
<data android:host="www.hitomi.la" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/reader/..*" />
|
|
||||||
<data android:pathPattern="/galleries/..*" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Pururin -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
<data android:scheme="http" />
|
|
||||||
|
|
||||||
<data android:host="pururin.io" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/gallery/..*" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- HBrowse -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
<data android:scheme="http" />
|
|
||||||
|
|
||||||
<data android:host="hbrowse.com" />
|
|
||||||
<data android:host="www.hbrowse.com" />
|
|
||||||
|
|
||||||
<!--<data android:pathPattern="/gallery/..*" />-->
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Mangadex -->
|
|
||||||
<intent-filter android:autoVerify="true">
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
|
|
||||||
<data android:host="mangadex.org" />
|
|
||||||
<data android:host="mangadex.cc" />
|
|
||||||
<data android:host="www.mangadex.org" />
|
|
||||||
<data android:host="www.mangadex.cc" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/manga/..*" />
|
|
||||||
<data android:pathPattern="/title/..*" />
|
|
||||||
<data android:pathPattern="/chapter/..*" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.captcha.BrowserActionActivity"
|
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:theme="@style/Theme.EHActivity" />
|
||||||
android:exported="false"/>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,31 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.res.Configuration
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Looper
|
import androidx.lifecycle.Lifecycle
|
||||||
import android.webkit.WebView
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.content.getSystemService
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.multidex.MultiDex
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.ImageLoaderFactory
|
|
||||||
import coil.decode.GifDecoder
|
|
||||||
import coil.decode.ImageDecoderDecoder
|
|
||||||
import coil.disk.DiskCache
|
|
||||||
import coil.util.DebugLogger
|
|
||||||
import com.elvishew.xlog.LogConfiguration
|
import com.elvishew.xlog.LogConfiguration
|
||||||
import com.elvishew.xlog.LogLevel
|
import com.elvishew.xlog.LogLevel
|
||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
@@ -34,185 +19,144 @@ import com.elvishew.xlog.printer.Printer
|
|||||||
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||||
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
|
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
|
||||||
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||||
|
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||||
|
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||||
|
import com.google.android.gms.security.ProviderInstaller
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
import com.google.firebase.analytics.ktx.analytics
|
import com.google.firebase.analytics.ktx.analytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import com.ms_square.debugoverlay.DebugOverlay
|
import com.ms_square.debugoverlay.DebugOverlay
|
||||||
import com.ms_square.debugoverlay.modules.FpsModule
|
import com.ms_square.debugoverlay.modules.FpsModule
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
|
||||||
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.log.CrashlyticsPrinter
|
import exh.log.CrashlyticsPrinter
|
||||||
import exh.log.EHDebugModeOverlay
|
import exh.log.EHDebugModeOverlay
|
||||||
import exh.log.EHLogLevel
|
import exh.log.EHLogLevel
|
||||||
import exh.log.EnhancedFilePrinter
|
import exh.log.EnhancedFilePrinter
|
||||||
import exh.log.XLogLogcatLogger
|
|
||||||
import exh.log.xLogD
|
|
||||||
import exh.log.xLogE
|
|
||||||
import exh.syDebugVersion
|
import exh.syDebugVersion
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import io.realm.Realm
|
||||||
import kotlinx.coroutines.flow.onEach
|
import io.realm.RealmConfiguration
|
||||||
import logcat.LogPriority
|
import kotlinx.coroutines.GlobalScope
|
||||||
import logcat.LogcatLogger
|
import kotlinx.coroutines.launch
|
||||||
import org.conscrypt.Conscrypt
|
import org.conscrypt.Conscrypt
|
||||||
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.time.Duration.Companion.days
|
import javax.net.ssl.SSLContext
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.days
|
||||||
|
|
||||||
open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
open class App : Application(), LifecycleObserver {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
private val disableIncognitoReceiver = DisableIncognitoReceiver()
|
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
||||||
|
|
||||||
@SuppressLint("LaunchActivityFromNotification")
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super<Application>.onCreate()
|
super.onCreate()
|
||||||
// if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
setupExhLogging() // EXH logging
|
setupExhLogging() // EXH logging
|
||||||
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
|
|
||||||
if (!BuildConfig.DEBUG) addAnalytics()
|
if (!BuildConfig.DEBUG) addAnalytics()
|
||||||
|
|
||||||
|
workaroundAndroid7BrokenSSL()
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
// TLS 1.3 support for Android < 10
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid potential crashes
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
val process = getProcessName()
|
|
||||||
if (packageName != process) WebView.setDataDirectorySuffix(process)
|
|
||||||
}
|
|
||||||
|
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
|
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
|
Realm.init(this)
|
||||||
|
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
|
||||||
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
||||||
setupDebugOverlay()
|
setupDebugOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||||
|
|
||||||
// Show notification to disable Incognito Mode when it's enabled
|
|
||||||
preferences.incognitoMode().asFlow()
|
|
||||||
.onEach { enabled ->
|
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
|
||||||
if (enabled) {
|
|
||||||
disableIncognitoReceiver.register()
|
|
||||||
val notification = notification(Notifications.CHANNEL_INCOGNITO_MODE) {
|
|
||||||
setContentTitle(getString(R.string.pref_incognito_mode))
|
|
||||||
setContentText(getString(R.string.notification_incognito_text))
|
|
||||||
setSmallIcon(R.drawable.ic_glasses_24dp)
|
|
||||||
setOngoing(true)
|
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
|
||||||
this@App,
|
|
||||||
0,
|
|
||||||
Intent(ACTION_DISABLE_INCOGNITO_MODE),
|
|
||||||
PendingIntent.FLAG_ONE_SHOT,
|
|
||||||
)
|
|
||||||
setContentIntent(pendingIntent)
|
|
||||||
}
|
|
||||||
notificationManager.notify(Notifications.ID_INCOGNITO_MODE, notification)
|
|
||||||
} else {
|
|
||||||
disableIncognitoReceiver.unregister()
|
|
||||||
notificationManager.cancel(Notifications.ID_INCOGNITO_MODE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
|
|
||||||
|
|
||||||
preferences.themeMode()
|
|
||||||
.asImmediateFlow {
|
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
|
||||||
when (it) {
|
|
||||||
PreferenceValues.ThemeMode.light -> AppCompatDelegate.MODE_NIGHT_NO
|
|
||||||
PreferenceValues.ThemeMode.dark -> AppCompatDelegate.MODE_NIGHT_YES
|
|
||||||
PreferenceValues.ThemeMode.system -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
|
|
||||||
|
|
||||||
/*if (!LogcatLogger.isInstalled && preferences.verboseLogging()) {
|
|
||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newImageLoader(): ImageLoader {
|
override fun attachBaseContext(base: Context) {
|
||||||
return ImageLoader.Builder(this).apply {
|
super.attachBaseContext(base)
|
||||||
val callFactoryInit = { Injekt.get<NetworkHelper>().client }
|
MultiDex.install(this)
|
||||||
val diskCacheInit = { CoilDiskCache.get(this@App) }
|
|
||||||
components {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
add(ImageDecoderDecoder.Factory())
|
|
||||||
} else {
|
|
||||||
add(GifDecoder.Factory())
|
|
||||||
}
|
|
||||||
add(TachiyomiImageDecoder.Factory())
|
|
||||||
add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit)))
|
|
||||||
add(MangaCoverKeyer())
|
|
||||||
}
|
|
||||||
callFactory(callFactoryInit)
|
|
||||||
diskCache(diskCacheInit)
|
|
||||||
crossfade((300 * this@App.animatorDurationScale).toInt())
|
|
||||||
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
|
||||||
if (preferences.verboseLogging()) logger(DebugLogger())
|
|
||||||
}.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addAnalytics() {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
if (syDebugVersion != "0") {
|
super.onConfigurationChanged(newConfig)
|
||||||
Firebase.analytics.setUserProperty("preview_version", syDebugVersion)
|
LocaleHelper.updateConfiguration(this, newConfig, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun workaroundAndroid7BrokenSSL() {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N ||
|
||||||
|
Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
SSLContext.getInstance("TLSv1.2")
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProviderInstaller.installIfNeeded(applicationContext)
|
||||||
|
} catch (e: GooglePlayServicesRepairableException) {
|
||||||
|
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||||
|
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
private fun addAnalytics() {
|
||||||
if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) {
|
firebaseAnalytics = Firebase.analytics
|
||||||
|
if (syDebugVersion != "0") {
|
||||||
|
firebaseAnalytics.setUserProperty("preview_version", syDebugVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
@Suppress("unused")
|
||||||
|
fun onAppBackgrounded() {
|
||||||
|
if (preferences.lockAppAfter().get() >= 0) {
|
||||||
SecureActivityDelegate.locked = true
|
SecureActivityDelegate.locked = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPackageName(): String {
|
protected open fun setupNotificationChannels() {
|
||||||
// This causes freezes in Android 6/7 for some reason
|
Notifications.createChannels(this)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
try {
|
|
||||||
// Override the value passed as X-Requested-With in WebView requests
|
|
||||||
val stackTrace = Looper.getMainLooper().thread.stackTrace
|
|
||||||
val chromiumElement = stackTrace.find {
|
|
||||||
it.className.equals(
|
|
||||||
"org.chromium.base.BuildInfo",
|
|
||||||
ignoreCase = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
|
||||||
return WebViewUtil.SPOOF_PACKAGE_NAME
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getPackageName()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupNotificationChannels() {
|
// EXH
|
||||||
try {
|
private fun deleteOldMetadataRealm() {
|
||||||
Notifications.createChannels(this)
|
val config = RealmConfiguration.Builder()
|
||||||
} catch (e: Exception) {
|
.name("gallery-metadata.realm")
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
.schemaVersion(3)
|
||||||
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
.build()
|
||||||
|
Realm.deleteRealm(config)
|
||||||
|
|
||||||
|
// Delete old paper db files
|
||||||
|
listOf(
|
||||||
|
File(filesDir, "gallery-ex"),
|
||||||
|
File(filesDir, "gallery-perveden"),
|
||||||
|
File(filesDir, "gallery-nhentai")
|
||||||
|
).forEach {
|
||||||
|
if (it.exists()) {
|
||||||
|
thread {
|
||||||
|
it.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,8 +165,8 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
EHLogLevel.init(this)
|
EHLogLevel.init(this)
|
||||||
|
|
||||||
val logLevel = when {
|
val logLevel = when {
|
||||||
EHLogLevel.shouldLog(EHLogLevel.EXTREME) -> LogLevel.ALL
|
EHLogLevel.shouldLog(EHLogLevel.EXTRA) -> LogLevel.ALL
|
||||||
EHLogLevel.shouldLog(EHLogLevel.EXTRA) || BuildConfig.DEBUG -> LogLevel.DEBUG
|
BuildConfig.DEBUG -> LogLevel.DEBUG
|
||||||
else -> LogLevel.WARN
|
else -> LogLevel.WARN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,27 +181,30 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
val logFolder = File(
|
val logFolder = File(
|
||||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||||
getString(R.string.app_name),
|
getString(R.string.app_name),
|
||||||
"logs",
|
"logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
printers += EnhancedFilePrinter
|
printers += EnhancedFilePrinter
|
||||||
.Builder(logFolder.absolutePath) {
|
.Builder(logFolder.absolutePath)
|
||||||
fileNameGenerator = object : DateFileNameGenerator() {
|
.fileNameGenerator(
|
||||||
|
object : DateFileNameGenerator() {
|
||||||
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||||
return super.generateFileName(
|
return super.generateFileName(
|
||||||
logLevel,
|
logLevel,
|
||||||
timestamp,
|
timestamp
|
||||||
) + "-${BuildConfig.BUILD_TYPE}.log"
|
) + "-${BuildConfig.BUILD_TYPE}.log"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flattener { timeMillis, level, tag, message ->
|
)
|
||||||
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
|
.flattener { timeMillis, level, tag, message ->
|
||||||
}
|
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
|
||||||
cleanStrategy = FileLastModifiedCleanStrategy(7.days.inWholeMilliseconds)
|
|
||||||
backupStrategy = NeverBackupStrategy()
|
|
||||||
}
|
}
|
||||||
|
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.toLongMilliseconds()))
|
||||||
|
.backupStrategy(NeverBackupStrategy())
|
||||||
|
.build()
|
||||||
|
|
||||||
// Install Crashlytics in prod
|
// Install Crashlytics in prod
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG) {
|
||||||
@@ -266,22 +213,20 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
|
|
||||||
XLog.init(
|
XLog.init(
|
||||||
logConfig,
|
logConfig,
|
||||||
*printers.toTypedArray(),
|
*printers.toTypedArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
xLogD("Application booting...")
|
XLog.tag("Init").d("Application booting...")
|
||||||
xLogD(
|
XLog.tag("Init").disableStackTrace().d(
|
||||||
"""
|
"App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})\n" +
|
||||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
|
"Preview build: $syDebugVersion\n" +
|
||||||
Preview build: $syDebugVersion
|
"Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) \n" +
|
||||||
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
|
"Android build ID: ${Build.DISPLAY}\n" +
|
||||||
Android build ID: ${Build.DISPLAY}
|
"Device brand: ${Build.BRAND}\n" +
|
||||||
Device brand: ${Build.BRAND}
|
"Device manufacturer: ${Build.MANUFACTURER}\n" +
|
||||||
Device manufacturer: ${Build.MANUFACTURER}
|
"Device name: ${Build.DEVICE}\n" +
|
||||||
Device name: ${Build.DEVICE}
|
"Device model: ${Build.MODEL}\n" +
|
||||||
Device model: ${Build.MODEL}
|
"Device product name: ${Build.PRODUCT}"
|
||||||
Device product name: ${Build.PRODUCT}
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,52 +242,7 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
.install()
|
.install()
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
// Crashes if app is in background
|
// Crashes if app is in background
|
||||||
xLogE("Failed to initialize debug overlay, app in background?", e)
|
XLog.tag("Init").e("Failed to initialize debug overlay, app in background?", e)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class DisableIncognitoReceiver : BroadcastReceiver() {
|
|
||||||
private var registered = false
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
preferences.incognitoMode().set(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun register() {
|
|
||||||
if (!registered) {
|
|
||||||
registerReceiver(this, IntentFilter(ACTION_DISABLE_INCOGNITO_MODE))
|
|
||||||
registered = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unregister() {
|
|
||||||
if (registered) {
|
|
||||||
unregisterReceiver(this)
|
|
||||||
registered = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
|
|
||||||
*/
|
|
||||||
internal object CoilDiskCache {
|
|
||||||
|
|
||||||
private const val FOLDER_NAME = "image_cache"
|
|
||||||
private var instance: DiskCache? = null
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun get(context: Context): DiskCache {
|
|
||||||
return instance ?: run {
|
|
||||||
val safeCacheDir = context.cacheDir.apply { mkdirs() }
|
|
||||||
// Create the singleton disk cache instance.
|
|
||||||
DiskCache.Builder()
|
|
||||||
.directory(safeCacheDir.resolve(FOLDER_NAME))
|
|
||||||
.build()
|
|
||||||
.also { instance = it }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by extensions.
|
|
||||||
*
|
|
||||||
* @since extension-lib 1.3
|
|
||||||
*/
|
|
||||||
object AppInfo {
|
|
||||||
fun getVersionCode() = BuildConfig.VERSION_CODE
|
|
||||||
fun getVersionName() = BuildConfig.VERSION_NAME
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.core.content.ContextCompat
|
import android.os.Handler
|
||||||
|
import com.google.gson.Gson
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.saver.ImageSaver
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
@@ -27,8 +26,6 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
addSingleton(app)
|
addSingleton(app)
|
||||||
|
|
||||||
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
|
||||||
|
|
||||||
addSingletonFactory { PreferencesHelper(app) }
|
addSingletonFactory { PreferencesHelper(app) }
|
||||||
|
|
||||||
addSingletonFactory { DatabaseHelper(app) }
|
addSingletonFactory { DatabaseHelper(app) }
|
||||||
@@ -47,9 +44,9 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { TrackManager(app) }
|
addSingletonFactory { TrackManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { DelayedTrackingStore(app) }
|
addSingletonFactory { Gson() }
|
||||||
|
|
||||||
addSingletonFactory { ImageSaver(app) }
|
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
addSingletonFactory { CustomMangaManager(app) }
|
addSingletonFactory { CustomMangaManager(app) }
|
||||||
@@ -58,7 +55,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// Asynchronously init expensive components for a faster cold start
|
// Asynchronously init expensive components for a faster cold start
|
||||||
ContextCompat.getMainExecutor(app).execute {
|
Handler().post {
|
||||||
get<PreferencesHelper>()
|
get<PreferencesHelper>()
|
||||||
|
|
||||||
get<NetworkHelper>()
|
get<NetworkHelper>()
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
import eu.kanade.tachiyomi.util.preference.minusAssign
|
|
||||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -39,29 +29,30 @@ object Migrations {
|
|||||||
fun upgrade(preferences: PreferencesHelper): Boolean {
|
fun upgrade(preferences: PreferencesHelper): Boolean {
|
||||||
val context = preferences.context
|
val context = preferences.context
|
||||||
|
|
||||||
|
// Cancel app updater job for debug builds that don't include it
|
||||||
|
if (BuildConfig.DEBUG && !BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
UpdaterJob.cancelTask(context)
|
||||||
|
}
|
||||||
|
|
||||||
val oldVersion = preferences.lastVersionCode().get()
|
val oldVersion = preferences.lastVersionCode().get()
|
||||||
if (oldVersion < BuildConfig.VERSION_CODE) {
|
if (oldVersion < BuildConfig.VERSION_CODE) {
|
||||||
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
|
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
|
||||||
|
|
||||||
// Always set up background tasks to ensure they're running
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
|
||||||
AppUpdateJob.setupTask(context)
|
|
||||||
}
|
|
||||||
ExtensionUpdateJob.setupTask(context)
|
|
||||||
LibraryUpdateJob.setupTask(context)
|
|
||||||
BackupCreatorJob.setupTask(context)
|
|
||||||
|
|
||||||
// Fresh install
|
// Fresh install
|
||||||
if (oldVersion == 0) {
|
if (oldVersion == 0) {
|
||||||
|
// Set up default background tasks
|
||||||
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
|
UpdaterJob.setupTask(context)
|
||||||
|
}
|
||||||
|
ExtensionUpdateJob.setupTask(context)
|
||||||
|
LibraryUpdateJob.setupTask(context)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
|
|
||||||
if (oldVersion < 14) {
|
if (oldVersion < 14) {
|
||||||
// Restore jobs after upgrading to Evernote's job scheduler.
|
// Restore jobs after upgrading to Evernote's job scheduler.
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
AppUpdateJob.setupTask(context)
|
UpdaterJob.setupTask(context)
|
||||||
}
|
}
|
||||||
LibraryUpdateJob.setupTask(context)
|
LibraryUpdateJob.setupTask(context)
|
||||||
}
|
}
|
||||||
@@ -94,7 +85,7 @@ object Migrations {
|
|||||||
if (oldVersion < 43) {
|
if (oldVersion < 43) {
|
||||||
// Restore jobs after migrating from Evernote's job scheduler to WorkManager.
|
// Restore jobs after migrating from Evernote's job scheduler to WorkManager.
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
AppUpdateJob.setupTask(context)
|
UpdaterJob.setupTask(context)
|
||||||
}
|
}
|
||||||
LibraryUpdateJob.setupTask(context)
|
LibraryUpdateJob.setupTask(context)
|
||||||
BackupCreatorJob.setupTask(context)
|
BackupCreatorJob.setupTask(context)
|
||||||
@@ -104,17 +95,14 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
if (oldVersion < 44) {
|
if (oldVersion < 44) {
|
||||||
// Reset sorting preference if using removed sort by source
|
// Reset sorting preference if using removed sort by source
|
||||||
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
if (oldSortingMode == LibrarySort.SOURCE) {
|
if (preferences.librarySortingMode().get() == LibrarySort.SOURCE) {
|
||||||
prefs.edit {
|
preferences.librarySortingMode().set(LibrarySort.ALPHA)
|
||||||
putInt(PreferenceKeys.librarySortingMode, LibrarySort.ALPHA)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 52) {
|
if (oldVersion < 52) {
|
||||||
// Migrate library filters to tri-state versions
|
// Migrate library filters to tri-state versions
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
fun convertBooleanPrefToTriState(key: String): Int {
|
fun convertBooleanPrefToTriState(key: String): Int {
|
||||||
val oldPrefValue = prefs.getBoolean(key, false)
|
val oldPrefValue = prefs.getBoolean(key, false)
|
||||||
return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE.value
|
return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE.value
|
||||||
@@ -131,7 +119,7 @@ object Migrations {
|
|||||||
remove("pref_filter_completed_key")
|
remove("pref_filter_completed_key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 54) {
|
if (oldVersion < 53) {
|
||||||
// Force MAL log out due to login flow change
|
// Force MAL log out due to login flow change
|
||||||
// v52: switched from scraping to WebView
|
// v52: switched from scraping to WebView
|
||||||
// v53: switched from WebView to OAuth
|
// v53: switched from WebView to OAuth
|
||||||
@@ -141,134 +129,6 @@ object Migrations {
|
|||||||
context.toast(R.string.myanimelist_relogin)
|
context.toast(R.string.myanimelist_relogin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 57) {
|
|
||||||
// Migrate DNS over HTTPS setting
|
|
||||||
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
|
|
||||||
if (wasDohEnabled) {
|
|
||||||
prefs.edit {
|
|
||||||
putInt(PreferenceKeys.dohProvider, PREF_DOH_CLOUDFLARE)
|
|
||||||
remove("enable_doh")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 59) {
|
|
||||||
// Reset rotation to Free after replacing Lock
|
|
||||||
if (prefs.contains("pref_rotation_type_key")) {
|
|
||||||
prefs.edit {
|
|
||||||
putInt("pref_rotation_type_key", 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable update check for Android 5.x users
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
|
|
||||||
AppUpdateJob.cancelTask(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 60) {
|
|
||||||
// Re-enable update check that was prevously accidentally disabled for M
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
|
|
||||||
AppUpdateJob.setupTask(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate Rotation and Viewer values to default values for viewer_flags
|
|
||||||
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
|
|
||||||
1 -> OrientationType.FREE.flagValue
|
|
||||||
2 -> OrientationType.PORTRAIT.flagValue
|
|
||||||
3 -> OrientationType.LANDSCAPE.flagValue
|
|
||||||
4 -> OrientationType.LOCKED_PORTRAIT.flagValue
|
|
||||||
5 -> OrientationType.LOCKED_LANDSCAPE.flagValue
|
|
||||||
else -> OrientationType.FREE.flagValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading mode flag and prefValue is the same value
|
|
||||||
val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
|
|
||||||
|
|
||||||
prefs.edit {
|
|
||||||
putInt("pref_default_orientation_type_key", newOrientation)
|
|
||||||
remove("pref_rotation_type_key")
|
|
||||||
putInt("pref_default_reading_mode_key", newReadingMode)
|
|
||||||
remove("pref_default_viewer_key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 61) {
|
|
||||||
// Handle removed every 1 or 2 hour library updates
|
|
||||||
val updateInterval = preferences.libraryUpdateInterval().get()
|
|
||||||
if (updateInterval == 1 || updateInterval == 2) {
|
|
||||||
preferences.libraryUpdateInterval().set(3)
|
|
||||||
LibraryUpdateJob.setupTask(context, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 64) {
|
|
||||||
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
|
|
||||||
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val newSortingMode = when (oldSortingMode) {
|
|
||||||
LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL
|
|
||||||
LibrarySort.LAST_READ -> SortModeSetting.LAST_READ
|
|
||||||
LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED
|
|
||||||
LibrarySort.UNREAD -> SortModeSetting.UNREAD
|
|
||||||
LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS
|
|
||||||
LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER
|
|
||||||
LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED
|
|
||||||
LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED
|
|
||||||
else -> SortModeSetting.ALPHABETICAL
|
|
||||||
}
|
|
||||||
|
|
||||||
val newSortingDirection = when (oldSortingDirection) {
|
|
||||||
true -> SortDirectionSetting.ASCENDING
|
|
||||||
else -> SortDirectionSetting.DESCENDING
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.edit(commit = true) {
|
|
||||||
remove(PreferenceKeys.librarySortingMode)
|
|
||||||
remove(PreferenceKeys.librarySortingDirection)
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.edit {
|
|
||||||
putString(PreferenceKeys.librarySortingMode, newSortingMode.name)
|
|
||||||
putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 70) {
|
|
||||||
if (preferences.enabledLanguages().isSet()) {
|
|
||||||
preferences.enabledLanguages() += "all"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 71) {
|
|
||||||
// Handle removed every 3, 4, 6, and 8 hour library updates
|
|
||||||
val updateInterval = preferences.libraryUpdateInterval().get()
|
|
||||||
if (updateInterval in listOf(3, 4, 6, 8)) {
|
|
||||||
preferences.libraryUpdateInterval().set(12)
|
|
||||||
LibraryUpdateJob.setupTask(context, 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 72) {
|
|
||||||
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
|
|
||||||
if (!oldUpdateOngoingOnly) {
|
|
||||||
preferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 75) {
|
|
||||||
val oldSecureScreen = prefs.getBoolean("secure_screen", false)
|
|
||||||
if (oldSecureScreen) {
|
|
||||||
preferences.secureScreen().set(PreferenceValues.SecureScreenMode.ALWAYS)
|
|
||||||
}
|
|
||||||
if (DeviceUtil.isMiui && preferences.extensionInstaller().get() == PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER) {
|
|
||||||
preferences.extensionInstaller().set(PreferenceValues.ExtensionInstaller.LEGACY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 76) {
|
|
||||||
BackupCreatorJob.setupTask(context)
|
|
||||||
}
|
|
||||||
if (oldVersion < 77) {
|
|
||||||
val oldReaderTap = prefs.getBoolean("reader_tap", false)
|
|
||||||
if (!oldReaderTap) {
|
|
||||||
preferences.navigationModePager().set(5)
|
|
||||||
preferences.navigationModeWebtoon().set(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package eu.kanade.tachiyomi.annotations
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Nsfw
|
||||||
@@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
@@ -24,11 +23,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||||||
internal val trackManager: TrackManager by injectLazy()
|
internal val trackManager: TrackManager by injectLazy()
|
||||||
protected val preferences: PreferencesHelper by injectLazy()
|
protected val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
// SY -->
|
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String?
|
||||||
protected val customMangaManager: CustomMangaManager by injectLazy()
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns manga
|
* Returns manga
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||||
@@ -25,10 +24,6 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
|
|||||||
protected val db: DatabaseHelper by injectLazy()
|
protected val db: DatabaseHelper by injectLazy()
|
||||||
protected val trackManager: TrackManager by injectLazy()
|
protected val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
// SY -->
|
|
||||||
protected val customMangaManager: CustomMangaManager by injectLazy()
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
|
|
||||||
protected lateinit var backupManager: T
|
protected lateinit var backupManager: T
|
||||||
@@ -122,7 +117,7 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
|
|||||||
internal fun showRestoreProgress(
|
internal fun showRestoreProgress(
|
||||||
progress: Int,
|
progress: Int,
|
||||||
amount: Int,
|
amount: Int,
|
||||||
title: String,
|
title: String
|
||||||
) {
|
) {
|
||||||
notifier.showRestoreProgress(title, progress, amount)
|
notifier.showRestoreProgress(title, progress, amount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,3 @@ abstract class AbstractBackupRestoreValidator {
|
|||||||
|
|
||||||
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ValidatorParseException(e: Exception) : RuntimeException(e)
|
|
||||||
|
|||||||
@@ -8,25 +8,8 @@ object BackupConst {
|
|||||||
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
||||||
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
||||||
const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
|
const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
|
||||||
|
const val EXTRA_TYPE = "$ID.$NAME.EXTRA_TYPE"
|
||||||
|
|
||||||
const val BACKUP_TYPE_LEGACY = 0
|
const val BACKUP_TYPE_LEGACY = 0
|
||||||
const val BACKUP_TYPE_FULL = 1
|
const val BACKUP_TYPE_FULL = 1
|
||||||
|
|
||||||
// Filter options
|
|
||||||
internal const val BACKUP_CATEGORY = 0x1
|
|
||||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
|
||||||
internal const val BACKUP_CHAPTER = 0x2
|
|
||||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
|
||||||
internal const val BACKUP_HISTORY = 0x4
|
|
||||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
|
||||||
internal const val BACKUP_TRACK = 0x8
|
|
||||||
internal const val BACKUP_TRACK_MASK = 0x8
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
internal const val BACKUP_CUSTOM_INFO = 0x10
|
|
||||||
internal const val BACKUP_CUSTOM_INFO_MASK = 0x10
|
|
||||||
internal const val BACKUP_READ_MANGA = 0x20
|
|
||||||
internal const val BACKUP_READ_MANGA_MASK = 0x20
|
|
||||||
internal const val BACKUP_ALL = 0x3F
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for backing up library information to a JSON file.
|
||||||
|
*/
|
||||||
|
class BackupCreateService : Service() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Filter options
|
||||||
|
internal const val BACKUP_CATEGORY = 0x1
|
||||||
|
internal const val BACKUP_CATEGORY_MASK = 0x1
|
||||||
|
internal const val BACKUP_CHAPTER = 0x2
|
||||||
|
internal const val BACKUP_CHAPTER_MASK = 0x2
|
||||||
|
internal const val BACKUP_HISTORY = 0x4
|
||||||
|
internal const val BACKUP_HISTORY_MASK = 0x4
|
||||||
|
internal const val BACKUP_TRACK = 0x8
|
||||||
|
internal const val BACKUP_TRACK_MASK = 0x8
|
||||||
|
internal const val BACKUP_ALL = 0xF
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of the service.
|
||||||
|
*
|
||||||
|
* @param context the application context.
|
||||||
|
* @return true if the service is running, false otherwise.
|
||||||
|
*/
|
||||||
|
fun isRunning(context: Context): Boolean =
|
||||||
|
context.isServiceRunning(BackupCreateService::class.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a backup from library
|
||||||
|
*
|
||||||
|
* @param context context of application
|
||||||
|
* @param uri path of Uri
|
||||||
|
* @param flags determines what to backup
|
||||||
|
*/
|
||||||
|
fun start(context: Context, uri: Uri, flags: Int, type: Int) {
|
||||||
|
if (!isRunning(context)) {
|
||||||
|
val intent = Intent(context, BackupCreateService::class.java).apply {
|
||||||
|
putExtra(BackupConst.EXTRA_URI, uri)
|
||||||
|
putExtra(BackupConst.EXTRA_FLAGS, flags)
|
||||||
|
putExtra(BackupConst.EXTRA_TYPE, type)
|
||||||
|
}
|
||||||
|
ContextCompat.startForegroundService(context, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wake lock that will be held until the service is destroyed.
|
||||||
|
*/
|
||||||
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
|
|
||||||
|
private lateinit var notifier: BackupNotifier
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
notifier = BackupNotifier(this)
|
||||||
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
|
|
||||||
|
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopService(name: Intent?): Boolean {
|
||||||
|
destroyJob()
|
||||||
|
return super.stopService(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
destroyJob()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun destroyJob() {
|
||||||
|
if (wakeLock.isHeld) {
|
||||||
|
wakeLock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
|
*/
|
||||||
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
if (intent == null) return START_NOT_STICKY
|
||||||
|
|
||||||
|
try {
|
||||||
|
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
||||||
|
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
|
||||||
|
val backupType = intent.getIntExtra(BackupConst.EXTRA_TYPE, BackupConst.BACKUP_TYPE_LEGACY)
|
||||||
|
val backupManager = when (backupType) {
|
||||||
|
BackupConst.BACKUP_TYPE_FULL -> FullBackupManager(this)
|
||||||
|
else -> LegacyBackupManager(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
|
||||||
|
val unifile = UniFile.fromUri(this, backupFileUri)
|
||||||
|
notifier.showBackupComplete(unifile, backupType == BackupConst.BACKUP_TYPE_LEGACY)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
notifier.showBackupError(e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSelf(startId)
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
|
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
|
||||||
import logcat.LogPriority
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -28,71 +19,39 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val notifier = BackupNotifier(context)
|
val uri = preferences.backupsDirectory().get().toUri()
|
||||||
val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) }
|
val flags = BackupCreateService.BACKUP_ALL
|
||||||
?: preferences.backupsDirectory().get().toUri()
|
|
||||||
val flags = inputData.getInt(BACKUP_FLAGS_KEY, BackupConst.BACKUP_ALL)
|
|
||||||
val isAutoBackup = inputData.getBoolean(IS_AUTO_BACKUP_KEY, true)
|
|
||||||
|
|
||||||
context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
|
||||||
return try {
|
return try {
|
||||||
val location = FullBackupManager(context).createBackup(uri, flags, isAutoBackup)
|
FullBackupManager(context).createBackup(uri, flags, true)
|
||||||
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
|
if (preferences.createLegacyBackup().get()) {
|
||||||
|
LegacyBackupManager(context).createBackup(uri, flags, true)
|
||||||
|
}
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
if (!isAutoBackup) notifier.showBackupError(e.message)
|
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} finally {
|
|
||||||
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun isManualJobRunning(context: Context): Boolean {
|
private const val TAG = "BackupCreator"
|
||||||
val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG_MANUAL).get()
|
|
||||||
return list.find { it.state == WorkInfo.State.RUNNING } != null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupTask(context: Context, prefInterval: Int? = null) {
|
fun setupTask(context: Context, prefInterval: Int? = null) {
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val interval = prefInterval ?: preferences.backupInterval().get()
|
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||||
val workManager = WorkManager.getInstance(context)
|
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||||
interval.toLong(),
|
interval.toLong(),
|
||||||
TimeUnit.HOURS,
|
TimeUnit.HOURS,
|
||||||
10,
|
10,
|
||||||
TimeUnit.MINUTES,
|
TimeUnit.MINUTES
|
||||||
)
|
)
|
||||||
.addTag(TAG_AUTO)
|
.addTag(TAG)
|
||||||
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.REPLACE, request)
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||||
} else {
|
} else {
|
||||||
workManager.cancelUniqueWork(TAG_AUTO)
|
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startNow(context: Context, uri: Uri, flags: Int) {
|
|
||||||
val inputData = workDataOf(
|
|
||||||
IS_AUTO_BACKUP_KEY to false,
|
|
||||||
LOCATION_URI_KEY to uri.toString(),
|
|
||||||
BACKUP_FLAGS_KEY to flags,
|
|
||||||
)
|
|
||||||
val request = OneTimeWorkRequestBuilder<BackupCreatorJob>()
|
|
||||||
.addTag(TAG_MANUAL)
|
|
||||||
.setInputData(inputData)
|
|
||||||
.build()
|
|
||||||
WorkManager.getInstance(context).enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG_AUTO = "BackupCreator"
|
|
||||||
private const val TAG_MANUAL = "$TAG_AUTO:manual"
|
|
||||||
|
|
||||||
private const val IS_AUTO_BACKUP_KEY = "is_auto_backup" // Boolean
|
|
||||||
private const val LOCATION_URI_KEY = "location_uri" // String
|
|
||||||
private const val BACKUP_FLAGS_KEY = "backup_flags" // Int
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class BackupNotifier(private val context: Context) {
|
|||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
setOnlyAlertOnce(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) {
|
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) {
|
||||||
@@ -42,6 +41,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
setContentTitle(context.getString(R.string.creating_backup))
|
setContentTitle(context.getString(R.string.creating_backup))
|
||||||
|
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.show(Notifications.ID_BACKUP_PROGRESS)
|
builder.show(Notifications.ID_BACKUP_PROGRESS)
|
||||||
@@ -60,7 +60,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showBackupComplete(unifile: UniFile) {
|
fun showBackupComplete(unifile: UniFile, isLegacyFormat: Boolean) {
|
||||||
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
||||||
|
|
||||||
with(completeNotificationBuilder) {
|
with(completeNotificationBuilder) {
|
||||||
@@ -73,7 +73,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_share_24dp,
|
R.drawable.ic_share_24dp,
|
||||||
context.getString(R.string.action_share),
|
context.getString(R.string.action_share),
|
||||||
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE),
|
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, isLegacyFormat, Notifications.ID_BACKUP_COMPLETE)
|
||||||
)
|
)
|
||||||
|
|
||||||
show(Notifications.ID_BACKUP_COMPLETE)
|
show(Notifications.ID_BACKUP_COMPLETE)
|
||||||
@@ -97,7 +97,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.getString(R.string.action_stop),
|
context.getString(R.string.action_stop),
|
||||||
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
|
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +124,8 @@ class BackupNotifier(private val context: Context) {
|
|||||||
R.string.restore_duration,
|
R.string.restore_duration,
|
||||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
TimeUnit.MILLISECONDS.toMinutes(time),
|
||||||
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
|
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
|
||||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
TimeUnit.MILLISECONDS.toMinutes(time)
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
with(completeNotificationBuilder) {
|
with(completeNotificationBuilder) {
|
||||||
@@ -139,12 +139,10 @@ class BackupNotifier(private val context: Context) {
|
|||||||
val destFile = File(path, file)
|
val destFile = File(path, file)
|
||||||
val uri = destFile.getUriCompat(context)
|
val uri = destFile.getUriCompat(context)
|
||||||
|
|
||||||
val errorLogIntent = NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
|
||||||
setContentIntent(errorLogIntent)
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_folder_24dp,
|
R.drawable.ic_folder_24dp,
|
||||||
context.getString(R.string.action_show_errors),
|
context.getString(R.string.action_open_log),
|
||||||
errorLogIntent,
|
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestore
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores backup.
|
* Restores backup.
|
||||||
@@ -44,11 +43,12 @@ class BackupRestoreService : Service() {
|
|||||||
* @param context context of application
|
* @param context context of application
|
||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
*/
|
*/
|
||||||
fun start(context: Context, uri: Uri, mode: Int) {
|
fun start(context: Context, uri: Uri, mode: Int, online: Boolean?) {
|
||||||
if (!isRunning(context)) {
|
if (!isRunning(context)) {
|
||||||
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
putExtra(BackupConst.EXTRA_URI, uri)
|
||||||
putExtra(BackupConst.EXTRA_MODE, mode)
|
putExtra(BackupConst.EXTRA_MODE, mode)
|
||||||
|
online?.let { putExtra(BackupConst.EXTRA_TYPE, it) }
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(context, intent)
|
ContextCompat.startForegroundService(context, intent)
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ class BackupRestoreService : Service() {
|
|||||||
|
|
||||||
private fun destroyJob() {
|
private fun destroyJob() {
|
||||||
backupRestore?.job?.cancel()
|
backupRestore?.job?.cancel()
|
||||||
ioScope.cancel()
|
ioScope?.cancel()
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
}
|
}
|
||||||
@@ -119,17 +119,18 @@ class BackupRestoreService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||||
val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL)
|
val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL)
|
||||||
|
val online = intent.getBooleanExtra(BackupConst.EXTRA_TYPE, true)
|
||||||
|
|
||||||
// Cancel any previous job if needed.
|
// Cancel any previous job if needed.
|
||||||
backupRestore?.job?.cancel()
|
backupRestore?.job?.cancel()
|
||||||
|
|
||||||
backupRestore = when (mode) {
|
backupRestore = when (mode) {
|
||||||
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier)
|
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online)
|
||||||
else -> LegacyBackupRestore(this, notifier)
|
else -> LegacyBackupRestore(this, notifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
val handler = CoroutineExceptionHandler { _, exception ->
|
val handler = CoroutineExceptionHandler { _, exception ->
|
||||||
logcat(LogPriority.ERROR, exception)
|
Timber.e(exception)
|
||||||
backupRestore?.writeErrorLog()
|
backupRestore?.writeErrorLog()
|
||||||
|
|
||||||
notifier.showRestoreError(exception.message)
|
notifier.showRestoreError(exception.message)
|
||||||
|
|||||||
@@ -3,20 +3,15 @@ package eu.kanade.tachiyomi.data.backup.full
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.Backup
|
import eu.kanade.tachiyomi.data.backup.full.models.Backup
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
|
||||||
@@ -34,22 +29,25 @@ import eu.kanade.tachiyomi.data.database.models.History
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||||
import exh.savedsearches.models.SavedSearch
|
import exh.savedsearches.JsonSavedSearch
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.util.executeOnIO
|
import exh.util.executeOnIO
|
||||||
import exh.util.nullIfBlank
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
import logcat.LogPriority
|
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import java.io.FileOutputStream
|
import timber.log.Timber
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||||
@@ -60,32 +58,26 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
* Create backup Json file from database
|
* Create backup Json file from database
|
||||||
*
|
*
|
||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
* @param isAutoBackup backup called from scheduled backup job
|
* @param isJob backup called from job
|
||||||
*/
|
*/
|
||||||
override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
|
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
|
||||||
// Create root object
|
// Create root object
|
||||||
var backup: Backup? = null
|
var backup: Backup? = null
|
||||||
|
|
||||||
databaseHelper.inTransaction {
|
databaseHelper.inTransaction {
|
||||||
val databaseManga = getFavoriteManga() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
|
val databaseManga = getFavoriteManga() /* SY --> */ + getReadManga() + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */
|
||||||
getReadManga()
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
} + getMergedManga() // SY <--
|
|
||||||
|
|
||||||
backup = Backup(
|
backup = Backup(
|
||||||
backupManga(databaseManga, flags),
|
backupManga(databaseManga, flags),
|
||||||
backupCategories(),
|
backupCategories(),
|
||||||
emptyList(),
|
|
||||||
backupExtensionInfo(databaseManga),
|
backupExtensionInfo(databaseManga),
|
||||||
backupSavedSearches(),
|
backupSavedSearches()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var file: UniFile? = null
|
|
||||||
try {
|
try {
|
||||||
file = (
|
val file: UniFile = (
|
||||||
if (isAutoBackup) {
|
if (isJob) {
|
||||||
// Get dir of file and create
|
// Get dir of file and create
|
||||||
var dir = UniFile.fromUri(context, uri)
|
var dir = UniFile.fromUri(context, uri)
|
||||||
dir = dir.createDirectory("automatic")
|
dir = dir.createDirectory("automatic")
|
||||||
@@ -107,28 +99,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
)
|
)
|
||||||
?: throw Exception("Couldn't create backup file")
|
?: throw Exception("Couldn't create backup file")
|
||||||
|
|
||||||
if (!file.isFile) {
|
|
||||||
throw IllegalStateException("Failed to get handle on file")
|
|
||||||
}
|
|
||||||
|
|
||||||
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
|
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
|
||||||
if (byteArray.isEmpty()) {
|
file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) }
|
||||||
throw IllegalStateException(context.getString(R.string.empty_backup_error))
|
return file.uri.toString()
|
||||||
}
|
|
||||||
|
|
||||||
file.openOutputStream().also {
|
|
||||||
// Force overwrite old file
|
|
||||||
(it as? FileOutputStream)?.channel?.truncate(0)
|
|
||||||
}.sink().gzip().buffer().use { it.write(byteArray) }
|
|
||||||
val fileUri = file.uri
|
|
||||||
|
|
||||||
// Make sure it's a valid backup file
|
|
||||||
FullBackupRestoreValidator().validate(context, fileUri)
|
|
||||||
|
|
||||||
return fileUri.toString()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
Timber.e(e)
|
||||||
file?.delete()
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,12 +142,14 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
* @return list of [BackupSavedSearch] to be backed up
|
* @return list of [BackupSavedSearch] to be backed up
|
||||||
*/
|
*/
|
||||||
private fun backupSavedSearches(): List<BackupSavedSearch> {
|
private fun backupSavedSearches(): List<BackupSavedSearch> {
|
||||||
return databaseHelper.getSavedSearches().executeAsBlocking().map {
|
return preferences.savedSearches().get().map {
|
||||||
|
val sourceId = it.substringBefore(':').toLong()
|
||||||
|
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||||
BackupSavedSearch(
|
BackupSavedSearch(
|
||||||
it.name,
|
content.name,
|
||||||
it.query.orEmpty(),
|
content.query,
|
||||||
it.filtersJson ?: "[]",
|
content.filters.toString(),
|
||||||
it.source,
|
sourceId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +164,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
*/
|
*/
|
||||||
private fun backupMangaObject(manga: Manga, options: Int): BackupManga {
|
private fun backupMangaObject(manga: Manga, options: Int): BackupManga {
|
||||||
// Entry for this manga
|
// Entry for this manga
|
||||||
val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */)
|
val mangaObject = BackupManga.copyFrom(manga)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (manga.source == MERGED_SOURCE_ID) {
|
if (manga.source == MERGED_SOURCE_ID) {
|
||||||
@@ -198,8 +175,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
|
val source = sourceManager.get(manga.source)?.getMainSource()
|
||||||
if (source != null) {
|
if (source is MetadataSource<*, *>) {
|
||||||
manga.id?.let { mangaId ->
|
manga.id?.let { mangaId ->
|
||||||
databaseHelper.getFlatMetadataForManga(mangaId).executeAsBlocking()?.let { flatMetadata ->
|
databaseHelper.getFlatMetadataForManga(mangaId).executeAsBlocking()?.let { flatMetadata ->
|
||||||
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
|
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
|
||||||
@@ -260,13 +237,24 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
/**
|
/**
|
||||||
* Fetches manga information
|
* Fetches manga information
|
||||||
*
|
*
|
||||||
|
* @param source source of manga
|
||||||
* @param manga manga that needs updating
|
* @param manga manga that needs updating
|
||||||
* @return Updated manga info.
|
* @return Updated manga info.
|
||||||
*/
|
*/
|
||||||
fun restoreManga(manga: Manga): Manga {
|
suspend fun restoreMangaFetch(source: Source?, manga: Manga, online: Boolean): Manga {
|
||||||
return manga.also {
|
return if (online && source != null /* SY --> */ && source !is MergedSource /* SY <-- */) {
|
||||||
it.initialized = it.description != null
|
val networkManga = source.getMangaDetails(manga.toMangaInfo())
|
||||||
it.id = insertManga(it)
|
manga.also {
|
||||||
|
it.copyFrom(networkManga.toSManga())
|
||||||
|
it.favorite = manga.favorite
|
||||||
|
it.initialized = true
|
||||||
|
it.id = insertManga(manga)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
manga.also {
|
||||||
|
it.initialized = it.description != null
|
||||||
|
it.id = insertManga(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,26 +363,29 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
val trackToUpdate = mutableListOf<Track>()
|
val trackToUpdate = mutableListOf<Track>()
|
||||||
|
|
||||||
tracks.forEach { track ->
|
tracks.forEach { track ->
|
||||||
var isInDatabase = false
|
val service = trackManager.getService(track.sync_id)
|
||||||
for (dbTrack in dbTracks) {
|
if (service != null && service.isLogged) {
|
||||||
if (track.sync_id == dbTrack.sync_id) {
|
var isInDatabase = false
|
||||||
// The sync is already in the db, only update its fields
|
for (dbTrack in dbTracks) {
|
||||||
if (track.media_id != dbTrack.media_id) {
|
if (track.sync_id == dbTrack.sync_id) {
|
||||||
dbTrack.media_id = track.media_id
|
// The sync is already in the db, only update its fields
|
||||||
|
if (track.media_id != dbTrack.media_id) {
|
||||||
|
dbTrack.media_id = track.media_id
|
||||||
|
}
|
||||||
|
if (track.library_id != dbTrack.library_id) {
|
||||||
|
dbTrack.library_id = track.library_id
|
||||||
|
}
|
||||||
|
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
||||||
|
isInDatabase = true
|
||||||
|
trackToUpdate.add(dbTrack)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if (track.library_id != dbTrack.library_id) {
|
|
||||||
dbTrack.library_id = track.library_id
|
|
||||||
}
|
|
||||||
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
|
||||||
isInDatabase = true
|
|
||||||
trackToUpdate.add(dbTrack)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
if (!isInDatabase) {
|
||||||
if (!isInDatabase) {
|
// Insert new sync. Let the db assign the id
|
||||||
// Insert new sync. Let the db assign the id
|
track.id = null
|
||||||
track.id = null
|
trackToUpdate.add(track)
|
||||||
trackToUpdate.add(track)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update database
|
// Update database
|
||||||
@@ -403,7 +394,47 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
|
/**
|
||||||
|
* Restore the chapters for manga if chapters already in database
|
||||||
|
*
|
||||||
|
* @param manga manga of chapters
|
||||||
|
* @param chapters list containing chapters that get restored
|
||||||
|
* @return boolean answering if chapter fetch is not needed
|
||||||
|
*/
|
||||||
|
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>): Boolean {
|
||||||
|
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
|
// Return if fetch is needed
|
||||||
|
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
chapters.forEach { chapter ->
|
||||||
|
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||||
|
if (dbChapter != null) {
|
||||||
|
chapter.id = dbChapter.id
|
||||||
|
chapter.copyFrom(dbChapter)
|
||||||
|
if (dbChapter.read && !chapter.read) {
|
||||||
|
chapter.read = dbChapter.read
|
||||||
|
chapter.last_page_read = dbChapter.last_page_read
|
||||||
|
} else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) {
|
||||||
|
chapter.last_page_read = dbChapter.last_page_read
|
||||||
|
}
|
||||||
|
if (!chapter.bookmark && dbChapter.bookmark) {
|
||||||
|
chapter.bookmark = dbChapter.bookmark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter.manga_id = manga.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the chapters that couldn't be found.
|
||||||
|
updateChapters(chapters.filter { it.id != null })
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun restoreChaptersForMangaOffline(manga: Manga, chapters: List<Chapter>) {
|
||||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
chapters.forEach { chapter ->
|
chapters.forEach { chapter ->
|
||||||
@@ -432,25 +463,33 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
|
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
|
||||||
val currentSavedSearches = databaseHelper.getSavedSearches()
|
val currentSavedSearches = preferences.savedSearches().get().map {
|
||||||
.executeAsBlocking()
|
val sourceId = it.substringBefore(':').toLong()
|
||||||
|
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||||
val newSavedSearches = backupSavedSearches.filter { backupSavedSearch ->
|
BackupSavedSearch(
|
||||||
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
content.name,
|
||||||
}.map {
|
content.query,
|
||||||
SavedSearch(
|
content.filters.toString(),
|
||||||
id = null,
|
sourceId
|
||||||
it.source,
|
|
||||||
it.name,
|
|
||||||
it.query.nullIfBlank(),
|
|
||||||
filtersJson = it.filterList.nullIfBlank()
|
|
||||||
?.takeUnless { it == "[]" },
|
|
||||||
)
|
)
|
||||||
}.ifEmpty { null }
|
|
||||||
|
|
||||||
if (newSavedSearches != null) {
|
|
||||||
databaseHelper.insertSavedSearches(newSavedSearches)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preferences.savedSearches()
|
||||||
|
.set(
|
||||||
|
(
|
||||||
|
backupSavedSearches.filter { backupSavedSearch -> currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } }
|
||||||
|
.map {
|
||||||
|
"${it.source}:" + Json.encodeToString(
|
||||||
|
JsonSavedSearch(
|
||||||
|
it.name,
|
||||||
|
it.query,
|
||||||
|
Json.decodeFromString(it.filterList)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} + preferences.savedSearches().get()
|
||||||
|
)
|
||||||
|
.toSet()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -488,9 +527,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
|
internal suspend fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
|
||||||
val mangaId = manga.id ?: return
|
manga.id?.let { mangaId ->
|
||||||
launchIO {
|
|
||||||
databaseHelper.getFlatMetadataForManga(mangaId).executeOnIO().let {
|
databaseHelper.getFlatMetadataForManga(mangaId).executeOnIO().let {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId)
|
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId)
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
|
|||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import exh.EXHMigrations
|
import exh.EXHMigrations
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
@@ -24,7 +24,7 @@ import okio.gzip
|
|||||||
import okio.source
|
import okio.source
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
|
class FullBackupRestore(context: Context, notifier: BackupNotifier, private val online: Boolean) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
|
||||||
|
|
||||||
override suspend fun performRestore(uri: Uri): Boolean {
|
override suspend fun performRestore(uri: Uri): Boolean {
|
||||||
// SY -->
|
// SY -->
|
||||||
@@ -49,8 +49,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// Store source mapping for error messages
|
// Store source mapping for error messages
|
||||||
var backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||||
sourceMapping = backupMaps.map { it.sourceId to it.name }.toMap()
|
|
||||||
|
|
||||||
// Restore individual manga, sort by merged source so that merged source manga go last and merged references get the proper ids
|
// Restore individual manga, sort by merged source so that merged source manga go last and merged references get the proper ids
|
||||||
backup.backupManga /* SY --> */.sortedBy { it.source == MERGED_SOURCE_ID } /* SY <-- */.forEach {
|
backup.backupManga /* SY --> */.sortedBy { it.source == MERGED_SOURCE_ID } /* SY <-- */.forEach {
|
||||||
@@ -58,11 +57,9 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreManga(it, backup.backupCategories)
|
restoreManga(it, backup.backupCategories, online)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optionally trigger online library + tracker update
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,26 +81,31 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
|
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) {
|
||||||
val manga = backupManga.getMangaImpl()
|
var manga = backupManga.getMangaImpl()
|
||||||
val chapters = backupManga.getChaptersImpl()
|
val chapters = backupManga.getChaptersImpl()
|
||||||
val categories = backupManga.categories
|
val categories = backupManga.categories
|
||||||
val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
|
val history = backupManga.history
|
||||||
val tracks = backupManga.getTrackingImpl()
|
val tracks = backupManga.getTrackingImpl()
|
||||||
// SY -->
|
// SY -->
|
||||||
val mergedMangaReferences = backupManga.mergedMangaReferences
|
val mergedMangaReferences = backupManga.mergedMangaReferences
|
||||||
val flatMetadata = backupManga.flatMetadata
|
val flatMetadata = backupManga.flatMetadata
|
||||||
val customManga = backupManga.getCustomMangaInfo()
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
EXHMigrations.migrateBackupEntry(manga)
|
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
val source = backupManager.sourceManager.get(manga.source)
|
||||||
|
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
restoreMangaData(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
|
if (source != null || !online) {
|
||||||
|
restoreMangaData(manga, source, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
|
||||||
|
} else {
|
||||||
|
errors.add(Date() to "${manga.title} [$sourceName]: ${context.getString(R.string.source_not_found_name, sourceName)}")
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
|
||||||
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,35 +117,35 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
* Returns a manga restore observable
|
* Returns a manga restore observable
|
||||||
*
|
*
|
||||||
* @param manga manga data from json
|
* @param manga manga data from json
|
||||||
|
* @param source source to get manga data from
|
||||||
* @param chapters chapters data from json
|
* @param chapters chapters data from json
|
||||||
* @param categories categories data from json
|
* @param categories categories data from json
|
||||||
* @param history history data from json
|
* @param history history data from json
|
||||||
* @param tracks tracking data from json
|
* @param tracks tracking data from json
|
||||||
*/
|
*/
|
||||||
private fun restoreMangaData(
|
private suspend fun restoreMangaData(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
|
source: Source?,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<Int>,
|
categories: List<Int>,
|
||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
// SY -->
|
|
||||||
mergedMangaReferences: List<BackupMergedMangaReference>,
|
mergedMangaReferences: List<BackupMergedMangaReference>,
|
||||||
flatMetadata: BackupFlatMetadata?,
|
flatMetadata: BackupFlatMetadata?,
|
||||||
customManga: CustomMangaManager.MangaJson?,
|
online: Boolean
|
||||||
// SY -->
|
|
||||||
) {
|
) {
|
||||||
|
val dbManga = backupManager.getMangaFromDatabase(manga)
|
||||||
|
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
|
||||||
if (dbManga == null) {
|
if (dbManga == null) {
|
||||||
// Manga not in database
|
// Manga not in database
|
||||||
restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
|
restoreMangaFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
|
||||||
} else {
|
} else { // Manga in database
|
||||||
// Manga in database
|
|
||||||
// Copy information from manga already in database
|
// Copy information from manga already in database
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
backupManager.restoreMangaNoFetch(manga, dbManga)
|
||||||
// Fetch rest of manga information
|
// Fetch rest of manga information
|
||||||
restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
|
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,60 +157,66 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
* @param chapters chapters of manga that needs updating
|
* @param chapters chapters of manga that needs updating
|
||||||
* @param categories categories that need updating
|
* @param categories categories that need updating
|
||||||
*/
|
*/
|
||||||
private fun restoreMangaFetch(
|
private suspend fun restoreMangaFetch(
|
||||||
|
source: Source?,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<Int>,
|
categories: List<Int>,
|
||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
// SY -->
|
|
||||||
mergedMangaReferences: List<BackupMergedMangaReference>,
|
mergedMangaReferences: List<BackupMergedMangaReference>,
|
||||||
flatMetadata: BackupFlatMetadata?,
|
flatMetadata: BackupFlatMetadata?,
|
||||||
customManga: CustomMangaManager.MangaJson?,
|
online: Boolean
|
||||||
// SY <--
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val fetchedManga = backupManager.restoreManga(manga)
|
val fetchedManga = backupManager.restoreMangaFetch(source, manga, online)
|
||||||
fetchedManga.id ?: return
|
fetchedManga.id ?: return
|
||||||
backupManager.restoreChaptersForManga(fetchedManga, chapters)
|
|
||||||
|
|
||||||
restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories /* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
|
if (online && source != null) {
|
||||||
|
// SY -->
|
||||||
|
if (source !is MergedSource) {
|
||||||
|
updateChapters(source, fetchedManga, chapters)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
} else {
|
||||||
|
backupManager.restoreChaptersForMangaOffline(fetchedManga, chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
|
||||||
|
|
||||||
|
updateTracking(fetchedManga, tracks)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errors.add(Date() to "${manga.title} - ${e.message}")
|
errors.add(Date() to "${manga.title} - ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreMangaNoFetch(
|
private suspend fun restoreMangaNoFetch(
|
||||||
|
source: Source?,
|
||||||
backupManga: Manga,
|
backupManga: Manga,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<Int>,
|
categories: List<Int>,
|
||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
// SY -->
|
|
||||||
mergedMangaReferences: List<BackupMergedMangaReference>,
|
mergedMangaReferences: List<BackupMergedMangaReference>,
|
||||||
flatMetadata: BackupFlatMetadata?,
|
flatMetadata: BackupFlatMetadata?,
|
||||||
customManga: CustomMangaManager.MangaJson?,
|
online: Boolean
|
||||||
// SY <--
|
|
||||||
) {
|
) {
|
||||||
backupManager.restoreChaptersForManga(backupManga, chapters)
|
if (online && source != null) {
|
||||||
|
if (/* SY --> */ source !is MergedSource && /* SY <-- */ !backupManager.restoreChaptersForManga(backupManga, chapters)) {
|
||||||
|
updateChapters(source, backupManga, chapters)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
backupManager.restoreChaptersForMangaOffline(backupManga, chapters)
|
||||||
|
}
|
||||||
|
|
||||||
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
|
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
|
||||||
|
|
||||||
|
updateTracking(backupManga, tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreExtraForManga(
|
private suspend fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>, mergedMangaReferences: List<BackupMergedMangaReference>, flatMetadata: BackupFlatMetadata?) {
|
||||||
manga: Manga,
|
|
||||||
categories: List<Int>,
|
|
||||||
history: List<BackupHistory>,
|
|
||||||
tracks: List<Track>,
|
|
||||||
backupCategories: List<BackupCategory>,
|
|
||||||
// SY -->
|
|
||||||
mergedMangaReferences: List<BackupMergedMangaReference>,
|
|
||||||
flatMetadata: BackupFlatMetadata?,
|
|
||||||
customManga: CustomMangaManager.MangaJson?,
|
|
||||||
// SY <--
|
|
||||||
) {
|
|
||||||
// Restore categories
|
// Restore categories
|
||||||
backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
|
backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
|
||||||
|
|
||||||
@@ -224,10 +232,6 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
|
|
||||||
// Restore flat metadata for metadata sources
|
// Restore flat metadata for metadata sources
|
||||||
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) }
|
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) }
|
||||||
|
|
||||||
// Restore Custom Info
|
|
||||||
customManga?.id = manga.id!!
|
|
||||||
customManga?.let { customMangaManager.saveMangaInfo(it) }
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-11
@@ -4,14 +4,12 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
||||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.source
|
import okio.source
|
||||||
|
|
||||||
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for critical backup file data.
|
* Checks for critical backup file data.
|
||||||
*
|
*
|
||||||
@@ -21,20 +19,14 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||||||
override fun validate(context: Context, uri: Uri): Results {
|
override fun validate(context: Context, uri: Uri): Results {
|
||||||
val backupManager = FullBackupManager(context)
|
val backupManager = FullBackupManager(context)
|
||||||
|
|
||||||
val backup = try {
|
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
|
||||||
val backupString =
|
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||||
context.contentResolver.openInputStream(uri)!!.source().gzip().buffer()
|
|
||||||
.use { it.readByteArray() }
|
|
||||||
backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw ValidatorParseException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backup.backupManga.isEmpty()) {
|
if (backup.backupManga.isEmpty()) {
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||||
val missingSources = sources
|
val missingSources = sources
|
||||||
.filter { sourceManager.get(it.key) == null }
|
.filter { sourceManager.get(it.key) == null }
|
||||||
.values
|
.values
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ data class Backup(
|
|||||||
@ProtoNumber(1) val backupManga: List<BackupManga>,
|
@ProtoNumber(1) val backupManga: List<BackupManga>,
|
||||||
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
|
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
|
||||||
// Bump by 100 to specify this is a 0.x value
|
// Bump by 100 to specify this is a 0.x value
|
||||||
@ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(),
|
@ProtoNumber(100) var backupSources: List<BackupSource> = emptyList(),
|
||||||
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
|
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList(),
|
@ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class BackupCategory(
|
|||||||
// Bump by 100 to specify this is a 0.x value
|
// Bump by 100 to specify this is a 0.x value
|
||||||
@ProtoNumber(100) var flags: Int = 0,
|
@ProtoNumber(100) var flags: Int = 0,
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),
|
@ProtoNumber(600) var mangaOrder: List<Long> = emptyList()
|
||||||
) {
|
) {
|
||||||
fun getCategoryImpl(): CategoryImpl {
|
fun getCategoryImpl(): CategoryImpl {
|
||||||
return CategoryImpl().apply {
|
return CategoryImpl().apply {
|
||||||
@@ -30,7 +30,7 @@ class BackupCategory(
|
|||||||
name = category.name,
|
name = category.name,
|
||||||
order = category.order,
|
order = category.order,
|
||||||
flags = category.flags,
|
flags = category.flags,
|
||||||
mangaOrder = category.mangaOrder,
|
mangaOrder = category.mangaOrder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ data class BackupChapter(
|
|||||||
lastPageRead = chapter.last_page_read,
|
lastPageRead = chapter.last_page_read,
|
||||||
dateFetch = chapter.date_fetch,
|
dateFetch = chapter.date_fetch,
|
||||||
dateUpload = chapter.date_upload,
|
dateUpload = chapter.date_upload,
|
||||||
sourceOrder = chapter.source_order,
|
sourceOrder = chapter.source_order
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
data class BackupFlatMetadata(
|
data class BackupFlatMetadata(
|
||||||
@ProtoNumber(1) var searchMetadata: BackupSearchMetadata,
|
@ProtoNumber(1) var searchMetadata: BackupSearchMetadata,
|
||||||
@ProtoNumber(2) var searchTags: List<BackupSearchTag> = emptyList(),
|
@ProtoNumber(2) var searchTags: List<BackupSearchTag> = emptyList(),
|
||||||
@ProtoNumber(3) var searchTitles: List<BackupSearchTitle> = emptyList(),
|
@ProtoNumber(3) var searchTitles: List<BackupSearchTitle> = emptyList()
|
||||||
) {
|
) {
|
||||||
fun getFlatMetadata(mangaId: Long): FlatMetadata {
|
fun getFlatMetadata(mangaId: Long): FlatMetadata {
|
||||||
return FlatMetadata(
|
return FlatMetadata(
|
||||||
metadata = searchMetadata.getSearchMetadata(mangaId),
|
metadata = searchMetadata.getSearchMetadata(mangaId),
|
||||||
tags = searchTags.map { it.getSearchTag(mangaId) },
|
tags = searchTags.map { it.getSearchTag(mangaId) },
|
||||||
titles = searchTitles.map { it.getSearchTitle(mangaId) },
|
titles = searchTitles.map { it.getSearchTitle(mangaId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ data class BackupFlatMetadata(
|
|||||||
return BackupFlatMetadata(
|
return BackupFlatMetadata(
|
||||||
searchMetadata = BackupSearchMetadata.copyFrom(flatMetadata.metadata),
|
searchMetadata = BackupSearchMetadata.copyFrom(flatMetadata.metadata),
|
||||||
searchTags = flatMetadata.tags.map { BackupSearchTag.copyFrom(it) },
|
searchTags = flatMetadata.tags.map { BackupSearchTag.copyFrom(it) },
|
||||||
searchTitles = flatMetadata.titles.map { BackupSearchTitle.copyFrom(it) },
|
searchTitles = flatMetadata.titles.map { BackupSearchTitle.copyFrom(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ package eu.kanade.tachiyomi.data.backup.full.models
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BrokenBackupHistory(
|
|
||||||
@ProtoNumber(0) var url: String,
|
|
||||||
@ProtoNumber(1) var lastRead: Long,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BackupHistory(
|
data class BackupHistory(
|
||||||
@ProtoNumber(1) var url: String,
|
@ProtoNumber(0) var url: String,
|
||||||
@ProtoNumber(2) var lastRead: Long,
|
@ProtoNumber(1) var lastRead: Long
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ data class BackupManga(
|
|||||||
// @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x
|
// @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x
|
||||||
// @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x
|
// @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x
|
||||||
@ProtoNumber(13) var dateAdded: Long = 0,
|
@ProtoNumber(13) var dateAdded: Long = 0,
|
||||||
@ProtoNumber(14) var viewer: Int = 0, // Replaced by viewer_flags
|
@ProtoNumber(14) var viewer: Int = 0,
|
||||||
// @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x
|
// @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x
|
||||||
@ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(),
|
@ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(),
|
||||||
@ProtoNumber(17) var categories: List<Int> = emptyList(),
|
@ProtoNumber(17) var categories: List<Int> = emptyList(),
|
||||||
@@ -34,25 +33,10 @@ data class BackupManga(
|
|||||||
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
|
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
|
||||||
@ProtoNumber(100) var favorite: Boolean = true,
|
@ProtoNumber(100) var favorite: Boolean = true,
|
||||||
@ProtoNumber(101) var chapterFlags: Int = 0,
|
@ProtoNumber(101) var chapterFlags: Int = 0,
|
||||||
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
|
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
|
||||||
@ProtoNumber(103) var viewer_flags: Int? = null,
|
|
||||||
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
|
|
||||||
|
|
||||||
// SY specific values
|
// SY specific values
|
||||||
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
|
||||||
@ProtoNumber(601) var flatMetadata: BackupFlatMetadata? = null,
|
@ProtoNumber(601) var flatMetadata: BackupFlatMetadata? = null
|
||||||
@ProtoNumber(602) var customStatus: Int = 0,
|
|
||||||
|
|
||||||
// J2K specific values
|
|
||||||
@ProtoNumber(800) var customTitle: String? = null,
|
|
||||||
@ProtoNumber(801) var customArtist: String? = null,
|
|
||||||
@ProtoNumber(802) var customAuthor: String? = null,
|
|
||||||
// skipping 803 due to using duplicate value in previous builds
|
|
||||||
@ProtoNumber(804) var customDescription: String? = null,
|
|
||||||
@ProtoNumber(805) var customGenre: List<String>? = null,
|
|
||||||
|
|
||||||
// Neko specific values
|
|
||||||
@ProtoNumber(901) var filtered_scanlators: String? = null,
|
|
||||||
) {
|
) {
|
||||||
fun getMangaImpl(): MangaImpl {
|
fun getMangaImpl(): MangaImpl {
|
||||||
return MangaImpl().apply {
|
return MangaImpl().apply {
|
||||||
@@ -67,9 +51,8 @@ data class BackupManga(
|
|||||||
favorite = this@BackupManga.favorite
|
favorite = this@BackupManga.favorite
|
||||||
source = this@BackupManga.source
|
source = this@BackupManga.source
|
||||||
date_added = this@BackupManga.dateAdded
|
date_added = this@BackupManga.dateAdded
|
||||||
viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer
|
viewer = this@BackupManga.viewer
|
||||||
chapter_flags = this@BackupManga.chapterFlags
|
chapter_flags = this@BackupManga.chapterFlags
|
||||||
filtered_scanlators = this@BackupManga.filtered_scanlators
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,29 +62,6 @@ data class BackupManga(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
|
||||||
fun getCustomMangaInfo(): CustomMangaManager.MangaJson? {
|
|
||||||
if (customTitle != null ||
|
|
||||||
customArtist != null ||
|
|
||||||
customAuthor != null ||
|
|
||||||
customDescription != null ||
|
|
||||||
customGenre != null ||
|
|
||||||
customStatus != 0
|
|
||||||
) {
|
|
||||||
return CustomMangaManager.MangaJson(
|
|
||||||
id = 0L,
|
|
||||||
title = customTitle,
|
|
||||||
author = customAuthor,
|
|
||||||
artist = customArtist,
|
|
||||||
description = customDescription,
|
|
||||||
genre = customGenre,
|
|
||||||
status = customStatus.takeUnless { it == 0 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
fun getTrackingImpl(): List<TrackImpl> {
|
fun getTrackingImpl(): List<TrackImpl> {
|
||||||
return tracking.map {
|
return tracking.map {
|
||||||
it.getTrackingImpl()
|
it.getTrackingImpl()
|
||||||
@@ -109,37 +69,22 @@ data class BackupManga(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun copyFrom(manga: Manga /* SY --> */, customMangaManager: CustomMangaManager?/* SY <-- */): BackupManga {
|
fun copyFrom(manga: Manga): BackupManga {
|
||||||
return BackupManga(
|
return BackupManga(
|
||||||
url = manga.url,
|
url = manga.url,
|
||||||
// SY -->
|
title = manga.title,
|
||||||
title = manga.originalTitle,
|
artist = manga.artist,
|
||||||
artist = manga.originalArtist,
|
author = manga.author,
|
||||||
author = manga.originalAuthor,
|
description = manga.description,
|
||||||
description = manga.originalDescription,
|
genre = manga.getGenres() ?: emptyList(),
|
||||||
genre = manga.getOriginalGenres() ?: emptyList(),
|
status = manga.status,
|
||||||
status = manga.originalStatus,
|
|
||||||
// SY <--
|
|
||||||
thumbnailUrl = manga.thumbnail_url,
|
thumbnailUrl = manga.thumbnail_url,
|
||||||
favorite = manga.favorite,
|
favorite = manga.favorite,
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
dateAdded = manga.date_added,
|
dateAdded = manga.date_added,
|
||||||
viewer = manga.readingModeType,
|
viewer = manga.viewer,
|
||||||
viewer_flags = manga.viewer_flags,
|
chapterFlags = manga.chapter_flags
|
||||||
chapterFlags = manga.chapter_flags,
|
)
|
||||||
filtered_scanlators = manga.filtered_scanlators,
|
|
||||||
// SY -->
|
|
||||||
).also { backupManga ->
|
|
||||||
customMangaManager?.getManga(manga)?.let {
|
|
||||||
backupManga.customTitle = it.title
|
|
||||||
backupManga.customArtist = it.artist
|
|
||||||
backupManga.customAuthor = it.author
|
|
||||||
backupManga.customDescription = it.description
|
|
||||||
backupManga.customGenre = it.getGenres()
|
|
||||||
backupManga.customStatus = it.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -16,7 +16,7 @@ data class BackupMergedMangaReference(
|
|||||||
@ProtoNumber(5) var downloadChapters: Boolean,
|
@ProtoNumber(5) var downloadChapters: Boolean,
|
||||||
@ProtoNumber(6) var mergeUrl: String,
|
@ProtoNumber(6) var mergeUrl: String,
|
||||||
@ProtoNumber(7) var mangaUrl: String,
|
@ProtoNumber(7) var mangaUrl: String,
|
||||||
@ProtoNumber(8) var mangaSourceId: Long,
|
@ProtoNumber(8) var mangaSourceId: Long
|
||||||
) {
|
) {
|
||||||
fun getMergedMangaReference(): MergedMangaReference {
|
fun getMergedMangaReference(): MergedMangaReference {
|
||||||
return MergedMangaReference(
|
return MergedMangaReference(
|
||||||
@@ -30,7 +30,7 @@ data class BackupMergedMangaReference(
|
|||||||
mangaSourceId = mangaSourceId,
|
mangaSourceId = mangaSourceId,
|
||||||
mergeId = null,
|
mergeId = null,
|
||||||
mangaId = null,
|
mangaId = null,
|
||||||
id = null,
|
id = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ data class BackupMergedMangaReference(
|
|||||||
downloadChapters = mergedMangaReference.downloadChapters,
|
downloadChapters = mergedMangaReference.downloadChapters,
|
||||||
mergeUrl = mergedMangaReference.mergeUrl,
|
mergeUrl = mergedMangaReference.mergeUrl,
|
||||||
mangaUrl = mergedMangaReference.mangaUrl,
|
mangaUrl = mergedMangaReference.mangaUrl,
|
||||||
mangaSourceId = mergedMangaReference.mangaSourceId,
|
mangaSourceId = mergedMangaReference.mangaSourceId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ data class BackupSavedSearch(
|
|||||||
@ProtoNumber(1) val name: String,
|
@ProtoNumber(1) val name: String,
|
||||||
@ProtoNumber(2) val query: String = "",
|
@ProtoNumber(2) val query: String = "",
|
||||||
@ProtoNumber(3) val filterList: String = "",
|
@ProtoNumber(3) val filterList: String = "",
|
||||||
@ProtoNumber(4) val source: Long = 0,
|
@ProtoNumber(4) val source: Long = 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,22 +4,16 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BrokenBackupSource(
|
|
||||||
@ProtoNumber(0) var name: String = "",
|
|
||||||
@ProtoNumber(1) var sourceId: Long,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BackupSource(
|
data class BackupSource(
|
||||||
@ProtoNumber(1) var name: String = "",
|
@ProtoNumber(0) var name: String = "",
|
||||||
@ProtoNumber(2) var sourceId: Long,
|
@ProtoNumber(1) var sourceId: Long
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun copyFrom(source: Source): BackupSource {
|
fun copyFrom(source: Source): BackupSource {
|
||||||
return BackupSource(
|
return BackupSource(
|
||||||
name = source.name,
|
name = source.name,
|
||||||
sourceId = source.id,
|
sourceId = source.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ data class BackupTracking(
|
|||||||
media_id = this@BackupTracking.mediaId
|
media_id = this@BackupTracking.mediaId
|
||||||
library_id = this@BackupTracking.libraryId
|
library_id = this@BackupTracking.libraryId
|
||||||
title = this@BackupTracking.title
|
title = this@BackupTracking.title
|
||||||
last_chapter_read = this@BackupTracking.lastChapterRead
|
// convert from float to int because of 1.x types
|
||||||
|
last_chapter_read = this@BackupTracking.lastChapterRead.toInt()
|
||||||
total_chapters = this@BackupTracking.totalChapters
|
total_chapters = this@BackupTracking.totalChapters
|
||||||
score = this@BackupTracking.score
|
score = this@BackupTracking.score
|
||||||
status = this@BackupTracking.status
|
status = this@BackupTracking.status
|
||||||
@@ -50,13 +51,14 @@ data class BackupTracking(
|
|||||||
// forced not null so its compatible with 1.x backup system
|
// forced not null so its compatible with 1.x backup system
|
||||||
libraryId = track.library_id!!,
|
libraryId = track.library_id!!,
|
||||||
title = track.title,
|
title = track.title,
|
||||||
lastChapterRead = track.last_chapter_read,
|
// convert to float for 1.x
|
||||||
|
lastChapterRead = track.last_chapter_read.toFloat(),
|
||||||
totalChapters = track.total_chapters,
|
totalChapters = track.total_chapters,
|
||||||
score = track.score,
|
score = track.score,
|
||||||
status = track.status,
|
status = track.status,
|
||||||
startedReadingDate = track.started_reading_date,
|
startedReadingDate = track.started_reading_date,
|
||||||
finishedReadingDate = track.finished_reading_date,
|
finishedReadingDate = track.finished_reading_date,
|
||||||
trackingUrl = track.tracking_url,
|
trackingUrl = track.tracking_url
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@ data class BackupSearchMetadata(
|
|||||||
@ProtoNumber(1) var uploader: String? = null,
|
@ProtoNumber(1) var uploader: String? = null,
|
||||||
@ProtoNumber(2) var extra: String,
|
@ProtoNumber(2) var extra: String,
|
||||||
@ProtoNumber(3) var indexedExtra: String? = null,
|
@ProtoNumber(3) var indexedExtra: String? = null,
|
||||||
@ProtoNumber(4) var extraVersion: Int,
|
@ProtoNumber(4) var extraVersion: Int
|
||||||
) {
|
) {
|
||||||
fun getSearchMetadata(mangaId: Long): SearchMetadata {
|
fun getSearchMetadata(mangaId: Long): SearchMetadata {
|
||||||
return SearchMetadata(
|
return SearchMetadata(
|
||||||
@@ -17,7 +17,7 @@ data class BackupSearchMetadata(
|
|||||||
uploader = uploader,
|
uploader = uploader,
|
||||||
extra = extra,
|
extra = extra,
|
||||||
indexedExtra = indexedExtra,
|
indexedExtra = indexedExtra,
|
||||||
extraVersion = extraVersion,
|
extraVersion = extraVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ data class BackupSearchMetadata(
|
|||||||
uploader = searchMetadata.uploader,
|
uploader = searchMetadata.uploader,
|
||||||
extra = searchMetadata.extra,
|
extra = searchMetadata.extra,
|
||||||
indexedExtra = searchMetadata.indexedExtra,
|
indexedExtra = searchMetadata.indexedExtra,
|
||||||
extraVersion = searchMetadata.extraVersion,
|
extraVersion = searchMetadata.extraVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
data class BackupSearchTag(
|
data class BackupSearchTag(
|
||||||
@ProtoNumber(1) var namespace: String? = null,
|
@ProtoNumber(1) var namespace: String? = null,
|
||||||
@ProtoNumber(2) var name: String,
|
@ProtoNumber(2) var name: String,
|
||||||
@ProtoNumber(3) var type: Int,
|
@ProtoNumber(3) var type: Int
|
||||||
) {
|
) {
|
||||||
fun getSearchTag(mangaId: Long): SearchTag {
|
fun getSearchTag(mangaId: Long): SearchTag {
|
||||||
return SearchTag(
|
return SearchTag(
|
||||||
@@ -16,7 +16,7 @@ data class BackupSearchTag(
|
|||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
namespace = namespace,
|
namespace = namespace,
|
||||||
name = name,
|
name = name,
|
||||||
type = type,
|
type = type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ data class BackupSearchTag(
|
|||||||
return BackupSearchTag(
|
return BackupSearchTag(
|
||||||
namespace = searchTag.namespace,
|
namespace = searchTag.namespace,
|
||||||
name = searchTag.name,
|
name = searchTag.name,
|
||||||
type = searchTag.type,
|
type = searchTag.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -7,14 +7,14 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class BackupSearchTitle(
|
data class BackupSearchTitle(
|
||||||
@ProtoNumber(1) var title: String,
|
@ProtoNumber(1) var title: String,
|
||||||
@ProtoNumber(2) var type: Int,
|
@ProtoNumber(2) var type: Int
|
||||||
) {
|
) {
|
||||||
fun getSearchTitle(mangaId: Long): SearchTitle {
|
fun getSearchTitle(mangaId: Long): SearchTitle {
|
||||||
return SearchTitle(
|
return SearchTitle(
|
||||||
id = null,
|
id = null,
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
title = title,
|
title = title,
|
||||||
type = type,
|
type = type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ data class BackupSearchTitle(
|
|||||||
fun copyFrom(searchTitle: SearchTitle): BackupSearchTitle {
|
fun copyFrom(searchTitle: SearchTitle): BackupSearchTitle {
|
||||||
return BackupSearchTitle(
|
return BackupSearchTitle(
|
||||||
title = searchTitle.title,
|
title = searchTitle.title,
|
||||||
type = searchTitle.type,
|
type = searchTitle.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,69 +2,84 @@ package eu.kanade.tachiyomi.data.backup.legacy
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
|
import com.github.salomonbrys.kotson.registerTypeAdapter
|
||||||
|
import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter
|
||||||
|
import com.github.salomonbrys.kotson.set
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CATEGORIES
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CHAPTERS
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CURRENT_VERSION
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.EXTENSIONS
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.HISTORY
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGA
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MERGEDMANGAREFERENCES
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.SAVEDSEARCHES
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.TRACK
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryImplTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterImplTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MergedMangaReferenceTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaImplTypeSerializer
|
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeAdapter
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeSerializer
|
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MergedMangaTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackImplTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import exh.eh.EHentaiThrottleManager
|
import exh.eh.EHentaiThrottleManager
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
import exh.savedsearches.models.SavedSearch
|
import exh.savedsearches.JsonSavedSearch
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.util.nullIfBlank
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import timber.log.Timber
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import uy.kohesive.injekt.Injekt
|
||||||
import kotlinx.serialization.json.jsonArray
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import java.lang.RuntimeException
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
import kotlinx.serialization.modules.contextual
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
|
class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
|
||||||
|
|
||||||
val parser: Json = when (version) {
|
val parser: Gson = when (version) {
|
||||||
2 -> Json {
|
2 -> GsonBuilder()
|
||||||
// Forks may have added items to backup
|
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
||||||
ignoreUnknownKeys = true
|
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||||
|
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||||
// Register custom serializers
|
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
||||||
serializersModule = SerializersModule {
|
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
||||||
contextual(MangaTypeSerializer)
|
// SY -->
|
||||||
contextual(MangaImplTypeSerializer)
|
.registerTypeAdapter<MergedMangaReference>(MergedMangaReferenceTypeAdapter.build())
|
||||||
contextual(ChapterTypeSerializer)
|
// SY <--
|
||||||
contextual(ChapterImplTypeSerializer)
|
.create()
|
||||||
contextual(CategoryTypeSerializer)
|
|
||||||
contextual(CategoryImplTypeSerializer)
|
|
||||||
contextual(TrackTypeSerializer)
|
|
||||||
contextual(TrackImplTypeSerializer)
|
|
||||||
contextual(HistoryTypeSerializer)
|
|
||||||
// SY -->
|
|
||||||
contextual(MergedMangaTypeSerializer)
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw Exception("Unknown backup version")
|
else -> throw Exception("Unknown backup version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,10 +87,182 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
|
|||||||
* Create backup Json file from database
|
* Create backup Json file from database
|
||||||
*
|
*
|
||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
* @param isAutoBackup backup called from scheduled backup job
|
* @param isJob backup called from job
|
||||||
*/
|
*/
|
||||||
override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean) =
|
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
|
||||||
throw IllegalStateException("Legacy backup creation is not supported")
|
// Create root object
|
||||||
|
val root = JsonObject()
|
||||||
|
|
||||||
|
// Create manga array
|
||||||
|
val mangaEntries = JsonArray()
|
||||||
|
|
||||||
|
// Create category array
|
||||||
|
val categoryEntries = JsonArray()
|
||||||
|
|
||||||
|
// Create extension ID/name mapping
|
||||||
|
val extensionEntries = JsonArray()
|
||||||
|
|
||||||
|
// Merged Manga References
|
||||||
|
val mergedMangaReferenceEntries = JsonArray()
|
||||||
|
|
||||||
|
// Add value's to root
|
||||||
|
root[Backup.VERSION] = CURRENT_VERSION
|
||||||
|
root[Backup.MANGAS] = mangaEntries
|
||||||
|
root[CATEGORIES] = categoryEntries
|
||||||
|
root[EXTENSIONS] = extensionEntries
|
||||||
|
// SY -->
|
||||||
|
root[MERGEDMANGAREFERENCES] = mergedMangaReferenceEntries
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
databaseHelper.inTransaction {
|
||||||
|
val mangas = getFavoriteManga()/* SY --> */.filterNot { it.source == MERGED_SOURCE_ID } + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */
|
||||||
|
|
||||||
|
val extensions: MutableSet<String> = mutableSetOf()
|
||||||
|
|
||||||
|
// Backup library manga and its dependencies
|
||||||
|
mangas.forEach { manga ->
|
||||||
|
mangaEntries.add(backupMangaObject(manga, flags))
|
||||||
|
|
||||||
|
// Maintain set of extensions/sources used (excludes local source)
|
||||||
|
if (manga.source != LocalSource.ID) {
|
||||||
|
sourceManager.get(manga.source)?.let {
|
||||||
|
extensions.add("${manga.source}:${it.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup categories
|
||||||
|
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
|
||||||
|
backupCategories(categoryEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup extension ID/name mapping
|
||||||
|
backupExtensionInfo(extensionEntries, extensions)
|
||||||
|
// SY -->
|
||||||
|
root[SAVEDSEARCHES] =
|
||||||
|
Injekt.get<PreferencesHelper>().savedSearches().get().joinToString(separator = "***")
|
||||||
|
|
||||||
|
backupMergedMangaReferences(mergedMangaReferenceEntries)
|
||||||
|
// SY <--
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val file: UniFile = (
|
||||||
|
if (isJob) {
|
||||||
|
// Get dir of file and create
|
||||||
|
var dir = UniFile.fromUri(context, uri)
|
||||||
|
dir = dir.createDirectory("automatic")
|
||||||
|
|
||||||
|
// Delete older backups
|
||||||
|
val numberOfBackups = numberOfBackups()
|
||||||
|
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
|
||||||
|
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
||||||
|
.orEmpty()
|
||||||
|
.sortedByDescending { it.name }
|
||||||
|
.drop(numberOfBackups - 1)
|
||||||
|
.forEach { it.delete() }
|
||||||
|
|
||||||
|
// Create new file to place backup
|
||||||
|
dir.createFile(Backup.getDefaultFilename())
|
||||||
|
} else {
|
||||||
|
UniFile.fromUri(context, uri)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
?: throw Exception("Couldn't create backup file")
|
||||||
|
|
||||||
|
file.openOutputStream().bufferedWriter().use {
|
||||||
|
parser.toJson(root, it)
|
||||||
|
}
|
||||||
|
return file.uri.toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun backupExtensionInfo(root: JsonArray, extensions: Set<String>) {
|
||||||
|
extensions.sorted().forEach {
|
||||||
|
root.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
private fun backupMergedMangaReferences(root: JsonArray) {
|
||||||
|
val mergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
|
||||||
|
mergedMangaReferences.forEach { root.add(parser.toJsonTree(it)) }
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup the categories of library
|
||||||
|
*
|
||||||
|
* @param root root of categories json
|
||||||
|
*/
|
||||||
|
internal fun backupCategories(root: JsonArray) {
|
||||||
|
val categories = databaseHelper.getCategories().executeAsBlocking()
|
||||||
|
categories.forEach { root.add(parser.toJsonTree(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a manga to Json
|
||||||
|
*
|
||||||
|
* @param manga manga that gets converted
|
||||||
|
* @return [JsonElement] containing manga information
|
||||||
|
*/
|
||||||
|
internal fun backupMangaObject(manga: Manga, options: Int): JsonElement {
|
||||||
|
// Entry for this manga
|
||||||
|
val entry = JsonObject()
|
||||||
|
|
||||||
|
// Backup manga fields
|
||||||
|
entry[MANGA] = parser.toJsonTree(manga)
|
||||||
|
|
||||||
|
// Check if user wants chapter information in backup
|
||||||
|
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
||||||
|
// Backup all the chapters
|
||||||
|
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
|
if (chapters.isNotEmpty()) {
|
||||||
|
val chaptersJson = parser.toJsonTree(chapters)
|
||||||
|
if (chaptersJson.asJsonArray.size() > 0) {
|
||||||
|
entry[CHAPTERS] = chaptersJson
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user wants category information in backup
|
||||||
|
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
||||||
|
// Backup categories for this manga
|
||||||
|
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
||||||
|
if (categoriesForManga.isNotEmpty()) {
|
||||||
|
val categoriesNames = categoriesForManga.map { it.name }
|
||||||
|
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user wants track information in backup
|
||||||
|
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
||||||
|
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
||||||
|
if (tracks.isNotEmpty()) {
|
||||||
|
entry[TRACK] = parser.toJsonTree(tracks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user wants history information in backup
|
||||||
|
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
||||||
|
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
||||||
|
if (historyForManga.isNotEmpty()) {
|
||||||
|
val historyData = historyForManga.mapNotNull { history ->
|
||||||
|
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
||||||
|
url?.let { DHistory(url, history.last_read) }
|
||||||
|
}
|
||||||
|
val historyJson = parser.toJsonTree(historyData)
|
||||||
|
if (historyJson.asJsonArray.size() > 0) {
|
||||||
|
entry[HISTORY] = historyJson
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
|
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
|
||||||
manga.id = dbManga.id
|
manga.id = dbManga.id
|
||||||
@@ -125,11 +312,12 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
|
|||||||
/**
|
/**
|
||||||
* Restore the categories from Json
|
* Restore the categories from Json
|
||||||
*
|
*
|
||||||
* @param backupCategories array containing categories
|
* @param jsonCategories array containing categories
|
||||||
*/
|
*/
|
||||||
internal fun restoreCategories(backupCategories: List<Category>) {
|
internal fun restoreCategories(jsonCategories: JsonArray) {
|
||||||
// Get categories from file and from db
|
// Get categories from file and from db
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
||||||
|
val backupCategories = parser.fromJson<List<CategoryImpl>>(jsonCategories)
|
||||||
|
|
||||||
// Iterate over them
|
// Iterate over them
|
||||||
backupCategories.forEach { category ->
|
backupCategories.forEach { category ->
|
||||||
@@ -289,39 +477,58 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
internal fun restoreSavedSearches(jsonSavedSearches: String) {
|
internal fun restoreSavedSearches(jsonSavedSearches: JsonElement) {
|
||||||
val backupSavedSearches = jsonSavedSearches.split("***").toSet()
|
val backupSavedSearches = jsonSavedSearches.asString.split("***").toSet()
|
||||||
|
|
||||||
val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking()
|
|
||||||
|
|
||||||
val newSavedSearches = backupSavedSearches.mapNotNull {
|
val newSavedSearches = backupSavedSearches.mapNotNull {
|
||||||
runCatching {
|
try {
|
||||||
val content = parser.decodeFromString<JsonObject>(it.substringAfter(':'))
|
val id = it.substringBefore(':').toLong()
|
||||||
SavedSearch(
|
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||||
id = null,
|
id to content
|
||||||
source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null,
|
} catch (t: RuntimeException) {
|
||||||
content["name"]!!.jsonPrimitive.content,
|
// Load failed
|
||||||
content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
|
Timber.e(t, "Failed to load saved search!")
|
||||||
Json.encodeToString(content["filters"]!!.jsonArray),
|
t.printStackTrace()
|
||||||
)
|
null
|
||||||
}.getOrNull()
|
}
|
||||||
}.filter { backupSavedSearch ->
|
}.toMutableList()
|
||||||
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
|
||||||
}.ifEmpty { null }
|
|
||||||
|
|
||||||
if (newSavedSearches != null) {
|
val currentSources = newSavedSearches.map { it.first }.toSet()
|
||||||
databaseHelper.insertSavedSearches(newSavedSearches)
|
|
||||||
|
newSavedSearches += preferences.savedSearches().get().mapNotNull {
|
||||||
|
try {
|
||||||
|
val id = it.substringBefore(':').toLong()
|
||||||
|
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||||
|
id to content
|
||||||
|
} catch (t: RuntimeException) {
|
||||||
|
// Load failed
|
||||||
|
Timber.e(t, "Failed to load saved search!")
|
||||||
|
t.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
|
val otherSerialized = preferences.savedSearches().get().mapNotNull {
|
||||||
|
val sourceId = it.split(":")[0].toLongOrNull() ?: return@mapNotNull null
|
||||||
|
if (sourceId in currentSources) return@mapNotNull null
|
||||||
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newSerialized = newSavedSearches.map {
|
||||||
|
"${it.first}:" + Json.encodeToString(it.second)
|
||||||
|
}
|
||||||
|
preferences.savedSearches().set((otherSerialized + newSerialized).toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore the categories from Json
|
* Restore the categories from Json
|
||||||
*
|
*
|
||||||
* @param backupMergedMangaReferences array containing md manga references
|
* @param jsonMergedMangaReferences array containing md manga references
|
||||||
*/
|
*/
|
||||||
internal fun restoreMergedMangaReferences(backupMergedMangaReferences: List<MergedMangaReference>) {
|
internal fun restoreMergedMangaReferences(jsonMergedMangaReferences: JsonArray) {
|
||||||
// Get merged manga references from file and from db
|
// Get merged manga references from file and from db
|
||||||
val dbMergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
|
val dbMergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
|
||||||
|
val backupMergedMangaReferences = parser.fromJson<List<MergedMangaReference>>(jsonMergedMangaReferences)
|
||||||
var lastMergeManga: Manga? = null
|
var lastMergeManga: Manga? = null
|
||||||
|
|
||||||
// Iterate over them
|
// Iterate over them
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ package eu.kanade.tachiyomi.data.backup.legacy
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGAS
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.MangaObject
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import exh.EXHMigrations
|
import exh.EXHMigrations
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import kotlinx.serialization.json.intOrNull
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import okio.source
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
|
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
|
||||||
@@ -30,59 +30,59 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
|
|||||||
// SY -->
|
// SY -->
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
// Read the json and create a Json Object,
|
val version = json.get(Backup.VERSION)?.asInt ?: 1
|
||||||
// cannot use the backupManager json deserializer one because its not initialized yet
|
|
||||||
val backupObject = Json.decodeFromStream<JsonObject>(
|
|
||||||
context.contentResolver.openInputStream(uri)!!,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get parser version
|
|
||||||
val version = backupObject["version"]?.jsonPrimitive?.intOrNull ?: 1
|
|
||||||
|
|
||||||
// Initialize manager
|
|
||||||
backupManager = LegacyBackupManager(context, version)
|
backupManager = LegacyBackupManager(context, version)
|
||||||
|
|
||||||
// Decode the json object to a Backup object
|
val mangasJson = json.get(MANGAS).asJsonArray
|
||||||
val backup = backupManager.parser.decodeFromJsonElement<Backup>(backupObject)
|
restoreAmount = mangasJson.size() + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
|
||||||
|
|
||||||
restoreAmount = backup.mangas.size + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
backup.savedSearches?.let { restoreSavedSearches(it) }
|
|
||||||
|
|
||||||
backup.mergedMangaReferences?.let { restoreMergedMangaReferences(it) }
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
// Restore categories
|
// Restore categories
|
||||||
backup.categories?.let { restoreCategories(it) }
|
json.get(Backup.CATEGORIES)?.let { restoreCategories(it) }
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
json.get(Backup.SAVEDSEARCHES)?.let { restoreSavedSearches(it) }
|
||||||
|
|
||||||
|
json.get(Backup.MERGEDMANGAREFERENCES)?.let { restoreMergedMangaReferences(it) }
|
||||||
|
// SY <--
|
||||||
|
|
||||||
// Store source mapping for error messages
|
// Store source mapping for error messages
|
||||||
sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(backup.extensions ?: emptyList())
|
sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(json)
|
||||||
|
|
||||||
// Restore individual manga
|
// Restore individual manga
|
||||||
backup.mangas.forEach {
|
mangasJson.forEach {
|
||||||
if (job?.isActive != true) {
|
if (job?.isActive != true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreManga(it)
|
restoreManga(it.asJsonObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun restoreCategories(categoriesJson: JsonElement) {
|
||||||
|
db.inTransaction {
|
||||||
|
backupManager.restoreCategories(categoriesJson.asJsonArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreProgress += 1
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
||||||
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private fun restoreSavedSearches(savedSearches: String) {
|
private fun restoreSavedSearches(savedSearchesJson: JsonElement) {
|
||||||
backupManager.restoreSavedSearches(savedSearches)
|
backupManager.restoreSavedSearches(savedSearchesJson)
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.saved_searches))
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.saved_searches))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreMergedMangaReferences(mergedMangaReferences: List<MergedMangaReference>) {
|
private fun restoreMergedMangaReferences(mergedMangaReferencesJson: JsonElement) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
backupManager.restoreMergedMangaReferences(mergedMangaReferences)
|
backupManager.restoreMergedMangaReferences(mergedMangaReferencesJson.asJsonArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
@@ -90,24 +90,31 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
private fun restoreCategories(categoriesJson: List<Category>) {
|
private suspend fun restoreManga(mangaJson: JsonObject) {
|
||||||
db.inTransaction {
|
/* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson<MangaImpl>(
|
||||||
backupManager.restoreCategories(categoriesJson)
|
mangaJson.get(
|
||||||
}
|
Backup.MANGA
|
||||||
|
)
|
||||||
restoreProgress += 1
|
)
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||||
}
|
mangaJson.get(Backup.CHAPTERS)
|
||||||
|
?: JsonArray()
|
||||||
private suspend fun restoreManga(mangaJson: MangaObject) {
|
)
|
||||||
val manga = mangaJson.manga
|
val categories = backupManager.parser.fromJson<List<String>>(
|
||||||
val chapters = mangaJson.chapters ?: emptyList()
|
mangaJson.get(Backup.CATEGORIES)
|
||||||
val categories = mangaJson.categories ?: emptyList()
|
?: JsonArray()
|
||||||
val history = mangaJson.history ?: emptyList()
|
)
|
||||||
val tracks = mangaJson.track ?: emptyList()
|
val history = backupManager.parser.fromJson<List<DHistory>>(
|
||||||
|
mangaJson.get(Backup.HISTORY)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
|
||||||
|
mangaJson.get(Backup.TRACK)
|
||||||
|
?: JsonArray()
|
||||||
|
)
|
||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
EXHMigrations.migrateBackupEntry(manga)
|
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||||
// <-- EXH
|
// <-- EXH
|
||||||
|
|
||||||
val source = backupManager.sourceManager.get(manga.source)
|
val source = backupManager.sourceManager.get(manga.source)
|
||||||
@@ -143,7 +150,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
|
|||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<String>,
|
categories: List<String>,
|
||||||
history: List<DHistory>,
|
history: List<DHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>
|
||||||
) {
|
) {
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
val dbManga = backupManager.getMangaFromDatabase(manga)
|
||||||
|
|
||||||
@@ -173,7 +180,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
|
|||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<String>,
|
categories: List<String>,
|
||||||
history: List<DHistory>,
|
history: List<DHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val fetchedManga = backupManager.fetchManga(source, manga)
|
val fetchedManga = backupManager.fetchManga(source, manga)
|
||||||
@@ -195,7 +202,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
|
|||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<String>,
|
categories: List<String>,
|
||||||
history: List<DHistory>,
|
history: List<DHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>
|
||||||
) {
|
) {
|
||||||
if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
|
if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
|
||||||
updateChapters(source, backupManga, chapters)
|
updateChapters(source, backupManga, chapters)
|
||||||
|
|||||||
+24
-24
@@ -2,14 +2,14 @@ package eu.kanade.tachiyomi.data.backup.legacy
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
||||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
|
|
||||||
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for critical backup file data.
|
* Checks for critical backup file data.
|
||||||
*
|
*
|
||||||
@@ -17,34 +17,30 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||||||
* @return List of missing sources or missing trackers.
|
* @return List of missing sources or missing trackers.
|
||||||
*/
|
*/
|
||||||
override fun validate(context: Context, uri: Uri): Results {
|
override fun validate(context: Context, uri: Uri): Results {
|
||||||
val backupManager = LegacyBackupManager(context)
|
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
val backup = try {
|
val version = json.get(Backup.VERSION)
|
||||||
backupManager.parser.decodeFromStream<Backup>(
|
val mangasJson = json.get(Backup.MANGAS)
|
||||||
context.contentResolver.openInputStream(uri)!!,
|
if (version == null || mangasJson == null) {
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw ValidatorParseException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backup.version == null) {
|
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backup.mangas.isEmpty()) {
|
val mangas = mangasJson.asJsonArray
|
||||||
|
if (mangas.size() == 0) {
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
val sources = getSourceMapping(backup.extensions ?: emptyList())
|
val sources = getSourceMapping(json)
|
||||||
val missingSources = sources
|
val missingSources = sources
|
||||||
.filter { sourceManager.get(it.key) == null }
|
.filter { sourceManager.get(it.key) == null }
|
||||||
.values
|
.values
|
||||||
.sorted()
|
.sorted()
|
||||||
|
|
||||||
val trackers = backup.mangas
|
val trackers = mangas
|
||||||
.filterNot { it.track.isNullOrEmpty() }
|
.filter { it.asJsonObject.has("track") }
|
||||||
.flatMap { it.track ?: emptyList() }
|
.flatMap { it.asJsonObject["track"].asJsonArray }
|
||||||
.map { it.sync_id }
|
.map { it.asJsonObject["s"].asInt }
|
||||||
.distinct()
|
.distinct()
|
||||||
val missingTrackers = trackers
|
val missingTrackers = trackers
|
||||||
.mapNotNull { trackManager.getService(it) }
|
.mapNotNull { trackManager.getService(it) }
|
||||||
@@ -56,11 +52,15 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getSourceMapping(extensionsMapping: List<String>): Map<Long, String> {
|
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||||
return extensionsMapping.associate {
|
val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap()
|
||||||
val items = it.split(":")
|
|
||||||
items[0].toLong() to items[1]
|
return extensionsMapping.asJsonArray
|
||||||
}
|
.map {
|
||||||
|
val items = it.asString.split(":")
|
||||||
|
items[0].toLong() to items[1]
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,30 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.models
|
package eu.kanade.tachiyomi.data.backup.legacy.models
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
|
||||||
import kotlinx.serialization.Contextual
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Serializable
|
/**
|
||||||
data class Backup(
|
* Json values
|
||||||
val version: Int? = null,
|
*/
|
||||||
var mangas: MutableList<MangaObject> = mutableListOf(),
|
object Backup {
|
||||||
var categories: List<@Contextual Category>? = null,
|
const val CURRENT_VERSION = 2
|
||||||
var extensions: List<String>? = null,
|
const val MANGA = "manga"
|
||||||
// SY Specific values
|
const val MANGAS = "mangas"
|
||||||
@SerialName("mergedmangareferences")
|
const val TRACK = "track"
|
||||||
var mergedMangaReferences: List<@Contextual MergedMangaReference>? = null,
|
const val CHAPTERS = "chapters"
|
||||||
var savedSearches: String? = null,
|
const val CATEGORIES = "categories"
|
||||||
) {
|
const val EXTENSIONS = "extensions"
|
||||||
companion object {
|
const val HISTORY = "history"
|
||||||
const val CURRENT_VERSION = 2
|
const val VERSION = "version"
|
||||||
|
|
||||||
fun getDefaultFilename(): String {
|
// SY -->
|
||||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
const val SAVEDSEARCHES = "savedsearches"
|
||||||
return "tachiyomi_$date.json"
|
const val MERGEDMANGAREFERENCES = "mergedmangareferences"
|
||||||
}
|
// SY <--
|
||||||
|
|
||||||
|
fun getDefaultFilename(): String {
|
||||||
|
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
||||||
|
return "tachiyomi_$date.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MangaObject(
|
|
||||||
var manga: @Contextual Manga,
|
|
||||||
var chapters: List<@Contextual Chapter>? = null,
|
|
||||||
var categories: List<String>? = null,
|
|
||||||
var track: List<@Contextual Track>? = null,
|
|
||||||
var history: List<@Contextual DHistory>? = null,
|
|
||||||
)
|
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [CategoryImpl] to / from json
|
||||||
|
*/
|
||||||
|
object CategoryTypeAdapter {
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<CategoryImpl> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
beginArray()
|
||||||
|
value(it.name)
|
||||||
|
value(it.order)
|
||||||
|
endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
beginArray()
|
||||||
|
val category = CategoryImpl()
|
||||||
|
category.name = nextString()
|
||||||
|
category.order = nextInt()
|
||||||
|
endArray()
|
||||||
|
category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [CategoryImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class CategoryBaseSerializer<T : Category> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Category")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.name)
|
|
||||||
add(value.order)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a category impl and cast as T so that the serializer accepts it
|
|
||||||
return CategoryImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
name = array[0].jsonPrimitive.content
|
|
||||||
order = array[1].jsonPrimitive.int
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a category and category impl
|
|
||||||
object CategoryTypeSerializer : CategoryBaseSerializer<Category>()
|
|
||||||
|
|
||||||
object CategoryImplTypeSerializer : CategoryBaseSerializer<CategoryImpl>()
|
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [ChapterImpl] to / from json
|
||||||
|
*/
|
||||||
|
object ChapterTypeAdapter {
|
||||||
|
|
||||||
|
private const val URL = "u"
|
||||||
|
private const val READ = "r"
|
||||||
|
private const val BOOKMARK = "b"
|
||||||
|
private const val LAST_READ = "l"
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<ChapterImpl> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
if (it.read || it.bookmark || it.last_page_read != 0) {
|
||||||
|
beginObject()
|
||||||
|
name(URL)
|
||||||
|
value(it.url)
|
||||||
|
if (it.read) {
|
||||||
|
name(READ)
|
||||||
|
value(1)
|
||||||
|
}
|
||||||
|
if (it.bookmark) {
|
||||||
|
name(BOOKMARK)
|
||||||
|
value(1)
|
||||||
|
}
|
||||||
|
if (it.last_page_read != 0) {
|
||||||
|
name(LAST_READ)
|
||||||
|
value(it.last_page_read)
|
||||||
|
}
|
||||||
|
endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
val chapter = ChapterImpl()
|
||||||
|
beginObject()
|
||||||
|
while (hasNext()) {
|
||||||
|
if (peek() == JsonToken.NAME) {
|
||||||
|
when (nextName()) {
|
||||||
|
URL -> chapter.url = nextString()
|
||||||
|
READ -> chapter.read = nextInt() == 1
|
||||||
|
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
||||||
|
LAST_READ -> chapter.last_page_read = nextInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endObject()
|
||||||
|
chapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.intOrNull
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [ChapterImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class ChapterBaseSerializer<T : Chapter> : KSerializer<T> {
|
|
||||||
|
|
||||||
override val descriptor = buildClassSerialDescriptor("Chapter")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonObject {
|
|
||||||
put(URL, value.url)
|
|
||||||
if (value.read) {
|
|
||||||
put(READ, 1)
|
|
||||||
}
|
|
||||||
if (value.bookmark) {
|
|
||||||
put(BOOKMARK, 1)
|
|
||||||
}
|
|
||||||
if (value.last_page_read != 0) {
|
|
||||||
put(LAST_READ, value.last_page_read)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a chapter impl and cast as T so that the serializer accepts it
|
|
||||||
return ChapterImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val jsonObject = decoder.decodeJsonElement().jsonObject
|
|
||||||
url = jsonObject[URL]!!.jsonPrimitive.content
|
|
||||||
read = jsonObject[READ]?.jsonPrimitive?.intOrNull == 1
|
|
||||||
bookmark = jsonObject[BOOKMARK]?.jsonPrimitive?.intOrNull == 1
|
|
||||||
last_page_read = jsonObject[LAST_READ]?.jsonPrimitive?.intOrNull ?: last_page_read
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val URL = "u"
|
|
||||||
private const val READ = "r"
|
|
||||||
private const val BOOKMARK = "b"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a chapter and chapter impl
|
|
||||||
object ChapterTypeSerializer : ChapterBaseSerializer<Chapter>()
|
|
||||||
|
|
||||||
object ChapterImplTypeSerializer : ChapterBaseSerializer<ChapterImpl>()
|
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [DHistory] to / from json
|
||||||
|
*/
|
||||||
|
object HistoryTypeAdapter {
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<DHistory> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
if (it.lastRead != 0L) {
|
||||||
|
beginArray()
|
||||||
|
value(it.url)
|
||||||
|
value(it.lastRead)
|
||||||
|
endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
beginArray()
|
||||||
|
val url = nextString()
|
||||||
|
val lastRead = nextLong()
|
||||||
|
endArray()
|
||||||
|
DHistory(url, lastRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [DHistory] to / from json
|
|
||||||
*/
|
|
||||||
object HistoryTypeSerializer : KSerializer<DHistory> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("History")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: DHistory) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.url)
|
|
||||||
add(value.lastRead)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): DHistory {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
return DHistory(
|
|
||||||
url = array[0].jsonPrimitive.content,
|
|
||||||
lastRead = array[1].jsonPrimitive.long,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [MangaImpl] to / from json
|
||||||
|
*/
|
||||||
|
object MangaTypeAdapter {
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<MangaImpl> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
beginArray()
|
||||||
|
value(it.url)
|
||||||
|
// SY -->
|
||||||
|
value(it.originalTitle)
|
||||||
|
// SY <--
|
||||||
|
value(it.source)
|
||||||
|
value(it.viewer)
|
||||||
|
value(it.chapter_flags)
|
||||||
|
endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
beginArray()
|
||||||
|
val manga = MangaImpl()
|
||||||
|
manga.url = nextString()
|
||||||
|
manga.title = nextString()
|
||||||
|
manga.source = nextLong()
|
||||||
|
manga.viewer = nextInt()
|
||||||
|
manga.chapter_flags = nextInt()
|
||||||
|
endArray()
|
||||||
|
manga
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [MangaImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class MangaBaseSerializer<T : Manga> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Manga")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.url)
|
|
||||||
add(value.title)
|
|
||||||
add(value.source)
|
|
||||||
add(value.viewer_flags)
|
|
||||||
add(value.chapter_flags)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a manga impl and cast as T so that the serializer accepts it
|
|
||||||
return MangaImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
url = array[0].jsonPrimitive.content
|
|
||||||
title = array[1].jsonPrimitive.content
|
|
||||||
source = array[2].jsonPrimitive.long
|
|
||||||
viewer_flags = array[3].jsonPrimitive.int
|
|
||||||
chapter_flags = array[4].jsonPrimitive.int
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a manga and manga impl
|
|
||||||
object MangaTypeSerializer : MangaBaseSerializer<Manga>()
|
|
||||||
|
|
||||||
object MangaImplTypeSerializer : MangaBaseSerializer<MangaImpl>()
|
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [MergedMangaReference] to / from json
|
||||||
|
*/
|
||||||
|
object MergedMangaReferenceTypeAdapter {
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<MergedMangaReference> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
beginArray()
|
||||||
|
value(it.mangaUrl)
|
||||||
|
value(it.mergeUrl)
|
||||||
|
value(it.mangaSourceId)
|
||||||
|
value(it.chapterSortMode)
|
||||||
|
value(it.chapterPriority)
|
||||||
|
value(it.getChapterUpdates)
|
||||||
|
value(it.isInfoManga)
|
||||||
|
value(it.downloadChapters)
|
||||||
|
endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
beginArray()
|
||||||
|
MergedMangaReference(
|
||||||
|
id = null,
|
||||||
|
mangaUrl = nextString(),
|
||||||
|
mergeUrl = nextString(),
|
||||||
|
mangaSourceId = nextLong(),
|
||||||
|
chapterSortMode = nextInt(),
|
||||||
|
chapterPriority = nextInt(),
|
||||||
|
getChapterUpdates = nextBoolean(),
|
||||||
|
isInfoManga = nextBoolean(),
|
||||||
|
downloadChapters = nextBoolean(),
|
||||||
|
mangaId = null,
|
||||||
|
mergeId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.boolean
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [MergedMangaReference] to / from json
|
|
||||||
*/
|
|
||||||
object MergedMangaTypeSerializer : KSerializer<MergedMangaReference> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Manga")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: MergedMangaReference) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.mangaUrl)
|
|
||||||
add(value.mergeUrl)
|
|
||||||
add(value.mangaSourceId)
|
|
||||||
add(value.chapterSortMode)
|
|
||||||
add(value.chapterPriority)
|
|
||||||
add(value.getChapterUpdates)
|
|
||||||
add(value.isInfoManga)
|
|
||||||
add(value.downloadChapters)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): MergedMangaReference {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
return MergedMangaReference(
|
|
||||||
id = null,
|
|
||||||
mangaUrl = array[0].jsonPrimitive.content,
|
|
||||||
mergeUrl = array[1].jsonPrimitive.content,
|
|
||||||
mangaSourceId = array[2].jsonPrimitive.long,
|
|
||||||
chapterSortMode = array[3].jsonPrimitive.int,
|
|
||||||
chapterPriority = array[4].jsonPrimitive.int,
|
|
||||||
getChapterUpdates = array[5].jsonPrimitive.boolean,
|
|
||||||
isInfoManga = array[6].jsonPrimitive.boolean,
|
|
||||||
downloadChapters = array[7].jsonPrimitive.boolean,
|
|
||||||
mangaId = null,
|
|
||||||
mergeId = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.typeAdapter
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Serializer used to write / read [TrackImpl] to / from json
|
||||||
|
*/
|
||||||
|
object TrackTypeAdapter {
|
||||||
|
|
||||||
|
private const val SYNC = "s"
|
||||||
|
private const val MEDIA = "r"
|
||||||
|
private const val LIBRARY = "ml"
|
||||||
|
private const val TITLE = "t"
|
||||||
|
private const val LAST_READ = "l"
|
||||||
|
private const val TRACKING_URL = "u"
|
||||||
|
|
||||||
|
fun build(): TypeAdapter<TrackImpl> {
|
||||||
|
return typeAdapter {
|
||||||
|
write {
|
||||||
|
beginObject()
|
||||||
|
name(TITLE)
|
||||||
|
value(it.title)
|
||||||
|
name(SYNC)
|
||||||
|
value(it.sync_id)
|
||||||
|
name(MEDIA)
|
||||||
|
value(it.media_id)
|
||||||
|
name(LIBRARY)
|
||||||
|
value(it.library_id)
|
||||||
|
name(LAST_READ)
|
||||||
|
value(it.last_chapter_read)
|
||||||
|
name(TRACKING_URL)
|
||||||
|
value(it.tracking_url)
|
||||||
|
endObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
read {
|
||||||
|
val track = TrackImpl()
|
||||||
|
beginObject()
|
||||||
|
while (hasNext()) {
|
||||||
|
if (peek() == JsonToken.NAME) {
|
||||||
|
when (nextName()) {
|
||||||
|
TITLE -> track.title = nextString()
|
||||||
|
SYNC -> track.sync_id = nextInt()
|
||||||
|
MEDIA -> track.media_id = nextInt()
|
||||||
|
LIBRARY -> track.library_id = nextLong()
|
||||||
|
LAST_READ -> track.last_chapter_read = nextInt()
|
||||||
|
TRACKING_URL -> track.tracking_url = nextString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endObject()
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-68
@@ -1,68 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.float
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [TrackImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class TrackBaseSerializer<T : Track> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Track")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonObject {
|
|
||||||
put(TITLE, value.title)
|
|
||||||
put(SYNC, value.sync_id)
|
|
||||||
put(MEDIA, value.media_id)
|
|
||||||
put(LIBRARY, value.library_id)
|
|
||||||
put(LAST_READ, value.last_chapter_read)
|
|
||||||
put(TRACKING_URL, value.tracking_url)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a track impl and cast as T so that the serializer accepts it
|
|
||||||
return TrackImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val jsonObject = decoder.decodeJsonElement().jsonObject
|
|
||||||
title = jsonObject[TITLE]!!.jsonPrimitive.content
|
|
||||||
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
|
|
||||||
media_id = jsonObject[MEDIA]!!.jsonPrimitive.int
|
|
||||||
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
|
|
||||||
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
|
|
||||||
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val SYNC = "s"
|
|
||||||
private const val MEDIA = "r"
|
|
||||||
private const val LIBRARY = "ml"
|
|
||||||
private const val TITLE = "t"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
private const val TRACKING_URL = "u"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a track and track impl
|
|
||||||
object TrackTypeSerializer : TrackBaseSerializer<Track>()
|
|
||||||
|
|
||||||
object TrackImplTypeSerializer : TrackBaseSerializer<TrackImpl>()
|
|
||||||
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.cache
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@@ -13,12 +15,10 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -48,12 +48,14 @@ class ChapterCache(private val context: Context) {
|
|||||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
/** Google Json class used for parsing JSON files. */
|
/** Google Json class used for parsing JSON files. */
|
||||||
private val json: Json by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
// --> EH
|
// --> EH
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
// <-- EH
|
||||||
|
|
||||||
/** Cache class used for cache management. */
|
/** Cache class used for cache management. */
|
||||||
|
// --> EH
|
||||||
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
|
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -71,7 +73,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Returns directory of cache.
|
* Returns directory of cache.
|
||||||
*/
|
*/
|
||||||
private val cacheDir: File
|
val cacheDir: File
|
||||||
get() = diskCache.directory
|
get() = diskCache.directory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,24 +95,48 @@ class ChapterCache(private val context: Context) {
|
|||||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||||
PARAMETER_APP_VERSION,
|
PARAMETER_APP_VERSION,
|
||||||
PARAMETER_VALUE_COUNT,
|
PARAMETER_VALUE_COUNT,
|
||||||
cacheSize * 1024 * 1024,
|
cacheSize * 1024 * 1024
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// <-- EH
|
// <-- EH
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove file from cache.
|
||||||
|
*
|
||||||
|
* @param file name of file "md5.0".
|
||||||
|
* @return status of deletion for the file.
|
||||||
|
*/
|
||||||
|
fun removeFileFromCache(file: String): Boolean {
|
||||||
|
// Make sure we don't delete the journal file (keeps track of cache).
|
||||||
|
if (file == "journal" || file.startsWith("journal.")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
// Remove the extension from the file to get the key of the cache
|
||||||
|
val key = file.substringBeforeLast(".")
|
||||||
|
// Remove file from cache.
|
||||||
|
diskCache.remove(key)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get page list from cache.
|
* Get page list from cache.
|
||||||
*
|
*
|
||||||
* @param chapter the chapter.
|
* @param chapter the chapter.
|
||||||
* @return the list of pages.
|
* @return an observable of the list of pages.
|
||||||
*/
|
*/
|
||||||
fun getPageListFromCache(chapter: Chapter): List<Page> {
|
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
|
||||||
// Get the key for the chapter.
|
return Observable.fromCallable {
|
||||||
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
// Get the key for the chapter.
|
||||||
|
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
||||||
|
|
||||||
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
||||||
return diskCache.get(key).use {
|
diskCache.get(key).use {
|
||||||
json.decodeFromString(it.getString(0))
|
gson.fromJson<List<Page>>(it.getString(0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +148,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
||||||
// Convert list of pages to json string.
|
// Convert list of pages to json string.
|
||||||
val cachedValue = json.encodeToString(pages)
|
val cachedValue = gson.toJson(pages)
|
||||||
|
|
||||||
// Initialize the editor (edits the values for an entry).
|
// Initialize the editor (edits the values for an entry).
|
||||||
var editor: DiskLruCache.Editor? = null
|
var editor: DiskLruCache.Editor? = null
|
||||||
@@ -202,38 +228,6 @@ class ChapterCache(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear(): Int {
|
|
||||||
var deletedFiles = 0
|
|
||||||
cacheDir.listFiles()?.forEach {
|
|
||||||
if (removeFileFromCache(it.name)) {
|
|
||||||
deletedFiles++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deletedFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove file from cache.
|
|
||||||
*
|
|
||||||
* @param file name of file "md5.0".
|
|
||||||
* @return status of deletion for the file.
|
|
||||||
*/
|
|
||||||
private fun removeFileFromCache(file: String): Boolean {
|
|
||||||
// Make sure we don't delete the journal file (keeps track of cache).
|
|
||||||
if (file == "journal" || file.startsWith("journal.")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
// Remove the extension from the file to get the key of the cache
|
|
||||||
val key = file.substringBeforeLast(".")
|
|
||||||
// Remove file from cache.
|
|
||||||
diskCache.remove(key)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getKey(chapter: Chapter): String {
|
private fun getKey(chapter: Chapter): String {
|
||||||
return "${chapter.manga_id}${chapter.url}"
|
return "${chapter.manga_id}${chapter.url}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import coil.imageLoader
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -100,13 +99,6 @@ class CoverCache(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear coil's memory cache.
|
|
||||||
*/
|
|
||||||
fun clearMemoryCache() {
|
|
||||||
context.imageLoader.memoryCache?.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCacheDir(dir: String): File {
|
private fun getCacheDir(dir: String): File {
|
||||||
return context.getExternalFilesDir(dir)
|
return context.getExternalFilesDir(dir)
|
||||||
?: File(context.filesDir, dir).also { it.mkdirs() }
|
?: File(context.filesDir, dir).also { it.mkdirs() }
|
||||||
|
|||||||
@@ -1,294 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.coil
|
|
||||||
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.decode.DataSource
|
|
||||||
import coil.decode.ImageSource
|
|
||||||
import coil.disk.DiskCache
|
|
||||||
import coil.fetch.FetchResult
|
|
||||||
import coil.fetch.Fetcher
|
|
||||||
import coil.fetch.SourceResult
|
|
||||||
import coil.network.HttpException
|
|
||||||
import coil.request.Options
|
|
||||||
import coil.request.Parameters
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.network.await
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import okhttp3.internal.closeQuietly
|
|
||||||
import okio.Path.Companion.toOkioPath
|
|
||||||
import okio.Source
|
|
||||||
import okio.buffer
|
|
||||||
import okio.sink
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Fetcher] that fetches cover image for [Manga] object.
|
|
||||||
*
|
|
||||||
* It uses [Manga.thumbnail_url] if custom cover is not set by the user.
|
|
||||||
* Disk caching for library items is handled by [CoverCache], otherwise
|
|
||||||
* handled by Coil's [DiskCache].
|
|
||||||
*
|
|
||||||
* Available request parameter:
|
|
||||||
* - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true
|
|
||||||
*/
|
|
||||||
class MangaCoverFetcher(
|
|
||||||
private val manga: Manga,
|
|
||||||
private val sourceLazy: Lazy<HttpSource?>,
|
|
||||||
private val options: Options,
|
|
||||||
private val coverCache: CoverCache,
|
|
||||||
private val callFactoryLazy: Lazy<Call.Factory>,
|
|
||||||
private val diskCacheLazy: Lazy<DiskCache>,
|
|
||||||
) : Fetcher {
|
|
||||||
|
|
||||||
// For non-custom cover
|
|
||||||
private val diskCacheKey: String? by lazy { MangaCoverKeyer().key(manga, options) }
|
|
||||||
private lateinit var url: String
|
|
||||||
|
|
||||||
override suspend fun fetch(): FetchResult {
|
|
||||||
// Use custom cover if exists
|
|
||||||
val useCustomCover = options.parameters.value(USE_CUSTOM_COVER) ?: true
|
|
||||||
val customCoverFile = coverCache.getCustomCoverFile(manga)
|
|
||||||
if (useCustomCover && customCoverFile.exists()) {
|
|
||||||
return fileLoader(customCoverFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// diskCacheKey is thumbnail_url
|
|
||||||
url = diskCacheKey ?: error("No cover specified")
|
|
||||||
return when (getResourceType(url)) {
|
|
||||||
Type.URL -> httpLoader()
|
|
||||||
Type.File -> fileLoader(File(url.substringAfter("file://")))
|
|
||||||
null -> error("Invalid image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fileLoader(file: File): FetchResult {
|
|
||||||
return SourceResult(
|
|
||||||
source = ImageSource(file = file.toOkioPath(), diskCacheKey = diskCacheKey),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = DataSource.DISK,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun httpLoader(): FetchResult {
|
|
||||||
// Only cache separately if it's a library item
|
|
||||||
val libraryCoverCacheFile = if (manga.favorite) {
|
|
||||||
coverCache.getCoverFile(manga) ?: error("No cover specified")
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (libraryCoverCacheFile?.exists() == true && options.diskCachePolicy.readEnabled) {
|
|
||||||
return fileLoader(libraryCoverCacheFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
var snapshot = readFromDiskCache()
|
|
||||||
try {
|
|
||||||
// Fetch from disk cache
|
|
||||||
if (snapshot != null) {
|
|
||||||
val snapshotCoverCache = moveSnapshotToCoverCache(snapshot, libraryCoverCacheFile)
|
|
||||||
if (snapshotCoverCache != null) {
|
|
||||||
// Read from cover cache after added to library
|
|
||||||
return fileLoader(snapshotCoverCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from snapshot
|
|
||||||
return SourceResult(
|
|
||||||
source = snapshot.toImageSource(),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = DataSource.DISK,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch from network
|
|
||||||
val response = executeNetworkRequest()
|
|
||||||
val responseBody = checkNotNull(response.body) { "Null response source" }
|
|
||||||
try {
|
|
||||||
// Read from cover cache after library manga cover updated
|
|
||||||
val responseCoverCache = writeResponseToCoverCache(response, libraryCoverCacheFile)
|
|
||||||
if (responseCoverCache != null) {
|
|
||||||
return fileLoader(responseCoverCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from disk cache
|
|
||||||
snapshot = writeToDiskCache(snapshot, response)
|
|
||||||
if (snapshot != null) {
|
|
||||||
return SourceResult(
|
|
||||||
source = snapshot.toImageSource(),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = DataSource.NETWORK,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from response if cache is unused or unusable
|
|
||||||
return SourceResult(
|
|
||||||
source = ImageSource(source = responseBody.source(), context = options.context),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK,
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
responseBody.closeQuietly()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
snapshot?.closeQuietly()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun executeNetworkRequest(): Response {
|
|
||||||
val client = sourceLazy.value?.client ?: callFactoryLazy.value
|
|
||||||
val response = client.newCall(newRequest()).await()
|
|
||||||
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
|
||||||
response.body?.closeQuietly()
|
|
||||||
throw HttpException(response)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newRequest(): Request {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.headers(sourceLazy.value?.headers ?: options.headers)
|
|
||||||
// Support attaching custom data to the network request.
|
|
||||||
.tag(Parameters::class.java, options.parameters)
|
|
||||||
|
|
||||||
val diskRead = options.diskCachePolicy.readEnabled
|
|
||||||
val networkRead = options.networkCachePolicy.readEnabled
|
|
||||||
when {
|
|
||||||
!networkRead && diskRead -> {
|
|
||||||
request.cacheControl(CacheControl.FORCE_CACHE)
|
|
||||||
}
|
|
||||||
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
|
|
||||||
request.cacheControl(CacheControl.FORCE_NETWORK)
|
|
||||||
} else {
|
|
||||||
request.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
|
|
||||||
}
|
|
||||||
!networkRead && !diskRead -> {
|
|
||||||
// This causes the request to fail with a 504 Unsatisfiable Request.
|
|
||||||
request.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return request.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun moveSnapshotToCoverCache(snapshot: DiskCache.Snapshot, cacheFile: File?): File? {
|
|
||||||
if (cacheFile == null) return null
|
|
||||||
return try {
|
|
||||||
diskCacheLazy.value.run {
|
|
||||||
fileSystem.source(snapshot.data).use { input ->
|
|
||||||
writeSourceToCoverCache(input, cacheFile)
|
|
||||||
}
|
|
||||||
remove(diskCacheKey!!)
|
|
||||||
}
|
|
||||||
cacheFile.takeIf { it.exists() }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to write snapshot data to cover cache ${cacheFile.name}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeResponseToCoverCache(response: Response, cacheFile: File?): File? {
|
|
||||||
if (cacheFile == null || !options.diskCachePolicy.writeEnabled) return null
|
|
||||||
return try {
|
|
||||||
response.peekBody(Long.MAX_VALUE).source().use { input ->
|
|
||||||
writeSourceToCoverCache(input, cacheFile)
|
|
||||||
}
|
|
||||||
cacheFile.takeIf { it.exists() }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to write response data to cover cache ${cacheFile.name}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeSourceToCoverCache(input: Source, cacheFile: File) {
|
|
||||||
cacheFile.parentFile?.mkdirs()
|
|
||||||
cacheFile.delete()
|
|
||||||
try {
|
|
||||||
cacheFile.sink().buffer().use { output ->
|
|
||||||
output.writeAll(input)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cacheFile.delete()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readFromDiskCache(): DiskCache.Snapshot? {
|
|
||||||
return if (options.diskCachePolicy.readEnabled) diskCacheLazy.value[diskCacheKey!!] else null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeToDiskCache(
|
|
||||||
snapshot: DiskCache.Snapshot?,
|
|
||||||
response: Response,
|
|
||||||
): DiskCache.Snapshot? {
|
|
||||||
if (!options.diskCachePolicy.writeEnabled) {
|
|
||||||
snapshot?.closeQuietly()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val editor = if (snapshot != null) {
|
|
||||||
snapshot.closeAndEdit()
|
|
||||||
} else {
|
|
||||||
diskCacheLazy.value.edit(diskCacheKey!!)
|
|
||||||
} ?: return null
|
|
||||||
try {
|
|
||||||
diskCacheLazy.value.fileSystem.write(editor.data) {
|
|
||||||
response.body!!.source().readAll(this)
|
|
||||||
}
|
|
||||||
return editor.commitAndGet()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
try {
|
|
||||||
editor.abort()
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
|
|
||||||
return ImageSource(file = data, diskCacheKey = diskCacheKey, closeable = this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getResourceType(cover: String?): Type? {
|
|
||||||
return when {
|
|
||||||
cover.isNullOrEmpty() -> null
|
|
||||||
cover.startsWith("http", true) || cover.startsWith("Custom-", true) -> Type.URL
|
|
||||||
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class Type {
|
|
||||||
File, URL
|
|
||||||
}
|
|
||||||
|
|
||||||
class Factory(
|
|
||||||
private val callFactoryLazy: Lazy<Call.Factory>,
|
|
||||||
private val diskCacheLazy: Lazy<DiskCache>,
|
|
||||||
) : Fetcher.Factory<Manga> {
|
|
||||||
|
|
||||||
private val coverCache: CoverCache by injectLazy()
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
|
||||||
|
|
||||||
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
|
|
||||||
val source = lazy { sourceManager.get(data.source) as? HttpSource }
|
|
||||||
return MangaCoverFetcher(data, source, options, coverCache, callFactoryLazy, diskCacheLazy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val USE_CUSTOM_COVER = "use_custom_cover"
|
|
||||||
|
|
||||||
private val CACHE_CONTROL_FORCE_NETWORK_NO_CACHE = CacheControl.Builder().noCache().noStore().build()
|
|
||||||
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.coil
|
|
||||||
|
|
||||||
import coil.key.Keyer
|
|
||||||
import coil.request.Options
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
|
|
||||||
class MangaCoverKeyer : Keyer<Manga> {
|
|
||||||
override fun key(data: Manga, options: Options): String? {
|
|
||||||
return data.thumbnail_url?.takeIf { it.isNotBlank() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.coil
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.decode.DecodeResult
|
|
||||||
import coil.decode.Decoder
|
|
||||||
import coil.decode.ImageDecoderDecoder
|
|
||||||
import coil.decode.ImageSource
|
|
||||||
import coil.fetch.SourceResult
|
|
||||||
import coil.request.Options
|
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
|
||||||
import okio.BufferedSource
|
|
||||||
import tachiyomi.decoder.ImageDecoder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Decoder] that uses built-in [ImageDecoder] to decode images that is not supported by the system.
|
|
||||||
*/
|
|
||||||
class TachiyomiImageDecoder(private val resources: ImageSource, private val options: Options) : Decoder {
|
|
||||||
|
|
||||||
override suspend fun decode(): DecodeResult {
|
|
||||||
val decoder = resources.sourceOrNull()?.use {
|
|
||||||
ImageDecoder.newInstance(it.inputStream())
|
|
||||||
}
|
|
||||||
|
|
||||||
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder." }
|
|
||||||
|
|
||||||
val bitmap = decoder.decode(rgb565 = options.allowRgb565)
|
|
||||||
decoder.recycle()
|
|
||||||
|
|
||||||
check(bitmap != null) { "Failed to decode image." }
|
|
||||||
|
|
||||||
return DecodeResult(
|
|
||||||
drawable = bitmap.toDrawable(options.context.resources),
|
|
||||||
isSampled = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Factory : Decoder.Factory {
|
|
||||||
|
|
||||||
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
|
|
||||||
if (!isApplicable(result.source.source())) return null
|
|
||||||
return TachiyomiImageDecoder(result.source, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isApplicable(source: BufferedSource): Boolean {
|
|
||||||
val type = source.peek().inputStream().use {
|
|
||||||
ImageUtil.findImageType(it)
|
|
||||||
}
|
|
||||||
return when (type) {
|
|
||||||
ImageUtil.ImageType.AVIF, ImageUtil.ImageType.JXL -> true
|
|
||||||
ImageUtil.ImageType.HEIF -> Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?) = other is ImageDecoderDecoder.Factory
|
|
||||||
|
|
||||||
override fun hashCode() = javaClass.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,9 +21,9 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
|
|||||||
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
||||||
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||||
import exh.favorites.sql.mappers.FavoriteEntryTypeMapping
|
import exh.md.similar.sql.mappers.SimilarTypeMapping
|
||||||
import exh.favorites.sql.models.FavoriteEntry
|
import exh.md.similar.sql.models.MangaSimilar
|
||||||
import exh.favorites.sql.queries.FavoriteEntryQueries
|
import exh.md.similar.sql.queries.SimilarQueries
|
||||||
import exh.merged.sql.mappers.MergedMangaTypeMapping
|
import exh.merged.sql.mappers.MergedMangaTypeMapping
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
import exh.merged.sql.queries.MergedQueries
|
import exh.merged.sql.queries.MergedQueries
|
||||||
@@ -36,33 +36,13 @@ import exh.metadata.sql.models.SearchTitle
|
|||||||
import exh.metadata.sql.queries.SearchMetadataQueries
|
import exh.metadata.sql.queries.SearchMetadataQueries
|
||||||
import exh.metadata.sql.queries.SearchTagQueries
|
import exh.metadata.sql.queries.SearchTagQueries
|
||||||
import exh.metadata.sql.queries.SearchTitleQueries
|
import exh.metadata.sql.queries.SearchTitleQueries
|
||||||
import exh.savedsearches.mappers.FeedSavedSearchTypeMapping
|
|
||||||
import exh.savedsearches.mappers.SavedSearchTypeMapping
|
|
||||||
import exh.savedsearches.models.FeedSavedSearch
|
|
||||||
import exh.savedsearches.models.SavedSearch
|
|
||||||
import exh.savedsearches.queries.FeedSavedSearchQueries
|
|
||||||
import exh.savedsearches.queries.SavedSearchQueries
|
|
||||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides operations to manage the database through its interfaces.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context) :
|
open class DatabaseHelper(context: Context) :
|
||||||
MangaQueries,
|
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, SimilarQueries /* SY <-- */ {
|
||||||
ChapterQueries,
|
|
||||||
TrackQueries,
|
|
||||||
CategoryQueries,
|
|
||||||
MangaCategoryQueries,
|
|
||||||
HistoryQueries,
|
|
||||||
/* SY --> */
|
|
||||||
SearchMetadataQueries,
|
|
||||||
SearchTagQueries,
|
|
||||||
SearchTitleQueries,
|
|
||||||
MergedQueries,
|
|
||||||
FavoriteEntryQueries,
|
|
||||||
SavedSearchQueries,
|
|
||||||
FeedSavedSearchQueries
|
|
||||||
/* SY <-- */ {
|
|
||||||
|
|
||||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
.name(DbOpenCallback.DATABASE_NAME)
|
||||||
@@ -82,9 +62,7 @@ open class DatabaseHelper(context: Context) :
|
|||||||
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||||
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
||||||
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
|
.addTypeMapping(MangaSimilar::class.java, SimilarTypeMapping())
|
||||||
.addTypeMapping(SavedSearch::class.java, SavedSearchTypeMapping())
|
|
||||||
.addTypeMapping(FeedSavedSearch::class.java, FeedSavedSearchTypeMapping())
|
|
||||||
// SY <--
|
// SY <--
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||||
import exh.favorites.sql.tables.FavoriteEntryTable
|
import exh.md.similar.sql.tables.SimilarTable
|
||||||
import exh.merged.sql.tables.MergedTable
|
import exh.merged.sql.tables.MergedTable
|
||||||
import exh.metadata.sql.tables.SearchMetadataTable
|
import exh.metadata.sql.tables.SearchMetadataTable
|
||||||
import exh.metadata.sql.tables.SearchTagTable
|
import exh.metadata.sql.tables.SearchTagTable
|
||||||
import exh.metadata.sql.tables.SearchTitleTable
|
import exh.metadata.sql.tables.SearchTitleTable
|
||||||
import exh.savedsearches.tables.FeedSavedSearchTable
|
|
||||||
import exh.savedsearches.tables.SavedSearchTable
|
|
||||||
|
|
||||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
|
|
||||||
@@ -27,7 +25,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
/**
|
/**
|
||||||
* Version of the database.
|
* Version of the database.
|
||||||
*/
|
*/
|
||||||
const val DATABASE_VERSION = /* SY --> */ 13 // SY <--
|
const val DATABASE_VERSION = /* SY --> */ 5 /* SY <-- */
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||||
@@ -42,9 +40,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(SearchTagTable.createTableQuery)
|
execSQL(SearchTagTable.createTableQuery)
|
||||||
execSQL(SearchTitleTable.createTableQuery)
|
execSQL(SearchTitleTable.createTableQuery)
|
||||||
execSQL(MergedTable.createTableQuery)
|
execSQL(MergedTable.createTableQuery)
|
||||||
execSQL(FavoriteEntryTable.createTableQuery)
|
execSQL(SimilarTable.createTableQuery)
|
||||||
execSQL(SavedSearchTable.createTableQuery)
|
|
||||||
execSQL(FeedSavedSearchTable.createTableQuery)
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// DB indexes
|
// DB indexes
|
||||||
@@ -61,7 +57,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||||
execSQL(SearchTitleTable.createTitleIndexQuery)
|
execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||||
execSQL(MergedTable.createIndexQuery)
|
execSQL(MergedTable.createIndexQuery)
|
||||||
execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery)
|
execSQL(SimilarTable.createMangaIdIndexQuery)
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,38 +74,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
db.execSQL(MergedTable.createTableQuery)
|
db.execSQL(MergedTable.createTableQuery)
|
||||||
db.execSQL(MergedTable.createIndexQuery)
|
db.execSQL(MergedTable.createIndexQuery)
|
||||||
}
|
}
|
||||||
/*if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
db.execSQL(SimilarTable.createTableQuery)
|
db.execSQL(SimilarTable.createTableQuery)
|
||||||
db.execSQL(SimilarTable.createMangaIdIndexQuery)
|
db.execSQL(SimilarTable.createMangaIdIndexQuery)
|
||||||
}*/
|
|
||||||
if (oldVersion < 6) {
|
|
||||||
db.execSQL(MangaTable.addFilteredScanlators)
|
|
||||||
}
|
|
||||||
if (oldVersion < 7) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS manga_related")
|
|
||||||
}
|
|
||||||
if (oldVersion < 8) {
|
|
||||||
db.execSQL(MangaTable.addNextUpdateCol)
|
|
||||||
}
|
|
||||||
if (oldVersion < 9) {
|
|
||||||
db.execSQL(TrackTable.renameTableToTemp)
|
|
||||||
db.execSQL(TrackTable.createTableQuery)
|
|
||||||
db.execSQL(TrackTable.insertFromTempTable)
|
|
||||||
db.execSQL(TrackTable.dropTempTable)
|
|
||||||
}
|
|
||||||
if (oldVersion < 10) {
|
|
||||||
db.execSQL(ChapterTable.fixDateUploadIfNeeded)
|
|
||||||
}
|
|
||||||
if (oldVersion < 11) {
|
|
||||||
db.execSQL(FavoriteEntryTable.createTableQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 12) {
|
|
||||||
db.execSQL(FavoriteEntryTable.fixTableQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 13) {
|
|
||||||
db.execSQL(SavedSearchTable.createTableQuery)
|
|
||||||
db.execSQL(FeedSavedSearchTable.createTableQuery)
|
|
||||||
db.execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
|||||||
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
||||||
CategoryPutResolver(),
|
CategoryPutResolver(),
|
||||||
CategoryGetResolver(),
|
CategoryGetResolver(),
|
||||||
CategoryDeleteResolver(),
|
CategoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
||||||
@@ -42,20 +42,20 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
|
|||||||
COL_NAME to obj.name,
|
COL_NAME to obj.name,
|
||||||
COL_ORDER to obj.order,
|
COL_ORDER to obj.order,
|
||||||
COL_FLAGS to obj.flags,
|
COL_FLAGS to obj.flags,
|
||||||
COL_MANGA_ORDER to obj.mangaOrder.joinToString("/"),
|
COL_MANGA_ORDER to obj.mangaOrder.joinToString("/")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoryGetResolver : DefaultGetResolver<Category>() {
|
class CategoryGetResolver : DefaultGetResolver<Category>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
|
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
|
||||||
id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getInt(cursor.getColumnIndex(COL_ID))
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||||
order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ORDER))
|
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||||
flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FLAGS))
|
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val orderString = cursor.getString(cursor.getColumnIndexOrThrow(COL_MANGA_ORDER))
|
val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER))
|
||||||
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() }.orEmpty()
|
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() }.orEmpty()
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
|
|||||||
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
||||||
ChapterPutResolver(),
|
ChapterPutResolver(),
|
||||||
ChapterGetResolver(),
|
ChapterGetResolver(),
|
||||||
ChapterDeleteResolver(),
|
ChapterDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
||||||
@@ -56,25 +56,25 @@ class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
|||||||
COL_DATE_UPLOAD to obj.date_upload,
|
COL_DATE_UPLOAD to obj.date_upload,
|
||||||
COL_LAST_PAGE_READ to obj.last_page_read,
|
COL_LAST_PAGE_READ to obj.last_page_read,
|
||||||
COL_CHAPTER_NUMBER to obj.chapter_number,
|
COL_CHAPTER_NUMBER to obj.chapter_number,
|
||||||
COL_SOURCE_ORDER to obj.source_order,
|
COL_SOURCE_ORDER to obj.source_order
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
|
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
|
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||||
scanlator = cursor.getString(cursor.getColumnIndexOrThrow(COL_SCANLATOR))
|
scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR))
|
||||||
read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_READ)) == 1
|
read = cursor.getInt(cursor.getColumnIndex(COL_READ)) == 1
|
||||||
bookmark = cursor.getInt(cursor.getColumnIndexOrThrow(COL_BOOKMARK)) == 1
|
bookmark = cursor.getInt(cursor.getColumnIndex(COL_BOOKMARK)) == 1
|
||||||
date_fetch = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_FETCH))
|
date_fetch = cursor.getLong(cursor.getColumnIndex(COL_DATE_FETCH))
|
||||||
date_upload = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_UPLOAD))
|
date_upload = cursor.getLong(cursor.getColumnIndex(COL_DATE_UPLOAD))
|
||||||
last_page_read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_LAST_PAGE_READ))
|
last_page_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_PAGE_READ))
|
||||||
chapter_number = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_CHAPTER_NUMBER))
|
chapter_number = cursor.getFloat(cursor.getColumnIndex(COL_CHAPTER_NUMBER))
|
||||||
source_order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SOURCE_ORDER))
|
source_order = cursor.getInt(cursor.getColumnIndex(COL_SOURCE_ORDER))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
|||||||
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
||||||
HistoryPutResolver(),
|
HistoryPutResolver(),
|
||||||
HistoryGetResolver(),
|
HistoryGetResolver(),
|
||||||
HistoryDeleteResolver(),
|
HistoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
||||||
@@ -40,17 +40,17 @@ open class HistoryPutResolver : DefaultPutResolver<History>() {
|
|||||||
COL_ID to obj.id,
|
COL_ID to obj.id,
|
||||||
COL_CHAPTER_ID to obj.chapter_id,
|
COL_CHAPTER_ID to obj.chapter_id,
|
||||||
COL_LAST_READ to obj.last_read,
|
COL_LAST_READ to obj.last_read,
|
||||||
COL_TIME_READ to obj.time_read,
|
COL_TIME_READ to obj.time_read
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class HistoryGetResolver : DefaultGetResolver<History>() {
|
class HistoryGetResolver : DefaultGetResolver<History>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
|
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
chapter_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_CHAPTER_ID))
|
chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
|
||||||
last_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_READ))
|
last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))
|
||||||
time_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_TIME_READ))
|
time_read = cursor.getLong(cursor.getColumnIndex(COL_TIME_READ))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
|
|||||||
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
||||||
MangaCategoryPutResolver(),
|
MangaCategoryPutResolver(),
|
||||||
MangaCategoryGetResolver(),
|
MangaCategoryGetResolver(),
|
||||||
MangaCategoryDeleteResolver(),
|
MangaCategoryDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
||||||
@@ -37,16 +37,16 @@ class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
|||||||
contentValuesOf(
|
contentValuesOf(
|
||||||
COL_ID to obj.id,
|
COL_ID to obj.id,
|
||||||
COL_MANGA_ID to obj.manga_id,
|
COL_MANGA_ID to obj.manga_id,
|
||||||
COL_CATEGORY_ID to obj.category_id,
|
COL_CATEGORY_ID to obj.category_id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
|
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
category_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY_ID))
|
category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFI
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FILTERED_SCANLATORS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
|
||||||
@@ -34,7 +33,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
|
|||||||
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
||||||
MangaPutResolver(),
|
MangaPutResolver(),
|
||||||
MangaGetResolver(),
|
MangaGetResolver(),
|
||||||
MangaDeleteResolver(),
|
MangaDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||||
@@ -60,40 +59,38 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
COL_DESCRIPTION to obj.originalDescription,
|
COL_DESCRIPTION to obj.originalDescription,
|
||||||
COL_GENRE to obj.originalGenre,
|
COL_GENRE to obj.originalGenre,
|
||||||
COL_TITLE to obj.originalTitle,
|
COL_TITLE to obj.originalTitle,
|
||||||
COL_STATUS to obj.originalStatus,
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
COL_STATUS to obj.status,
|
||||||
COL_THUMBNAIL_URL to obj.thumbnail_url,
|
COL_THUMBNAIL_URL to obj.thumbnail_url,
|
||||||
COL_FAVORITE to obj.favorite,
|
COL_FAVORITE to obj.favorite,
|
||||||
COL_LAST_UPDATE to obj.last_update,
|
COL_LAST_UPDATE to obj.last_update,
|
||||||
COL_INITIALIZED to obj.initialized,
|
COL_INITIALIZED to obj.initialized,
|
||||||
COL_VIEWER to obj.viewer_flags,
|
COL_VIEWER to obj.viewer,
|
||||||
COL_CHAPTER_FLAGS to obj.chapter_flags,
|
COL_CHAPTER_FLAGS to obj.chapter_flags,
|
||||||
COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
|
COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
|
||||||
COL_DATE_ADDED to obj.date_added,
|
COL_DATE_ADDED to obj.date_added
|
||||||
COL_FILTERED_SCANLATORS to obj.filtered_scanlators,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseMangaGetResolver {
|
interface BaseMangaGetResolver {
|
||||||
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
|
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE))
|
source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE))
|
||||||
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
|
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
||||||
artist = cursor.getString(cursor.getColumnIndexOrThrow(COL_ARTIST))
|
artist = cursor.getString(cursor.getColumnIndex(COL_ARTIST))
|
||||||
author = cursor.getString(cursor.getColumnIndexOrThrow(COL_AUTHOR))
|
author = cursor.getString(cursor.getColumnIndex(COL_AUTHOR))
|
||||||
description = cursor.getString(cursor.getColumnIndexOrThrow(COL_DESCRIPTION))
|
description = cursor.getString(cursor.getColumnIndex(COL_DESCRIPTION))
|
||||||
genre = cursor.getString(cursor.getColumnIndexOrThrow(COL_GENRE))
|
genre = cursor.getString(cursor.getColumnIndex(COL_GENRE))
|
||||||
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
||||||
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
|
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
||||||
thumbnail_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_THUMBNAIL_URL))
|
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
|
||||||
favorite = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FAVORITE)) == 1
|
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
|
||||||
last_update = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_UPDATE))
|
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
|
||||||
initialized = cursor.getInt(cursor.getColumnIndexOrThrow(COL_INITIALIZED)) == 1
|
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
||||||
viewer_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_VIEWER))
|
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||||
chapter_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_FLAGS))
|
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||||
cover_last_modified = cursor.getLong(cursor.getColumnIndexOrThrow(COL_COVER_LAST_MODIFIED))
|
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
|
||||||
date_added = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_ADDED))
|
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
|
||||||
filtered_scanlators = cursor.getString(cursor.getColumnIndexOrThrow(COL_FILTERED_SCANLATORS))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
|||||||
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
||||||
TrackPutResolver(),
|
TrackPutResolver(),
|
||||||
TrackGetResolver(),
|
TrackGetResolver(),
|
||||||
TrackDeleteResolver(),
|
TrackDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class TrackPutResolver : DefaultPutResolver<Track>() {
|
class TrackPutResolver : DefaultPutResolver<Track>() {
|
||||||
@@ -58,26 +58,26 @@ class TrackPutResolver : DefaultPutResolver<Track>() {
|
|||||||
COL_TRACKING_URL to obj.tracking_url,
|
COL_TRACKING_URL to obj.tracking_url,
|
||||||
COL_SCORE to obj.score,
|
COL_SCORE to obj.score,
|
||||||
COL_START_DATE to obj.started_reading_date,
|
COL_START_DATE to obj.started_reading_date,
|
||||||
COL_FINISH_DATE to obj.finished_reading_date,
|
COL_FINISH_DATE to obj.finished_reading_date
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrackGetResolver : DefaultGetResolver<Track>() {
|
class TrackGetResolver : DefaultGetResolver<Track>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
|
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
||||||
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
|
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
||||||
media_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
|
media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID))
|
||||||
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
|
library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID))
|
||||||
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
||||||
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
|
last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ))
|
||||||
total_chapters = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TOTAL_CHAPTERS))
|
total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS))
|
||||||
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
|
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
||||||
score = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_SCORE))
|
score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE))
|
||||||
tracking_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_TRACKING_URL))
|
tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL))
|
||||||
started_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_START_DATE))
|
started_reading_date = cursor.getLong(cursor.getColumnIndex(COL_START_DATE))
|
||||||
finished_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_FINISH_DATE))
|
finished_reading_date = cursor.getLong(cursor.getColumnIndex(COL_FINISH_DATE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
interface Category : Serializable {
|
interface Category : Serializable {
|
||||||
@@ -21,28 +16,12 @@ interface Category : Serializable {
|
|||||||
var mangaOrder: List<Long>
|
var mangaOrder: List<Long>
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
private fun setFlags(flag: Int, mask: Int) {
|
|
||||||
flags = flags and mask.inv() or (flag and mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayMode: Int
|
|
||||||
get() = flags and DisplayModeSetting.MASK
|
|
||||||
set(mode) = setFlags(mode, DisplayModeSetting.MASK)
|
|
||||||
|
|
||||||
var sortMode: Int
|
|
||||||
get() = flags and SortModeSetting.MASK
|
|
||||||
set(mode) = setFlags(mode, SortModeSetting.MASK)
|
|
||||||
|
|
||||||
var sortDirection: Int
|
|
||||||
get() = flags and SortDirectionSetting.MASK
|
|
||||||
set(mode) = setFlags(mode, SortDirectionSetting.MASK)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun create(name: String): Category = CategoryImpl().apply {
|
fun create(name: String): Category = CategoryImpl().apply {
|
||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createDefault(context: Context): Category = create(context.getString(R.string.label_default)).apply { id = 0 }
|
fun createDefault(): Category = create("Default").apply { id = 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,7 @@ package eu.kanade.tachiyomi.data.database.models
|
|||||||
|
|
||||||
class LibraryManga : MangaImpl() {
|
class LibraryManga : MangaImpl() {
|
||||||
|
|
||||||
var unreadCount: Int = 0
|
var unread: Int = 0
|
||||||
var readCount: Int = 0
|
|
||||||
|
|
||||||
val totalChapters
|
|
||||||
get() = readCount + unreadCount
|
|
||||||
|
|
||||||
val hasStarted
|
|
||||||
get() = readCount > 0
|
|
||||||
|
|
||||||
var category: Int = 0
|
var category: Int = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
package eu.kanade.tachiyomi.data.database.models
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|
||||||
import tachiyomi.source.model.MangaInfo
|
import tachiyomi.source.model.MangaInfo
|
||||||
|
|
||||||
interface Manga : SManga {
|
interface Manga : SManga {
|
||||||
@@ -13,30 +11,26 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
var favorite: Boolean
|
var favorite: Boolean
|
||||||
|
|
||||||
// last time the chapter list changed in any way
|
|
||||||
var last_update: Long
|
var last_update: Long
|
||||||
|
|
||||||
var date_added: Long
|
var date_added: Long
|
||||||
|
|
||||||
var viewer_flags: Int
|
var viewer: Int
|
||||||
|
|
||||||
var chapter_flags: Int
|
var chapter_flags: Int
|
||||||
|
|
||||||
var cover_last_modified: Long
|
var cover_last_modified: Long
|
||||||
|
|
||||||
var filtered_scanlators: String?
|
|
||||||
|
|
||||||
fun setChapterOrder(order: Int) {
|
fun setChapterOrder(order: Int) {
|
||||||
setChapterFlags(order, CHAPTER_SORT_MASK)
|
setFlags(order, SORT_MASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sortDescending(): Boolean {
|
fun sortDescending(): Boolean {
|
||||||
return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC
|
return chapter_flags and SORT_MASK == SORT_DESC
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGenres(): List<String>? {
|
fun getGenres(): List<String>? {
|
||||||
if (genre.isNullOrBlank()) return null
|
return genre?.split(", ")?.map { it.trim() }
|
||||||
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
@@ -45,72 +39,60 @@ interface Manga : SManga {
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
private fun setChapterFlags(flag: Int, mask: Int) {
|
private fun setFlags(flag: Int, mask: Int) {
|
||||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setViewerFlags(flag: Int, mask: Int) {
|
|
||||||
viewer_flags = viewer_flags and mask.inv() or (flag and mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
// Used to display the chapter's title one way or another
|
||||||
var displayMode: Int
|
var displayMode: Int
|
||||||
get() = chapter_flags and CHAPTER_DISPLAY_MASK
|
get() = chapter_flags and DISPLAY_MASK
|
||||||
set(mode) = setChapterFlags(mode, CHAPTER_DISPLAY_MASK)
|
set(mode) = setFlags(mode, DISPLAY_MASK)
|
||||||
|
|
||||||
var readFilter: Int
|
var readFilter: Int
|
||||||
get() = chapter_flags and CHAPTER_READ_MASK
|
get() = chapter_flags and READ_MASK
|
||||||
set(filter) = setChapterFlags(filter, CHAPTER_READ_MASK)
|
set(filter) = setFlags(filter, READ_MASK)
|
||||||
|
|
||||||
var downloadedFilter: Int
|
var downloadedFilter: Int
|
||||||
get() = chapter_flags and CHAPTER_DOWNLOADED_MASK
|
get() = chapter_flags and DOWNLOADED_MASK
|
||||||
set(filter) = setChapterFlags(filter, CHAPTER_DOWNLOADED_MASK)
|
set(filter) = setFlags(filter, DOWNLOADED_MASK)
|
||||||
|
|
||||||
var bookmarkedFilter: Int
|
var bookmarkedFilter: Int
|
||||||
get() = chapter_flags and CHAPTER_BOOKMARKED_MASK
|
get() = chapter_flags and BOOKMARKED_MASK
|
||||||
set(filter) = setChapterFlags(filter, CHAPTER_BOOKMARKED_MASK)
|
set(filter) = setFlags(filter, BOOKMARKED_MASK)
|
||||||
|
|
||||||
var sorting: Int
|
var sorting: Int
|
||||||
get() = chapter_flags and CHAPTER_SORTING_MASK
|
get() = chapter_flags and SORTING_MASK
|
||||||
set(sort) = setChapterFlags(sort, CHAPTER_SORTING_MASK)
|
set(sort) = setFlags(sort, SORTING_MASK)
|
||||||
|
|
||||||
var readingModeType: Int
|
|
||||||
get() = viewer_flags and ReadingModeType.MASK
|
|
||||||
set(readingMode) = setViewerFlags(readingMode, ReadingModeType.MASK)
|
|
||||||
|
|
||||||
var orientationType: Int
|
|
||||||
get() = viewer_flags and OrientationType.MASK
|
|
||||||
set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
const val SORT_DESC = 0x00000000
|
||||||
|
const val SORT_ASC = 0x00000001
|
||||||
|
const val SORT_MASK = 0x00000001
|
||||||
|
|
||||||
// Generic filter that does not filter anything
|
// Generic filter that does not filter anything
|
||||||
const val SHOW_ALL = 0x00000000
|
const val SHOW_ALL = 0x00000000
|
||||||
|
|
||||||
const val CHAPTER_SORT_DESC = 0x00000000
|
const val SHOW_UNREAD = 0x00000002
|
||||||
const val CHAPTER_SORT_ASC = 0x00000001
|
const val SHOW_READ = 0x00000004
|
||||||
const val CHAPTER_SORT_MASK = 0x00000001
|
const val READ_MASK = 0x00000006
|
||||||
|
|
||||||
const val CHAPTER_SHOW_UNREAD = 0x00000002
|
const val SHOW_DOWNLOADED = 0x00000008
|
||||||
const val CHAPTER_SHOW_READ = 0x00000004
|
const val SHOW_NOT_DOWNLOADED = 0x00000010
|
||||||
const val CHAPTER_READ_MASK = 0x00000006
|
const val DOWNLOADED_MASK = 0x00000018
|
||||||
|
|
||||||
const val CHAPTER_SHOW_DOWNLOADED = 0x00000008
|
const val SHOW_BOOKMARKED = 0x00000020
|
||||||
const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010
|
const val SHOW_NOT_BOOKMARKED = 0x00000040
|
||||||
const val CHAPTER_DOWNLOADED_MASK = 0x00000018
|
const val BOOKMARKED_MASK = 0x00000060
|
||||||
|
|
||||||
const val CHAPTER_SHOW_BOOKMARKED = 0x00000020
|
const val SORTING_SOURCE = 0x00000000
|
||||||
const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040
|
const val SORTING_NUMBER = 0x00000100
|
||||||
const val CHAPTER_BOOKMARKED_MASK = 0x00000060
|
const val SORTING_UPLOAD_DATE = 0x00000200
|
||||||
|
const val SORTING_MASK = 0x00000300
|
||||||
|
|
||||||
const val CHAPTER_SORTING_SOURCE = 0x00000000
|
const val DISPLAY_NAME = 0x00000000
|
||||||
const val CHAPTER_SORTING_NUMBER = 0x00000100
|
const val DISPLAY_NUMBER = 0x00100000
|
||||||
const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200
|
const val DISPLAY_MASK = 0x00100000
|
||||||
const val CHAPTER_SORTING_MASK = 0x00000300
|
|
||||||
|
|
||||||
const val CHAPTER_DISPLAY_NAME = 0x00000000
|
|
||||||
const val CHAPTER_DISPLAY_NUMBER = 0x00100000
|
|
||||||
const val CHAPTER_DISPLAY_MASK = 0x00100000
|
|
||||||
|
|
||||||
fun create(source: Long): Manga = MangaImpl().apply {
|
fun create(source: Long): Manga = MangaImpl().apply {
|
||||||
this.source = source
|
this.source = source
|
||||||
@@ -133,6 +115,6 @@ fun Manga.toMangaInfo(): MangaInfo {
|
|||||||
genres = this.getGenres() ?: emptyList(),
|
genres = this.getGenres() ?: emptyList(),
|
||||||
key = this.url,
|
key = this.url,
|
||||||
status = this.status,
|
status = this.status,
|
||||||
title = this.title,
|
title = this.title
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ open class MangaImpl : Manga {
|
|||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||||
|
|
||||||
override var title: String
|
override var title: String
|
||||||
get() = if (favorite) {
|
get() = if (favorite) {
|
||||||
val customTitle = customMangaManager.getManga(this)?.title
|
val customTitle = customMangaManager.getManga(this)?.title
|
||||||
@@ -38,12 +40,10 @@ open class MangaImpl : Manga {
|
|||||||
override var genre: String?
|
override var genre: String?
|
||||||
get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre
|
get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre
|
||||||
set(value) { ogGenre = value }
|
set(value) { ogGenre = value }
|
||||||
|
|
||||||
override var status: Int
|
|
||||||
get() = if (favorite) customMangaManager.getManga(this)?.status?.takeUnless { it == 0 } ?: ogStatus else ogStatus
|
|
||||||
set(value) { ogStatus = value }
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
override var status: Int = 0
|
||||||
|
|
||||||
override var thumbnail_url: String? = null
|
override var thumbnail_url: String? = null
|
||||||
|
|
||||||
override var favorite: Boolean = false
|
override var favorite: Boolean = false
|
||||||
@@ -54,14 +54,12 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var initialized: Boolean = false
|
override var initialized: Boolean = false
|
||||||
|
|
||||||
override var viewer_flags: Int = 0
|
override var viewer: Int = 0
|
||||||
|
|
||||||
override var chapter_flags: Int = 0
|
override var chapter_flags: Int = 0
|
||||||
|
|
||||||
override var cover_last_modified: Long = 0
|
override var cover_last_modified: Long = 0
|
||||||
|
|
||||||
override var filtered_scanlators: String? = null
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
lateinit var ogTitle: String
|
lateinit var ogTitle: String
|
||||||
private set
|
private set
|
||||||
@@ -73,8 +71,6 @@ open class MangaImpl : Manga {
|
|||||||
private set
|
private set
|
||||||
var ogGenre: String? = null
|
var ogGenre: String? = null
|
||||||
private set
|
private set
|
||||||
var ogStatus: Int = 0
|
|
||||||
private set
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -89,10 +85,4 @@ open class MangaImpl : Manga {
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return url.hashCode() + id.hashCode()
|
return url.hashCode() + id.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
|
||||||
companion object {
|
|
||||||
private val customMangaManager: CustomMangaManager by injectLazy()
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
data class SourceIdMangaCount(val source: Long, val count: Int)
|
|
||||||
@@ -16,7 +16,7 @@ interface Track : Serializable {
|
|||||||
|
|
||||||
var title: String
|
var title: String
|
||||||
|
|
||||||
var last_chapter_read: Float
|
var last_chapter_read: Int
|
||||||
|
|
||||||
var total_chapters: Int
|
var total_chapters: Int
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TrackImpl : Track {
|
|||||||
|
|
||||||
override lateinit var title: String
|
override lateinit var title: String
|
||||||
|
|
||||||
override var last_chapter_read: Float = 0F
|
override var last_chapter_read: Int = 0
|
||||||
|
|
||||||
override var total_chapters: Int = 0
|
override var total_chapters: Int = 0
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface CategoryQueries : DbProvider {
|
|||||||
Query.builder()
|
Query.builder()
|
||||||
.table(CategoryTable.TABLE)
|
.table(CategoryTable.TABLE)
|
||||||
.orderBy(CategoryTable.COL_ORDER)
|
.orderBy(CategoryTable.COL_ORDER)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ interface CategoryQueries : DbProvider {
|
|||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getCategoriesForMangaQuery())
|
.query(getCategoriesForMangaQuery())
|
||||||
.args(manga.id)
|
.args(manga.id)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -37,7 +37,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.query(getRecentsQuery())
|
.query(getRecentsQuery())
|
||||||
.args(date.time)
|
.args(date.time)
|
||||||
.observesTables(ChapterTable.TABLE)
|
.observesTables(ChapterTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
@@ -49,7 +49,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_ID} = ?")
|
.where("${ChapterTable.COL_ID} = ?")
|
||||||
.whereArgs(id)
|
.whereArgs(id)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.whereArgs(url)
|
.whereArgs(url)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(url, mangaId)
|
.whereArgs(url, mangaId)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.whereArgs(url)
|
.whereArgs(url)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ interface ChapterQueries : DbProvider {
|
|||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} IN (?) AND (${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0)")
|
.where("${ChapterTable.COL_URL} IN (?) AND (${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0)")
|
||||||
.whereArgs(urls.joinToString { "\"$it\"" })
|
.whereArgs(urls.joinToString { "\"$it\"" })
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
|||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryChapterIdPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||||
@@ -33,7 +32,7 @@ interface HistoryQueries : DbProvider {
|
|||||||
.query(getRecentMangasQuery(search))
|
.query(getRecentMangasQuery(search))
|
||||||
.args(date.time, limit, offset)
|
.args(date.time, limit, offset)
|
||||||
.observesTables(HistoryTable.TABLE)
|
.observesTables(HistoryTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
@@ -45,7 +44,7 @@ interface HistoryQueries : DbProvider {
|
|||||||
.query(getHistoryByMangaId())
|
.query(getHistoryByMangaId())
|
||||||
.args(mangaId)
|
.args(mangaId)
|
||||||
.observesTables(HistoryTable.TABLE)
|
.observesTables(HistoryTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ interface HistoryQueries : DbProvider {
|
|||||||
.query(getHistoryByChapterUrl())
|
.query(getHistoryByChapterUrl())
|
||||||
.args(chapterUrl)
|
.args(chapterUrl)
|
||||||
.observesTables(HistoryTable.TABLE)
|
.observesTables(HistoryTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ interface HistoryQueries : DbProvider {
|
|||||||
.byQuery(
|
.byQuery(
|
||||||
DeleteQuery.builder()
|
DeleteQuery.builder()
|
||||||
.table(HistoryTable.TABLE)
|
.table(HistoryTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -94,24 +93,7 @@ interface HistoryQueries : DbProvider {
|
|||||||
.table(HistoryTable.TABLE)
|
.table(HistoryTable.TABLE)
|
||||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
.where("${HistoryTable.COL_LAST_READ} = ?")
|
||||||
.whereArgs(0)
|
.whereArgs(0)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
// SY -->
|
|
||||||
fun updateHistoryChapterIds(history: List<History>) = db.put()
|
|
||||||
.objects(history)
|
|
||||||
.withPutResolver(HistoryChapterIdPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun deleteHistoryIds(ids: List<Long>) = db.delete()
|
|
||||||
.byQuery(
|
|
||||||
DeleteQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.where("${HistoryTable.COL_ID} IN (?)")
|
|
||||||
.whereArgs(ids.joinToString())
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface MangaCategoryQueries : DbProvider {
|
|||||||
.table(MangaCategoryTable.TABLE)
|
.table(MangaCategoryTable.TABLE)
|
||||||
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import com.pushtorefresh.storio.Queries
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.SourceIdMangaCount
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFilteredScanlatorsPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaThumbnailPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
@@ -29,48 +24,38 @@ import exh.metadata.sql.tables.SearchMetadataTable
|
|||||||
|
|
||||||
interface MangaQueries : DbProvider {
|
interface MangaQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getMangas() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getLibraryMangas() = db.get()
|
fun getLibraryMangas() = db.get()
|
||||||
.listOfObjects(LibraryManga::class.java)
|
.listOfObjects(LibraryManga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(libraryQuery)
|
.query(libraryQuery)
|
||||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getDuplicateLibraryManga(manga: Manga) = db.get()
|
fun getFavoriteMangas() = db.get()
|
||||||
.`object`(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
Query.builder()
|
Query.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_FAVORITE} = 1 AND LOWER(${MangaTable.COL_TITLE}) = ? AND ${MangaTable.COL_SOURCE} != ?")
|
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||||
.whereArgs(
|
.whereArgs(1)
|
||||||
manga.title.lowercase(),
|
.orderBy(MangaTable.COL_TITLE)
|
||||||
manga.source,
|
.build()
|
||||||
)
|
|
||||||
.limit(1)
|
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getFavoriteMangas(sortByTitle: Boolean = true): PreparedGetListOfObjects<Manga> {
|
|
||||||
var queryBuilder = Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
|
||||||
.whereArgs(1)
|
|
||||||
|
|
||||||
if (sortByTitle) {
|
|
||||||
queryBuilder = queryBuilder.orderBy(MangaTable.COL_TITLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(queryBuilder.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getManga(url: String, sourceId: Long) = db.get()
|
fun getManga(url: String, sourceId: Long) = db.get()
|
||||||
.`object`(Manga::class.java)
|
.`object`(Manga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
@@ -78,7 +63,7 @@ interface MangaQueries : DbProvider {
|
|||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
|
||||||
.whereArgs(url, sourceId)
|
.whereArgs(url, sourceId)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -89,37 +74,17 @@ interface MangaQueries : DbProvider {
|
|||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
.whereArgs(id)
|
.whereArgs(id)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getSourceIdsWithNonLibraryManga() = db.get()
|
|
||||||
.listOfObjects(SourceIdMangaCount::class.java)
|
|
||||||
.withQuery(
|
|
||||||
RawQuery.builder()
|
|
||||||
.query(getSourceIdsWithNonLibraryMangaQuery())
|
|
||||||
.observesTables(MangaTable.TABLE)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.withGetResolver(SourceIdMangaCountGetResolver.INSTANCE)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun getMangas() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(
|
|
||||||
Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getReadNotInLibraryMangas() = db.get()
|
fun getReadNotInLibraryMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getReadMangaNotInLibraryQuery())
|
.query(getReadMangaNotInLibraryQuery())
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -137,35 +102,20 @@ interface MangaQueries : DbProvider {
|
|||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaMigrationPutResolver())
|
.withPutResolver(MangaMigrationPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateMangaThumbnail(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaThumbnailPutResolver())
|
|
||||||
.prepare()
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||||
|
|
||||||
fun updateChapterFlags(manga: Manga) = db.put()
|
fun updateFlags(manga: Manga) = db.put()
|
||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
|
.withPutResolver(MangaFlagsPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateChapterFlags(manga: List<Manga>) = db.put()
|
fun updateFlags(mangas: List<Manga>) = db.put()
|
||||||
.objects(manga)
|
.objects(mangas)
|
||||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
|
.withPutResolver(MangaFlagsPutResolver(true))
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateViewerFlags(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateViewerFlags(manga: List<Manga>) = db.put()
|
|
||||||
.objects(manga)
|
|
||||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
|
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateLastUpdated(manga: Manga) = db.put()
|
fun updateLastUpdated(manga: Manga) = db.put()
|
||||||
@@ -178,6 +128,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaFavoritePutResolver())
|
.withPutResolver(MangaFavoritePutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaViewer(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaViewerPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun updateMangaTitle(manga: Manga) = db.put()
|
fun updateMangaTitle(manga: Manga) = db.put()
|
||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaTitlePutResolver())
|
.withPutResolver(MangaTitlePutResolver())
|
||||||
@@ -188,60 +143,25 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
// SY -->
|
|
||||||
fun updateMangaFilteredScanlators(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaFilteredScanlatorsPutResolver())
|
|
||||||
.prepare()
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
|
|
||||||
fun deleteMangasNotInLibraryBySourceIds(sourceIds: List<Long>) = db.delete()
|
fun deleteMangasNotInLibrary() = db.delete()
|
||||||
.byQuery(
|
.byQuery(
|
||||||
DeleteQuery.builder()
|
DeleteQuery.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
// SY -->
|
.where("${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE})")
|
||||||
.where(
|
.whereArgs(0)
|
||||||
"""
|
.build()
|
||||||
${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_SOURCE} IN (${Queries.placeholders(sourceIds.size)}) AND ${MangaTable.COL_ID} NOT IN (
|
|
||||||
SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE} WHERE ${MergedTable.COL_MANGA_ID} != ${MergedTable.COL_MERGE_ID}
|
|
||||||
)
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
// SY <--
|
|
||||||
.whereArgs(0, *sourceIds.toTypedArray())
|
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
// SY -->
|
|
||||||
fun deleteMangasNotInLibraryAndNotReadBySourceIds(sourceIds: List<Long>) = db.delete()
|
|
||||||
.byQuery(
|
|
||||||
DeleteQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where(
|
|
||||||
"""
|
|
||||||
${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_SOURCE} IN (${Queries.placeholders(sourceIds.size)}) AND ${MangaTable.COL_ID} NOT IN (
|
|
||||||
SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE} WHERE ${MergedTable.COL_MANGA_ID} != ${MergedTable.COL_MERGE_ID}
|
|
||||||
) AND ${MangaTable.COL_ID} NOT IN (
|
|
||||||
SELECT ${ChapterTable.COL_MANGA_ID} FROM ${ChapterTable.TABLE} WHERE ${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0
|
|
||||||
)
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
.whereArgs(0, *sourceIds.toTypedArray())
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
fun deleteMangas() = db.delete()
|
fun deleteMangas() = db.delete()
|
||||||
.byQuery(
|
.byQuery(
|
||||||
DeleteQuery.builder()
|
DeleteQuery.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -251,7 +171,7 @@ interface MangaQueries : DbProvider {
|
|||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getLastReadMangaQuery())
|
.query(getLastReadMangaQuery())
|
||||||
.observesTables(MangaTable.TABLE)
|
.observesTables(MangaTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -261,7 +181,7 @@ interface MangaQueries : DbProvider {
|
|||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getTotalChapterMangaQuery())
|
.query(getTotalChapterMangaQuery())
|
||||||
.observesTables(MangaTable.TABLE)
|
.observesTables(MangaTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -271,17 +191,7 @@ interface MangaQueries : DbProvider {
|
|||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getLatestChapterMangaQuery())
|
.query(getLatestChapterMangaQuery())
|
||||||
.observesTables(MangaTable.TABLE)
|
.observesTables(MangaTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getChapterFetchDateManga() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(
|
|
||||||
RawQuery.builder()
|
|
||||||
.query(getChapterFetchDateMangaQuery())
|
|
||||||
.observesTables(MangaTable.TABLE)
|
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -296,9 +206,9 @@ interface MangaQueries : DbProvider {
|
|||||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
""".trimIndent(),
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -313,9 +223,9 @@ interface MangaQueries : DbProvider {
|
|||||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
""".trimIndent(),
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -330,9 +240,9 @@ interface MangaQueries : DbProvider {
|
|||||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||||
""".trimIndent(),
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
|
|
||||||
import exh.savedsearches.tables.FeedSavedSearchTable
|
|
||||||
import exh.savedsearches.tables.SavedSearchTable
|
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||||
@@ -72,54 +69,28 @@ fun getReadMangaNotInLibraryQuery() =
|
|||||||
SELECT ${Manga.TABLE}.*
|
SELECT ${Manga.TABLE}.*
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
WHERE ${Manga.COL_FAVORITE} = 0 AND ${Manga.COL_ID} IN(
|
WHERE ${Manga.COL_FAVORITE} = 0 AND ${Manga.COL_ID} IN(
|
||||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} FROM ${Chapter.TABLE} WHERE ${Chapter.COL_READ} = 1 OR ${Chapter.COL_LAST_PAGE_READ} != 0
|
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} FROM ${Chapter.TABLE} WHERE ${Chapter.COL_READ} = 1
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the global feed saved searches
|
* Query to get the manga from the library, with their categories and unread count.
|
||||||
*/
|
|
||||||
fun getGlobalFeedSavedSearchQuery() =
|
|
||||||
"""
|
|
||||||
SELECT ${SavedSearchTable.TABLE}.*
|
|
||||||
FROM (
|
|
||||||
SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 1
|
|
||||||
) AS M
|
|
||||||
JOIN ${SavedSearchTable.TABLE}
|
|
||||||
ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID}
|
|
||||||
"""
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query to get the source feed saved searches
|
|
||||||
*/
|
|
||||||
fun getSourceFeedSavedSearchQuery() =
|
|
||||||
"""
|
|
||||||
SELECT ${SavedSearchTable.TABLE}.*
|
|
||||||
FROM (
|
|
||||||
SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 0 AND ${FeedSavedSearchTable.COL_SOURCE} = ?
|
|
||||||
) AS M
|
|
||||||
JOIN ${SavedSearchTable.TABLE}
|
|
||||||
ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID}
|
|
||||||
"""
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query to get the manga from the library, with their categories, read and unread count.
|
|
||||||
*/
|
*/
|
||||||
val libraryQuery =
|
val libraryQuery =
|
||||||
"""
|
"""
|
||||||
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unreadCount, 0) AS ${Manga.COMPUTED_COL_UNREAD_COUNT}, COALESCE(R.readCount, 0) AS ${Manga.COMPUTED_COL_READ_COUNT}
|
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}, COUNT(*) AS unreadCount
|
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
|
||||||
FROM ${Chapter.TABLE}
|
FROM ${Chapter.TABLE}
|
||||||
WHERE ${Chapter.COL_READ} = 0
|
WHERE ${Chapter.COL_READ} = 0
|
||||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
) AS C
|
) AS C
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
|
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS readCount
|
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS read
|
||||||
FROM ${Chapter.TABLE}
|
FROM ${Chapter.TABLE}
|
||||||
WHERE ${Chapter.COL_READ} = 1
|
WHERE ${Chapter.COL_READ} = 1
|
||||||
GROUP BY ${Chapter.COL_MANGA_ID}
|
GROUP BY ${Chapter.COL_MANGA_ID}
|
||||||
@@ -128,10 +99,10 @@ val libraryQuery =
|
|||||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} <> $MERGED_SOURCE_ID
|
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} <> $MERGED_SOURCE_ID
|
||||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||||
UNION
|
UNION
|
||||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unreadCount, 0) AS ${Manga.COMPUTED_COL_UNREAD_COUNT}, COALESCE(R.readCount, 0) AS ${Manga.COMPUTED_COL_READ_COUNT}
|
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as unreadCount
|
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as unread
|
||||||
FROM ${Merged.TABLE}
|
FROM ${Merged.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
||||||
@@ -140,7 +111,7 @@ val libraryQuery =
|
|||||||
) AS C
|
) AS C
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Merged.COL_MERGE_ID}
|
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Merged.COL_MERGE_ID}
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as readCount
|
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as read
|
||||||
FROM ${Merged.TABLE}
|
FROM ${Merged.TABLE}
|
||||||
JOIN ${Chapter.TABLE}
|
JOIN ${Chapter.TABLE}
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
||||||
@@ -250,16 +221,6 @@ fun getLatestChapterMangaQuery() =
|
|||||||
ORDER by max DESC
|
ORDER by max DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getChapterFetchDateMangaQuery() =
|
|
||||||
"""
|
|
||||||
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_FETCH}) AS max
|
|
||||||
FROM ${Manga.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
|
||||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
|
||||||
ORDER by max DESC
|
|
||||||
"""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the categories for a manga.
|
* Query to get the categories for a manga.
|
||||||
*/
|
*/
|
||||||
@@ -270,14 +231,3 @@ fun getCategoriesForMangaQuery() =
|
|||||||
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
|
||||||
WHERE ${MangaCategory.COL_MANGA_ID} = ?
|
WHERE ${MangaCategory.COL_MANGA_ID} = ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
/** Query to get the list of sources in the database that have
|
|
||||||
* non-library manga, and how many
|
|
||||||
*/
|
|
||||||
fun getSourceIdsWithNonLibraryMangaQuery() =
|
|
||||||
"""
|
|
||||||
SELECT ${Manga.COL_SOURCE}, COUNT(*) as ${SourceIdMangaCountGetResolver.COL_COUNT}
|
|
||||||
FROM ${Manga.TABLE}
|
|
||||||
WHERE ${Manga.COL_FAVORITE} = 0
|
|
||||||
GROUP BY ${Manga.COL_SOURCE}
|
|
||||||
"""
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface TrackQueries : DbProvider {
|
|||||||
.withQuery(
|
.withQuery(
|
||||||
Query.builder()
|
Query.builder()
|
||||||
.table(TrackTable.TABLE)
|
.table(TrackTable.TABLE)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ interface TrackQueries : DbProvider {
|
|||||||
.table(TrackTable.TABLE)
|
.table(TrackTable.TABLE)
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
.where("${TrackTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(manga.id)
|
.whereArgs(manga.id)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ interface TrackQueries : DbProvider {
|
|||||||
.table(TrackTable.TABLE)
|
.table(TrackTable.TABLE)
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
||||||
.whereArgs(manga.id, sync.id)
|
.whereArgs(manga.id, sync.id)
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -29,6 +29,6 @@ class ChapterBackupPutResolver : PutResolver<Chapter>() {
|
|||||||
contentValuesOf(
|
contentValuesOf(
|
||||||
ChapterTable.COL_READ to chapter.read,
|
ChapterTable.COL_READ to chapter.read,
|
||||||
ChapterTable.COL_BOOKMARK to chapter.bookmark,
|
ChapterTable.COL_BOOKMARK to chapter.bookmark,
|
||||||
ChapterTable.COL_LAST_PAGE_READ to chapter.last_page_read,
|
ChapterTable.COL_LAST_PAGE_READ to chapter.last_page_read
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user