Compare commits
214 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63617f3079 | |||
| 0f9f7ffc28 | |||
| 9aab9d4ca4 | |||
| 92ae67630c | |||
| 02b90000f0 | |||
| 6ed5d858aa | |||
| 83bc059573 | |||
| ad39af55d6 | |||
| 8d5b2f40b3 | |||
| 49bee1af91 | |||
| a48508a98e | |||
| 4e3288b2af | |||
| 0f16150613 | |||
| 6dd7491ffe | |||
| 02e6eaae12 | |||
| 73523dbff8 | |||
| 58a503814d | |||
| 7a834ea9f4 | |||
| dcca19e6b8 | |||
| 210fd000b3 | |||
| 3d6f8ddd13 | |||
| a4578611d7 | |||
| bb6932ff80 | |||
| 8dce9a674b | |||
| b93298c411 | |||
| 8928aa77eb | |||
| a6e6fa0099 | |||
| c23edd5b72 | |||
| c8a4ec37e0 | |||
| f62d277894 | |||
| b7efc21ea9 | |||
| 238b2d108d | |||
| bdaf0f7492 | |||
| ced8dc750a | |||
| 035fb9e755 | |||
| 86defec57c | |||
| f20e5d864d | |||
| d83f938e07 | |||
| b4e73cb1eb | |||
| 604c7c703a | |||
| 20021dbf54 | |||
| 281fb9c67b | |||
| 7579bb026f | |||
| 61fb836be4 | |||
| d83361dfe3 | |||
| a5a79c1127 | |||
| 2f0f938d5e | |||
| 750a6c3d11 | |||
| b65305c73e | |||
| 85ef1031b5 | |||
| 69c530dc34 | |||
| ea620a8c74 | |||
| 1fdbae5bf8 | |||
| a1d54880c3 | |||
| d21a652944 | |||
| 444d346874 | |||
| 41b8786415 | |||
| d97805e38b | |||
| eafe3a62e4 | |||
| 0a502fcf31 | |||
| 80960d87f2 | |||
| 166aebdf25 | |||
| b8836b9b6f | |||
| 1d70f0b1dd | |||
| 1240cc5232 | |||
| 0abee585fc | |||
| 4870bb153d | |||
| f2b90bd772 | |||
| d77c65b515 | |||
| 5004e2d62c | |||
| 6e4a0ca1ea | |||
| 883ffaa815 | |||
| 3967a569c4 | |||
| 79e4e3d2a0 | |||
| 4b12e977c0 | |||
| 4333999b85 | |||
| fae290cf22 | |||
| 9ecf83f842 | |||
| f594962731 | |||
| 048eecf655 | |||
| 90253f3bd4 | |||
| 0deb6f6b8d | |||
| 294ade035e | |||
| 64bb34b50d | |||
| 9efb1482f9 | |||
| aff15b3ee2 | |||
| d86f3ffad8 | |||
| 2a3eef0610 | |||
| 3b87111f22 | |||
| b654613345 | |||
| 136b25fb92 | |||
| f3875bda50 | |||
| c41148b465 | |||
| eb0a1668f8 | |||
| f7bc3e0a82 | |||
| 2e4def13e3 | |||
| 9e0e2db25d | |||
| 20aa5b9aa1 | |||
| 6ce4612aa7 | |||
| b51d147986 | |||
| bc896cf605 | |||
| e48f274072 | |||
| a6b98e24dc | |||
| 9a26a3e5a2 | |||
| eeb0f76cce | |||
| bcd36c8fad | |||
| 7f7b2901cb | |||
| 84b9b4db55 | |||
| a5c10dbf28 | |||
| 5fee3ac05a | |||
| d3603a664c | |||
| 7ccaea0d72 | |||
| 921f7aad01 | |||
| bc549c56d6 | |||
| b639e1e4d7 | |||
| 4f877820b2 | |||
| c35e0a0c29 | |||
| 08d11914af | |||
| 309bd83730 | |||
| 6b158cc864 | |||
| 7d82be964c | |||
| 525c3f84e4 | |||
| ef37811020 | |||
| 0d033c7080 | |||
| 7557369d1f | |||
| 3e1804da8a | |||
| 2ab0927313 | |||
| 98b6a221fd | |||
| 4733a62980 | |||
| a5276fdadc | |||
| 906ac9e00c | |||
| 8ae3b8e313 | |||
| ce36e6b242 | |||
| cfd3d59516 | |||
| ae915d7823 | |||
| b4b4497e7e | |||
| 19055e1699 | |||
| f006467138 | |||
| a740f79b56 | |||
| 6c388ae906 | |||
| 3fa5322133 | |||
| 5a1bc6e25b | |||
| 51d0a67908 | |||
| 69f4d1fd46 | |||
| 8b53568fc8 | |||
| d978b6d088 | |||
| 9a3fdc23e6 | |||
| f8efe5d189 | |||
| aae23f5ef3 | |||
| eee2c34abf | |||
| c272eb6059 | |||
| 74065afc27 | |||
| ead5a258be | |||
| f9cf017594 | |||
| 1211b2c86a | |||
| 2bc845b1d2 | |||
| da8ed5c74f | |||
| d7d1d97f5f | |||
| 63510b2e60 | |||
| d7976e6054 | |||
| c82d7db570 | |||
| b6f8db81ee | |||
| a2fb89066c | |||
| 6f71bb3abe | |||
| e945de74f2 | |||
| 0e43234c23 | |||
| f8c4bbdfd8 | |||
| c834c2fbb0 | |||
| ad62a6b10b | |||
| 04fbb70981 | |||
| 340e534ca9 | |||
| 5714f183a8 | |||
| b6f6607d91 | |||
| aa794f4703 | |||
| a6d6a0fca6 | |||
| aa6f1a0de5 | |||
| 52b9a1dbd2 | |||
| 2f98dd2046 | |||
| f60b29c763 | |||
| c2adf2fe0a | |||
| c340884adb | |||
| 0125f326b4 | |||
| 2de87f8d29 | |||
| 165b243aab | |||
| 961f7f9b6d | |||
| 61094edeed | |||
| fbe4f6ad62 | |||
| c552934acc | |||
| 44c9df8c9b | |||
| 594a02fa69 | |||
| 510a67a755 | |||
| 882856a028 | |||
| 76adeae5ed | |||
| eb3c9a1d58 | |||
| 29f74ba423 | |||
| 571778adc1 | |||
| 64c5b70c78 | |||
| bb87392eef | |||
| 29e1697d2e | |||
| 025d794962 | |||
| a284f5cd08 | |||
| ee6b536b94 | |||
| 285f65ca4f | |||
| d07dbee9b0 | |||
| a5e1f92b05 | |||
| 417a31cfad | |||
| a84df3501a | |||
| 01137bf476 | |||
| 4c20ba38cb | |||
| cb4daa81c4 | |||
| 4f803494ff | |||
| fb19f6b860 | |||
| 885c94f9c8 | |||
| a5b7ad6495 |
@@ -2,7 +2,7 @@
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v1.1.1)
|
||||
- I have updated to the latest version of the app (stable is v1.3.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/inorichi/tachiyomi-extensions
|
||||
|
||||
@@ -10,7 +10,7 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
### Device information
|
||||
## Device information
|
||||
* Tachiyomi version: ?
|
||||
* Android version: ?
|
||||
* Device: ?
|
||||
|
||||
@@ -9,7 +9,7 @@ labels: "bug"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v1.1.1)
|
||||
- I have updated to the latest version of the app (stable is v1.3.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/inorichi/tachiyomi-extensions
|
||||
|
||||
@@ -17,7 +17,7 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
### Device information
|
||||
## Device information
|
||||
* Tachiyomi version: ?
|
||||
* Android version: ?
|
||||
* Device: ?
|
||||
@@ -32,5 +32,5 @@ This should happen.
|
||||
### Actual behavior
|
||||
This happened instead.
|
||||
|
||||
### Other details
|
||||
## Other details
|
||||
Additional details and attachments.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Tachiyomi help website
|
||||
url: https://tachiyomi.org/help/
|
||||
about: Common questions are answered here.
|
||||
- name: Tachiyomi extensions GitHub repository
|
||||
url: https://github.com/inorichi/tachiyomi-extensions
|
||||
about: Issues about an extension/source/catalogue should be opened here instead.
|
||||
@@ -9,7 +9,7 @@ labels: "feature"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v1.1.1)
|
||||
- I have updated to the latest version of the app (stable is v1.3.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/inorichi/tachiyomi-extensions
|
||||
|
||||
@@ -17,8 +17,8 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
### Why/User Benefit/User Problem
|
||||
## Why/User Benefit/User Problem
|
||||
(explain why this feature should be added)
|
||||
|
||||
### What/Requirements
|
||||
## What/Requirements
|
||||
(explain how this feature would behave)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: "Extension/source/catalogue issue"
|
||||
about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions"
|
||||
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions"
|
||||
labels: "catalog"
|
||||
labels: "catalog, invalid"
|
||||
---
|
||||
|
||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
Before Width: | Height: | Size: 453 KiB After Width: | Height: | Size: 482 KiB |
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="svg8" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 172 172" style="enable-background:new 0 0 172 172;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{stroke:#CE2828;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st1{fill:#F7D009;}
|
||||
.st2{fill:#E40F85;}
|
||||
</style>
|
||||
<title>sy_hobo_stds_mine</title>
|
||||
<g id="layer1">
|
||||
<path id="path4535" class="st0" d="M85.3,7C129,6.6,164.6,41.7,165,85.3c0.4,43.6-34.7,79.3-78.3,79.7C43.1,165.4,7.4,130.3,7,86.7
|
||||
c0-0.5,0-0.9,0-1.4C7.4,42.2,42.2,7.4,85.3,7z"/>
|
||||
<g id="text4543">
|
||||
<path id="path4545" class="st1" d="M76,64.2c2.9,0,8.4-9.4,8.4-12.5S73.5,40.7,58.2,40.7c-21.4,0-27.4,15-27.4,23.8
|
||||
c0,9.1,2.5,19.6,25.6,26.6c6.1,2,15.6,5.1,15.6,12.8c0,6.5-6.9,9.9-15,9.9c-22.6,0-20.9-21.3-22.6-21.3c-1.1,0-6.4,5.1-6.4,14.2
|
||||
c0,16.7,15.2,24.7,30.1,24.7c22.3,0,31-15,31-27.2c0-9.9-4.5-20.7-26.7-28.1c-5.8-2-16.2-4.8-16.2-12.5c0-6.2,6.8-8.8,12-8.8
|
||||
C69.2,54.8,73.3,64.2,76,64.2L76,64.2z"/>
|
||||
<path id="path4547" class="st2" d="M95.4,128.7c0,1.4,1.1,2.6,2.6,2.6c23.2,0,47-29.8,46-60.7c0-4.5,0.3-7.9-1.7-8.2h-9.4
|
||||
c-1.2,0-3.8-0.3-3.8,1.4s1.2,6.2,1.2,11.3c0,8.2-2.8,21-7.1,21c-2.1,0-12.4-11.6-12.4-24.1c0-3.1,1-6.2,1-7.9c0-2-2.3-1.7-3.7-1.7
|
||||
h-8.6c-4.1,0-4,0-4,4.8c0,29.5,18.3,36.8,18.3,41.4c0,1.1-3.1,5.1-15.3,5.1c-2.8,0-3.1,3.1-3.1,4.3L95.4,128.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -2,6 +2,8 @@ name: Remote Dispatch Action Initiator
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
name: Release Builder
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
apk:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Get NDK
|
||||
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
- name: Write google-services.json
|
||||
uses: DamianReeves/write-file-action@v1.0
|
||||
with:
|
||||
# The path to the file to write
|
||||
path: app/google-services.json
|
||||
# The contents of the file
|
||||
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
|
||||
# The mode of writing to use: `overwrite`, `append`, or `preserve`.
|
||||
write-mode: overwrite # optional, default is preserve
|
||||
- name: Build Release APK
|
||||
run: bash ./gradlew assembleRelease --stacktrace
|
||||
- name: Sign Android Release
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
# The directory to find your release to sign
|
||||
releaseDirectory: app/build/outputs/apk/standard/release
|
||||
# The key used to sign your release in base64 encoded format
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
# The key alias
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
# The password to the keystore
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
# The password for the key
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.run_number }}
|
||||
release_name: TachiyomiSY
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: Upload Release APK
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ env.SIGNED_RELEASE_FILE }}
|
||||
asset_name: TachiyomiSY.apk
|
||||
asset_content_type: application/vnd.android.package-archive
|
||||
@@ -0,0 +1,11 @@
|
||||
name: Validate Gradle Wrapper
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: Validation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
@@ -1,5 +1,6 @@
|
||||
name: Issue closer
|
||||
on: [issues]
|
||||
|
||||
jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -31,4 +32,4 @@ jobs:
|
||||
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."
|
||||
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
name: Pull Request Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
name: Pull request build check
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
apk:
|
||||
name: Generate APK
|
||||
runs-on: ubuntu-18.04
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Get NDK
|
||||
- name: Install NDK
|
||||
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
|
||||
- name: Build Release APK
|
||||
run: bash ./gradlew assembleDebug --stacktrace
|
||||
- name: Build project
|
||||
run: ./gradlew assembleDebug
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: TachiyomiSY-${{ github.sha }}.apk
|
||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
||||
@@ -1,6 +1,6 @@
|
||||
| Preview Builds | Release Builds | Tachiyomi Support Server |
|
||||
|-------|----------|----------|
|
||||
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases) | [](https://discord.gg/tachiyomi) |
|
||||
| [](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [](https://github.com/jobobby04/tachiyomisy/releases/latest) | [](https://discord.gg/tachiyomi) |
|
||||
|
||||
|
||||
# TachiyomiSY
|
||||
@@ -22,7 +22,6 @@ Features of Tachiyomi(original) include:
|
||||
|
||||
Features of TachiyomiSY include:
|
||||
* Uses the new Tachiyomi Stable UI
|
||||
* Custom manga page, all your needs, such as info and chapters, in front of your face
|
||||
* Latest tab, store up to 5 sources where you can easily view the latest manga by viewing the tab
|
||||
* Hentai features enable/disable, in advanced settings
|
||||
* Automatic webtoon detection, allowing the reader to switch to webtoon mode automatically when viewing one
|
||||
@@ -37,20 +36,25 @@ Features of TachiyomiSY include:
|
||||
* Manga info edit
|
||||
* Enhanced views for internal and integrated sources
|
||||
* Enhanced usability for internal and delegated sources
|
||||
* Dynamic Categories, view the library in multiple ways
|
||||
* Smart background for reading modes like LTR or Vertical, changes the backgorund based on the page color
|
||||
* Force disable webtoon zoom
|
||||
* Continue reading button in library
|
||||
* Quick clean titles
|
||||
|
||||
Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified in TachiyomiSY
|
||||
* Source migration, migrate all your manga from one source to another
|
||||
* Custom hentai sources:
|
||||
* * E-Hentai/ExHentai
|
||||
* * nHentai
|
||||
* * Hitomi.la
|
||||
* * 8Muses
|
||||
* * Perv Eden
|
||||
* Additional features for some extensions, features include custom description, opening in app, batch add to library:
|
||||
* * 8Muses (EroMuse)
|
||||
* * HBrowse
|
||||
* * HentaiCafe (Foolside)
|
||||
* * Hitomi.la
|
||||
* * NHentai
|
||||
* * PervEden (EN and IT)
|
||||
* * Puruin
|
||||
* * Tsumino
|
||||
* * HentaiCafe (Foolside)
|
||||
* * HBrowse
|
||||
* Saving searches
|
||||
* Autoscroll
|
||||
* Page preload customization
|
||||
@@ -64,10 +68,11 @@ Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified
|
||||
* Click tag for local search, long click tag for global search
|
||||
* Merge multiple of the same manga from different sources
|
||||
* Drag and drop library sorting
|
||||
* Library search engine, includes exclude, quotes as absolute, and a bunch of other ways to search
|
||||
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases).
|
||||
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases).
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||
// Realm (EH)
|
||||
apply plugin: 'realm-android'
|
||||
@@ -42,8 +43,8 @@ android {
|
||||
minSdkVersion AndroidConfig.minSdk
|
||||
targetSdkVersion AndroidConfig.targetSdk
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 4
|
||||
versionName "1.1.1"
|
||||
versionCode 8
|
||||
versionName "1.3.0"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||
@@ -65,7 +66,6 @@ android {
|
||||
debug {
|
||||
versionNameSuffix "-${getCommitCount()}"
|
||||
applicationIdSuffix ".debug"
|
||||
ext.enableCrashlytics = false
|
||||
}
|
||||
releaseTest {
|
||||
applicationIdSuffix ".rt"
|
||||
@@ -140,32 +140,32 @@ dependencies {
|
||||
|
||||
// AndroidX libraries
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
|
||||
implementation 'androidx.biometric:biometric:1.1.0-alpha02'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.4.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha04'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.webkit:webkit:1.3.0-rc01'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
|
||||
final lifecycle_version = '2.3.0-alpha05'
|
||||
final lifecycle_version = '2.3.0-alpha07'
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
|
||||
// Job scheduling
|
||||
final work_version = '2.4.0-rc01'
|
||||
final work_version = '2.5.0-alpha01'
|
||||
implementation "androidx.work:work-runtime:$work_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
// UI library
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
||||
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.4.4'
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.5.0'
|
||||
|
||||
// ReactiveX
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
@@ -174,14 +174,14 @@ dependencies {
|
||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||
|
||||
// Network client
|
||||
final okhttp_version = '4.7.2'
|
||||
final okhttp_version = '4.9.0'
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
|
||||
implementation 'com.squareup.okio:okio:2.6.0'
|
||||
implementation 'com.squareup.okio:okio:2.8.0'
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.1'
|
||||
|
||||
// REST
|
||||
final retrofit_version = '2.9.0'
|
||||
@@ -214,10 +214,10 @@ dependencies {
|
||||
implementation 'androidx.sqlite:sqlite:2.1.0'
|
||||
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
|
||||
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
|
||||
implementation 'io.requery:sqlite-android:3.31.0'
|
||||
implementation 'io.requery:sqlite-android:3.32.2'
|
||||
|
||||
// Preferences
|
||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.0'
|
||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.1'
|
||||
|
||||
// Model View Presenter
|
||||
final nucleus_version = '3.0.0'
|
||||
@@ -239,8 +239,7 @@ dependencies {
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
// Crash reports
|
||||
//final acra_version = '5.5.0'
|
||||
//implementation "ch.acra:acra-http:$acra_version"
|
||||
//implementation 'ch.acra:acra-http:5.7.0'
|
||||
|
||||
// Sort
|
||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||
@@ -278,13 +277,12 @@ dependencies {
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
||||
|
||||
// Licenses
|
||||
final aboutlibraries_version = '8.2.0'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$aboutlibraries_version"
|
||||
implementation "com.mikepenz:aboutlibraries:$aboutlibraries_version"
|
||||
// NOTE: REMEMBER TO UPDATE GRADLE PLUGIN
|
||||
implementation 'com.mikepenz:aboutlibraries:8.3.0'
|
||||
|
||||
// Tests
|
||||
testImplementation 'junit:junit:4.13'
|
||||
testImplementation 'org.assertj:assertj-core:3.12.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.16.1'
|
||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||
|
||||
final robolectric_version = '3.1.4'
|
||||
@@ -292,54 +290,38 @@ dependencies {
|
||||
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$BuildPluginsVersion.KOTLIN"
|
||||
|
||||
// SY for mangadex utils
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.0-RC"
|
||||
|
||||
|
||||
final coroutines_version = '1.3.8'
|
||||
final coroutines_version = '1.3.9'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version"
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||
|
||||
// Debug tool; see https://fbflipper.com/
|
||||
// debugImplementation 'com.facebook.flipper:flipper:0.49.0'
|
||||
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
|
||||
|
||||
// Text distance (EH)
|
||||
implementation 'info.debatty:java-string-similarity:1.2.1'
|
||||
|
||||
// Reprint (EH)
|
||||
implementation 'com.github.ajalt.reprint:core:3.2.1@aar'
|
||||
implementation 'com.github.ajalt.reprint:rxjava:3.2.1@aar' // optional: the RxJava 1 interface
|
||||
|
||||
// Swirl (EH)
|
||||
implementation 'com.mattprecious.swirl:swirl:1.2.0'
|
||||
|
||||
// RxJava 2 interop for Realm (EH)
|
||||
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
|
||||
|
||||
// Firebase (EH)
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx:17.5.0'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ktx:17.2.1'
|
||||
|
||||
// Better logging (EH)
|
||||
implementation 'com.elvishew:xlog:1.6.1'
|
||||
|
||||
// Time utils (EH)
|
||||
def typed_time_version = '1.0.2'
|
||||
implementation "com.github.kizitonwose.time:time:$typed_time_version"
|
||||
implementation "com.github.kizitonwose.time:time-android:$typed_time_version"
|
||||
|
||||
// Debug utils (EH)
|
||||
debugImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseTestImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
testImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
final def debug_overlay_version = '1.1.3'
|
||||
debugImplementation "com.ms-square:debugoverlay:$debug_overlay_version"
|
||||
releaseTestImplementation "com.ms-square:debugoverlay:$debug_overlay_version"
|
||||
releaseImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
|
||||
testImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
|
||||
|
||||
// Humanize (EH)
|
||||
// Humanize (EH) used for E-Hentai updater statistics
|
||||
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
||||
|
||||
// RatingBar (SY)
|
||||
@@ -347,7 +329,7 @@ dependencies {
|
||||
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
||||
final def markwon_version = '4.1.0'
|
||||
final def markwon_version = '4.5.1'
|
||||
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
|
||||
@@ -356,16 +338,15 @@ dependencies {
|
||||
implementation "io.noties.markwon:image:$markwon_version"
|
||||
implementation "io.noties.markwon:linkify:$markwon_version"
|
||||
|
||||
implementation 'com.google.guava:guava:27.0.1-android'
|
||||
implementation 'com.google.guava:guava:29.0-android'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.72'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$BuildPluginsVersion.KOTLIN"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +356,7 @@ repositories {
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
||||
tasks.withType(AbstractKotlinCompile).all {
|
||||
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
|
||||
kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.Experimental"]
|
||||
}
|
||||
|
||||
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||
@@ -385,10 +366,10 @@ task copyResources(type: Copy) {
|
||||
include '**/*'
|
||||
}
|
||||
|
||||
preBuild.dependsOn(ktlintFormat, copyResources)
|
||||
preBuild.dependsOn(formatKotlin, copyResources)
|
||||
|
||||
if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Debug")) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
// Firebase (EH)
|
||||
apply plugin: 'io.fabric'
|
||||
}
|
||||
// Firebase Crashlytics
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
}
|
||||
@@ -37,6 +37,14 @@
|
||||
public *;
|
||||
}
|
||||
|
||||
# Hitomi extension crash fix
|
||||
-keepclassmembers class rx.Single {
|
||||
*** onSubscribe;
|
||||
final *;
|
||||
protected *;
|
||||
public *;
|
||||
}
|
||||
|
||||
# RxJava 1.1.0
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
@@ -282,7 +282,7 @@
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- MangaDex -->
|
||||
<!-- <data
|
||||
<data
|
||||
android:host="mangadex.org"
|
||||
android:pathPattern="\/(title|manga)\/"
|
||||
android:scheme="http" />
|
||||
@@ -297,7 +297,7 @@
|
||||
<data
|
||||
android:host="www.mangadex.org"
|
||||
android:pathPattern="\/(title|manga)\/"
|
||||
android:scheme="https" />-->
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
||||
@@ -23,7 +23,9 @@ 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.kizitonwose.time.days
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.ms_square.debugoverlay.DebugOverlay
|
||||
import com.ms_square.debugoverlay.modules.FpsModule
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
@@ -34,13 +36,9 @@ import exh.debug.DebugToggles
|
||||
import exh.log.CrashlyticsPrinter
|
||||
import exh.log.EHDebugModeOverlay
|
||||
import exh.log.EHLogLevel
|
||||
import exh.syDebugVersion
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.io.File
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.Security
|
||||
import javax.net.ssl.SSLContext
|
||||
import kotlin.concurrent.thread
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.conscrypt.Conscrypt
|
||||
@@ -49,13 +47,23 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.InjektScope
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
import java.io.File
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.Security
|
||||
import javax.net.ssl.SSLContext
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.days
|
||||
|
||||
open class App : Application(), LifecycleObserver {
|
||||
|
||||
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||
setupExhLogging() // EXH logging
|
||||
if (!BuildConfig.DEBUG) addAnalytics()
|
||||
|
||||
workaroundAndroid7BrokenSSL()
|
||||
|
||||
@@ -77,8 +85,8 @@ open class App : Application(), LifecycleObserver {
|
||||
Injekt.importModule(AppModule(this))
|
||||
|
||||
setupNotificationChannels()
|
||||
Realm.init(this)
|
||||
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
|
||||
// Reprint.initialize(this) //Setup fingerprint (EH)
|
||||
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
||||
setupDebugOverlay()
|
||||
}
|
||||
@@ -118,6 +126,13 @@ open class App : Application(), LifecycleObserver {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAnalytics() {
|
||||
firebaseAnalytics = Firebase.analytics
|
||||
if (syDebugVersion != "0") {
|
||||
firebaseAnalytics.setUserProperty("preview_version", syDebugVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
@Suppress("unused")
|
||||
fun onAppBackgrounded() {
|
||||
@@ -133,7 +148,6 @@ open class App : Application(), LifecycleObserver {
|
||||
|
||||
// EXH
|
||||
private fun deleteOldMetadataRealm() {
|
||||
Realm.init(this)
|
||||
val config = RealmConfiguration.Builder()
|
||||
.name("gallery-metadata.realm")
|
||||
.schemaVersion(3)
|
||||
@@ -159,15 +173,14 @@ open class App : Application(), LifecycleObserver {
|
||||
private fun setupExhLogging() {
|
||||
EHLogLevel.init(this)
|
||||
|
||||
val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) {
|
||||
LogLevel.ALL
|
||||
} else {
|
||||
LogLevel.WARN
|
||||
val logLevel = when {
|
||||
EHLogLevel.shouldLog(EHLogLevel.EXTRA) -> LogLevel.ALL
|
||||
BuildConfig.DEBUG -> LogLevel.DEBUG
|
||||
else -> LogLevel.WARN
|
||||
}
|
||||
|
||||
val logConfig = LogConfiguration.Builder()
|
||||
.logLevel(logLevel)
|
||||
.t()
|
||||
.st(2)
|
||||
.nb()
|
||||
.build()
|
||||
@@ -180,14 +193,17 @@ open class App : Application(), LifecycleObserver {
|
||||
"logs"
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
printers += FilePrinter
|
||||
.Builder(logFolder.absolutePath)
|
||||
.fileNameGenerator(object : DateFileNameGenerator() {
|
||||
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}"
|
||||
.fileNameGenerator(
|
||||
object : DateFileNameGenerator() {
|
||||
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}.log"
|
||||
}
|
||||
}
|
||||
})
|
||||
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue))
|
||||
)
|
||||
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.toLongMilliseconds()))
|
||||
.backupStrategy(NeverBackupStrategy())
|
||||
.build()
|
||||
|
||||
@@ -202,6 +218,17 @@ open class App : Application(), LifecycleObserver {
|
||||
)
|
||||
|
||||
XLog.d("Application booting...")
|
||||
XLog.nst().d(
|
||||
"App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})\n" +
|
||||
"Preview build: $syDebugVersion\n" +
|
||||
"Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) \n" +
|
||||
"Android build ID: ${Build.DISPLAY}\n" +
|
||||
"Device brand: ${Build.BRAND}\n" +
|
||||
"Device manufacturer: ${Build.MANUFACTURER}\n" +
|
||||
"Device name: ${Build.DEVICE}\n" +
|
||||
"Device model: ${Build.MODEL}\n" +
|
||||
"Device product name: ${Build.PRODUCT}"
|
||||
)
|
||||
}
|
||||
|
||||
// EXH
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.annoations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Nsfw
|
||||
@@ -8,9 +8,9 @@ import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||
if (interval > 0) {
|
||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
@@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MERGEDMANGAREFERENCES
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.SAVEDSEARCHES
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
||||
@@ -39,6 +40,7 @@ import eu.kanade.tachiyomi.data.backup.serializer.CategoryTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.backup.serializer.ChapterTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.backup.serializer.HistoryTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.backup.serializer.MangaTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.backup.serializer.MergedMangaReferenceTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.backup.serializer.TrackTypeAdapter
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||
@@ -57,17 +59,23 @@ import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import exh.EXHSavedSearch
|
||||
import exh.MERGED_SOURCE_ID
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import java.lang.RuntimeException
|
||||
import kotlin.math.max
|
||||
import exh.merged.sql.models.MergedMangaReference
|
||||
import exh.util.asObservable
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
import java.lang.RuntimeException
|
||||
import kotlin.math.max
|
||||
|
||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
@@ -106,6 +114,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
||||
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
||||
// SY -->
|
||||
.registerTypeAdapter<MergedMangaReference>(MergedMangaReferenceTypeAdapter.build())
|
||||
// SY <--
|
||||
.create()
|
||||
else -> throw Exception("Json version unknown")
|
||||
}
|
||||
@@ -129,15 +140,21 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
// 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 {
|
||||
// Get manga from database
|
||||
val mangas = getFavoriteManga()
|
||||
val mangas = getFavoriteManga() /* SY --> */ + getMergedManga() /* SY <-- */
|
||||
|
||||
val extensions: MutableSet<String> = mutableSetOf()
|
||||
|
||||
@@ -163,6 +180,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
// SY -->
|
||||
root[SAVEDSEARCHES] =
|
||||
Injekt.get<PreferencesHelper>().eh_savedSearches().get().joinToString(separator = "***")
|
||||
|
||||
backupMergedMangaReferences(mergedMangaReferenceEntries)
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -212,6 +231,13 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
private fun backupMergedMangaReferences(root: JsonArray) {
|
||||
val mergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
|
||||
mergedMangaReferences.forEach { root.add(parser.toJsonTree(it)) }
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Backup the categories of library
|
||||
*
|
||||
@@ -317,29 +343,40 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
*/
|
||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
// SY -->
|
||||
return (
|
||||
if (source is EHentai) {
|
||||
source.fetchChapterList(manga, throttleManager::throttle)
|
||||
} else {
|
||||
source.fetchChapterList(manga)
|
||||
}
|
||||
).map {
|
||||
if (it.last().chapter_number == -99F) {
|
||||
chapters.forEach { chapter ->
|
||||
chapter.name = "Chapter ${chapter.chapter_number} restored by dummy source"
|
||||
}
|
||||
syncChaptersWithSource(databaseHelper, chapters, manga, source)
|
||||
} else {
|
||||
syncChaptersWithSource(databaseHelper, it, manga, source)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
.doOnNext { pair ->
|
||||
if (source is MergedSource) {
|
||||
val syncedChapters = runBlocking { source.fetchChaptersAndSync(manga, false) }
|
||||
return syncedChapters.onEach { pair ->
|
||||
if (pair.first.isNotEmpty()) {
|
||||
chapters.forEach { it.manga_id = manga.id }
|
||||
insertChapters(chapters)
|
||||
}
|
||||
}.asObservable()
|
||||
} else {
|
||||
return (
|
||||
if (source is EHentai) {
|
||||
source.fetchChapterList(manga, throttleManager::throttle)
|
||||
} else {
|
||||
source.fetchChapterList(manga)
|
||||
}
|
||||
).map {
|
||||
if (it.last().chapter_number == -99F) {
|
||||
chapters.forEach { chapter ->
|
||||
chapter.name =
|
||||
"Chapter ${chapter.chapter_number} restored by dummy source"
|
||||
}
|
||||
syncChaptersWithSource(databaseHelper, chapters, manga, source)
|
||||
} else {
|
||||
syncChaptersWithSource(databaseHelper, it, manga, source)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
.doOnNext { pair ->
|
||||
if (pair.first.isNotEmpty()) {
|
||||
chapters.forEach { it.manga_id = manga.id }
|
||||
insertChapters(chapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -584,6 +621,49 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
}
|
||||
preferences.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the categories from Json
|
||||
*
|
||||
* @param jsonMergedMangaReferences array containing md manga references
|
||||
*/
|
||||
internal fun restoreMergedMangaReferences(jsonMergedMangaReferences: JsonArray) {
|
||||
// Get merged manga references from file and from db
|
||||
val dbMergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
|
||||
val backupMergedMangaReferences = parser.fromJson<List<MergedMangaReference>>(jsonMergedMangaReferences)
|
||||
var lastMergeManga: Manga? = null
|
||||
|
||||
// Iterate over them
|
||||
backupMergedMangaReferences.forEach { mergedMangaReference ->
|
||||
// Used to know if the merged manga reference is already in the db
|
||||
var found = false
|
||||
for (dbMergedMangaReference in dbMergedMangaReferences) {
|
||||
// If the mergedMangaReference is already in the db, assign the id to the file's mergedMangaReference
|
||||
// and do nothing
|
||||
if (mergedMangaReference.mergeUrl == dbMergedMangaReference.mergeUrl && mergedMangaReference.mangaUrl == dbMergedMangaReference.mangaUrl) {
|
||||
mergedMangaReference.id = dbMergedMangaReference.id
|
||||
mergedMangaReference.mergeId = dbMergedMangaReference.mergeId
|
||||
mergedMangaReference.mangaId = dbMergedMangaReference.mangaId
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the mergedMangaReference isn't in the db, remove the id and insert a new mergedMangaReference
|
||||
// Store the inserted id in the mergedMangaReference
|
||||
if (!found) {
|
||||
// Let the db assign the id
|
||||
val mergedManga = (if (mergedMangaReference.mergeUrl != lastMergeManga?.url) databaseHelper.getManga(mergedMangaReference.mergeUrl, MERGED_SOURCE_ID).executeAsBlocking() else lastMergeManga) ?: return@forEach
|
||||
val manga = databaseHelper.getManga(mergedMangaReference.mangaUrl, mergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach
|
||||
lastMergeManga = mergedManga
|
||||
|
||||
mergedMangaReference.mergeId = mergedManga.id
|
||||
mergedMangaReference.mangaId = manga.id
|
||||
mergedMangaReference.id = null
|
||||
val result = databaseHelper.insertMergedManga(mergedMangaReference).executeAsBlocking()
|
||||
mergedMangaReference.id = result.insertedId()
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
@@ -602,6 +682,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
internal fun getFavoriteManga(): List<Manga> =
|
||||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||
|
||||
internal fun getMergedManga(): List<Manga> =
|
||||
databaseHelper.getMergedMangas().executeAsBlocking()
|
||||
|
||||
/**
|
||||
* Inserts manga and returns id
|
||||
*
|
||||
|
||||
@@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class BackupNotifier(private val context: Context) {
|
||||
|
||||
|
||||
@@ -33,14 +33,11 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.EXHMigrations
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -48,6 +45,10 @@ import kotlinx.coroutines.launch
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Restores backup from a JSON file.
|
||||
@@ -238,7 +239,7 @@ class BackupRestoreService : Service() {
|
||||
}
|
||||
|
||||
totalAmount = mangasJson.size()
|
||||
restoreAmount = validManga.count() + 1 // +1 for categories
|
||||
restoreAmount = validManga.count() + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
|
||||
skippedAmount = mangasJson.size() - validManga.count()
|
||||
// SY <--
|
||||
restoreProgress = 0
|
||||
@@ -288,6 +289,15 @@ class BackupRestoreService : Service() {
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.saved_searches))
|
||||
}
|
||||
|
||||
private fun restoreMergedMangaReferences(mergedMangaReferencesJson: JsonElement) {
|
||||
db.inTransaction {
|
||||
backupManager.restoreMergedMangaReferences(mergedMangaReferencesJson.asJsonArray)
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
|
||||
}
|
||||
// SY <--
|
||||
|
||||
private fun restoreManga(mangaJson: JsonObject) {
|
||||
@@ -445,7 +455,12 @@ class BackupRestoreService : Service() {
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||
val errorMessage = if (it is NoChaptersException) {
|
||||
getString(R.string.no_chapters_error)
|
||||
} else {
|
||||
it.message
|
||||
}
|
||||
errors.add(Date() to "${manga.title} - $errorMessage")
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,22 @@ import com.google.gson.JsonParser
|
||||
import com.google.gson.stream.JsonReader
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
object BackupRestoreValidator {
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Checks for critical backup file data.
|
||||
*
|
||||
* @throws Exception if version or manga cannot be found.
|
||||
* @return List of required sources.
|
||||
* @return List of missing sources or missing trackers.
|
||||
*/
|
||||
fun validate(context: Context, uri: Uri): Map<Long, String> {
|
||||
fun validate(context: Context, uri: Uri): Results {
|
||||
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||
val json = JsonParser.parseReader(reader).asJsonObject
|
||||
|
||||
@@ -26,11 +32,29 @@ object BackupRestoreValidator {
|
||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||
}
|
||||
|
||||
if (mangasJson.asJsonArray.size() == 0) {
|
||||
val mangas = mangasJson.asJsonArray
|
||||
if (mangas.size() == 0) {
|
||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||
}
|
||||
|
||||
return getSourceMapping(json)
|
||||
val sources = getSourceMapping(json)
|
||||
val missingSources = sources
|
||||
.filter { sourceManager.get(it.key) == null }
|
||||
.values
|
||||
.sorted()
|
||||
|
||||
val trackers = mangas
|
||||
.filter { it.asJsonObject.has("track") }
|
||||
.flatMap { it.asJsonObject["track"].asJsonArray }
|
||||
.map { it.asJsonObject["s"].asInt }
|
||||
.distinct()
|
||||
val missingTrackers = trackers
|
||||
.mapNotNull { trackManager.getService(it) }
|
||||
.filter { !it.isLogged }
|
||||
.map { it.name }
|
||||
.sorted()
|
||||
|
||||
return Results(missingSources, missingTrackers)
|
||||
}
|
||||
|
||||
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||
@@ -43,4 +67,6 @@ object BackupRestoreValidator {
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ object Backup {
|
||||
const val VERSION = "version"
|
||||
// SY -->
|
||||
const val SAVEDSEARCHES = "savedsearches"
|
||||
const val MERGEDMANGAREFERENCES = "mergedmangareferences"
|
||||
// SY <--
|
||||
|
||||
fun getDefaultFilename(): String {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.kanade.tachiyomi.data.backup.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -22,6 +20,8 @@ import okio.buffer
|
||||
import okio.sink
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Class used to create chapter cache
|
||||
|
||||
@@ -21,6 +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.MangaQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||
import exh.merged.sql.mappers.MergedMangaTypeMapping
|
||||
import exh.merged.sql.models.MergedMangaReference
|
||||
import exh.merged.sql.queries.MergedQueries
|
||||
import exh.metadata.sql.mappers.SearchMetadataTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTagTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTitleTypeMapping
|
||||
@@ -36,7 +39,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* EXH --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries /* EXH <-- */ {
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
@@ -51,11 +54,12 @@ open class DatabaseHelper(context: Context) :
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
// EXH -->
|
||||
// SY -->
|
||||
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||
// EXH <--
|
||||
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
||||
// SY <--
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
|
||||
@@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||
import exh.merged.sql.tables.MergedTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
import exh.metadata.sql.tables.SearchTagTable
|
||||
import exh.metadata.sql.tables.SearchTitleTable
|
||||
@@ -24,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = /* SY --> */ 3 /* SY <-- */
|
||||
const val DATABASE_VERSION = /* SY --> */ 4 /* SY <-- */
|
||||
}
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||
@@ -34,14 +34,12 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(CategoryTable.createTableQuery)
|
||||
execSQL(MangaCategoryTable.createTableQuery)
|
||||
execSQL(HistoryTable.createTableQuery)
|
||||
// EXH -->
|
||||
// SY -->
|
||||
execSQL(SearchMetadataTable.createTableQuery)
|
||||
execSQL(SearchTagTable.createTableQuery)
|
||||
execSQL(SearchTitleTable.createTableQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
execSQL(MergedTable.createTableQuery)
|
||||
// AZ <--
|
||||
// SY <--
|
||||
|
||||
// DB indexes
|
||||
execSQL(MangaTable.createUrlIndexQuery)
|
||||
@@ -49,17 +47,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
// EXH -->
|
||||
db.execSQL(SearchMetadataTable.createUploaderIndexQuery)
|
||||
db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
|
||||
db.execSQL(SearchTagTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTagTable.createNamespaceNameIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
// SY -->
|
||||
execSQL(SearchMetadataTable.createUploaderIndexQuery)
|
||||
execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
|
||||
execSQL(SearchTagTable.createMangaIdIndexQuery)
|
||||
execSQL(SearchTagTable.createNamespaceNameIndexQuery)
|
||||
execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||
execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||
execSQL(MergedTable.createIndexQuery)
|
||||
// AZ <--
|
||||
// SY <--
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
@@ -70,6 +66,11 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
db.execSQL(MangaTable.addDateAdded)
|
||||
db.execSQL(MangaTable.backfillDateAdded)
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
db.execSQL(MergedTable.dropTableQuery)
|
||||
db.execSQL(MergedTable.createTableQuery)
|
||||
db.execSQL(MergedTable.createIndexQuery)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||
|
||||
@@ -5,4 +5,8 @@ class LibraryManga : MangaImpl() {
|
||||
var unread: Int = 0
|
||||
|
||||
var category: Int = 0
|
||||
|
||||
// SY -->
|
||||
var read: Int = 0
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ interface Manga : SManga {
|
||||
return genre?.split(", ")?.map { it.trim() }
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun getOriginalGenres(): List<String>? {
|
||||
return originalGenre?.split(", ")?.map { it.trim() }
|
||||
}
|
||||
// SY <--
|
||||
|
||||
private fun setFlags(flag: Int, mask: Int) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import java.util.Date
|
||||
|
||||
interface ChapterQueries : DbProvider {
|
||||
// SY -->
|
||||
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
|
||||
fun getChapters(manga: Manga) = getChapters(manga.id)
|
||||
|
||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||
fun getChapters(mangaId: Long?) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
@@ -27,15 +27,6 @@ interface ChapterQueries : DbProvider {
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedChaptersQuery(mangaId))
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
// SY <--
|
||||
|
||||
fun getRecentChapters(date: Date) = db.get()
|
||||
@@ -94,6 +85,17 @@ interface ChapterQueries : DbProvider {
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChaptersReadByUrls(urls: List<String>) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} IN (?) AND (${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0)")
|
||||
.whereArgs(urls.joinToString { "\"$it\"" })
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
// SY <--
|
||||
|
||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||
|
||||
@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import exh.merged.sql.tables.MergedTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
|
||||
interface MangaQueries : DbProvider {
|
||||
@@ -77,15 +78,6 @@ interface MangaQueries : DbProvider {
|
||||
.prepare()
|
||||
|
||||
// SY -->
|
||||
fun getMergedMangas(id: Long) = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedMangaQuery(id))
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun updateMangaInfo(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaInfoPutResolver())
|
||||
@@ -139,7 +131,7 @@ interface MangaQueries : DbProvider {
|
||||
.byQuery(
|
||||
DeleteQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_FAVORITE} = ?")
|
||||
.where("${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE})")
|
||||
.whereArgs(0)
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
package eu.kanade.tachiyomi.data.database.queries
|
||||
|
||||
import exh.MERGED_SOURCE_ID
|
||||
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.HistoryTable as History
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
|
||||
import exh.merged.sql.tables.MergedTable as Merged
|
||||
|
||||
// SY -->
|
||||
/**
|
||||
* Query to get the manga merged into a merged manga
|
||||
*/
|
||||
fun getMergedMangaQuery(id: Long) =
|
||||
fun getMergedMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
|
||||
) AS M
|
||||
JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get all the manga that are merged into other manga
|
||||
*/
|
||||
fun getAllMergedMangaQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE}
|
||||
) AS M
|
||||
JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the manga merged into a merged manga using the Url
|
||||
*/
|
||||
fun getMergedMangaFromUrlQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_URL} = ?
|
||||
) AS M
|
||||
JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||
@@ -24,16 +51,15 @@ fun getMergedMangaQuery(id: Long) =
|
||||
/**
|
||||
* Query to get the chapters of all manga in a merged manga
|
||||
*/
|
||||
fun getMergedChaptersQuery(id: Long) =
|
||||
fun getMergedChaptersQuery() =
|
||||
"""
|
||||
SELECT ${Chapter.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
|
||||
) AS M
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Query to get the manga from the library, with their categories and unread count.
|
||||
@@ -42,23 +68,55 @@ val libraryQuery =
|
||||
"""
|
||||
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
|
||||
FROM (
|
||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}
|
||||
FROM ${Manga.TABLE}
|
||||
LEFT JOIN (
|
||||
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
|
||||
FROM ${Chapter.TABLE}
|
||||
WHERE ${Chapter.COL_READ} = 0
|
||||
GROUP BY ${Chapter.COL_MANGA_ID}
|
||||
) AS C
|
||||
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
GROUP BY ${Manga.COL_ID}
|
||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
|
||||
FROM ${Manga.TABLE}
|
||||
LEFT JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
|
||||
FROM ${Chapter.TABLE}
|
||||
WHERE ${Chapter.COL_READ} = 0
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
) AS C
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
|
||||
LEFT JOIN (
|
||||
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS read
|
||||
FROM ${Chapter.TABLE}
|
||||
WHERE ${Chapter.COL_READ} = 1
|
||||
GROUP BY ${Chapter.COL_MANGA_ID}
|
||||
) AS R
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} <> $MERGED_SOURCE_ID
|
||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||
UNION
|
||||
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
|
||||
FROM ${Manga.TABLE}
|
||||
LEFT JOIN (
|
||||
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as unread
|
||||
FROM ${Merged.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
||||
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 0
|
||||
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
|
||||
) AS C
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Merged.COL_MERGE_ID}
|
||||
LEFT JOIN (
|
||||
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as read
|
||||
FROM ${Merged.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
|
||||
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 1
|
||||
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
|
||||
) AS R
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Merged.COL_MERGE_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} = $MERGED_SOURCE_ID
|
||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||
ORDER BY ${Manga.COL_TITLE}
|
||||
) AS M
|
||||
LEFT JOIN (
|
||||
SELECT * FROM ${MangaCategory.TABLE}) AS MC
|
||||
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID}
|
||||
SELECT * FROM ${MangaCategory.TABLE}
|
||||
) AS MC
|
||||
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID};
|
||||
"""
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Query to get the recent chapters of manga from the library up to a date.
|
||||
|
||||
@@ -18,6 +18,9 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
|
||||
mapBaseFromCursor(manga, cursor)
|
||||
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
|
||||
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY))
|
||||
// SY -->
|
||||
manga.read = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_READ))
|
||||
// SY <--
|
||||
|
||||
return manga
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ object MangaTable {
|
||||
|
||||
const val COL_UNREAD = "unread"
|
||||
|
||||
// SY ->>
|
||||
const val COL_READ = "read"
|
||||
// SY <--
|
||||
|
||||
const val COL_CATEGORY = "category"
|
||||
|
||||
const val COL_COVER_LAST_MODIFIED = "cover_last_modified"
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object MergedTable {
|
||||
|
||||
const val TABLE = "merged"
|
||||
|
||||
const val COL_MERGE_ID = "mergeID"
|
||||
|
||||
const val COL_MANGA_ID = "mangaID"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_MERGE_ID INTEGER NOT NULL,
|
||||
$COL_MANGA_ID INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
val createIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_MERGE_ID}_index ON $TABLE($COL_MERGE_ID)"
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
||||
@@ -81,7 +81,7 @@ class DownloadCache(
|
||||
if (sourceDir != null) {
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
|
||||
if (mangaDir != null) {
|
||||
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files }
|
||||
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files || "$it.cbz" in mangaDir.files }
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -145,7 +145,7 @@ class DownloadCache(
|
||||
mangaDirs.values.forEach { mangaDir ->
|
||||
val chapterDirs = mangaDir.dir.listFiles()
|
||||
.orEmpty()
|
||||
.mapNotNull { it.name }
|
||||
.mapNotNull { it.name?.replace(".cbz", "") }
|
||||
.toHashSet()
|
||||
|
||||
mangaDir.files = chapterDirs
|
||||
@@ -196,10 +196,24 @@ class DownloadCache(
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
} else if ("$it.cbz" in mangaDir.files) {
|
||||
mangaDir.files -= "$it.cbz"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun removeFolders(folders: List<String>, manga: Manga) {
|
||||
val sourceDir = rootDir.files[manga.source] ?: return
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||
folders.forEach { chapter ->
|
||||
if (chapter in mangaDir.files) {
|
||||
mangaDir.files -= chapter
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Removes a list of chapters that have been deleted from this cache.
|
||||
*
|
||||
@@ -214,6 +228,8 @@ class DownloadCache(
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
} else if ("$it.cbz" in mangaDir.files) {
|
||||
mangaDir.files -= "$it.cbz"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@@ -24,10 +25,8 @@ import uy.kohesive.injekt.injectLazy
|
||||
*/
|
||||
class DownloadManager(/* SY private */ val context: Context) {
|
||||
|
||||
/**
|
||||
* The sources manager.
|
||||
*/
|
||||
private val sourceManager by injectLazy<SourceManager>()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* Downloads provider, used to retrieve the folders where the chapters are or should be stored.
|
||||
@@ -199,16 +198,74 @@ class DownloadManager(/* SY private */ val context: Context) {
|
||||
* @param manga the manga of the chapters.
|
||||
* @param source the source of the chapters.
|
||||
*/
|
||||
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source) {
|
||||
queue.remove(chapters)
|
||||
val chapterDirs = provider.findChapterDirs(chapters, manga, source)
|
||||
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
|
||||
val filteredChapters = getChaptersToDelete(chapters)
|
||||
|
||||
queue.remove(filteredChapters)
|
||||
|
||||
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
|
||||
chapterDirs.forEach { it.delete() }
|
||||
cache.removeChapters(chapters, manga)
|
||||
cache.removeChapters(filteredChapters, manga)
|
||||
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
|
||||
chapterDirs.firstOrNull()?.parentFile?.delete()
|
||||
}
|
||||
|
||||
return filteredChapters
|
||||
}
|
||||
|
||||
// SY -->
|
||||
/**
|
||||
* return the list of all manga folders
|
||||
*/
|
||||
fun getMangaFolders(source: Source): List<UniFile> {
|
||||
return provider.findSourceDir(source)?.listFiles()?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the directories of chapters that were read or have no match
|
||||
*
|
||||
* @param chapters the list of chapters to delete.
|
||||
* @param manga the manga of the chapters.
|
||||
* @param source the source of the chapters.
|
||||
*/
|
||||
fun cleanupChapters(allChapters: List<Chapter>, manga: Manga, source: Source, removeRead: Boolean, removeNonFavorite: Boolean): Int {
|
||||
var cleaned = 0
|
||||
|
||||
if (removeNonFavorite && !manga.favorite) {
|
||||
val mangaFolder = provider.getMangaDir(manga, source)
|
||||
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0)
|
||||
mangaFolder.delete()
|
||||
cache.removeManga(manga)
|
||||
return cleaned
|
||||
}
|
||||
|
||||
val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source)
|
||||
cleaned += filesWithNoChapter.size
|
||||
cache.removeFolders(filesWithNoChapter.mapNotNull { it.name }, manga)
|
||||
filesWithNoChapter.forEach { it.delete() }
|
||||
|
||||
if (removeRead) {
|
||||
val readChapters = allChapters.filter { it.read }
|
||||
val readChapterDirs = provider.findChapterDirs(readChapters, manga, source)
|
||||
readChapterDirs.forEach { it.delete() }
|
||||
cleaned += readChapterDirs.size
|
||||
cache.removeChapters(readChapters, manga)
|
||||
}
|
||||
|
||||
if (cache.getDownloadCount(manga) == 0) {
|
||||
val mangaFolder = provider.getMangaDir(manga, source)
|
||||
val size = mangaFolder.listFiles()?.size ?: 0
|
||||
if (size == 0) {
|
||||
mangaFolder.delete()
|
||||
cache.removeManga(manga)
|
||||
} else {
|
||||
Timber.e("Cache and download folder doesn't match for %s", manga.title)
|
||||
}
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Deletes the directory of a downloaded manga.
|
||||
*
|
||||
@@ -228,7 +285,7 @@ class DownloadManager(/* SY private */ val context: Context) {
|
||||
* @param manga the manga of the chapters.
|
||||
*/
|
||||
fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) {
|
||||
pendingDeleter.addChapters(chapters, manga)
|
||||
pendingDeleter.addChapters(getChaptersToDelete(chapters), manga)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,14 +314,22 @@ class DownloadManager(/* SY private */ val context: Context) {
|
||||
|
||||
// Assume there's only 1 version of the chapter name formats present
|
||||
val oldFolder = oldNames.asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") }
|
||||
.firstOrNull()
|
||||
|
||||
if (oldFolder?.renameTo(newName) == true) {
|
||||
if (oldFolder?.renameTo(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "") == true) {
|
||||
cache.removeChapter(oldChapter, manga)
|
||||
cache.addChapter(newName, mangaDir, manga)
|
||||
cache.addChapter(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "", mangaDir, manga)
|
||||
} else {
|
||||
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChaptersToDelete(chapters: List<Chapter>): List<Chapter> {
|
||||
return if (!preferences.removeBookmarkedChapters()) {
|
||||
chapters.filterNot { it.bookmark }
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.regex.Pattern
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
@@ -79,7 +79,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
* Dismiss the downloader's notification. Downloader error notifications use a different id, so
|
||||
* those can only be dismissed by the user.
|
||||
*/
|
||||
fun dismiss() {
|
||||
fun dismissProgress() {
|
||||
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
}
|
||||
|
||||
val downloadingProgressText = context.getString(
|
||||
R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size
|
||||
R.string.chapter_downloading_progress,
|
||||
download.downloadedImages,
|
||||
download.pages!!.size
|
||||
)
|
||||
|
||||
if (preferences.hideNotificationContent()) {
|
||||
|
||||
@@ -89,7 +89,7 @@ class DownloadProvider(private val context: Context) {
|
||||
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
|
||||
val mangaDir = findMangaDir(manga, source)
|
||||
return getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir?.findFile(it) }
|
||||
.mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
@@ -104,11 +104,37 @@ class DownloadProvider(private val context: Context) {
|
||||
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
||||
return chapters.mapNotNull { chapter ->
|
||||
getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") }
|
||||
.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
/**
|
||||
* Returns a list of all files in manga directory
|
||||
*
|
||||
* @param chapters the chapters to query.
|
||||
* @param manga the manga of the chapter.
|
||||
* @param source the source of the chapter.
|
||||
*/
|
||||
fun findUnmatchedChapterDirs(
|
||||
chapters: List<Chapter>,
|
||||
manga: Manga,
|
||||
source: Source
|
||||
): List<UniFile> {
|
||||
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
||||
return mangaDir.listFiles()!!.asList().filter {
|
||||
(
|
||||
chapters.find { chp ->
|
||||
getValidChapterDirNames(chp).any { dir ->
|
||||
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
|
||||
}
|
||||
} == null
|
||||
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Returns the download directory name for a source.
|
||||
*
|
||||
|
||||
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
@@ -22,7 +23,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.async
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
@@ -30,7 +30,13 @@ import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import rx.subscriptions.CompositeSubscription
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.util.zip.CRC32
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class is the one in charge of downloading chapters.
|
||||
@@ -53,6 +59,8 @@ class Downloader(
|
||||
private val sourceManager: SourceManager
|
||||
) {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
private val chapterCache: ChapterCache by injectLazy()
|
||||
|
||||
/**
|
||||
@@ -139,6 +147,7 @@ class Downloader(
|
||||
notifier.paused = false
|
||||
notifier.onPaused()
|
||||
} else {
|
||||
notifier.dismissProgress()
|
||||
notifier.onComplete()
|
||||
}
|
||||
}
|
||||
@@ -170,7 +179,7 @@ class Downloader(
|
||||
.forEach { it.status = Download.NOT_DOWNLOADED }
|
||||
}
|
||||
queue.clear()
|
||||
notifier.dismiss()
|
||||
notifier.dismissProgress()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,15 +275,16 @@ class Downloader(
|
||||
* @param download the chapter to be downloaded.
|
||||
*/
|
||||
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||
|
||||
if (DiskUtil.getAvailableStorageSpace(mangaDir) < MIN_DISK_SPACE) {
|
||||
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
|
||||
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
|
||||
download.status = Download.ERROR
|
||||
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
|
||||
return@defer Observable.just(download)
|
||||
}
|
||||
|
||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
|
||||
|
||||
val pageListObservable = if (download.pages == null) {
|
||||
@@ -462,7 +472,39 @@ class Downloader(
|
||||
|
||||
// Only rename the directory if it's downloaded.
|
||||
if (download.status == Download.DOWNLOADED) {
|
||||
tmpDir.renameTo(dirname)
|
||||
if (preferences.saveChaptersAsCBZ().get()) {
|
||||
val zip = mangaDir.createFile("$dirname.cbz.tmp")
|
||||
val zipOut = ZipOutputStream(BufferedOutputStream(zip.openOutputStream()))
|
||||
val compressionLevel = preferences.saveChaptersAsCBZLevel().get()
|
||||
|
||||
zipOut.setLevel(compressionLevel)
|
||||
|
||||
if (compressionLevel == 0) {
|
||||
zipOut.setMethod(ZipEntry.STORED)
|
||||
}
|
||||
|
||||
tmpDir.listFiles()?.forEach { img ->
|
||||
val input = img.openInputStream()
|
||||
val data = input.readBytes()
|
||||
val entry = ZipEntry(img.name)
|
||||
if (compressionLevel == 0) {
|
||||
val crc = CRC32()
|
||||
val size = img.length()
|
||||
crc.update(data)
|
||||
entry.crc = crc.value
|
||||
entry.compressedSize = size
|
||||
entry.size = size
|
||||
}
|
||||
zipOut.putNextEntry(entry)
|
||||
zipOut.write(data)
|
||||
input.close()
|
||||
}
|
||||
zipOut.close()
|
||||
zip.renameTo("$dirname.cbz")
|
||||
tmpDir.delete()
|
||||
} else {
|
||||
tmpDir.renameTo(dirname)
|
||||
}
|
||||
cache.addChapter(dirname, mangaDir, download.manga)
|
||||
|
||||
DiskUtil.createNoMediaFile(tmpDir, context)
|
||||
|
||||
@@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadStore
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class DownloadQueue(
|
||||
private val store: DownloadStore,
|
||||
|
||||
@@ -5,12 +5,12 @@ import android.util.Log
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import timber.log.Timber
|
||||
|
||||
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.isLocal
|
||||
import java.io.InputStream
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
||||
|
||||
@@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import java.io.InputStream
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Class used to update Glide module settings
|
||||
|
||||
@@ -29,7 +29,8 @@ class CustomMangaManager(val context: Context) {
|
||||
|
||||
val json = try {
|
||||
Gson().fromJson(
|
||||
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java
|
||||
Scanner(editJson).useDelimiter("\\Z").next(),
|
||||
JsonObject::class.java
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
@@ -83,7 +84,12 @@ class CustomMangaManager(val context: Context) {
|
||||
|
||||
fun Manga.toJson(): MangaJson {
|
||||
return MangaJson(
|
||||
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray()
|
||||
id!!,
|
||||
title,
|
||||
author,
|
||||
artist,
|
||||
description,
|
||||
genre?.split(", ")?.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
||||
@@ -17,14 +17,15 @@ import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LibraryUpdateNotifier(private val context: Context) {
|
||||
|
||||
@@ -65,7 +66,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
* @param current the current progress.
|
||||
* @param total the total progress.
|
||||
*/
|
||||
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
||||
fun showProgressNotification(manga: /* SY --> */ SManga /* SY <-- */, current: Int, total: Int) {
|
||||
val title = if (preferences.hideNotificationContent()) {
|
||||
context.getString(R.string.notification_check_updates)
|
||||
} else {
|
||||
@@ -198,18 +199,23 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
|
||||
// Mark chapters as read action
|
||||
addAction(
|
||||
R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read),
|
||||
R.drawable.ic_glasses_black_24dp,
|
||||
context.getString(R.string.action_mark_as_read),
|
||||
NotificationReceiver.markAsReadPendingBroadcast(
|
||||
context,
|
||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||
manga,
|
||||
chapters,
|
||||
Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
// View chapters action
|
||||
addAction(
|
||||
R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters),
|
||||
R.drawable.ic_book_24dp,
|
||||
context.getString(R.string.action_view_chapters),
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
context,
|
||||
manga, Notifications.ID_NEW_CHAPTERS
|
||||
manga,
|
||||
Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import com.elvishew.xlog.XLog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
@@ -17,10 +19,15 @@ import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||
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.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryGroup
|
||||
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
@@ -28,14 +35,25 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import exh.MERGED_SOURCE_ID
|
||||
import exh.md.utils.FollowStatus
|
||||
import exh.md.utils.MdUtil
|
||||
import exh.metadata.metadata.base.insertFlatMetadata
|
||||
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||
import exh.util.asObservable
|
||||
import exh.util.await
|
||||
import exh.util.awaitSingle
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* This class will take care of updating the chapters of the manga from the library. It can be
|
||||
@@ -72,7 +90,10 @@ class LibraryUpdateService(
|
||||
enum class Target {
|
||||
CHAPTERS, // Manga chapters
|
||||
COVERS, // Manga covers
|
||||
TRACKING // Tracking metadata
|
||||
TRACKING, // Tracking metadata
|
||||
// SY -->
|
||||
SYNC_FOLLOWS // MangaDex specific, pull mangadex manga in reading, rereading
|
||||
// SY <--
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -87,6 +108,14 @@ class LibraryUpdateService(
|
||||
*/
|
||||
const val KEY_TARGET = "target"
|
||||
|
||||
// SY -->
|
||||
/**
|
||||
* Key for group to update.
|
||||
*/
|
||||
const val KEY_GROUP = "group"
|
||||
const val KEY_GROUP_EXTRA = "group_extra"
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Returns the status of the service.
|
||||
*
|
||||
@@ -106,11 +135,15 @@ class LibraryUpdateService(
|
||||
* @param target defines what should be updated.
|
||||
* @return true if service newly started, false otherwise
|
||||
*/
|
||||
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS): Boolean {
|
||||
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS /* SY --> */, group: Int = LibraryGroup.BY_DEFAULT, groupExtra: String? = null /* SY <-- */): Boolean {
|
||||
if (!isRunning(context)) {
|
||||
val intent = Intent(context, LibraryUpdateService::class.java).apply {
|
||||
putExtra(KEY_TARGET, target)
|
||||
category?.let { putExtra(KEY_CATEGORY, it.id) }
|
||||
// SY -->
|
||||
putExtra(KEY_GROUP, group)
|
||||
groupExtra?.let { putExtra(KEY_GROUP_EXTRA, it) }
|
||||
// SY <--
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
context.startService(intent)
|
||||
@@ -194,6 +227,9 @@ class LibraryUpdateService(
|
||||
Target.CHAPTERS -> updateChapterList(mangaList)
|
||||
Target.COVERS -> updateCovers(mangaList)
|
||||
Target.TRACKING -> updateTrackings(mangaList)
|
||||
// SY -->
|
||||
Target.SYNC_FOLLOWS -> syncFollows()
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
@@ -221,10 +257,15 @@ class LibraryUpdateService(
|
||||
*/
|
||||
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
|
||||
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||
// SY -->
|
||||
val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
|
||||
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
|
||||
// SY <--
|
||||
|
||||
var listToUpdate = if (categoryId != -1) {
|
||||
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
||||
} else {
|
||||
// SY -->
|
||||
} else if (group == LibraryGroup.BY_DEFAULT || groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.GLOBAL || (groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)) {
|
||||
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
|
||||
if (categoriesToUpdate.isNotEmpty()) {
|
||||
db.getLibraryMangas().executeAsBlocking()
|
||||
@@ -233,6 +274,43 @@ class LibraryUpdateService(
|
||||
} else {
|
||||
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
||||
}
|
||||
} else {
|
||||
val libraryManga = db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
||||
when (group) {
|
||||
LibraryGroup.BY_TRACK_STATUS -> {
|
||||
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
|
||||
libraryManga.filter {
|
||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||
val status: String = {
|
||||
val tracks = db.getTracks(it).executeAsBlocking()
|
||||
val track = tracks.find { track ->
|
||||
loggedServices.any { it.id == track?.sync_id }
|
||||
}
|
||||
val service = loggedServices.find { it.id == track?.sync_id }
|
||||
if (track != null && service != null) {
|
||||
service.getStatus(track.status)
|
||||
} else {
|
||||
"not tracked"
|
||||
}
|
||||
}()
|
||||
trackManager.mapTrackingOrder(status, applicationContext) == trackingExtra
|
||||
}
|
||||
}
|
||||
LibraryGroup.BY_SOURCE -> {
|
||||
val sourceExtra = intent.getStringExtra(KEY_GROUP_EXTRA).nullIfBlank()
|
||||
val source = sourceManager.getCatalogueSources().find { it.name == sourceExtra }
|
||||
if (source != null) libraryManga.filter { it.source == source.id } else emptyList()
|
||||
}
|
||||
LibraryGroup.BY_STATUS -> {
|
||||
val statusExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
|
||||
libraryManga.filter {
|
||||
it.status == statusExtra
|
||||
}
|
||||
}
|
||||
LibraryGroup.UNGROUPED -> libraryManga
|
||||
else -> libraryManga
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
|
||||
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
||||
@@ -275,7 +353,12 @@ class LibraryUpdateService(
|
||||
updateManga(manga)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
failedUpdates.add(Pair(manga, it.message))
|
||||
val errorMessage = if (it is NoChaptersException) {
|
||||
getString(R.string.no_chapters_error)
|
||||
} else {
|
||||
it.message
|
||||
}
|
||||
failedUpdates.add(Pair(manga, errorMessage))
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
@@ -328,7 +411,12 @@ class LibraryUpdateService(
|
||||
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||
// We don't want to start downloading while the library is updating, because websites
|
||||
// may don't like it and they could ban the user.
|
||||
downloadManager.downloadChapters(manga, chapters, false)
|
||||
// SY -->
|
||||
val chapterFilter = if (manga.source == MERGED_SOURCE_ID) {
|
||||
db.getMergedMangaReferences(manga.id!!).executeAsBlocking().filterNot { it.downloadChapters }.mapNotNull { it.mangaId }
|
||||
} else emptyList()
|
||||
// SY <--
|
||||
downloadManager.downloadChapters(manga, /* SY --> */ chapters.filter { it.manga_id !in chapterFilter } /* SY <-- */, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,7 +426,7 @@ class LibraryUpdateService(
|
||||
* @return a pair of the inserted and removed chapters.
|
||||
*/
|
||||
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
val source = sourceManager.get(manga.source) ?: return Observable.empty()
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
// Update manga details metadata in the background
|
||||
if (preferences.autoUpdateMetadata()) {
|
||||
@@ -360,8 +448,38 @@ class LibraryUpdateService(
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
return source.fetchChapterList(manga)
|
||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||
// SY -->
|
||||
if (source.getMainSource() is MangaDex && trackManager.mdList.isLogged) {
|
||||
try {
|
||||
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
|
||||
var track = trackManager.mdList.createInitialTracker(manga)
|
||||
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XLog.e(e)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
return (
|
||||
/* SY --> */ if (source is MergedSource) runBlocking { source.fetchChaptersAndSync(manga, false).asObservable() }
|
||||
else /* SY <-- */ source.fetchChapterList(manga)
|
||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||
// SY -->
|
||||
)
|
||||
.doOnNext {
|
||||
if (source.getMainSource() is MangaDex) {
|
||||
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
|
||||
var track = trackManager.mdList.createInitialTracker(manga)
|
||||
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
||||
@@ -427,6 +545,48 @@ class LibraryUpdateService(
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
// filter all follows from Mangadex and only add reading or rereading manga to library
|
||||
private fun syncFollows(): Observable<LibraryManga> {
|
||||
val count = AtomicInteger(0)
|
||||
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager)!!
|
||||
return mangaDex.fetchAllFollows(true)
|
||||
.asObservable()
|
||||
.map { listManga ->
|
||||
listManga.filter { (_, metadata) ->
|
||||
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
|
||||
}
|
||||
}
|
||||
.doOnNext { listManga ->
|
||||
listManga.forEach { (networkManga, metadata) ->
|
||||
notifier.showProgressNotification(networkManga, count.andIncrement, listManga.size)
|
||||
var dbManga = db.getManga(networkManga.url, mangaDex.id)
|
||||
.executeAsBlocking()
|
||||
if (dbManga == null) {
|
||||
dbManga = Manga.create(
|
||||
networkManga.url,
|
||||
networkManga.title,
|
||||
mangaDex.id
|
||||
)
|
||||
dbManga.date_added = Date().time
|
||||
}
|
||||
|
||||
dbManga.copyFrom(networkManga)
|
||||
dbManga.favorite = true
|
||||
val id = db.insertManga(dbManga).executeAsBlocking().insertedId()
|
||||
if (id != null) {
|
||||
metadata.mangaId = id
|
||||
db.insertFlatMetadata(metadata.flatten()).await()
|
||||
}
|
||||
}
|
||||
}
|
||||
.doOnCompleted {
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
.map { LibraryManga() }
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Writes basic file of update errors to cache dir.
|
||||
*/
|
||||
@@ -437,7 +597,8 @@ class LibraryUpdateService(
|
||||
|
||||
destFile.bufferedWriter().use { out ->
|
||||
errors.forEach { (manga, error) ->
|
||||
out.write("${manga.title}: $error\n")
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
out.write("${manga.title} ($source): $error\n")
|
||||
}
|
||||
}
|
||||
return destFile
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
@@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import java.io.File
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
|
||||
/**
|
||||
* Global [BroadcastReceiver] that runs on UI thread
|
||||
@@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Launch share activity and dismiss notification
|
||||
ACTION_SHARE_IMAGE ->
|
||||
shareImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Delete image from path and dismiss notification
|
||||
ACTION_DELETE_IMAGE ->
|
||||
deleteImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Share backup file
|
||||
ACTION_SHARE_BACKUP ->
|
||||
shareBackup(
|
||||
context, intent.getParcelableExtra(EXTRA_URI),
|
||||
context,
|
||||
intent.getParcelableExtra(EXTRA_URI),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
ACTION_CANCEL_RESTORE -> cancelRestore(
|
||||
@@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Open reader activity
|
||||
ACTION_OPEN_CHAPTER -> {
|
||||
openChapter(
|
||||
context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
context,
|
||||
intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
||||
)
|
||||
}
|
||||
@@ -155,7 +159,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
* @param mangaId id of manga
|
||||
* @param chapterId id of chapter
|
||||
*/
|
||||
internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
||||
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
||||
val db = DatabaseHelper(context)
|
||||
val manga = db.getManga(mangaId).executeAsBlocking()
|
||||
val chapter = db.getChapter(chapterId).executeAsBlocking()
|
||||
|
||||
@@ -82,53 +82,62 @@ object Notifications {
|
||||
|
||||
listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
CHANNEL_COMMON,
|
||||
context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_LIBRARY, context.getString(R.string.channel_library),
|
||||
CHANNEL_LIBRARY,
|
||||
context.getString(R.string.channel_library),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_DOWNLOADER_PROGRESS,
|
||||
context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
|
||||
CHANNEL_DOWNLOADER_COMPLETE,
|
||||
context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors),
|
||||
CHANNEL_DOWNLOADER_ERROR,
|
||||
context.getString(R.string.channel_errors),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
|
||||
CHANNEL_NEW_CHAPTERS,
|
||||
context.getString(R.string.channel_new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates),
|
||||
CHANNEL_UPDATES_TO_EXTS,
|
||||
context.getString(R.string.channel_ext_updates),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS,
|
||||
context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE,
|
||||
context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
|
||||
@@ -97,6 +97,8 @@ object PreferenceKeys {
|
||||
|
||||
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
|
||||
|
||||
const val removeBookmarkedChapters = "pref_remove_bookmarked"
|
||||
|
||||
const val libraryUpdateInterval = "pref_library_update_interval_key"
|
||||
|
||||
const val libraryUpdateRestriction = "library_update_restriction"
|
||||
@@ -113,6 +115,8 @@ object PreferenceKeys {
|
||||
|
||||
const val filterCompleted = "pref_filter_completed_key"
|
||||
|
||||
const val filterStarted = "pref_filter_started_key"
|
||||
|
||||
const val filterTracked = "pref_filter_tracked_key"
|
||||
|
||||
const val filterLewd = "pref_filter_lewd_key"
|
||||
@@ -121,6 +125,8 @@ object PreferenceKeys {
|
||||
|
||||
const val automaticExtUpdates = "automatic_ext_updates"
|
||||
|
||||
const val allowNsfwSource = "allow_nsfw_source"
|
||||
|
||||
const val startScreen = "start_screen"
|
||||
|
||||
const val useBiometricLock = "use_biometric_lock"
|
||||
@@ -183,8 +189,6 @@ object PreferenceKeys {
|
||||
|
||||
const val eh_lock_manually = "eh_lock_manually"
|
||||
|
||||
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
|
||||
|
||||
const val eh_showSyncIntro = "eh_show_sync_intro"
|
||||
|
||||
const val eh_readOnlySync = "eh_sync_read_only"
|
||||
@@ -237,8 +241,6 @@ object PreferenceKeys {
|
||||
|
||||
const val eh_aggressivePageLoading = "eh_aggressive_page_loading"
|
||||
|
||||
const val eh_hl_useHighQualityThumbs = "eh_hl_hq_thumbs"
|
||||
|
||||
const val eh_preload_size = "eh_preload_size"
|
||||
|
||||
const val eh_tag_filtering_value = "eh_tag_filtering_value"
|
||||
@@ -273,7 +275,45 @@ object PreferenceKeys {
|
||||
|
||||
const val recommendsInOverflow = "recommends_in_overflow"
|
||||
|
||||
const val hitomiAlwaysWebp = "hitomi_always_webp"
|
||||
|
||||
const val enhancedEHentaiView = "enhanced_e_hentai_view"
|
||||
|
||||
const val webtoonEnableZoomOut = "webtoon_enable_zoom_out"
|
||||
|
||||
const val startReadingButton = "start_reading_button"
|
||||
|
||||
const val groupLibraryBy = "group_library_by"
|
||||
|
||||
const val continuousVerticalTappingByPage = "continuous_vertical_tapping_by_page"
|
||||
|
||||
const val groupLibraryUpdateType = "group_library_update_type"
|
||||
|
||||
const val useNewSourceNavigation = "use_new_source_navigation"
|
||||
|
||||
const val mangaDexLowQualityCovers = "manga_dex_low_quality_covers"
|
||||
|
||||
const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers"
|
||||
|
||||
const val preferredMangaDexId = "preferred_mangaDex_id"
|
||||
|
||||
const val dataSaver = "data_saver"
|
||||
|
||||
const val ignoreJpeg = "ignore_jpeg"
|
||||
|
||||
const val ignoreGif = "ignore_gif"
|
||||
|
||||
const val dataSaverImageQuality = "data_saver_image_quality"
|
||||
|
||||
const val dataSaverImageFormatJpeg = "data_saver_image_format_jpeg"
|
||||
|
||||
const val dataSaverServer = "data_saver_server"
|
||||
|
||||
const val dataSaverColorBW = "data_saver_color_bw"
|
||||
|
||||
const val saveChaptersAsCBZ = "save_chapter_as_cbz"
|
||||
|
||||
const val saveChaptersAsCBZLevel = "save_chapter_as_cbz_level"
|
||||
|
||||
const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders"
|
||||
|
||||
const val biometricTimeRanges = "biometric_time_ranges"
|
||||
}
|
||||
|
||||
@@ -23,11 +23,15 @@ object PreferenceValues {
|
||||
default,
|
||||
blue,
|
||||
amoled,
|
||||
red,
|
||||
}
|
||||
|
||||
enum class DisplayMode {
|
||||
COMPACT_GRID,
|
||||
COMFORTABLE_GRID,
|
||||
// SY -->
|
||||
NO_TITLE_GRID,
|
||||
// SY <--
|
||||
LIST,
|
||||
}
|
||||
|
||||
@@ -37,4 +41,18 @@ object PreferenceValues {
|
||||
VERTICAL,
|
||||
BOTH
|
||||
}
|
||||
|
||||
enum class NsfwAllowance {
|
||||
ALLOWED,
|
||||
PARTIAL,
|
||||
BLOCKED
|
||||
}
|
||||
|
||||
// SY -->
|
||||
enum class GroupLibraryMode {
|
||||
GLOBAL,
|
||||
ALL_BUT_UNGROUPED,
|
||||
ALL
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -7,18 +7,19 @@ import androidx.preference.PreferenceManager
|
||||
import com.tfcporciuncula.flow.FlowSharedPreferences
|
||||
import com.tfcporciuncula.flow.Preference
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
||||
@@ -113,7 +114,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun zoomStart() = flowPrefs.getInt(Keys.zoomStart, 1)
|
||||
|
||||
fun readerTheme() = flowPrefs.getInt(Keys.readerTheme, 1)
|
||||
fun readerTheme() = flowPrefs.getInt(Keys.readerTheme, 3)
|
||||
|
||||
fun alwaysShowChapterTransition() = flowPrefs.getBoolean(Keys.alwaysShowChapterTransition, true)
|
||||
|
||||
@@ -187,6 +188,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
|
||||
|
||||
fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false)
|
||||
|
||||
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
|
||||
|
||||
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi"))
|
||||
@@ -212,6 +215,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0)
|
||||
|
||||
fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, 0)
|
||||
|
||||
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0)
|
||||
|
||||
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0)
|
||||
@@ -222,6 +227,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
|
||||
|
||||
fun allowNsfwSource() = flowPrefs.getEnum(Keys.allowNsfwSource, NsfwAllowance.ALLOWED)
|
||||
|
||||
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
|
||||
|
||||
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
|
||||
@@ -307,8 +314,6 @@ class PreferencesHelper(val context: Context) {
|
||||
fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
|
||||
fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
|
||||
|
||||
fun eh_nh_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
|
||||
|
||||
fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
|
||||
|
||||
fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
|
||||
@@ -351,8 +356,6 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
|
||||
|
||||
fun eh_hl_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_hl_useHighQualityThumbs, false)
|
||||
|
||||
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
|
||||
|
||||
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
|
||||
@@ -377,7 +380,45 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false)
|
||||
|
||||
fun hitomiAlwaysWebp() = flowPrefs.getBoolean(Keys.hitomiAlwaysWebp, true)
|
||||
|
||||
fun enhancedEHentaiView() = flowPrefs.getBoolean(Keys.enhancedEHentaiView, true)
|
||||
|
||||
fun webtoonEnableZoomOut() = flowPrefs.getBoolean(Keys.webtoonEnableZoomOut, false)
|
||||
|
||||
fun startReadingButton() = flowPrefs.getBoolean(Keys.startReadingButton, true)
|
||||
|
||||
fun groupLibraryBy() = flowPrefs.getInt(Keys.groupLibraryBy, 0)
|
||||
|
||||
fun continuousVerticalTappingByPage() = flowPrefs.getBoolean(Keys.continuousVerticalTappingByPage, false)
|
||||
|
||||
fun groupLibraryUpdateType() = flowPrefs.getEnum(Keys.groupLibraryUpdateType, Values.GroupLibraryMode.GLOBAL)
|
||||
|
||||
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, false)
|
||||
|
||||
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
|
||||
|
||||
fun mangaDexForceLatestCovers() = flowPrefs.getBoolean(Keys.mangaDexForceLatestCovers, false)
|
||||
|
||||
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
|
||||
|
||||
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
|
||||
|
||||
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)
|
||||
|
||||
fun ignoreGif() = flowPrefs.getBoolean(Keys.ignoreGif, true)
|
||||
|
||||
fun dataSaverImageQuality() = flowPrefs.getInt(Keys.dataSaverImageQuality, 80)
|
||||
|
||||
fun dataSaverImageFormatJpeg() = flowPrefs.getBoolean(Keys.dataSaverImageFormatJpeg, false)
|
||||
|
||||
fun dataSaverServer() = flowPrefs.getString(Keys.dataSaverServer, "")
|
||||
|
||||
fun dataSaverColorBW() = flowPrefs.getBoolean(Keys.dataSaverColorBW, false)
|
||||
|
||||
fun saveChaptersAsCBZ() = flowPrefs.getBoolean(Keys.saveChaptersAsCBZ, false)
|
||||
|
||||
fun saveChaptersAsCBZLevel() = flowPrefs.getInt(Keys.saveChaptersAsCBZLevel, 0)
|
||||
|
||||
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false)
|
||||
|
||||
fun biometricTimeRanges() = flowPrefs.getStringSet(Keys.biometricTimeRanges, mutableSetOf())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.preference
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceDataStore
|
||||
|
||||
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {
|
||||
@@ -10,7 +11,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
prefs.edit().putBoolean(key, value).apply()
|
||||
prefs.edit {
|
||||
putBoolean(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInt(key: String?, defValue: Int): Int {
|
||||
@@ -18,7 +21,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
prefs.edit().putInt(key, value).apply()
|
||||
prefs.edit {
|
||||
putInt(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLong(key: String?, defValue: Long): Long {
|
||||
@@ -26,7 +31,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
prefs.edit().putLong(key, value).apply()
|
||||
prefs.edit {
|
||||
putLong(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFloat(key: String?, defValue: Float): Float {
|
||||
@@ -34,7 +41,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
prefs.edit().putFloat(key, value).apply()
|
||||
prefs.edit {
|
||||
putFloat(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getString(key: String?, defValue: String?): String? {
|
||||
@@ -42,7 +51,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putString(key: String?, value: String?) {
|
||||
prefs.edit().putString(key, value).apply()
|
||||
prefs.edit {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||
@@ -50,6 +61,8 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
||||
}
|
||||
|
||||
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
||||
prefs.edit().putStringSet(key, values).apply()
|
||||
prefs.edit {
|
||||
putStringSet(key, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
|
||||
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
||||
import eu.kanade.tachiyomi.data.track.mdlist.MdList
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||
|
||||
@@ -15,8 +16,24 @@ class TrackManager(context: Context) {
|
||||
const val KITSU = 3
|
||||
const val SHIKIMORI = 4
|
||||
const val BANGUMI = 5
|
||||
|
||||
// SY --> Mangadex from Neko
|
||||
const val MDLIST = 6
|
||||
// SY <--
|
||||
|
||||
// SY -->
|
||||
const val READING = 1
|
||||
const val REREADING = 2
|
||||
const val PLANTOREAD = 3
|
||||
const val PAUSED = 4
|
||||
const val COMPLETED = 5
|
||||
const val DROPPED = 6
|
||||
const val OTHER = 7
|
||||
// SY <--
|
||||
}
|
||||
|
||||
val mdList = MdList(context, MDLIST)
|
||||
|
||||
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
||||
|
||||
val aniList = Anilist(context, ANILIST)
|
||||
@@ -27,9 +44,25 @@ class TrackManager(context: Context) {
|
||||
|
||||
val bangumi = Bangumi(context, BANGUMI)
|
||||
|
||||
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi)
|
||||
val services = listOf(mdList, myAnimeList, aniList, kitsu, shikimori, bangumi)
|
||||
|
||||
fun getService(id: Int) = services.find { it.id == id }
|
||||
|
||||
fun hasLoggedServices() = services.any { it.isLogged }
|
||||
fun hasLoggedServices(isMangaDexManga: Boolean = true) = services.any { it.isLogged && ((it.id == MDLIST && isMangaDexManga) || it.id != MDLIST) }
|
||||
|
||||
// SY -->
|
||||
fun mapTrackingOrder(status: String, context: Context): Int {
|
||||
with(context) {
|
||||
return when (status) {
|
||||
getString(eu.kanade.tachiyomi.R.string.reading), getString(eu.kanade.tachiyomi.R.string.currently_reading) -> READING
|
||||
getString(eu.kanade.tachiyomi.R.string.repeating) -> REREADING
|
||||
getString(eu.kanade.tachiyomi.R.string.plan_to_read), getString(eu.kanade.tachiyomi.R.string.want_to_read) -> PLANTOREAD
|
||||
getString(eu.kanade.tachiyomi.R.string.on_hold), getString(eu.kanade.tachiyomi.R.string.paused) -> PAUSED
|
||||
getString(eu.kanade.tachiyomi.R.string.completed) -> COMPLETED
|
||||
getString(eu.kanade.tachiyomi.R.string.dropped) -> DROPPED
|
||||
else -> OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ import com.google.gson.JsonParser
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import java.util.Calendar
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import rx.Observable
|
||||
import java.util.Calendar
|
||||
|
||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|
||||
@@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
|
||||
return ALManga(
|
||||
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
|
||||
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(),
|
||||
date, struct["chapters"].nullInt ?: 0
|
||||
struct["id"].asInt,
|
||||
struct["title"]["romaji"].asString,
|
||||
struct["coverImage"]["large"].asString,
|
||||
struct["description"].nullString.orEmpty(),
|
||||
struct["type"].asString,
|
||||
struct["status"].nullString.orEmpty(),
|
||||
date,
|
||||
struct["chapters"].nullInt ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
data class ALManga(
|
||||
val media_id: Int,
|
||||
|
||||
@@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import java.net.URLEncoder
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URLEncoder
|
||||
|
||||
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import java.text.DecimalFormat
|
||||
import rx.Completable
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package eu.kanade.tachiyomi.data.track.mdlist
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import exh.md.utils.FollowStatus
|
||||
import exh.md.utils.MdUtil
|
||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadata
|
||||
import exh.util.asObservable
|
||||
import exh.util.floor
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Completable
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MdList(private val context: Context, id: Int) : TrackService(id) {
|
||||
|
||||
private val mdex by lazy { MdUtil.getEnabledMangaDex() }
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
|
||||
override val name = "MDList"
|
||||
|
||||
override fun getLogo(): Int {
|
||||
return R.drawable.ic_tracker_mangadex_logo
|
||||
}
|
||||
|
||||
override fun getLogoColor(): Int {
|
||||
return Color.rgb(43, 48, 53)
|
||||
}
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
return FollowStatus.values().map { it.int }
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): String =
|
||||
context.resources.getStringArray(R.array.md_follows_options).asList()[status]
|
||||
|
||||
override fun getScoreList() = IntRange(0, 10).map(Int::toString)
|
||||
|
||||
override fun displayScore(track: Track) = track.score.toInt().toString()
|
||||
|
||||
override fun add(track: Track): Observable<Track> {
|
||||
return update(track)
|
||||
}
|
||||
|
||||
override fun update(track: Track): Observable<Track> {
|
||||
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||
return Observable.defer {
|
||||
db.getManga(track.tracking_url.substringAfter(".org"), mdex.id)
|
||||
.asRxObservable()
|
||||
.map { manga ->
|
||||
val mangaMetadata = db.getFlatMetadataForManga(manga.id!!).executeAsBlocking()?.raise(MangaDexSearchMetadata::class) ?: throw Exception("Invalid manga metadata")
|
||||
val followStatus = FollowStatus.fromInt(track.status)!!
|
||||
|
||||
// allow follow status to update
|
||||
if (mangaMetadata.follow_status != followStatus.int) {
|
||||
runBlocking { mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus).collect() }
|
||||
mangaMetadata.follow_status = followStatus.int
|
||||
db.insertFlatMetadata(mangaMetadata.flatten()).await()
|
||||
}
|
||||
|
||||
if (track.score.toInt() > 0) {
|
||||
runBlocking { mdex.updateRating(track).collect() }
|
||||
}
|
||||
|
||||
// mangadex wont update chapters if manga is not follows this prevents unneeded network call
|
||||
|
||||
if (followStatus != FollowStatus.UNFOLLOWED) {
|
||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||
track.status = FollowStatus.COMPLETED.int
|
||||
}
|
||||
|
||||
runBlocking { mdex.updateReadingProgress(track).collect() }
|
||||
} else if (track.last_chapter_read != 0) {
|
||||
// When followStatus has been changed to unfollowed 0 out read chapters since dex does
|
||||
track.last_chapter_read = 0
|
||||
}
|
||||
track
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCompletionStatus(): Int = FollowStatus.COMPLETED.int
|
||||
|
||||
override fun bind(track: Track): Observable<Track> {
|
||||
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
|
||||
.doOnNext { remoteTrack ->
|
||||
track.copyPersonalFrom(remoteTrack)
|
||||
track.total_chapters = if (remoteTrack.total_chapters == 0) {
|
||||
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
|
||||
} else {
|
||||
remoteTrack.total_chapters
|
||||
}
|
||||
update(track)
|
||||
}
|
||||
}
|
||||
|
||||
override fun refresh(track: Track): Observable<Track> {
|
||||
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
|
||||
.map { remoteTrack ->
|
||||
track.copyPersonalFrom(remoteTrack)
|
||||
track.total_chapters = if (remoteTrack.total_chapters == 0) {
|
||||
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
|
||||
} else {
|
||||
remoteTrack.total_chapters
|
||||
}
|
||||
track
|
||||
}
|
||||
}
|
||||
|
||||
fun createInitialTracker(manga: Manga): Track {
|
||||
val track = Track.create(TrackManager.MDLIST)
|
||||
track.manga_id = manga.id!!
|
||||
track.status = FollowStatus.UNFOLLOWED.int
|
||||
track.tracking_url = MdUtil.baseUrl + manga.url
|
||||
track.title = manga.title
|
||||
return track
|
||||
}
|
||||
|
||||
override fun search(query: String): Observable<List<TrackSearch>> = throw Exception("not used")
|
||||
|
||||
override fun login(username: String, password: String): Completable = throw Exception("not used")
|
||||
}
|
||||
@@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.util.lang.toCalendar
|
||||
import eu.kanade.tachiyomi.util.selectInt
|
||||
import eu.kanade.tachiyomi.util.selectText
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.zip.GZIPInputStream
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.parser.Parser
|
||||
import rx.Observable
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
||||
|
||||
@@ -476,7 +476,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||
fun copyPersonalFrom(track: Track) {
|
||||
num_read_chapters = track.last_chapter_read.toString()
|
||||
val numScore = track.score.toInt()
|
||||
if (numScore in 1..9) {
|
||||
if (numScore == 0) {
|
||||
score = ""
|
||||
} else if (numScore in 1..10) {
|
||||
score = numScore.toString()
|
||||
}
|
||||
status = track.status.toString()
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||
|
||||
abstract class UpdateChecker {
|
||||
|
||||
companion object {
|
||||
fun getUpdateChecker(): UpdateChecker {
|
||||
// SY -->
|
||||
return GithubUpdateChecker()
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns observable containing release information
|
||||
*/
|
||||
abstract suspend fun checkForUpdate(): UpdateResult
|
||||
}
|
||||
@@ -13,9 +13,10 @@ import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
override fun doWork(): Result {
|
||||
return runBlocking {
|
||||
try {
|
||||
val result = UpdateChecker.getUpdateChecker().checkForUpdate()
|
||||
val result = GithubUpdateChecker().checkForUpdate()
|
||||
|
||||
if (result is UpdateResult.NewUpdate<*>) {
|
||||
val url = result.release.downloadLink
|
||||
@@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
||||
3, TimeUnit.DAYS,
|
||||
3, TimeUnit.HOURS
|
||||
3,
|
||||
TimeUnit.DAYS,
|
||||
3,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
||||
@@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import java.io.File
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
class UpdaterService : Service() {
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
||||
|
||||
import eu.kanade.tachiyomi.data.updater.Release
|
||||
|
||||
class DevRepoRelease(override val info: String) : Release {
|
||||
|
||||
override val downloadLink: String
|
||||
get() = LATEST_URL
|
||||
|
||||
companion object {
|
||||
const val LATEST_URL = "https://tachiyomi.kanade.eu/latest"
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class DevRepoUpdateChecker : UpdateChecker() {
|
||||
|
||||
private val client: OkHttpClient by lazy {
|
||||
Injekt.get<NetworkHelper>().client.newBuilder()
|
||||
.followRedirects(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
private val versionRegex: Regex by lazy {
|
||||
Regex("tachiyomi-r(\\d+).apk")
|
||||
}
|
||||
|
||||
override suspend fun checkForUpdate(): UpdateResult {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
client.newCall(GET(DevRepoRelease.LATEST_URL)).await(assertSuccess = false)
|
||||
}
|
||||
|
||||
// Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
|
||||
val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
|
||||
|
||||
return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
|
||||
DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
|
||||
} else {
|
||||
DevRepoUpdateResult.NoNewUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
||||
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||
|
||||
sealed class DevRepoUpdateResult : UpdateResult() {
|
||||
|
||||
class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
|
||||
class NoNewUpdate : UpdateResult.NoNewUpdate()
|
||||
}
|
||||
@@ -28,5 +28,5 @@ class GithubRelease(
|
||||
* Assets class containing download url.
|
||||
* @param downloadLink download url.
|
||||
*/
|
||||
inner class Assets(@SerializedName("browser_download_url") val downloadLink: String)
|
||||
class Assets(@SerializedName("browser_download_url") val downloadLink: String)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Used to connect with the GitHub API.
|
||||
* Used to connect with the GitHub API to get the latest release version from a repo.
|
||||
*/
|
||||
interface GithubService {
|
||||
|
||||
@@ -24,11 +25,6 @@ interface GithubService {
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
@GET("/repos/jobobby04/tachiyomiSY/releases/latest")
|
||||
suspend fun getLatestVersion(): GithubRelease
|
||||
|
||||
@GET("/repos/jobobby04/TachiyomiSYPreview/releases/latest")
|
||||
suspend fun getLatestDebugVersion(): GithubRelease
|
||||
// SY <--
|
||||
@GET("/repos/{repo}/releases/latest")
|
||||
suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
|
||||
}
|
||||
|
||||
@@ -1,28 +1,49 @@
|
||||
package eu.kanade.tachiyomi.data.updater.github
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||
import exh.syDebugVersion
|
||||
// SY -->
|
||||
class GithubUpdateChecker(val debug: Boolean = false) : UpdateChecker() {
|
||||
|
||||
class GithubUpdateChecker {
|
||||
|
||||
private val service: GithubService = GithubService.create()
|
||||
|
||||
override suspend fun checkForUpdate(): UpdateResult {
|
||||
val release = if (syDebugVersion != "0") {
|
||||
service.getLatestDebugVersion()
|
||||
private val repo: String by lazy {
|
||||
// Sy -->
|
||||
if (syDebugVersion != "0") {
|
||||
"jobobby04/TachiyomiSYPreview"
|
||||
} else {
|
||||
service.getLatestVersion()
|
||||
"jobobby04/tachiyomiSY"
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
suspend fun checkForUpdate(): UpdateResult {
|
||||
val release = service.getLatestVersion(repo)
|
||||
|
||||
val newVersion = release.version
|
||||
// Check if latest version is different from current version
|
||||
// SY -->
|
||||
val newVersion = release.version
|
||||
return if ((newVersion != BuildConfig.VERSION_NAME && (syDebugVersion == "0")) || ((syDebugVersion != "0") && newVersion != syDebugVersion)) {
|
||||
// SY <--
|
||||
GithubUpdateResult.NewUpdate(release)
|
||||
} else {
|
||||
GithubUpdateResult.NoNewUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNewVersion(versionTag: String): Boolean {
|
||||
// Removes prefixes like "r" or "v"
|
||||
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
|
||||
|
||||
return if (BuildConfig.DEBUG) {
|
||||
// Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
|
||||
// tagged as something like "r1234"
|
||||
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
|
||||
} else {
|
||||
// Release builds: based on releases in "inorichi/tachiyomi" repo
|
||||
// tagged as something like "v0.1.2"
|
||||
newVersion != BuildConfig.VERSION_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@@ -19,13 +19,8 @@ import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.lang.launchNow
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EIGHTMUSES_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HITOMI_SOURCE_ID
|
||||
import exh.MERGED_SOURCE_ID
|
||||
import exh.NHENTAI_SOURCE_ID
|
||||
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||
import exh.source.BlacklistedSources
|
||||
import kotlinx.coroutines.async
|
||||
import rx.Observable
|
||||
@@ -83,11 +78,6 @@ class ExtensionManager(
|
||||
return when (source.id) {
|
||||
EH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
|
||||
EXH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
|
||||
PERV_EDEN_EN_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
|
||||
PERV_EDEN_IT_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
|
||||
NHENTAI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_nhentai_source)
|
||||
HITOMI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_hitomi_source)
|
||||
EIGHTMUSES_SOURCE_ID -> context.getDrawable(R.mipmap.ic_8muses_source)
|
||||
MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
@@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
||||
12, TimeUnit.HOURS,
|
||||
1, TimeUnit.HOURS
|
||||
12,
|
||||
TimeUnit.HOURS,
|
||||
1,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
||||
@@ -1,44 +1,30 @@
|
||||
package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import android.content.Context
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.get
|
||||
import com.github.salomonbrys.kotson.int
|
||||
import com.github.salomonbrys.kotson.string
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import exh.source.BlacklistedSources
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Date
|
||||
|
||||
internal class ExtensionGithubApi {
|
||||
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
suspend fun findExtensions(): List<Extension.Available> {
|
||||
val call = GET(EXT_URL)
|
||||
val service: ExtensionGithubService = ExtensionGithubService.create()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = network.client.newCall(call).await()
|
||||
if (response.isSuccessful) {
|
||||
parseResponse(response)
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Failed to get extensions")
|
||||
}
|
||||
val response = service.getRepo()
|
||||
parseResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +58,7 @@ internal class ExtensionGithubApi {
|
||||
return extensionsWithUpdate
|
||||
}
|
||||
|
||||
private fun parseResponse(response: Response): List<Extension.Available> {
|
||||
val text = response.body?.use { it.string() } ?: return emptyList()
|
||||
|
||||
val json = gson.fromJson<JsonArray>(text)
|
||||
|
||||
private fun parseResponse(json: JsonArray): List<Extension.Available> {
|
||||
return json
|
||||
.filter { element ->
|
||||
val versionName = element["version"].string
|
||||
@@ -90,14 +72,15 @@ internal class ExtensionGithubApi {
|
||||
val versionName = element["version"].string
|
||||
val versionCode = element["code"].int
|
||||
val lang = element["lang"].string
|
||||
val icon = "$REPO_URL/icon/${apkName.replace(".apk", ".png")}"
|
||||
val nsfw = element["nsfw"].int == 1
|
||||
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
|
||||
|
||||
Extension.Available(name, pkgName, versionName, versionCode, lang, apkName, icon)
|
||||
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
||||
}
|
||||
}
|
||||
|
||||
fun getApkUrl(extension: Extension.Available): String {
|
||||
return "$REPO_URL/apk/${extension.apkName}"
|
||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
||||
}
|
||||
|
||||
// SY -->
|
||||
@@ -110,7 +93,7 @@ internal class ExtensionGithubApi {
|
||||
// SY <--
|
||||
|
||||
companion object {
|
||||
private const val REPO_URL = "https://raw.githubusercontent.com/inorichi/tachiyomi-extensions/repo"
|
||||
private const val EXT_URL = "$REPO_URL/index.json"
|
||||
const val BASE_URL = "https://raw.githubusercontent.com/"
|
||||
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Used to get the extension repo listing from GitHub.
|
||||
*/
|
||||
interface ExtensionGithubService {
|
||||
|
||||
companion object {
|
||||
private val client by lazy {
|
||||
val network: NetworkHelper by injectLazy()
|
||||
network.client.newBuilder()
|
||||
.addNetworkInterceptor { chain ->
|
||||
val originalResponse = chain.proceed(chain.request())
|
||||
originalResponse.newBuilder()
|
||||
.header("Content-Encoding", "gzip")
|
||||
.header("Content-Type", "application/json")
|
||||
.build()
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
fun create(): ExtensionGithubService {
|
||||
val adapter = Retrofit.Builder()
|
||||
.baseUrl(ExtensionGithubApi.BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(client)
|
||||
.build()
|
||||
|
||||
return adapter.create(ExtensionGithubService::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}index.json.gz")
|
||||
suspend fun getRepo(): JsonArray
|
||||
}
|
||||
@@ -9,14 +9,16 @@ sealed class Extension {
|
||||
abstract val versionName: String
|
||||
abstract val versionCode: Int
|
||||
abstract val lang: String?
|
||||
abstract val isNsfw: Boolean
|
||||
|
||||
data class Installed(
|
||||
override val name: String,
|
||||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
val sources: List<Source>,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val sources: List<Source>,
|
||||
val hasUpdate: Boolean = false,
|
||||
val isObsolete: Boolean = false,
|
||||
val isUnofficial: Boolean = false,
|
||||
@@ -31,6 +33,7 @@ sealed class Extension {
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val apkName: String,
|
||||
val iconUrl: String
|
||||
) : Extension()
|
||||
@@ -41,6 +44,7 @@ sealed class Extension {
|
||||
override val versionName: String,
|
||||
override val versionCode: Int,
|
||||
val signatureHash: String,
|
||||
override val lang: String? = null
|
||||
override val lang: String? = null,
|
||||
override val isNsfw: Boolean = false
|
||||
) : Extension()
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* The installer which installs, updates and uninstalls the extensions.
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import dalvik.system.PathClassLoader
|
||||
import eu.kanade.tachiyomi.annoations.Nsfw
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
@@ -15,8 +17,7 @@ import eu.kanade.tachiyomi.util.lang.Hash
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Class that handles the loading of the extensions installed in the system.
|
||||
@@ -24,20 +25,25 @@ import uy.kohesive.injekt.api.get
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
internal object ExtensionLoader {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
private val allowNsfwSource by lazy {
|
||||
preferences.allowNsfwSource().get()
|
||||
}
|
||||
|
||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
||||
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
||||
const val LIB_VERSION_MIN = 1.2
|
||||
const val LIB_VERSION_MAX = 1.2
|
||||
|
||||
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
||||
|
||||
// inorichi's key
|
||||
val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||
/**
|
||||
* List of the trusted signatures.
|
||||
*/
|
||||
var trustedSignatures = mutableSetOf<String>() +
|
||||
Injekt.get<PreferencesHelper>().trustedSignatures().get() + officialSignature
|
||||
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
||||
|
||||
/**
|
||||
* Return a list of all the installed extensions initialized concurrently.
|
||||
@@ -125,6 +131,11 @@ internal object ExtensionLoader {
|
||||
return LoadResult.Untrusted(extension)
|
||||
}
|
||||
|
||||
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
||||
if (allowNsfwSource == PreferenceValues.NsfwAllowance.BLOCKED && isNsfw) {
|
||||
return LoadResult.Error("NSFW extension $pkgName not allowed")
|
||||
}
|
||||
|
||||
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
||||
|
||||
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
||||
@@ -141,7 +152,13 @@ internal object ExtensionLoader {
|
||||
try {
|
||||
when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
||||
is Source -> listOf(obj)
|
||||
is SourceFactory -> obj.createSources()
|
||||
is SourceFactory -> {
|
||||
if (isSourceNsfw(obj)) {
|
||||
emptyList()
|
||||
} else {
|
||||
obj.createSources()
|
||||
}
|
||||
}
|
||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -149,10 +166,11 @@ internal object ExtensionLoader {
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
.filter { !isSourceNsfw(it) }
|
||||
|
||||
val langs = sources.filterIsInstance<CatalogueSource>()
|
||||
.map { it.lang }
|
||||
.toSet()
|
||||
|
||||
val lang = when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
@@ -160,7 +178,13 @@ internal object ExtensionLoader {
|
||||
}
|
||||
|
||||
val extension = Extension.Installed(
|
||||
extName, pkgName, versionName, versionCode, sources, lang,
|
||||
extName,
|
||||
pkgName,
|
||||
versionName,
|
||||
versionCode,
|
||||
lang,
|
||||
isNsfw,
|
||||
sources,
|
||||
isUnofficial = signatureHash != officialSignature
|
||||
)
|
||||
return LoadResult.Success(extension)
|
||||
@@ -188,4 +212,22 @@ internal object ExtensionLoader {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
||||
*/
|
||||
private fun isSourceNsfw(clazz: Any): Boolean {
|
||||
if (allowNsfwSource == PreferenceValues.NsfwAllowance.ALLOWED) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (clazz !is Source && clazz !is SourceFactory) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Annotations are proxied, hence this janky way of checking for them
|
||||
return clazz.javaClass.annotations
|
||||
.flatMap { it.javaClass.interfaces.map { it.simpleName } }
|
||||
.firstOrNull { it == Nsfw::class.java.simpleName } != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,29 @@ package eu.kanade.tachiyomi.network
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.webkit.WebViewClientCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||
|
||||
@@ -91,7 +89,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||
var isWebViewOutdated = false
|
||||
|
||||
val origRequestUrl = request.url.toString()
|
||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
|
||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
|
||||
|
||||
handler.post {
|
||||
val webview = WebView(context)
|
||||
@@ -116,7 +115,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||
}
|
||||
|
||||
// HTTP error codes are only received since M
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR) &&
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
url == origRequestUrl && !challengeFound
|
||||
) {
|
||||
// The first request didn't return the challenge, abort.
|
||||
@@ -124,13 +123,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedHttpError(
|
||||
override fun onReceivedErrorCompat(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
errorResponse: WebResourceResponse
|
||||
errorCode: Int,
|
||||
description: String?,
|
||||
failingUrl: String,
|
||||
isMainFrame: Boolean
|
||||
) {
|
||||
if (request.isForMainFrame) {
|
||||
if (errorResponse.statusCode == 503) {
|
||||
if (isMainFrame) {
|
||||
if (errorCode == 503) {
|
||||
// Found the Cloudflare challenge page.
|
||||
challengeFound = true
|
||||
} else {
|
||||
|
||||
@@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.Cache
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) {
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@@ -13,6 +9,10 @@ import okhttp3.Response
|
||||
import rx.Observable
|
||||
import rx.Producer
|
||||
import rx.Subscription
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
fun Call.asObservable(): Observable<Response> {
|
||||
return Observable.unsafeCreate { subscriber ->
|
||||
@@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
|
||||
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
||||
suspend fun Call.await(assertSuccess: Boolean = false): Response {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (assertSuccess && !response.isSuccessful) {
|
||||
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||
return
|
||||
enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (assertSuccess && !response.isSuccessful) {
|
||||
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||
return
|
||||
}
|
||||
|
||||
continuation.resume(response)
|
||||
}
|
||||
|
||||
continuation.resume(response)
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
// Don't bother with resuming the continuation if it is already cancelled.
|
||||
if (continuation.isCancelled) return
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
// Don't bother with resuming the continuation if it is already cancelled.
|
||||
if (continuation.isCancelled) return
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import java.io.IOException
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.ResponseBody
|
||||
import okio.Buffer
|
||||
@@ -8,6 +7,7 @@ import okio.BufferedSource
|
||||
import okio.ForwardingSource
|
||||
import okio.Source
|
||||
import okio.buffer
|
||||
import java.io.IOException
|
||||
|
||||
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import java.util.concurrent.TimeUnit.MINUTES
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import java.util.concurrent.TimeUnit.MINUTES
|
||||
|
||||
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
|
||||
private val DEFAULT_HEADERS = Headers.Builder().build()
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParser
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
@@ -15,6 +16,12 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import junrar.Archive
|
||||
import junrar.rarfile.FileHeader
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
@@ -22,10 +29,6 @@ import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import junrar.Archive
|
||||
import junrar.rarfile.FileHeader
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
|
||||
class LocalSource(private val context: Context) : CatalogueSource {
|
||||
companion object {
|
||||
@@ -74,6 +77,9 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
val baseDirs = getBaseDirectories(context)
|
||||
// SY -->
|
||||
val allowLocalSourceHiddenFolders = Injekt.get<PreferencesHelper>().allowLocalSourceHiddenFolders().get()
|
||||
// SY <--
|
||||
|
||||
val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L
|
||||
var mangaDirs = baseDirs
|
||||
@@ -81,6 +87,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
.mapNotNull { it.listFiles()?.toList() }
|
||||
.flatten()
|
||||
.filter { it.isDirectory }
|
||||
.filterNot { it.name.startsWith('.') /* SY --> */ && !allowLocalSourceHiddenFolders /* SY <-- */ }
|
||||
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
|
||||
.distinctBy { it.name }
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.source.online.all.Hitomi
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||
@@ -20,20 +21,24 @@ import eu.kanade.tachiyomi.source.online.english.HentaiCafe
|
||||
import eu.kanade.tachiyomi.source.online.english.Pururin
|
||||
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EIGHTMUSES_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.HBROWSE_SOURCE_ID
|
||||
import exh.HENTAI_CAFE_SOURCE_ID
|
||||
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||
import exh.metadata.metadata.PervEdenLang
|
||||
import exh.PURURIN_SOURCE_ID
|
||||
import exh.TSUMINO_SOURCE_ID
|
||||
import exh.source.BlacklistedSources
|
||||
import exh.source.DelegatedHttpSource
|
||||
import exh.source.EnhancedHttpSource
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class SourceManager(private val context: Context) {
|
||||
|
||||
@@ -104,7 +109,7 @@ open class SourceManager(private val context: Context) {
|
||||
source,
|
||||
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context)
|
||||
)
|
||||
val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.originalSourceQualifiedClassName }
|
||||
val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.sourceId }
|
||||
currentDelegatedSources.plusAssign(map)
|
||||
enhancedSource
|
||||
} else source
|
||||
@@ -136,11 +141,6 @@ open class SourceManager(private val context: Context) {
|
||||
if (prefs.enableExhentai().get()) {
|
||||
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
||||
}
|
||||
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, PervEdenLang.en, context)
|
||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it, context)
|
||||
exSrcs += NHentai(context)
|
||||
exSrcs += Hitomi(context)
|
||||
exSrcs += EightMuses(context)
|
||||
return exSrcs
|
||||
}
|
||||
// SY <--
|
||||
@@ -173,42 +173,74 @@ open class SourceManager(private val context: Context) {
|
||||
|
||||
// SY -->
|
||||
companion object {
|
||||
private const val fillInSourceId = 9999L
|
||||
private const val fillInSourceId = Long.MAX_VALUE
|
||||
val DELEGATED_SOURCES = listOf(
|
||||
DelegatedSource(
|
||||
"Hentai Cafe",
|
||||
260868874183818481,
|
||||
HENTAI_CAFE_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe",
|
||||
HentaiCafe::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"Pururin",
|
||||
2221515250486218861,
|
||||
PURURIN_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.en.pururin.Pururin",
|
||||
Pururin::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"Tsumino",
|
||||
6707338697138388238,
|
||||
TSUMINO_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.en.tsumino.Tsumino",
|
||||
Tsumino::class
|
||||
)/*,
|
||||
),
|
||||
DelegatedSource(
|
||||
"MangaDex",
|
||||
fillInSourceId,
|
||||
"eu.kanade.tachiyomi.extension.all.mangadex",
|
||||
MangaDex::class,
|
||||
true
|
||||
)*/,
|
||||
),
|
||||
DelegatedSource(
|
||||
"HBrowse",
|
||||
1401584337232758222,
|
||||
HBROWSE_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse",
|
||||
HBrowse::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"8Muses",
|
||||
EIGHTMUSES_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.all.eromuse.EroMuse",
|
||||
EightMuses::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"Hitomi",
|
||||
fillInSourceId,
|
||||
"eu.kanade.tachiyomi.extension.all.hitomi.Hitomi",
|
||||
Hitomi::class,
|
||||
true
|
||||
),
|
||||
DelegatedSource(
|
||||
"PervEden English",
|
||||
PERV_EDEN_EN_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.en.perveden.Perveden",
|
||||
PervEden::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"PervEden Italian",
|
||||
PERV_EDEN_IT_SOURCE_ID,
|
||||
"eu.kanade.tachiyomi.extension.it.perveden.Perveden",
|
||||
PervEden::class
|
||||
),
|
||||
DelegatedSource(
|
||||
"NHentai",
|
||||
fillInSourceId,
|
||||
"eu.kanade.tachiyomi.extension.all.nhentai.NHentai",
|
||||
NHentai::class,
|
||||
true
|
||||
)
|
||||
).associateBy { it.originalSourceQualifiedClassName }
|
||||
|
||||
var currentDelegatedSources = mutableMapOf<String, DelegatedSource>()
|
||||
var currentDelegatedSources = mutableMapOf<Long, DelegatedSource>()
|
||||
|
||||
data class DelegatedSource(
|
||||
val sourceName: String,
|
||||
|
||||
@@ -2,16 +2,26 @@ package eu.kanade.tachiyomi.source.model
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.network.ProgressListener
|
||||
import exh.util.DataSaver
|
||||
import rx.subjects.Subject
|
||||
|
||||
open class Page(
|
||||
val index: Int,
|
||||
/* SY --> */
|
||||
var /* SY <-- */ url: String = "",
|
||||
var imageUrl: String? = null,
|
||||
/* SY --> var <-- SY */
|
||||
imageUrl: String? = null,
|
||||
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
|
||||
) : ProgressListener {
|
||||
|
||||
// SY -->
|
||||
var imageUrl = imageUrl
|
||||
get() {
|
||||
if (field == null) return null
|
||||
return DataSaver().compress(field!!)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
val number: Int
|
||||
get() = index + 1
|
||||
|
||||
|
||||
@@ -75,6 +75,11 @@ interface SManga : Serializable {
|
||||
const val ONGOING = 1
|
||||
const val COMPLETED = 2
|
||||
const val LICENSED = 3
|
||||
// SY --> Mangadex specific statuses
|
||||
const val PUBLICATION_COMPLETE = 61
|
||||
const val CANCELLED = 62
|
||||
const val HIATUS = 63
|
||||
// SY <--
|
||||
|
||||
fun create(): SManga {
|
||||
return SMangaImpl()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
|
||||
interface BrowseSourceFilterHeader {
|
||||
fun getFilterHeader(controller: Controller): RecyclerView.Adapter<*>
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import exh.md.utils.FollowStatus
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import rx.Observable
|
||||
|
||||
interface FollowsSource {
|
||||
fun fetchFollows(): Observable<MangasPage>
|
||||
|
||||
/**
|
||||
* Returns a list of all Follows retrieved by Coroutines
|
||||
*
|
||||
* @param SManga all smanga found for user
|
||||
*/
|
||||
fun fetchAllFollows(forceHd: Boolean = false): Flow<List<Pair<SManga, RaisedSearchMetadata>>>
|
||||
|
||||
/**
|
||||
* updates the follow status for a manga
|
||||
*/
|
||||
fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Get a MdList Track of the manga
|
||||
*/
|
||||
fun fetchTrackingInfo(url: String): Flow<Track>
|
||||
}
|
||||
@@ -13,11 +13,9 @@ import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import exh.log.maybeInjectEHLogger
|
||||
import exh.patch.injectPatches
|
||||
import exh.source.DelegatedHttpSource
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -25,6 +23,9 @@ import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* A simple implementation for sources from a website.
|
||||
@@ -36,22 +37,24 @@ abstract class HttpSource : CatalogueSource {
|
||||
*/
|
||||
// SY -->
|
||||
protected val network: NetworkHelper by lazy {
|
||||
val original = Injekt.get<NetworkHelper>()
|
||||
val network = Injekt.get<NetworkHelper>()
|
||||
object : NetworkHelper(Injekt.get<Application>()) {
|
||||
override val client: OkHttpClient
|
||||
get() = delegate?.networkHttpClient ?: original.client
|
||||
get() = delegate?.networkHttpClient ?: network.client
|
||||
.newBuilder()
|
||||
.injectPatches { id }
|
||||
.maybeInjectEHLogger()
|
||||
.build()
|
||||
|
||||
override val cloudflareClient: OkHttpClient
|
||||
get() = delegate?.networkCloudflareClient ?: original.cloudflareClient
|
||||
get() = delegate?.networkCloudflareClient ?: network.cloudflareClient
|
||||
.newBuilder()
|
||||
.injectPatches { id }
|
||||
.maybeInjectEHLogger()
|
||||
.build()
|
||||
|
||||
override val cookieManager: AndroidCookieJar
|
||||
get() = original.cookieManager
|
||||
get() = network.cookieManager
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
@@ -88,7 +91,7 @@ abstract class HttpSource : CatalogueSource {
|
||||
/**
|
||||
* Headers used for requests.
|
||||
*/
|
||||
val headers: Headers by lazy { headersBuilder().build() }
|
||||
/* SY --> */ open /* SY <-- */ val headers: Headers by lazy { headersBuilder().build() }
|
||||
|
||||
/**
|
||||
* Default network client for doing requests.
|
||||
|
||||