Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
706fa82a37 | |||
57c54fa275 | |||
38c5234e46 | |||
767ee164e6 |
24
.gitattributes
vendored
@ -1,24 +0,0 @@
|
|||||||
* text=auto
|
|
||||||
* text eol=lf
|
|
||||||
|
|
||||||
# Windows forced line-endings
|
|
||||||
/.idea/* text eol=crlf
|
|
||||||
|
|
||||||
# Gradle wrapper
|
|
||||||
*.jar binary
|
|
||||||
|
|
||||||
# Images
|
|
||||||
*.webp binary
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.ico binary
|
|
||||||
*.gz binary
|
|
||||||
*.zip binary
|
|
||||||
*.7z binary
|
|
||||||
*.ttf binary
|
|
||||||
*.eot binary
|
|
||||||
*.woff binary
|
|
||||||
*.pyc binary
|
|
||||||
*.swp binary
|
|
32
.github/CONTRIBUTING.md
vendored
@ -1,27 +1,20 @@
|
|||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
|
||||||
3. What is your type of issue?
|
|
||||||
* [Catalogue request](#catalogue-requests)
|
|
||||||
* [Bugs](#bugs)
|
|
||||||
* [Feature requests](#feature-requests)
|
|
||||||
* [Translations](https://github.com/inorichi/tachiyomi/wiki/Translation)
|
|
||||||
4. After following 1. and 3. you can [open your issue](https://github.com/inorichi/tachiyomi/issues/new)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
# Catalogue requests
|
|
||||||
|
|
||||||
* Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions#readme, not here
|
|
||||||
|
|
||||||
# Bugs
|
# Bugs
|
||||||
* Include version (Setting > About > Version)
|
* Include version (Setting > About > Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* If not latest, try updating, it may have already been solved
|
||||||
* Dev version is equal to the number of commits as seen in the main page
|
* Dev version is equal to the number of commits as seen in the main page
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* Include steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible), include results and device names, OS, modifications (root, Xposed)
|
||||||
|
* **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||||
* For large logs use http://pastebin.com/ (or similar)
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
* Don't group unrelated requests into one issue
|
* For multipart issues **use list** like this:
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
* Don't put together too many unrelated requests into one issue
|
||||||
|
|
||||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
||||||
|
|
||||||
@ -31,3 +24,8 @@ DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
|||||||
|
|
||||||
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
File `app/src/main/res/values/strings.xml` should be copied over to appropriate directories and then translated.
|
||||||
|
Consult [Android.com](http://developer.android.com/training/basics/supporting-devices/languages.html#CreateDirs)
|
||||||
|
2
.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
|||||||
github: inorichi
|
|
||||||
ko_fi: inorichi
|
|
18
.github/ISSUE_TEMPLATE.md
vendored
@ -1,17 +1,7 @@
|
|||||||
**DO NOT OPEN ISSUES/REQUESTS RELATING TO EXTENSIONS/CATALOGUES IN THIS REPOSITORY. Open them at the following repository https://github.com/inorichi/tachiyomi-extensions/**
|
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
||||||
|
|
||||||
**For all other requests Please fill out the form below and remove the first 3 lines of this template**
|
Remove line above and describe your issue here. Fill out version below. Use Preview.
|
||||||
|
|
||||||
**App version:**
|
|
||||||
|
|
||||||
**Android version:**
|
Version: r000 or v0.0.0
|
||||||
|
(other relevant info like OS)
|
||||||
**Issue/Request:**
|
|
||||||
|
|
||||||
**Steps to reproduce (if applicable)**
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
**Other details:**
|
|
||||||
|
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🐞 Bug report"
|
|
||||||
about: Report a bug
|
|
||||||
title: "[Bug] Write short description here"
|
|
||||||
labels: "bug"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Device information
|
|
||||||
* Tachiyomi version: ?
|
|
||||||
* Android version: ?
|
|
||||||
|
|
||||||
## Steps to reproduce
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
This should happen.
|
|
||||||
|
|
||||||
### Actual behavior
|
|
||||||
This happened instead.
|
|
||||||
|
|
||||||
### Other details
|
|
||||||
Additional details and attachments.
|
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🌟 Feature request"
|
|
||||||
about: Suggest a feature to improve Tachiyomi
|
|
||||||
title: "[Feature Request] Write short description here"
|
|
||||||
labels: "feature"
|
|
||||||
|
|
||||||
---
|
|
||||||
### Why/User Benefit/User Problem
|
|
||||||
(explain why this feature should be added)
|
|
||||||
|
|
||||||
### What/Requirements
|
|
||||||
(explain how this feature would behave)
|
|
BIN
.github/readme-images/app-icon.png
vendored
Before Width: | Height: | Size: 1.1 KiB |
BIN
.github/readme-images/screens.png
vendored
Before Width: | Height: | Size: 1.0 MiB |
73
.travis.yml
@ -1,59 +1,28 @@
|
|||||||
dist: trusty
|
|
||||||
language: android
|
language: android
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-29.0.2
|
- platform-tools
|
||||||
- android-28
|
- tools
|
||||||
- extra-android-m2repository
|
|
||||||
- extra-google-m2repository
|
# The BuildTools version used by your project
|
||||||
- extra-android-support
|
- build-tools-23.0.3
|
||||||
- extra-google-google_play_services
|
- android-23
|
||||||
licenses:
|
- extra-android-m2repository
|
||||||
- android-sdk-license-.+
|
- extra-google-m2repository
|
||||||
before_install:
|
- extra-android-support
|
||||||
- yes | sdkmanager "platforms;android-28" # workaround for accepting the license
|
- extra-google-google_play_services
|
||||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
|
||||||
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
before_script:
|
||||||
tar xf secrets.tar;
|
- chmod +x gradlew
|
||||||
mv debug.keystore "$HOME/.android";
|
#Build, and run tests
|
||||||
fi
|
script: "./gradlew clean buildDebug"
|
||||||
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk-root
|
sudo: false
|
||||||
- export ANDROID_NDK_HOME=$HOME/android-ndk-root
|
|
||||||
- mkdir "$ANDROID_HOME/licenses" || true
|
|
||||||
- echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
|
|
||||||
- echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
|
|
||||||
script: ".travis/build.sh"
|
|
||||||
before_cache:
|
before_cache:
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- "$HOME/.gradle/caches/"
|
- $HOME/.gradle/caches/
|
||||||
- "$HOME/.gradle/wrapper/"
|
- $HOME/.gradle/wrapper/
|
||||||
- "$HOME/.android/build-cache"
|
|
||||||
deploy:
|
|
||||||
- provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: qmS9SyMq8xPDqaY83rvAFyZcvic24lGBj3MFt22RhVJzIXAAN/vqL1R70PnNiCF7CE+R7PaDlBpwjxDMBiuh0QQNc1oX6cgepUbro4/Nt7NFFfCvKXaFdR1cSgYouhuHmy0SS0/alrcfhQ2bPwcm1/vAOiSa8Wu7hsXhCcxbFyEbXZVD11QZmiffEM0py+OeuqOFo2JxZaGRu2z04E/u5TWep1ZEuhFRCC87PGgFqABgg6jYYebQOUZADG/0G8581HTGU0mdwueYsiA35ncRzpV2V8DajEEBd5wOe5d8SyMtE+6Qs5PD9KcXAqGGe4QRmrJMX5EcLQaLZf/Qd5s9SFZVHb1aJIw/y05w4L5dlVpsjx5WuUAYAVg7Ol5UawofFo/hYkYCNmfub67wJQdHSIxPif7V6YeON6RQQMpc5GBYY9eA6ZxhrdA2m7eyoOT3jcbdaVJwC0jMGhn26hkgJfTo1LfAUs85Cs3BrK8w6Poqc/Jb+4Y0NhdGIKgO5tS3vY54cTJVVrQTq4/XmME4ZnzOX3HaOqzfyt/6M4gEQMvaeFksxwoFhocV7wfaCq9ps/Kdq2dl4KwoqRV2WqVaauqzCP4XPSlVLaJqztsw0wboupcaZepWJ2a/6j9IrKo1pEnyeHF5y+k0SUAxL0X8iKZ0uPxsgoVrlNtqXJWNGvA=
|
|
||||||
file: tachiyomi-v*.apk
|
|
||||||
file_glob: true
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
repo: inorichi/tachiyomi
|
|
||||||
- provider: script
|
|
||||||
script: ".travis/deploy.sh"
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
condition: "-z $TRAVIS_TAG"
|
|
||||||
repo: inorichi/tachiyomi
|
|
||||||
env:
|
env:
|
||||||
global:
|
- GRADLE_OPTS="-XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m"
|
||||||
- secure: Ita1+tzo7P5IC2yqU3KgRcXt+5DTpP0103Hx/ECYi42/7rLt+TC7PMjl2yH3Z189+tGwLq0Ol1KJ2Z5Rn3q7EaQgD0+WRkH/ijtrjKoVh7dyItIBp7yowZpA0TJHQ4EZpGSxZakKbIP4di8XMxJ2+5VzEivYUt04LCUpzugemL6b6XOfUmOZykVxV2UDAlPPggklITYBXkHUa0mwJhjS1aPPeeR3PhVXomkqfuOJOKejPXXXJope9fhAnmopHA7ISfjIrTuwDVQJqNSuco+O9kQShmlu0C8pob1vFGPEDvafaDS8SZ9A6gKT1ZfgSUqVmvDbx0WLX8XugBLrQedtZv63esOa1WUyGhgFVpeJjexlszXlhyfP1gH5QbzRr+EiSaagCyjf9II2veLAtU5cFY+nj6KBdKQsazIMRHf8SAQlWASyJYMED/N9RnUFxSf1rnLGqiY2ezjycx4ieFj7vhlbTgyao1GHjjR9cwNuntdMYWhY8+Vc7Fctmzm46xOyyz9oJGdyim76Y4w4MZvQNKeZOBAjdEgX6cXBk15scoM2Vj9ENox+MKZLaKRawXg2U1ujK+bWAQkXiVvPriv05/JtYsNUft8qAsm+69vtohDsUW7Wu0bBIKDL+v0W30ty1PpyNehBB2OquUE7fp53gitOmYl7TyuxktkMY8PXKKU=
|
|
||||||
- secure: NABCfigMUVM/9TLALYBpQLp/p3rG6MbH5y34/oqCSej/oh2u0nyhFSi8veS0lFpDIcv0TZvxHJXoSA0zeZijb1fUU8jYVNT2azuPWE6Gu7sf0TfBeCvulqbgLMoaA6JuWbEbZwHcxpKHg4vLSMjNk+ZP4v2dffI6A620fxLltxxhTpsYkYYsfKG857CpQtdgN/HqcOaxyvzXFmWWyVWHala1uMcMeXZCwgnlVAqau9o0bsU092txSmHqoesHoAinidSCTCmTlEqp/1AFaYQTbxmnfNC1yLgzxWAlJcS3NWzNo3ellMvKjsiIGn3JJpAjTGcyf3yPsvhs1cY3MZbmJYVyKH5HbnkA5ms6mx0DDJ2UOs5H2dmED82m14+hu62Xb8XN8zAdq+bySNSwgsGzvr1PT74pT4BW1T+D7L1xvUe6k1enZ38GIMJbJPhBybRQazhjKPmXRB30Thxoqe5VqU8UeiXHAEps7JYAWUR1PLZvEYQb6MWurmTxs9be/OTwrUT1LDiqeJZg6XkDGgQwuR2YBaQJHJD17Piq6q1BUX8abhK6wzAAYVqyGvpmUCmQCtHZgenE6ulwcKChzBv4n97OjE21LGWnbNF5ViUhfAbGcKOVufd1chZsfbkJ7a3tHYCfLnxHUIhKvHk26f5Em8h68D0wQkPnkcVVkfh7XpI=
|
|
||||||
- secure: C93UADV5aR0zhLCLwR6tCyz+fwUYslZbhjBl3PHQp+0boIGS/Be2UQTFHp/NB9mQmhWqbwqHoAVFENZFytV04ePgOuNtMFcjAIfnzm19Am7iRAMFixD45pF/CuYYfLupElkAcSequtAzN0g4G0sQ5KR1hibaDIoz9kfA2YcUAMaZ4T5bhCr8os/xA2nOlmvPDWsRWCFBYkSpnmbsSsgYAhulA/V5bSNAWnp9LPw3CBLibW3WsVP4wuhZAkXznKwn/mHT31kfQlpMH3qNhXpsN9huUkZ/k8QWeakcHJKugung0Z2T1StK8rlI8OrJstVcwueHTa2ses4f5VbhWog/Z8HDkdll9W9RM/QqXjNDtOVBt/SPuhCp4k2rvJixFUxzvSqgSWQvQnbHwjWxIUVVyHtnb0/zc/S9ONZG14TOwB/+Lkgacb85PNszurZ2f3mH0O6slIh1mH+5d9J4+L976ot4nTPlK1OtothagVyKGOrn9HycrPk/MjftIJuElHzo7NEJd/wRPqIb5y12iZN1RSPriU+itg1uSAVP891/o3peJyuqY9WSB7dYwgDJos6dDvbr19emtdyxkQR+eAb5duyH6s4R58wh1kJ1d4zu0X6uSnF4AIc+6teKkN24rSXcqB/hrcojS49jgLy5P0/CVsUbYZPI/tx8E/IJfr8m36E=
|
|
||||||
- secure: mawwBvllvESc/mp+JHvncq1iUhiC7nyokPgXmOehffc0K3byMLs2L25HjNsU6EnXG9Lcae1cfP8S9bWLquU2C3kpAkLBUpjEbdx7K0654uvs7Rrvb5hcTRHwjzaEVmVaBFX4ROcjUhBYny/Wjj/YENCkSWpkfcMd1esFbVsO+fOLyaAPvrb6auKY7H+pUSqlEwaEnrkYeBBZIHa7KqwL4g5DHbq6K368tjmval/wBzwMB0V8V3dt/ik8RMVDtKPrik4Bu0V9UmXZUIo/a06ii/CM82ekFRh3eUb0DKkdkmYbdH6MBMoLTfQtMa6A4luXaA0oycAnTX3OGB5MWIjK39KhWRavh6ybSIt4aHKoolxzH8Zgmk7xMhFSot/laX5q5IzjZu5KU6F2SmdV0kcQugM8oAjANFySetPvY1q7nZ8pM+NO1xKS/mH0w4vChhdJFD1mw7aCoh8FdeUf0Eym2+pp5Q9uAisWMmNn5XN8/fL5q6PzAxkXmkedfrr1N61FmIL6EKx8qiWpOUNlRRTIMJ4GMhCyckCF6cNxDkBItp52c+Hmkbn+ZEInEyX6gpjYVm3xyEi0Z5kLCi/fMX2nBNczc5BuGLzzmJnITv4ovpeYn2/vPvHbaPgPC4LqDK3AjlpVadMZk/M5Egn+hWY7Mni57CmpZD+SpxUbbsItI0c=
|
|
||||||
- secure: PJPDkUg1zc57brxUvNpSh+Q3ZEaGpBqZzwDavqslkn0WmjBTLrE6/OG7TFHKNmO+P56qFl+pMEKqThxqR3+4bWEeEx8ykkixDVzxNJMmws+7A7ImJ75iQyB6giMW/4tykVMMHgIPNAdcnI8VOWn0LGHnpFWUd70yoyAGX8s6cspHCKgcuWMA3GS410KJfHpyd0B9/QS7ZyWzSETW7zSPyLPa81SBO95EhOF3TOGZYLt/mBhdtU3YGFs4k9fZ8jDDcm9XmBfqVlUhb8HiZcxJiZDdRvxODERfNnwc47uaJk6+kxGDzIW2uAxrMXXVKkG04GeMOokXoR9kW1Hl2JmoyySLKLZmB7I/XEtVWdzZw16mWi+4zmhjLhfB0phSW+/5I+0VtZZ6jO031J5FL/JqVrcq1ws/aw4QlaOdPUco/x2u4LNHyYYgOi5arD9xSyu6IRy0jCC4Xa1zuqM5adGJX+rZyVfKZ0TxOW661HTxlo8COtkB2i0WR2deZGVN75ooCAEO8DauQoUcFH1OelahmPtzVs1/6ZczuxGdp9ED7ZQq9NHEOsOdUGCj/D79Dm1hWFQsIsslnnGYWitAycNCgEwmlt2Q6fbrv2CJrmLqZ9a9r3AhzxoHn9Qx1GyuyfhZJzm/6Ff2kcOjma2kcz13KUwTxdW+2G5dDCotK3f7aiI=
|
|
||||||
- secure: FIIZfEEYfjNMKODs33Czh603CYVn6LRrzpFNIiPHYTb8iQWv9qAYhsg4FpHfOjDikokTwb5X/h8G7AX93Z0xKyyDi75ACT11oPeTNTArDdcmdDVlOYBvYHc2Ci7pMW5r8LGejB7Y3mWM8uKyA3oKvneEFutB65vO3JVZvFWrm03Lmqqe7+mA4qNqNqTbN7R7fmk5b7zt7A3DHvDu0JPTbSSUwpso/p2I5WJYjrf71I7YMQwIFLoMfplC1onVA3EFS3lZsF65zE+xVRy34AKa41iZAMbhVDyqUHEnx6L0dwEdn2Z5XLlK0ov1+qLTLlQsBE4Knre6TNkWMfktk7MKA+ch8RYxvEYLODhQkIrOkLSNWhZPhdaT+xD4fr0RCKSHo6uWRC4aofsJx8wSqb8ZL4j2zopUp9VisMOI202UEnvFDBtOkVGJSxxYbFjifIB7NCJBn788w+3k+k4IbOg537VdyoK2PMBR8/TDdjImWhWHY1i7+345ejwmzHL7ZPfb6GTNnQTWkajT77/n6Yk41twR5vvegOSTKuuO++WN/pUks4PGqtcQe9fnSfx2OcOq1ofLiG+JDorJ7z8kHSG13wHLq+QYMDayQbyJEYpDzmn/w3Ou1s2o0a7A41+cIkRzAgH9y3v4lgjp9GcMP2S74ZPA7OecWbFSexM7tL/dYxY=
|
|
||||||
- secure: DKCGc4E9PKeTX68r9pbbNg5qITsN0bApQ1m0x8xdEoi8GLRKVMYNn6ahoAxvy1YsBXC9Zlt5++gLmUV1I1JyDMyJXMr/lZrp4oarW0xWpTBmn3HzOph/K2W4i/fTGgMFieumPEbQIFOnU3JSjK6UJB8qVGEXD2OqS7A//EdrGDbAYVDL3ZTKE6JUlTNHgaKaNHhn+Dq4aBLTSYPwlLyqo+WNBVUUCKCHOq62ULF8MpX5YGaPFNxKYzircV7HpF1hCbV31dmpkeYT9xztra5V0SIBM27jAcQqGmtHH2mhx1sLu+gjhFJbbtY6cggA9EedzYYLDx/NPmgfyuOJfyVbSwTF3vhDUYfskqc1THWpwOSKO0Ry+8/xYb9crxg+FSwuI5hnfkIFk9woBvRGBhjto3/1buMNY9dSFiWtEbN6Let8e747l0wIGJCpJxSeh7vn7F1mWjixhf9GX1+V9BrUvGTd3XJDNb9cVnafYa1RTj8BLteA4HBza7Z9R3dvG4YWp16L/94UuaTzgAQfERLTZGopQth/hsaVTlYesJmJLF70lGM+W83y3YuNkSaX1zQ5FAIvp7oH0O16t7ISm6GprUFwN2Uox7AAbPZdWHxJbly+D+yCFNcqS3Bz9mV3YCLo690Sy1ePNHr+nCseVfBMo7OYyavSS/EjPWfEy65Wq04=
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
git fetch --unshallow #required for commit count
|
|
||||||
|
|
||||||
cp .travis/google-services.json app/
|
|
||||||
|
|
||||||
if [ -z "$TRAVIS_TAG" ]; then
|
|
||||||
./gradlew clean assembleStandardDebug
|
|
||||||
|
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
|
||||||
export ARTIFACT="tachiyomi-r${COMMIT_COUNT}.apk"
|
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/debug/app-standard-debug.apk $ARTIFACT
|
|
||||||
else
|
|
||||||
./gradlew clean assembleStandardRelease
|
|
||||||
|
|
||||||
TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
|
|
||||||
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
|
||||||
|
|
||||||
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
|
||||||
${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:STORE_PASS --key-pass env:KEY_PASS --out $ARTIFACT app-aligned.apk
|
|
||||||
fi
|
|
@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
pattern="tachiyomi-r*"
|
|
||||||
files=( $pattern )
|
|
||||||
export ARTIFACT="${files[0]}"
|
|
||||||
|
|
||||||
if [ -z "$ARTIFACT" ]; then
|
|
||||||
echo "Artifact not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export SSHOPTIONS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${DEPLOY_KEY}"
|
|
||||||
|
|
||||||
scp $SSHOPTIONS $ARTIFACT $DEPLOY_USER@$DEPLOY_HOST:builds/
|
|
||||||
ssh $SSHOPTIONS $DEPLOY_USER@$DEPLOY_HOST ln -sf $ARTIFACT builds/latest
|
|
@ -1,73 +0,0 @@
|
|||||||
{
|
|
||||||
"project_info": {
|
|
||||||
"project_number": "777921915939",
|
|
||||||
"firebase_url": "https://tachiyomi-47364.firebaseio.com",
|
|
||||||
"project_id": "tachiyomi-47364",
|
|
||||||
"storage_bucket": "tachiyomi-47364.appspot.com"
|
|
||||||
},
|
|
||||||
"client": [
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:777921915939:android:36544cd2d96c50c7",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "eu.kanade.tachiyomi"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "777921915939-9q25jvgbdtpk91daqlk7sa1cbdcg77o6.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAHr8RxyeiSPC_MxJTnivz-hmdo5oX0QQQ"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
|
||||||
"status": 1,
|
|
||||||
"other_platform_oauth_client": []
|
|
||||||
},
|
|
||||||
"ads_service": {
|
|
||||||
"status": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:777921915939:android:564fdc1d62efd1de",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "eu.kanade.tachiyomi.debug"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "777921915939-9q25jvgbdtpk91daqlk7sa1cbdcg77o6.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAHr8RxyeiSPC_MxJTnivz-hmdo5oX0QQQ"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
|
||||||
"status": 1,
|
|
||||||
"other_platform_oauth_client": []
|
|
||||||
},
|
|
||||||
"ads_service": {
|
|
||||||
"status": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"configuration_version": "1"
|
|
||||||
}
|
|
74
README.md
@ -1,70 +1,24 @@
|
|||||||
| Build | Stable | Dev | Contribute | Support Server |
|
| Build | Download | Auto Update |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|----------|-------------|
|
||||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) | [](http://tachiyomi.kanade.eu/latest) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) |
|
||||||
|
|
||||||
|
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
**Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.**
|
||||||
|
|
||||||
# Tachiyomi
|
|
||||||
Tachiyomi is a free and open source manga reader for Android.
|
Tachiyomi is a free and open source manga reader for Android.
|
||||||
|
|
||||||

|
Keep in mind it's still a beta, so expect it to crash sometimes.
|
||||||
|
|
||||||
## Features
|
# Features
|
||||||
|
|
||||||
Features include:
|
* Online and offline reading
|
||||||
* Online reading from sources such as KissManga, MangaDex, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
* Configurable reader with multiple viewers and settings
|
||||||
* Local reading of downloaded manga
|
* MyAnimeList support
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* Resume from the next unread chapter
|
||||||
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/explore/anime), and [Shikimori](https://shikimori.one) support
|
* Chapter filtering
|
||||||
|
* Schedule searching for updates
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
|
||||||
* Schedule updating your library for new chapters
|
|
||||||
* Create backups locally to read offline or to your desired cloud service
|
|
||||||
|
|
||||||
## Download
|
|
||||||
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
|
|
||||||
|
|
||||||
If you want to try new features before they get to the stable release, you can download the dev version [here](http://tachiyomi.kanade.eu/latest).
|
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
|
||||||
|
|
||||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
|
||||||
|
|
||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Bugs</summary>
|
|
||||||
|
|
||||||
* Include version (Setting > About > Version)
|
|
||||||
* If not latest, try updating, it may have already been solved
|
|
||||||
* Dev version is equal to the number of commits as seen in the main page
|
|
||||||
* Include steps to reproduce (if not obvious from description)
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
|
||||||
* For large logs use http://pastebin.com/ (or similar)
|
|
||||||
* Don't group unrelated requests into one issue
|
|
||||||
|
|
||||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
|
||||||
|
|
||||||
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Feature Requests</summary>
|
|
||||||
|
|
||||||
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
|
|
||||||
Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
|
||||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
3
app/.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
.idea
|
||||||
google-services.json
|
|
238
app/build.gradle
@ -3,10 +3,6 @@ import java.text.SimpleDateFormat
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
|
||||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
|
||||||
|
|
||||||
shortcutHelper.filePath = './shortcuts.xml'
|
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
// Git is needed in your system PATH for these commands to work.
|
// Git is needed in your system PATH for these commands to work.
|
||||||
@ -28,53 +24,40 @@ ext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def includeUpdater() {
|
||||||
|
return hasProperty("include_updater")
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 23
|
||||||
buildToolsVersion '29.0.2'
|
buildToolsVersion "23.0.3"
|
||||||
publishNonDefault true
|
publishNonDefault true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "eu.kanade.tachiyomi"
|
applicationId "eu.kanade.tachiyomi"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 23
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 42
|
versionCode 9
|
||||||
versionName "0.8.5"
|
versionName "0.2.2-1"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
buildConfigField "boolean", "INCLUDE_UPDATER", "${includeUpdater()}"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
multiDexEnabled true
|
|
||||||
|
|
||||||
ndk {
|
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
versionNameSuffix "-${getCommitCount()}"
|
versionNameSuffix ".${getCommitCount()}"
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
}
|
}
|
||||||
}
|
release {
|
||||||
|
minifyEnabled true
|
||||||
flavorDimensions "default"
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
productFlavors {
|
|
||||||
standard {
|
|
||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
|
||||||
dimension "default"
|
|
||||||
}
|
|
||||||
fdroid {
|
|
||||||
dimension "default"
|
|
||||||
}
|
|
||||||
dev {
|
|
||||||
resConfigs "en", "xxhdpi"
|
|
||||||
dimension "default"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,153 +74,123 @@ android {
|
|||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
sourceSets {
|
||||||
sourceCompatibility = 1.8
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
targetCompatibility = 1.8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
// http://stackoverflow.com/questions/32759529/androidhttpclient-not-found-when-running-robolectric
|
||||||
jvmTarget = "1.8"
|
useLibrary 'org.apache.http.legacy'
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
generateStubs = true
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
final SUPPORT_LIBRARY_VERSION = '23.4.0'
|
||||||
|
final DAGGER_VERSION = '2.4'
|
||||||
|
final RETROFIT_VERSION = '2.0.2'
|
||||||
|
final NUCLEUS_VERSION = '3.0.0'
|
||||||
|
final STORIO_VERSION = '1.8.0'
|
||||||
|
final MOCKITO_VERSION = '1.10.19'
|
||||||
|
|
||||||
// Modified dependencies
|
// Modified dependencies
|
||||||
implementation 'com.github.inorichi:subsampling-scale-image-view:ac0dae7'
|
compile 'com.github.inorichi:subsampling-scale-image-view:421fb81'
|
||||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
compile 'com.github.inorichi:ReactiveNetwork:69092ed'
|
||||||
|
|
||||||
// Android support library
|
// Android support library
|
||||||
final support_library_version = '28.0.0'
|
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:support-v4:$support_library_version"
|
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:appcompat-v7:$support_library_version"
|
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:cardview-v7:$support_library_version"
|
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:design:$support_library_version"
|
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:recyclerview-v7:$support_library_version"
|
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:preference-v7:$support_library_version"
|
compile "com.android.support:preference-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:support-annotations:$support_library_version"
|
compile "com.android.support:preference-v14:$SUPPORT_LIBRARY_VERSION"
|
||||||
implementation "com.android.support:customtabs:$support_library_version"
|
compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
|
||||||
|
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
|
||||||
|
|
||||||
standardImplementation 'com.google.firebase:firebase-core:11.8.0'
|
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
compile 'io.reactivex:rxandroid:1.2.0'
|
||||||
implementation 'io.reactivex:rxjava:1.3.8'
|
compile 'io.reactivex:rxjava:1.1.5'
|
||||||
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
||||||
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
|
||||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
implementation "com.squareup.okhttp3:okhttp:3.10.0"
|
compile "com.squareup.okhttp3:okhttp:3.3.1"
|
||||||
implementation 'com.squareup.okio:okio:1.14.0'
|
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
final retrofit_version = '2.3.0'
|
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
|
||||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
|
||||||
|
// IO
|
||||||
|
compile 'com.squareup.okio:okio:1.8.0'
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
implementation 'com.google.code.gson:gson:2.8.2'
|
compile 'com.google.code.gson:gson:2.6.2'
|
||||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
|
||||||
|
// YAML
|
||||||
|
compile 'org.yaml:snakeyaml:1.17'
|
||||||
|
|
||||||
// JavaScript engine
|
// JavaScript engine
|
||||||
implementation 'com.squareup.duktape:duktape-android:1.2.0'
|
compile 'com.squareup.duktape:duktape-android:0.9.5'
|
||||||
|
|
||||||
// Disk
|
// Disk cache
|
||||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||||
implementation 'com.github.inorichi:unifile:e9ee588'
|
|
||||||
|
|
||||||
// HTML parser
|
// Parse HTML
|
||||||
implementation 'org.jsoup:jsoup:1.12.1'
|
compile 'org.jsoup:jsoup:1.9.2'
|
||||||
|
|
||||||
// Job scheduling
|
|
||||||
implementation 'com.evernote:android-job:1.2.5'
|
|
||||||
implementation 'com.google.android.gms:play-services-gcm:11.8.0'
|
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation 'android.arch.persistence:db:1.1.1'
|
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
||||||
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
|
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
||||||
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
implementation 'io.requery:sqlite-android:3.25.2'
|
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
final nucleus_version = '3.0.0'
|
compile "info.android15.nucleus:nucleus:$NUCLEUS_VERSION"
|
||||||
implementation "info.android15.nucleus:nucleus:$nucleus_version"
|
compile "info.android15.nucleus:nucleus-support-v4:$NUCLEUS_VERSION"
|
||||||
implementation "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
compile "info.android15.nucleus:nucleus-support-v7:$NUCLEUS_VERSION"
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
provided 'org.glassfish:javax.annotation:10.0-b28'
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
final glide_version = '4.6.1'
|
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
|
||||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
|
||||||
|
|
||||||
// Transformations
|
|
||||||
implementation 'jp.wasabeef:glide-transformations:3.1.1'
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
compile 'com.jakewharton.timber:timber:4.1.2'
|
||||||
|
|
||||||
// Crash reports
|
// Crash reports
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
compile 'ch.acra:acra:4.8.5'
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
compile 'eu.davidea:flexible-adapter:4.2.0'
|
||||||
implementation 'eu.davidea:flexible-adapter:5.0.0-rc4'
|
compile 'com.nononsenseapps:filepicker:2.5.2'
|
||||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1'
|
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
||||||
implementation 'com.nononsenseapps:filepicker:2.5.2'
|
compile 'com.afollestad.material-dialogs:core:0.8.5.9'
|
||||||
implementation 'com.github.amulyakhare:TextDrawable:558677e'
|
|
||||||
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
|
|
||||||
implementation 'me.zhanghai.android.systemuihelper:library:1.0.0'
|
|
||||||
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4'
|
|
||||||
implementation 'com.github.mthli:Slice:v1.2'
|
|
||||||
implementation 'me.gujun.android.taggroup:library:1.4@aar'
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
|
||||||
implementation 'com.github.inorichi:DirectionalViewPager:3acc51a'
|
|
||||||
|
|
||||||
// Conductor
|
|
||||||
implementation 'com.bluelinelabs:conductor:2.1.5'
|
|
||||||
implementation ("com.bluelinelabs:conductor-support:2.1.5") {
|
|
||||||
exclude group: "com.android.support"
|
|
||||||
}
|
|
||||||
implementation 'com.github.inorichi:conductor-support-preference:27.0.2'
|
|
||||||
|
|
||||||
// RxBindings
|
|
||||||
final rxbindings_version = '1.0.1'
|
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-kotlin:$rxbindings_version"
|
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:$rxbindings_version"
|
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-support-v4-kotlin:$rxbindings_version"
|
|
||||||
implementation "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version"
|
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testImplementation 'org.assertj:assertj-core:1.7.1'
|
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
|
||||||
|
testCompile('org.robolectric:robolectric:3.0') {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||||
|
}
|
||||||
|
|
||||||
final robolectric_version = '3.1.4'
|
kaptTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
testImplementation "org.robolectric:robolectric:$robolectric_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
|
||||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
||||||
|
|
||||||
final coroutines_version = '1.3.3'
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.61'
|
ext.kotlin_version = '1.0.2'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
@ -249,16 +202,3 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
|
|
||||||
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
|
|
||||||
}
|
|
||||||
|
|
||||||
androidExtensions {
|
|
||||||
experimental = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
|
||||||
}
|
|
||||||
|
111
app/proguard-rules.pro
vendored
@ -1,30 +1,24 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-dontwarn eu.kanade.tachiyomi.**
|
-keep class eu.kanade.tachiyomi.injection.** { *; }
|
||||||
-keep class eu.kanade.tachiyomi.**
|
|
||||||
-keep class eu.kanade.tachiyomi.source.model.** { *; }
|
|
||||||
|
|
||||||
-keep class com.hippo.image.** { *; }
|
|
||||||
-keep interface com.hippo.image.** { *; }
|
|
||||||
-dontwarn nucleus.view.NucleusActionBarActivity
|
|
||||||
|
|
||||||
# Extensions may require methods unused in the core app
|
|
||||||
-keep class org.jsoup.** { *; }
|
|
||||||
-keep class kotlin.** { *; }
|
|
||||||
-keep class okhttp3.** { *; }
|
|
||||||
-keep class com.google.gson.** { *; }
|
|
||||||
-keep class com.github.salomonbrys.kotson.** { *; }
|
|
||||||
|
|
||||||
# OkHttp
|
# OkHttp
|
||||||
|
-keepattributes Signature
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keep class okhttp3.** { *; }
|
||||||
|
-keep interface okhttp3.** { *; }
|
||||||
-dontwarn okhttp3.**
|
-dontwarn okhttp3.**
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
-dontwarn javax.annotation.**
|
|
||||||
-dontwarn retrofit2.Platform$Java8
|
# Okio
|
||||||
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-dontwarn java.nio.file.*
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
# Glide specific rules #
|
# Glide specific rules #
|
||||||
# https://github.com/bumptech/glide
|
# https://github.com/bumptech/glide
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
|
||||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||||
**[] $VALUES;
|
**[] $VALUES;
|
||||||
public *;
|
public *;
|
||||||
@ -46,46 +40,89 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
### Support v7, Design
|
# Retrofit 2.X
|
||||||
# http://stackoverflow.com/questions/29679177/cardview-shadow-not-appearing-in-lollipop-after-obfuscate-with-proguard/29698051
|
## https://square.github.io/retrofit/ ##
|
||||||
-keep class android.support.v7.widget.RoundRectDrawable { *; }
|
|
||||||
|
|
||||||
|
-dontwarn retrofit2.**
|
||||||
|
-keep class retrofit2.** { *; }
|
||||||
|
-keepattributes Signature
|
||||||
|
-keepattributes Exceptions
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit2.http.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# AppCombat
|
||||||
-keep public class android.support.v7.widget.** { *; }
|
-keep public class android.support.v7.widget.** { *; }
|
||||||
-keep public class android.support.v7.internal.widget.** { *; }
|
-keep public class android.support.v7.internal.widget.** { *; }
|
||||||
-keep public class android.support.v7.internal.view.menu.** { *; }
|
-keep public class android.support.v7.internal.view.menu.** { *; }
|
||||||
-keep public class android.support.v7.graphics.drawable.** { *; }
|
|
||||||
|
|
||||||
-keep public class * extends android.support.v4.view.ActionProvider {
|
-keep public class * extends android.support.v4.view.ActionProvider {
|
||||||
public <init>(android.content.Context);
|
public <init>(android.content.Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
-dontwarn android.support.**
|
## GSON 2.2.4 specific rules ##
|
||||||
-dontwarn android.support.design.**
|
|
||||||
-keep class android.support.design.** { *; }
|
|
||||||
-keep interface android.support.design.** { *; }
|
|
||||||
-keep public class android.support.design.R$* { *; }
|
|
||||||
|
|
||||||
|
|
||||||
# ReactiveNetwork
|
|
||||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
|
||||||
|
|
||||||
## GSON ##
|
|
||||||
|
|
||||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||||
# removes such information by default, so configure it to keep all of it.
|
# removes such information by default, so configure it to keep all of it.
|
||||||
-keepattributes Signature
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
-keepattributes EnclosingMethod
|
||||||
|
|
||||||
# Gson specific classes
|
# Gson specific classes
|
||||||
-keep class sun.misc.Unsafe { *; }
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
## ACRA 4.5.0 specific rules ##
|
||||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
|
||||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
# we need line numbers in our stack traces otherwise they are pretty useless
|
||||||
-keep class * implements com.google.gson.JsonSerializer
|
-renamesourcefileattribute SourceFile
|
||||||
-keep class * implements com.google.gson.JsonDeserializer
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# ACRA needs "annotations" so add this...
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
# keep this class so that logging will show 'ACRA' and not a obfuscated name like 'a'.
|
||||||
|
# Note: if you are removing log messages elsewhere in this file then this isn't necessary
|
||||||
|
-keep class org.acra.ACRA {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this around for some enums that ACRA needs
|
||||||
|
-keep class org.acra.ReportingInteractionMode {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.sender.HttpSender$** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.ReportField {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void addCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void putCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void removeCustomData(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void handleSilentException(java.lang.Throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep the support library
|
||||||
|
-keep class org.acra.** { *; }
|
||||||
|
-keep interface org.acra.** { *; }
|
||||||
|
|
||||||
# SnakeYaml
|
# SnakeYaml
|
||||||
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
||||||
|
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
||||||
-dontwarn org.yaml.snakeyaml.**
|
-dontwarn org.yaml.snakeyaml.**
|
||||||
|
|
||||||
# Duktape
|
# Duktape
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_book_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_library"
|
|
||||||
android:shortcutLongLabel="@string/label_library"
|
|
||||||
android:shortcutShortLabel="@string/label_library">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_update_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_updated"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_updates"
|
|
||||||
android:shortcutShortLabel="@string/short_recent_updates">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_glasses_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_read"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_manga"
|
|
||||||
android:shortcutShortLabel="@string/label_recent_manga">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_explore_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_catalogues"
|
|
||||||
android:shortcutLongLabel="@string/label_catalogues"
|
|
||||||
android:shortcutShortLabel="@string/label_catalogues">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
@ -1,34 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108.0"
|
|
||||||
android:viewportHeight="108.0">
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#455A64"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#607D8B"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#CE2828"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-19.94,0a19.94,19.94 0,1 1,39.87 0a19.94,19.94 0,1 1,-39.87 0"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#FFF"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.04,46.3L47.42,46.3C46.14,46.3 44.93,46.23 44.2,46.14L44.2,49.76C45,49.65 46.16,49.6 47.42,49.6L60.58,49.6C61.86,49.6 63.02,49.65 63.82,49.76L63.82,46.14C63.09,46.23 61.86,46.3 60.58,46.3L55.69,46.3L55.69,45.07C55.69,44.43 55.73,43.95 55.82,43.45L51.9,43.45C51.99,44 52.04,44.43 52.04,45.07L52.04,46.3ZM46.78,60.68C45.46,60.68 44.29,60.63 43.45,60.52L43.45,64.14C44.34,64.03 45.46,63.98 46.78,63.98L61.29,63.98C62.57,63.98 63.71,64.03 64.57,64.14L64.57,60.52C63.73,60.63 62.57,60.68 61.29,60.68L58.24,60.68C59.33,58.06 59.99,56.23 60.7,53.91C61.34,51.81 61.34,51.81 61.56,51.13L57.58,50.06C57.51,50.93 57.37,51.52 56.89,53.41C56.19,56.14 55.32,58.74 54.5,60.68L46.78,60.68ZM46.48,51.36C47.55,54.02 48.28,56.53 49.03,60.15L52.66,58.9C51.65,54.98 50.92,52.66 49.94,50.11L46.48,51.36Z"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 18 KiB |
@ -1,135 +1,108 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="eu.kanade.tachiyomi">
|
package="eu.kanade.tachiyomi">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:theme="@style/Theme.Tachiyomi">
|
android:theme="@style/Theme.Tachiyomi" >
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTask">
|
android:theme="@style/Theme.BrandedLaunch">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
|
|
||||||
<!--suppress AndroidDomInspection -->
|
|
||||||
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity" />
|
android:name=".ui.manga.MangaActivity"
|
||||||
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".widget.CustomLayoutPickerActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
|
android:theme="@style/Theme.Reader">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.setting.SettingsActivity"
|
||||||
|
android:label="@string/label_settings"
|
||||||
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.category.CategoryActivity"
|
||||||
|
android:label="@string/label_categories"
|
||||||
|
android:parentActivityName=".ui.main.MainActivity">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/FilePickerTheme" />
|
android:theme="@style/FilePickerTheme">
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.AnilistLoginActivity"
|
|
||||||
android:label="Anilist">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="anilist-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.ShikimoriLoginActivity"
|
|
||||||
android:label="Shikimori">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="shikimori-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.BangumiLoginActivity"
|
|
||||||
android:label="Bangumi">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="bangumi-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<service android:name=".data.library.LibraryUpdateService"
|
||||||
android:name=".extension.util.ExtensionInstallActivity"
|
android:exported="false"/>
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
|
|
||||||
|
|
||||||
<provider
|
<service android:name=".data.download.DownloadService"
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:exported="false"/>
|
||||||
android:authorities="${applicationId}.provider"
|
|
||||||
android:exported="false"
|
<service android:name=".data.mangasync.UpdateMangaSyncService"
|
||||||
android:grantUriPermissions="true">
|
android:exported="false"/>
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/provider_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateService"
|
android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.download.DownloadService"
|
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.updater.UpdaterService"
|
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.backup.BackupCreateService"
|
android:name=".data.library.LibraryUpdateAlarm">
|
||||||
android:exported="false"/>
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.UPDATE_LIBRARY" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.updater.UpdateDownloaderAlarm">
|
||||||
android:exported="false"/>
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.CHECK_UPDATE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||||
|
android:value="GlideModule" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
@ -2,75 +2,61 @@ package eu.kanade.tachiyomi
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import android.support.multidex.MultiDex
|
import eu.kanade.tachiyomi.injection.ComponentReflectionInjector
|
||||||
import com.evernote.android.job.JobManager
|
import eu.kanade.tachiyomi.injection.component.AppComponent
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
import eu.kanade.tachiyomi.injection.component.DaggerAppComponent
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.injection.module.AppModule
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
|
||||||
import eu.kanade.tachiyomi.util.LocaleHelper
|
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.InjektScope
|
|
||||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
|
||||||
|
|
||||||
@ReportsCrashes(
|
@ReportsCrashes(
|
||||||
formUri = "http://tachiyomi.kanade.eu/crash_report",
|
formUri = "http://tachiyomi.kanade.eu/crash_report",
|
||||||
reportType = org.acra.sender.HttpSender.Type.JSON,
|
reportType = org.acra.sender.HttpSender.Type.JSON,
|
||||||
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
||||||
buildConfigClass = BuildConfig::class,
|
buildConfigClass = BuildConfig::class,
|
||||||
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
|
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*")
|
||||||
)
|
)
|
||||||
open class App : Application() {
|
open class App : Application() {
|
||||||
|
|
||||||
|
lateinit var component: AppComponent
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var componentReflection: ComponentReflectionInjector<AppComponent>
|
||||||
|
private set
|
||||||
|
|
||||||
|
var appTheme = 0
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
Injekt = InjektScope(DefaultRegistrar())
|
component = prepareAppComponent().build()
|
||||||
Injekt.importModule(AppModule(this))
|
|
||||||
|
|
||||||
|
componentReflection = ComponentReflectionInjector(AppComponent::class.java, component)
|
||||||
|
|
||||||
|
setupTheme()
|
||||||
setupAcra()
|
setupAcra()
|
||||||
setupJobManager()
|
|
||||||
setupNotificationChannels()
|
|
||||||
|
|
||||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
private fun setupTheme() {
|
||||||
super.attachBaseContext(base)
|
appTheme = PreferencesHelper.getTheme(this)
|
||||||
MultiDex.install(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
protected open fun prepareAppComponent(): DaggerAppComponent.Builder {
|
||||||
super.onConfigurationChanged(newConfig)
|
return DaggerAppComponent.builder()
|
||||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
.appModule(AppModule(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupAcra() {
|
protected open fun setupAcra() {
|
||||||
ACRA.init(this)
|
ACRA.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupJobManager() {
|
companion object {
|
||||||
try {
|
@JvmStatic
|
||||||
JobManager.create(this).addJobCreator { tag ->
|
fun get(context: Context): App {
|
||||||
when (tag) {
|
return context.applicationContext as App
|
||||||
LibraryUpdateJob.TAG -> LibraryUpdateJob()
|
|
||||||
UpdaterJob.TAG -> UpdaterJob()
|
|
||||||
BackupCreatorJob.TAG -> BackupCreatorJob()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.w("Can't initialize job manager")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupNotificationChannels() {
|
|
||||||
Notifications.createChannels(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import rx.Observable
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import uy.kohesive.injekt.api.*
|
|
||||||
|
|
||||||
class AppModule(val app: Application) : InjektModule {
|
|
||||||
|
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
|
||||||
|
|
||||||
addSingleton(app)
|
|
||||||
|
|
||||||
addSingletonFactory { PreferencesHelper(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { DatabaseHelper(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { ChapterCache(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { CoverCache(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { SourceManager(app).also { get<ExtensionManager>().init(it) } }
|
|
||||||
|
|
||||||
addSingletonFactory { ExtensionManager(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { DownloadManager(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { TrackManager(app) }
|
|
||||||
|
|
||||||
addSingletonFactory { Gson() }
|
|
||||||
|
|
||||||
// Asynchronously init expensive components for a faster cold start
|
|
||||||
|
|
||||||
rxAsync { get<PreferencesHelper>() }
|
|
||||||
|
|
||||||
rxAsync { get<NetworkHelper>() }
|
|
||||||
|
|
||||||
rxAsync { get<SourceManager>() }
|
|
||||||
|
|
||||||
rxAsync { get<DatabaseHelper>() }
|
|
||||||
|
|
||||||
rxAsync { get<DownloadManager>() }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rxAsync(block: () -> Unit) {
|
|
||||||
Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
8
app/src/main/java/eu/kanade/tachiyomi/Constants.kt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val NOTIFICATION_LIBRARY_ID = 1
|
||||||
|
const val NOTIFICATION_UPDATER_ID = 2
|
||||||
|
const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
|
||||||
|
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object Migrations {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a migration when the application is updated.
|
|
||||||
*
|
|
||||||
* @param preferences Preferences of the application.
|
|
||||||
* @return true if a migration is performed, false otherwise.
|
|
||||||
*/
|
|
||||||
fun upgrade(preferences: PreferencesHelper): Boolean {
|
|
||||||
val context = preferences.context
|
|
||||||
val oldVersion = preferences.lastVersionCode().getOrDefault()
|
|
||||||
if (oldVersion < BuildConfig.VERSION_CODE) {
|
|
||||||
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
|
|
||||||
|
|
||||||
if (oldVersion == 0) return false
|
|
||||||
|
|
||||||
if (oldVersion < 14) {
|
|
||||||
// Restore jobs after upgrading to evernote's job scheduler.
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
|
|
||||||
UpdaterJob.setupTask()
|
|
||||||
}
|
|
||||||
LibraryUpdateJob.setupTask()
|
|
||||||
}
|
|
||||||
if (oldVersion < 15) {
|
|
||||||
// Delete internal chapter cache dir.
|
|
||||||
File(context.cacheDir, "chapter_disk_cache").deleteRecursively()
|
|
||||||
}
|
|
||||||
if (oldVersion < 19) {
|
|
||||||
// Move covers to external files dir.
|
|
||||||
val oldDir = File(context.externalCacheDir, "cover_disk_cache")
|
|
||||||
if (oldDir.exists()) {
|
|
||||||
val destDir = context.getExternalFilesDir("covers")
|
|
||||||
if (destDir != null) {
|
|
||||||
oldDir.listFiles().forEach {
|
|
||||||
it.renameTo(File(destDir, it.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion < 26) {
|
|
||||||
// Delete external chapter cache dir.
|
|
||||||
val extCache = context.externalCacheDir
|
|
||||||
if (extCache != null) {
|
|
||||||
val chapterCache = File(extCache, "chapter_disk_cache")
|
|
||||||
if (chapterCache.exists()) {
|
|
||||||
chapterCache.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
|
|
||||||
|
|
||||||
object BackupConst {
|
|
||||||
|
|
||||||
const val INTENT_FILTER = "SettingsBackupFragment"
|
|
||||||
const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG"
|
|
||||||
const val ACTION_SET_PROGRESS_DIALOG = "$ID.$INTENT_FILTER.ACTION_SET_PROGRESS_DIALOG"
|
|
||||||
const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG"
|
|
||||||
const val ACTION_ERROR_RESTORE_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_RESTORE_DIALOG"
|
|
||||||
const val ACTION_RESTORE_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_RESTORE_COMPLETED_DIALOG"
|
|
||||||
const val ACTION = "$ID.$INTENT_FILTER.ACTION"
|
|
||||||
const val EXTRA_PROGRESS = "$ID.$INTENT_FILTER.EXTRA_PROGRESS"
|
|
||||||
const val EXTRA_AMOUNT = "$ID.$INTENT_FILTER.EXTRA_AMOUNT"
|
|
||||||
const val EXTRA_ERRORS = "$ID.$INTENT_FILTER.EXTRA_ERRORS"
|
|
||||||
const val EXTRA_CONTENT = "$ID.$INTENT_FILTER.EXTRA_CONTENT"
|
|
||||||
const val EXTRA_ERROR_MESSAGE = "$ID.$INTENT_FILTER.EXTRA_ERROR_MESSAGE"
|
|
||||||
const val EXTRA_URI = "$ID.$INTENT_FILTER.EXTRA_URI"
|
|
||||||
const val EXTRA_TIME = "$ID.$INTENT_FILTER.EXTRA_TIME"
|
|
||||||
const val EXTRA_ERROR_FILE_PATH = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE_PATH"
|
|
||||||
const val EXTRA_ERROR_FILE = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE"
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
|
||||||
|
|
||||||
import android.app.IntentService
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [IntentService] used to backup [Manga] information to [JsonArray]
|
|
||||||
*/
|
|
||||||
class BackupCreateService : IntentService(NAME) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// Name of class
|
|
||||||
private const val NAME = "BackupCreateService"
|
|
||||||
|
|
||||||
// Options for backup
|
|
||||||
private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
|
||||||
|
|
||||||
// Filter options
|
|
||||||
internal const val BACKUP_CATEGORY = 0x1
|
|
||||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
|
||||||
internal const val BACKUP_CHAPTER = 0x2
|
|
||||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
|
||||||
internal const val BACKUP_HISTORY = 0x4
|
|
||||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
|
||||||
internal const val BACKUP_TRACK = 0x8
|
|
||||||
internal const val BACKUP_TRACK_MASK = 0x8
|
|
||||||
internal const val BACKUP_ALL = 0xF
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a backup from library
|
|
||||||
*
|
|
||||||
* @param context context of application
|
|
||||||
* @param uri path of Uri
|
|
||||||
* @param flags determines what to backup
|
|
||||||
*/
|
|
||||||
fun makeBackup(context: Context, uri: Uri, flags: Int) {
|
|
||||||
val intent = Intent(context, BackupCreateService::class.java).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
|
||||||
putExtra(EXTRA_FLAGS, flags)
|
|
||||||
}
|
|
||||||
context.startService(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private val backupManager by lazy { BackupManager(this) }
|
|
||||||
|
|
||||||
override fun onHandleIntent(intent: Intent?) {
|
|
||||||
if (intent == null) return
|
|
||||||
|
|
||||||
// Get values
|
|
||||||
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
|
||||||
val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
|
|
||||||
// Create backup
|
|
||||||
backupManager.createBackup(uri, flags, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import com.evernote.android.job.Job
|
|
||||||
import com.evernote.android.job.JobManager
|
|
||||||
import com.evernote.android.job.JobRequest
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class BackupCreatorJob : Job() {
|
|
||||||
|
|
||||||
override fun onRunJob(params: Params): Result {
|
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
|
||||||
val backupManager = BackupManager(context)
|
|
||||||
val uri = Uri.parse(preferences.backupsDirectory().getOrDefault())
|
|
||||||
val flags = BackupCreateService.BACKUP_ALL
|
|
||||||
backupManager.createBackup(uri, flags, true)
|
|
||||||
return Result.SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "BackupCreator"
|
|
||||||
|
|
||||||
fun setupTask(prefInterval: Int? = null) {
|
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
|
||||||
val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
|
|
||||||
if (interval > 0) {
|
|
||||||
JobRequest.Builder(TAG)
|
|
||||||
.setPeriodic(interval * 60 * 60 * 1000L, 10 * 60 * 1000)
|
|
||||||
.setUpdateCurrent(true)
|
|
||||||
.build()
|
|
||||||
.schedule()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancelTask() {
|
|
||||||
JobManager.instance().cancelAllForTag(TAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,308 +1,201 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import com.github.salomonbrys.kotson.*
|
|
||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import com.hippo.unifile.UniFile
|
import com.google.gson.reflect.TypeToken
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
|
|
||||||
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.TRACK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
|
||||||
import eu.kanade.tachiyomi.data.backup.serializer.*
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import java.io.*
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import java.lang.reflect.Type
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import java.util.*
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
|
||||||
import rx.Observable
|
|
||||||
import timber.log.Timber
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
/**
|
||||||
|
* This class provides the necessary methods to create and restore backups for the data of the
|
||||||
|
* application. The backup follows a JSON structure, with the following scheme:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "mangas": [
|
||||||
|
* {
|
||||||
|
* "manga": {"id": 1, ...},
|
||||||
|
* "chapters": [{"id": 1, ...}, {...}],
|
||||||
|
* "sync": [{"id": 1, ...}, {...}],
|
||||||
|
* "categories": ["cat1", "cat2", ...]
|
||||||
|
* },
|
||||||
|
* { ... }
|
||||||
|
* ],
|
||||||
|
* "categories": [
|
||||||
|
* {"id": 1, ...},
|
||||||
|
* {"id": 2, ...}
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param db the database helper.
|
||||||
|
*/
|
||||||
|
class BackupManager(private val db: DatabaseHelper) {
|
||||||
|
|
||||||
|
private val MANGA = "manga"
|
||||||
|
private val MANGAS = "mangas"
|
||||||
|
private val CHAPTERS = "chapters"
|
||||||
|
private val MANGA_SYNC = "sync"
|
||||||
|
private val CATEGORIES = "categories"
|
||||||
|
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||||
|
private val gson = GsonBuilder()
|
||||||
|
.registerTypeAdapter(Integer::class.java, IntegerSerializer())
|
||||||
|
.setExclusionStrategies(IdExclusion())
|
||||||
|
.create()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database.
|
* Backups the data of the application to a file.
|
||||||
*/
|
|
||||||
internal val databaseHelper: DatabaseHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source manager.
|
|
||||||
*/
|
|
||||||
internal val sourceManager: SourceManager by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tracking manager
|
|
||||||
*/
|
|
||||||
internal val trackManager: TrackManager by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of parser
|
|
||||||
*/
|
|
||||||
var version: Int = version
|
|
||||||
private set
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Json Parser
|
|
||||||
*/
|
|
||||||
var parser: Gson = initParser()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preferences
|
|
||||||
*/
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set version of parser
|
|
||||||
*
|
*
|
||||||
* @param version version of parser
|
* @param file the file where the backup will be saved.
|
||||||
|
* @throws IOException if there's any IO error.
|
||||||
*/
|
*/
|
||||||
internal fun setVersion(version: Int) {
|
@Throws(IOException::class)
|
||||||
this.version = version
|
fun backupToFile(file: File) {
|
||||||
parser = initParser()
|
val root = backupToJson()
|
||||||
}
|
|
||||||
|
|
||||||
private fun initParser(): Gson = when (version) {
|
FileWriter(file).use {
|
||||||
1 -> GsonBuilder().create()
|
gson.toJson(root, it)
|
||||||
2 -> GsonBuilder()
|
}
|
||||||
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
|
||||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
|
||||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
|
||||||
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
|
||||||
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
|
||||||
.create()
|
|
||||||
else -> throw Exception("Json version unknown")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create backup Json file from database
|
* Creates a JSON object containing the backup of the app's data.
|
||||||
*
|
*
|
||||||
* @param uri path of Uri
|
* @return the backup as a JSON object.
|
||||||
* @param isJob backup called from job
|
|
||||||
*/
|
*/
|
||||||
fun createBackup(uri: Uri, flags: Int, isJob: Boolean) {
|
fun backupToJson(): JsonObject {
|
||||||
// Create root object
|
|
||||||
val root = JsonObject()
|
val root = JsonObject()
|
||||||
|
|
||||||
// Create manga array
|
// Backup library mangas and its dependencies
|
||||||
val mangaEntries = JsonArray()
|
val mangaEntries = JsonArray()
|
||||||
|
root.add(MANGAS, mangaEntries)
|
||||||
|
for (manga in db.getFavoriteMangas().executeAsBlocking()) {
|
||||||
|
mangaEntries.add(backupManga(manga))
|
||||||
|
}
|
||||||
|
|
||||||
// Create category array
|
// Backup categories
|
||||||
val categoryEntries = JsonArray()
|
val categoryEntries = JsonArray()
|
||||||
|
root.add(CATEGORIES, categoryEntries)
|
||||||
// Add value's to root
|
for (category in db.getCategories().executeAsBlocking()) {
|
||||||
root[Backup.VERSION] = Backup.CURRENT_VERSION
|
categoryEntries.add(backupCategory(category))
|
||||||
root[Backup.MANGAS] = mangaEntries
|
|
||||||
root[CATEGORIES] = categoryEntries
|
|
||||||
|
|
||||||
databaseHelper.inTransaction {
|
|
||||||
// Get manga from database
|
|
||||||
val mangas = getFavoriteManga()
|
|
||||||
|
|
||||||
// Backup library manga and its dependencies
|
|
||||||
mangas.forEach { manga ->
|
|
||||||
mangaEntries.add(backupMangaObject(manga, flags))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup categories
|
|
||||||
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
|
|
||||||
backupCategories(categoryEntries)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return root
|
||||||
// When BackupCreatorJob
|
|
||||||
if (isJob) {
|
|
||||||
// Get dir of file and create
|
|
||||||
var dir = UniFile.fromUri(context, uri)
|
|
||||||
dir = dir.createDirectory("automatic")
|
|
||||||
|
|
||||||
// Delete older backups
|
|
||||||
val numberOfBackups = numberOfBackups()
|
|
||||||
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
|
|
||||||
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
|
||||||
.orEmpty()
|
|
||||||
.sortedByDescending { it.name }
|
|
||||||
.drop(numberOfBackups - 1)
|
|
||||||
.forEach { it.delete() }
|
|
||||||
|
|
||||||
// Create new file to place backup
|
|
||||||
val newFile = dir.createFile(Backup.getDefaultFilename())
|
|
||||||
?: throw Exception("Couldn't create backup file")
|
|
||||||
|
|
||||||
newFile.openOutputStream().bufferedWriter().use {
|
|
||||||
parser.toJson(root, it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val file = UniFile.fromUri(context, uri)
|
|
||||||
?: throw Exception("Couldn't create backup file")
|
|
||||||
file.openOutputStream().bufferedWriter().use {
|
|
||||||
parser.toJson(root, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show completed dialog
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED_DIALOG)
|
|
||||||
putExtra(BackupConst.EXTRA_URI, file.uri.toString())
|
|
||||||
}
|
|
||||||
context.sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
if (!isJob) {
|
|
||||||
// Show error dialog
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_BACKUP_DIALOG)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message)
|
|
||||||
}
|
|
||||||
context.sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup the categories of library
|
* Backups a manga and its related data (chapters, categories this manga is in, sync...).
|
||||||
*
|
*
|
||||||
* @param root root of categories json
|
* @param manga the manga to backup.
|
||||||
|
* @return a JSON object containing all the data of the manga.
|
||||||
*/
|
*/
|
||||||
internal fun backupCategories(root: JsonArray) {
|
private fun backupManga(manga: Manga): JsonObject {
|
||||||
val categories = databaseHelper.getCategories().executeAsBlocking()
|
|
||||||
categories.forEach { root.add(parser.toJsonTree(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a manga to Json
|
|
||||||
*
|
|
||||||
* @param manga manga that gets converted
|
|
||||||
* @return [JsonElement] containing manga information
|
|
||||||
*/
|
|
||||||
internal fun backupMangaObject(manga: Manga, options: Int): JsonElement {
|
|
||||||
// Entry for this manga
|
// Entry for this manga
|
||||||
val entry = JsonObject()
|
val entry = JsonObject()
|
||||||
|
|
||||||
// Backup manga fields
|
// Backup manga fields
|
||||||
entry[MANGA] = parser.toJsonTree(manga)
|
entry.add(MANGA, gson.toJsonTree(manga))
|
||||||
|
|
||||||
// Check if user wants chapter information in backup
|
// Backup all the chapters
|
||||||
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
val chapters = db.getChapters(manga).executeAsBlocking()
|
||||||
// Backup all the chapters
|
if (!chapters.isEmpty()) {
|
||||||
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
entry.add(CHAPTERS, gson.toJsonTree(chapters))
|
||||||
if (!chapters.isEmpty()) {
|
|
||||||
val chaptersJson = parser.toJsonTree(chapters)
|
|
||||||
if (chaptersJson.asJsonArray.size() > 0) {
|
|
||||||
entry[CHAPTERS] = chaptersJson
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user wants category information in backup
|
// Backup manga sync
|
||||||
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
val mangaSync = db.getMangasSync(manga).executeAsBlocking()
|
||||||
// Backup categories for this manga
|
if (!mangaSync.isEmpty()) {
|
||||||
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
entry.add(MANGA_SYNC, gson.toJsonTree(mangaSync))
|
||||||
if (!categoriesForManga.isEmpty()) {
|
|
||||||
val categoriesNames = categoriesForManga.map { it.name }
|
|
||||||
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user wants track information in backup
|
// Backup categories for this manga
|
||||||
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
|
||||||
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
if (!categoriesForManga.isEmpty()) {
|
||||||
if (!tracks.isEmpty()) {
|
val categoriesNames = ArrayList<String>()
|
||||||
entry[TRACK] = parser.toJsonTree(tracks)
|
for (category in categoriesForManga) {
|
||||||
}
|
categoriesNames.add(category.name)
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user wants history information in backup
|
|
||||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
|
||||||
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
|
||||||
if (!historyForManga.isEmpty()) {
|
|
||||||
val historyData = historyForManga.mapNotNull { history ->
|
|
||||||
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
|
||||||
url?.let { DHistory(url, history.last_read) }
|
|
||||||
}
|
|
||||||
val historyJson = parser.toJsonTree(historyData)
|
|
||||||
if (historyJson.asJsonArray.size() > 0) {
|
|
||||||
entry[HISTORY] = historyJson
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
entry.add(CATEGORIES, gson.toJsonTree(categoriesNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
|
/**
|
||||||
manga.id = dbManga.id
|
* Backups a category.
|
||||||
manga.copyFrom(dbManga)
|
*
|
||||||
manga.favorite = true
|
* @param category the category to backup.
|
||||||
insertManga(manga)
|
* @return a JSON object containing the data of the category.
|
||||||
|
*/
|
||||||
|
private fun backupCategory(category: Category): JsonElement {
|
||||||
|
return gson.toJsonTree(category)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Observable] that fetches manga information
|
* Restores a backup from a file.
|
||||||
*
|
*
|
||||||
* @param source source of manga
|
* @param file the file containing the backup.
|
||||||
* @param manga manga that needs updating
|
* @throws IOException if there's any IO error.
|
||||||
* @return [Observable] that contains manga
|
|
||||||
*/
|
*/
|
||||||
fun restoreMangaFetchObservable(source: Source, manga: Manga): Observable<Manga> {
|
@Throws(IOException::class)
|
||||||
return source.fetchMangaDetails(manga)
|
fun restoreFromFile(file: File) {
|
||||||
.map { networkManga ->
|
JsonReader(FileReader(file)).use {
|
||||||
manga.copyFrom(networkManga)
|
val root = JsonParser().parse(it).asJsonObject
|
||||||
manga.favorite = true
|
restoreFromJson(root)
|
||||||
manga.initialized = true
|
}
|
||||||
manga.id = insertManga(manga)
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Observable] that fetches chapter information
|
* Restores a backup from an input stream.
|
||||||
*
|
*
|
||||||
* @param source source of manga
|
* @param stream the stream containing the backup.
|
||||||
* @param manga manga that needs updating
|
* @throws IOException if there's any IO error.
|
||||||
* @return [Observable] that contains manga
|
|
||||||
*/
|
*/
|
||||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
@Throws(IOException::class)
|
||||||
return source.fetchChapterList(manga)
|
fun restoreFromStream(stream: InputStream) {
|
||||||
.map { syncChaptersWithSource(databaseHelper, it, manga, source) }
|
JsonReader(InputStreamReader(stream)).use {
|
||||||
.doOnNext {
|
val root = JsonParser().parse(it).asJsonObject
|
||||||
if (it.first.isNotEmpty()) {
|
restoreFromJson(root)
|
||||||
chapters.forEach { it.manga_id = manga.id }
|
}
|
||||||
insertChapters(chapters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore the categories from Json
|
* Restores a backup from a JSON object. Everything executes in a single transaction so that
|
||||||
|
* nothing is modified if there's an error.
|
||||||
*
|
*
|
||||||
* @param jsonCategories array containing categories
|
* @param root the root of the JSON.
|
||||||
*/
|
*/
|
||||||
internal fun restoreCategories(jsonCategories: JsonArray) {
|
fun restoreFromJson(root: JsonObject) {
|
||||||
|
db.inTransaction {
|
||||||
|
// Restore categories
|
||||||
|
root.get(CATEGORIES)?.let {
|
||||||
|
restoreCategories(it.asJsonArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore mangas
|
||||||
|
root.get(MANGAS)?.let {
|
||||||
|
restoreMangas(it.asJsonArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the categories.
|
||||||
|
*
|
||||||
|
* @param jsonCategories the categories of the json.
|
||||||
|
*/
|
||||||
|
private fun restoreCategories(jsonCategories: JsonArray) {
|
||||||
// Get categories from file and from db
|
// Get categories from file and from db
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
val backupCategories = parser.fromJson<List<CategoryImpl>>(jsonCategories)
|
val backupCategories = getArrayOrEmpty<Category>(jsonCategories,
|
||||||
|
object : TypeToken<List<Category>>() {}.type)
|
||||||
|
|
||||||
// Iterate over them
|
// Iterate over them
|
||||||
backupCategories.forEach { category ->
|
for (category in backupCategories) {
|
||||||
// Used to know if the category is already in the db
|
// Used to know if the category is already in the db
|
||||||
var found = false
|
var found = false
|
||||||
for (dbCategory in dbCategories) {
|
for (dbCategory in dbCategories) {
|
||||||
@ -319,20 +212,106 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
// Let the db assign the id
|
// Let the db assign the id
|
||||||
category.id = null
|
category.id = null
|
||||||
val result = databaseHelper.insertCategory(category).executeAsBlocking()
|
val result = db.insertCategory(category).executeAsBlocking()
|
||||||
category.id = result.insertedId()?.toInt()
|
category.id = result.insertedId()?.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores all the mangas and its related data.
|
||||||
|
*
|
||||||
|
* @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json.
|
||||||
|
*/
|
||||||
|
private fun restoreMangas(jsonMangas: JsonArray) {
|
||||||
|
val chapterToken = object : TypeToken<List<Chapter>>() {}.type
|
||||||
|
val mangaSyncToken = object : TypeToken<List<MangaSync>>() {}.type
|
||||||
|
val categoriesNamesToken = object : TypeToken<List<String>>() {}.type
|
||||||
|
|
||||||
|
for (backupManga in jsonMangas) {
|
||||||
|
// Map every entry to objects
|
||||||
|
val element = backupManga.asJsonObject
|
||||||
|
val manga = gson.fromJson(element.get(MANGA), Manga::class.java)
|
||||||
|
val chapters = getArrayOrEmpty<Chapter>(element.get(CHAPTERS), chapterToken)
|
||||||
|
val sync = getArrayOrEmpty<MangaSync>(element.get(MANGA_SYNC), mangaSyncToken)
|
||||||
|
val categories = getArrayOrEmpty<String>(element.get(CATEGORIES), categoriesNamesToken)
|
||||||
|
|
||||||
|
// Restore everything related to this manga
|
||||||
|
restoreManga(manga)
|
||||||
|
restoreChaptersForManga(manga, chapters)
|
||||||
|
restoreSyncForManga(manga, sync)
|
||||||
|
restoreCategoriesForManga(manga, categories)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores a manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga to restore.
|
||||||
|
*/
|
||||||
|
private fun restoreManga(manga: Manga) {
|
||||||
|
// Try to find existing manga in db
|
||||||
|
val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking()
|
||||||
|
if (dbManga == null) {
|
||||||
|
// Let the db assign the id
|
||||||
|
manga.id = null
|
||||||
|
val result = db.insertManga(manga).executeAsBlocking()
|
||||||
|
manga.id = result.insertedId()
|
||||||
|
} else {
|
||||||
|
// If it exists already, we copy only the values related to the source from the db
|
||||||
|
// (they can be up to date). Local values (flags) are kept from the backup.
|
||||||
|
manga.id = dbManga.id
|
||||||
|
manga.copyFrom(dbManga)
|
||||||
|
manga.favorite = true
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the chapters of a manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga whose chapters have to be restored.
|
||||||
|
* @param chapters the chapters to restore.
|
||||||
|
*/
|
||||||
|
private fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
|
||||||
|
// Fix foreign keys with the current manga id
|
||||||
|
for (chapter in chapters) {
|
||||||
|
chapter.manga_id = manga.id
|
||||||
|
}
|
||||||
|
|
||||||
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
val chaptersToUpdate = ArrayList<Chapter>()
|
||||||
|
for (backupChapter in chapters) {
|
||||||
|
// Try to find existing chapter in db
|
||||||
|
val pos = dbChapters.indexOf(backupChapter)
|
||||||
|
if (pos != -1) {
|
||||||
|
// The chapter is already in the db, only update its fields
|
||||||
|
val dbChapter = dbChapters[pos]
|
||||||
|
// If one of them was read, the chapter will be marked as read
|
||||||
|
dbChapter.read = backupChapter.read || dbChapter.read
|
||||||
|
dbChapter.last_page_read = Math.max(backupChapter.last_page_read, dbChapter.last_page_read)
|
||||||
|
chaptersToUpdate.add(dbChapter)
|
||||||
|
} else {
|
||||||
|
// Insert new chapter. Let the db assign the id
|
||||||
|
backupChapter.id = null
|
||||||
|
chaptersToUpdate.add(backupChapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
if (!chaptersToUpdate.isEmpty()) {
|
||||||
|
db.insertChapters(chaptersToUpdate).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the categories a manga is in.
|
* Restores the categories a manga is in.
|
||||||
*
|
*
|
||||||
* @param manga the manga whose categories have to be restored.
|
* @param manga the manga whose categories have to be restored.
|
||||||
* @param categories the categories to restore.
|
* @param categories the categories to restore.
|
||||||
*/
|
*/
|
||||||
internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
|
private fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
val mangaCategoriesToUpdate = ArrayList<MangaCategory>()
|
val mangaCategoriesToUpdate = ArrayList<MangaCategory>()
|
||||||
for (backupCategoryStr in categories) {
|
for (backupCategoryStr in categories) {
|
||||||
for (dbCategory in dbCategories) {
|
for (dbCategory in dbCategories) {
|
||||||
@ -347,152 +326,56 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (!mangaCategoriesToUpdate.isEmpty()) {
|
if (!mangaCategoriesToUpdate.isEmpty()) {
|
||||||
val mangaAsList = ArrayList<Manga>()
|
val mangaAsList = ArrayList<Manga>()
|
||||||
mangaAsList.add(manga)
|
mangaAsList.add(manga)
|
||||||
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
db.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
||||||
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore history from Json
|
|
||||||
*
|
|
||||||
* @param history list containing history to be restored
|
|
||||||
*/
|
|
||||||
internal fun restoreHistoryForManga(history: List<DHistory>) {
|
|
||||||
// List containing history to be updated
|
|
||||||
val historyToBeUpdated = ArrayList<History>()
|
|
||||||
for ((url, lastRead) in history) {
|
|
||||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
|
||||||
// Check if history already in database and update
|
|
||||||
if (dbHistory != null) {
|
|
||||||
dbHistory.apply {
|
|
||||||
last_read = Math.max(lastRead, dbHistory.last_read)
|
|
||||||
}
|
|
||||||
historyToBeUpdated.add(dbHistory)
|
|
||||||
} else {
|
|
||||||
// If not in database create
|
|
||||||
databaseHelper.getChapter(url).executeAsBlocking()?.let {
|
|
||||||
val historyToAdd = History.create(it).apply {
|
|
||||||
last_read = lastRead
|
|
||||||
}
|
|
||||||
historyToBeUpdated.add(historyToAdd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the sync of a manga.
|
* Restores the sync of a manga.
|
||||||
*
|
*
|
||||||
* @param manga the manga whose sync have to be restored.
|
* @param manga the manga whose sync have to be restored.
|
||||||
* @param tracks the track list to restore.
|
* @param sync the sync to restore.
|
||||||
*/
|
*/
|
||||||
internal fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
|
private fun restoreSyncForManga(manga: Manga, sync: List<MangaSync>) {
|
||||||
// Fix foreign keys with the current manga id
|
// Fix foreign keys with the current manga id
|
||||||
tracks.map { it.manga_id = manga.id!! }
|
for (mangaSync in sync) {
|
||||||
|
mangaSync.manga_id = manga.id
|
||||||
// Get tracks from database
|
|
||||||
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
|
||||||
val trackToUpdate = ArrayList<Track>()
|
|
||||||
|
|
||||||
for (track in tracks) {
|
|
||||||
val service = trackManager.getService(track.sync_id)
|
|
||||||
if (service != null && service.isLogged) {
|
|
||||||
var isInDatabase = false
|
|
||||||
for (dbTrack in dbTracks) {
|
|
||||||
if (track.sync_id == dbTrack.sync_id) {
|
|
||||||
// The sync is already in the db, only update its fields
|
|
||||||
if (track.media_id != dbTrack.media_id) {
|
|
||||||
dbTrack.media_id = track.media_id
|
|
||||||
}
|
|
||||||
if (track.library_id != dbTrack.library_id) {
|
|
||||||
dbTrack.library_id = track.library_id
|
|
||||||
}
|
|
||||||
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read)
|
|
||||||
isInDatabase = true
|
|
||||||
trackToUpdate.add(dbTrack)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isInDatabase) {
|
|
||||||
// Insert new sync. Let the db assign the id
|
|
||||||
track.id = null
|
|
||||||
trackToUpdate.add(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Update database
|
|
||||||
if (!trackToUpdate.isEmpty()) {
|
|
||||||
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
val dbSyncs = db.getMangasSync(manga).executeAsBlocking()
|
||||||
* Restore the chapters for manga if chapters already in database
|
val syncToUpdate = ArrayList<MangaSync>()
|
||||||
*
|
for (backupSync in sync) {
|
||||||
* @param manga manga of chapters
|
// Try to find existing chapter in db
|
||||||
* @param chapters list containing chapters that get restored
|
val pos = dbSyncs.indexOf(backupSync)
|
||||||
* @return boolean answering if chapter fetch is not needed
|
|
||||||
*/
|
|
||||||
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>): Boolean {
|
|
||||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
|
||||||
|
|
||||||
// Return if fetch is needed
|
|
||||||
if (dbChapters.isEmpty() || dbChapters.size < chapters.size)
|
|
||||||
return false
|
|
||||||
|
|
||||||
for (chapter in chapters) {
|
|
||||||
val pos = dbChapters.indexOf(chapter)
|
|
||||||
if (pos != -1) {
|
if (pos != -1) {
|
||||||
val dbChapter = dbChapters[pos]
|
// The sync is already in the db, only update its fields
|
||||||
chapter.id = dbChapter.id
|
val dbSync = dbSyncs[pos]
|
||||||
chapter.copyFrom(dbChapter)
|
// Mark the max chapter as read and nothing else
|
||||||
break
|
dbSync.last_chapter_read = Math.max(backupSync.last_chapter_read, dbSync.last_chapter_read)
|
||||||
|
syncToUpdate.add(dbSync)
|
||||||
|
} else {
|
||||||
|
// Insert new sync. Let the db assign the id
|
||||||
|
backupSync.id = null
|
||||||
|
syncToUpdate.add(backupSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Filter the chapters that couldn't be found.
|
|
||||||
chapters.filter { it.id != null }
|
|
||||||
chapters.map { it.manga_id = manga.id }
|
|
||||||
|
|
||||||
insertChapters(chapters)
|
// Update database
|
||||||
return true
|
if (!syncToUpdate.isEmpty()) {
|
||||||
|
db.insertMangasSync(syncToUpdate).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns manga
|
* Returns a list of items from a json element, or an empty list if the element is null.
|
||||||
*
|
*
|
||||||
* @return [Manga], null if not found
|
* @param element the json to be mapped to a list of items.
|
||||||
|
* @param type the gson mapping to restore the list.
|
||||||
|
* @return a list of items.
|
||||||
*/
|
*/
|
||||||
internal fun getMangaFromDatabase(manga: Manga): Manga? =
|
private fun <T> getArrayOrEmpty(element: JsonElement?, type: Type): List<T> {
|
||||||
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
return gson.fromJson<List<T>>(element, type) ?: ArrayList<T>()
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns list containing manga from library
|
|
||||||
*
|
|
||||||
* @return [Manga] from library
|
|
||||||
*/
|
|
||||||
internal fun getFavoriteManga(): List<Manga> =
|
|
||||||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts manga and returns id
|
|
||||||
*
|
|
||||||
* @return id of [Manga], null if not found
|
|
||||||
*/
|
|
||||||
internal fun insertManga(manga: Manga): Long? =
|
|
||||||
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts list of chapters
|
|
||||||
*/
|
|
||||||
private fun insertChapters(chapters: List<Chapter>) {
|
|
||||||
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return number of backups.
|
|
||||||
*
|
|
||||||
* @return number of backups selected by user
|
|
||||||
*/
|
|
||||||
fun numberOfBackups(): Int = preferences.numberOfBackups().getOrDefault()
|
|
||||||
}
|
}
|
||||||
|
@ -1,445 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.PowerManager
|
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonParser
|
|
||||||
import com.google.gson.stream.JsonReader
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
|
|
||||||
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.MANGAS
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.util.chop
|
|
||||||
import eu.kanade.tachiyomi.util.isServiceRunning
|
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
|
||||||
import rx.Observable
|
|
||||||
import rx.Subscription
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
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
|
|
||||||
import java.util.concurrent.ExecutorService
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores backup from json file
|
|
||||||
*/
|
|
||||||
class BackupRestoreService : Service() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the status of the service.
|
|
||||||
*
|
|
||||||
* @param context the application context.
|
|
||||||
* @return true if the service is running, false otherwise.
|
|
||||||
*/
|
|
||||||
private fun isRunning(context: Context): Boolean =
|
|
||||||
context.isServiceRunning(BackupRestoreService::class.java)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a service to restore a backup from Json
|
|
||||||
*
|
|
||||||
* @param context context of application
|
|
||||||
* @param uri path of Uri
|
|
||||||
*/
|
|
||||||
fun start(context: Context, uri: Uri) {
|
|
||||||
if (!isRunning(context)) {
|
|
||||||
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
|
||||||
}
|
|
||||||
context.startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the service.
|
|
||||||
*
|
|
||||||
* @param context the application context.
|
|
||||||
*/
|
|
||||||
fun stop(context: Context) {
|
|
||||||
context.stopService(Intent(context, BackupRestoreService::class.java))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wake lock that will be held until the service is destroyed.
|
|
||||||
*/
|
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription where the update is done.
|
|
||||||
*/
|
|
||||||
private var subscription: Subscription? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The progress of a backup restore
|
|
||||||
*/
|
|
||||||
private var restoreProgress = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Amount of manga in Json file (needed for restore)
|
|
||||||
*/
|
|
||||||
private var restoreAmount = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List containing errors
|
|
||||||
*/
|
|
||||||
private val errors = mutableListOf<Pair<Date, String>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backup manager
|
|
||||||
*/
|
|
||||||
private lateinit var backupManager: BackupManager
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database
|
|
||||||
*/
|
|
||||||
private val db: DatabaseHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tracking manager
|
|
||||||
*/
|
|
||||||
internal val trackManager: TrackManager by injectLazy()
|
|
||||||
|
|
||||||
|
|
||||||
private lateinit var executor: ExecutorService
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called when the service is created. It injects dependencies and acquire the wake lock.
|
|
||||||
*/
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
|
||||||
wakeLock.acquire()
|
|
||||||
executor = Executors.newSingleThreadExecutor()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called when the service is destroyed. It destroys the running subscription and
|
|
||||||
* releases the wake lock.
|
|
||||||
*/
|
|
||||||
override fun onDestroy() {
|
|
||||||
subscription?.unsubscribe()
|
|
||||||
executor.shutdown() // must be called after unsubscribe
|
|
||||||
if (wakeLock.isHeld) {
|
|
||||||
wakeLock.release()
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method needs to be implemented, but it's not used/needed.
|
|
||||||
*/
|
|
||||||
override fun onBind(intent: Intent): IBinder? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called when the service receives an intent.
|
|
||||||
*
|
|
||||||
* @param intent the start intent from.
|
|
||||||
* @param flags the flags of the command.
|
|
||||||
* @param startId the start id of this command.
|
|
||||||
* @return the start value of the command.
|
|
||||||
*/
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
if (intent == null) return Service.START_NOT_STICKY
|
|
||||||
|
|
||||||
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
|
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed.
|
|
||||||
subscription?.unsubscribe()
|
|
||||||
|
|
||||||
subscription = Observable.using(
|
|
||||||
{ db.lowLevel().beginTransaction() },
|
|
||||||
{ getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } },
|
|
||||||
{ executor.execute { db.lowLevel().endTransaction() } })
|
|
||||||
.doAfterTerminate { stopSelf(startId) }
|
|
||||||
.subscribeOn(Schedulers.from(executor))
|
|
||||||
.subscribe()
|
|
||||||
|
|
||||||
return Service.START_NOT_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an [Observable] containing restore process.
|
|
||||||
*
|
|
||||||
* @param uri restore file
|
|
||||||
* @return [Observable<Manga>]
|
|
||||||
*/
|
|
||||||
private fun getRestoreObservable(uri: Uri): Observable<List<Manga>> {
|
|
||||||
val startTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
return Observable.just(Unit)
|
|
||||||
.map {
|
|
||||||
val reader = JsonReader(contentResolver.openInputStream(uri).bufferedReader())
|
|
||||||
val json = JsonParser().parse(reader).asJsonObject
|
|
||||||
|
|
||||||
// Get parser version
|
|
||||||
val version = json.get(VERSION)?.asInt ?: 1
|
|
||||||
|
|
||||||
// Initialize manager
|
|
||||||
backupManager = BackupManager(this, version)
|
|
||||||
|
|
||||||
val mangasJson = json.get(MANGAS).asJsonArray
|
|
||||||
|
|
||||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
|
||||||
restoreProgress = 0
|
|
||||||
errors.clear()
|
|
||||||
|
|
||||||
// Restore categories
|
|
||||||
json.get(CATEGORIES)?.let {
|
|
||||||
backupManager.restoreCategories(it.asJsonArray)
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, "Categories added", errors.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
mangasJson
|
|
||||||
}
|
|
||||||
.flatMap { Observable.from(it) }
|
|
||||||
.concatMap {
|
|
||||||
val obj = it.asJsonObject
|
|
||||||
val manga = backupManager.parser.fromJson<MangaImpl>(obj.get(MANGA))
|
|
||||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(obj.get(CHAPTERS) ?: JsonArray())
|
|
||||||
val categories = backupManager.parser.fromJson<List<String>>(obj.get(CATEGORIES) ?: JsonArray())
|
|
||||||
val history = backupManager.parser.fromJson<List<DHistory>>(obj.get(HISTORY) ?: JsonArray())
|
|
||||||
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(obj.get(TRACK) ?: JsonArray())
|
|
||||||
|
|
||||||
val observable = getMangaRestoreObservable(manga, chapters, categories, history, tracks)
|
|
||||||
if (observable != null) {
|
|
||||||
observable
|
|
||||||
} else {
|
|
||||||
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found)}")
|
|
||||||
restoreProgress += 1
|
|
||||||
val content = getString(R.string.dialog_restoring_source_not_found, manga.title.chop(15))
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, errors.size, content)
|
|
||||||
Observable.just(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
.doOnNext {
|
|
||||||
val endTime = System.currentTimeMillis()
|
|
||||||
val time = endTime - startTime
|
|
||||||
val logFile = writeErrorLog()
|
|
||||||
val completeIntent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_TIME, time)
|
|
||||||
putExtra(BackupConst.EXTRA_ERRORS, errors.size)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_FILE_PATH, logFile.parent)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_FILE, logFile.name)
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_RESTORE_COMPLETED_DIALOG)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(completeIntent)
|
|
||||||
|
|
||||||
}
|
|
||||||
.doOnError { error ->
|
|
||||||
Timber.e(error)
|
|
||||||
writeErrorLog()
|
|
||||||
val errorIntent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_RESTORE_DIALOG)
|
|
||||||
putExtra(BackupConst.EXTRA_ERROR_MESSAGE, error.message)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(errorIntent)
|
|
||||||
}
|
|
||||||
.onErrorReturn { emptyList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write errors to error log
|
|
||||||
*/
|
|
||||||
private fun writeErrorLog(): File {
|
|
||||||
try {
|
|
||||||
if (errors.isNotEmpty()) {
|
|
||||||
val destFile = File(externalCacheDir, "tachiyomi_restore.log")
|
|
||||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
|
||||||
|
|
||||||
destFile.bufferedWriter().use { out ->
|
|
||||||
errors.forEach { (date, message) ->
|
|
||||||
out.write("[${sdf.format(date)}] $message\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return destFile
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
return File("")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a manga restore observable
|
|
||||||
*
|
|
||||||
* @param manga manga data from json
|
|
||||||
* @param chapters chapters data from json
|
|
||||||
* @param categories categories data from json
|
|
||||||
* @param history history data from json
|
|
||||||
* @param tracks tracking data from json
|
|
||||||
* @return [Observable] containing manga restore information
|
|
||||||
*/
|
|
||||||
private fun getMangaRestoreObservable(manga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga>? {
|
|
||||||
// Get source
|
|
||||||
val source = backupManager.sourceManager.getOrStub(manga.source)
|
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
|
||||||
|
|
||||||
return if (dbManga == null) {
|
|
||||||
// Manga not in database
|
|
||||||
mangaFetchObservable(source, manga, chapters, categories, history, tracks)
|
|
||||||
} else { // Manga in database
|
|
||||||
// Copy information from manga already in database
|
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
|
||||||
// Fetch rest of manga information
|
|
||||||
mangaNoFetchObservable(source, manga, chapters, categories, history, tracks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Observable] that fetches manga information
|
|
||||||
*
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @param chapters chapters of manga that needs updating
|
|
||||||
* @param categories categories that need updating
|
|
||||||
*/
|
|
||||||
private fun mangaFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga> {
|
|
||||||
return backupManager.restoreMangaFetchObservable(source, manga)
|
|
||||||
.onErrorReturn {
|
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
.filter { it.id != null }
|
|
||||||
.flatMap {
|
|
||||||
chapterFetchObservable(source, it, chapters)
|
|
||||||
// Convert to the manga that contains new chapters.
|
|
||||||
.map { manga }
|
|
||||||
}
|
|
||||||
.doOnNext {
|
|
||||||
restoreExtraForManga(it, categories, history, tracks)
|
|
||||||
}
|
|
||||||
.flatMap {
|
|
||||||
trackingFetchObservable(it, tracks)
|
|
||||||
// Convert to the manga that contains new chapters.
|
|
||||||
.map { manga }
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, errors.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mangaNoFetchObservable(source: Source, backupManga: Manga, chapters: List<Chapter>,
|
|
||||||
categories: List<String>, history: List<DHistory>,
|
|
||||||
tracks: List<Track>): Observable<Manga> {
|
|
||||||
|
|
||||||
return Observable.just(backupManga)
|
|
||||||
.flatMap { manga ->
|
|
||||||
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
|
|
||||||
chapterFetchObservable(source, manga, chapters)
|
|
||||||
.map { manga }
|
|
||||||
} else {
|
|
||||||
Observable.just(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.doOnNext {
|
|
||||||
restoreExtraForManga(it, categories, history, tracks)
|
|
||||||
}
|
|
||||||
.flatMap { manga ->
|
|
||||||
trackingFetchObservable(manga, tracks)
|
|
||||||
// Convert to the manga that contains new chapters.
|
|
||||||
.map { manga }
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, backupManga.title, errors.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
|
|
||||||
// Restore categories
|
|
||||||
backupManager.restoreCategoriesForManga(manga, categories)
|
|
||||||
|
|
||||||
// Restore history
|
|
||||||
backupManager.restoreHistoryForManga(history)
|
|
||||||
|
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(manga, tracks)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Observable] that fetches chapter information
|
|
||||||
*
|
|
||||||
* @param source source of manga
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @return [Observable] that contains manga
|
|
||||||
*/
|
|
||||||
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
|
||||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
|
||||||
// If there's any error, return empty update and continue.
|
|
||||||
.onErrorReturn {
|
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
|
||||||
Pair(emptyList(), emptyList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Observable] that refreshes tracking information
|
|
||||||
* @param manga manga that needs updating.
|
|
||||||
* @param tracks list containing tracks from restore file.
|
|
||||||
* @return [Observable] that contains updated track item
|
|
||||||
*/
|
|
||||||
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
|
|
||||||
return Observable.from(tracks)
|
|
||||||
.concatMap { track ->
|
|
||||||
val service = trackManager.getService(track.sync_id)
|
|
||||||
if (service != null && service.isLogged) {
|
|
||||||
service.refresh(track)
|
|
||||||
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
|
||||||
.onErrorReturn {
|
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
|
||||||
track
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.add(Date() to "${manga.title} - ${service?.name} not logged in")
|
|
||||||
Observable.empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to update dialog in [BackupConst]
|
|
||||||
*
|
|
||||||
* @param progress restore progress
|
|
||||||
* @param amount total restoreAmount of manga
|
|
||||||
* @param title title of restored manga
|
|
||||||
*/
|
|
||||||
private fun showRestoreProgress(progress: Int, amount: Int, title: String, errors: Int,
|
|
||||||
content: String = getString(R.string.dialog_restoring_backup, title.chop(15))) {
|
|
||||||
val intent = Intent(BackupConst.INTENT_FILTER).apply {
|
|
||||||
putExtra(BackupConst.EXTRA_PROGRESS, progress)
|
|
||||||
putExtra(BackupConst.EXTRA_AMOUNT, amount)
|
|
||||||
putExtra(BackupConst.EXTRA_CONTENT, content)
|
|
||||||
putExtra(BackupConst.EXTRA_ERRORS, errors)
|
|
||||||
putExtra(BackupConst.ACTION, BackupConst.ACTION_SET_PROGRESS_DIALOG)
|
|
||||||
}
|
|
||||||
sendLocalBroadcast(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.models
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Json values
|
|
||||||
*/
|
|
||||||
object Backup {
|
|
||||||
const val CURRENT_VERSION = 2
|
|
||||||
const val MANGA = "manga"
|
|
||||||
const val MANGAS = "mangas"
|
|
||||||
const val TRACK = "track"
|
|
||||||
const val CHAPTERS = "chapters"
|
|
||||||
const val CATEGORIES = "categories"
|
|
||||||
const val HISTORY = "history"
|
|
||||||
const val VERSION = "version"
|
|
||||||
|
|
||||||
fun getDefaultFilename(): String {
|
|
||||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
|
||||||
return "tachiyomi_$date.json"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.models
|
|
||||||
|
|
||||||
data class DHistory(val url: String,val lastRead: Long)
|
|
@ -1,31 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.serializer
|
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.typeAdapter
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [CategoryImpl] to / from json
|
|
||||||
*/
|
|
||||||
object CategoryTypeAdapter {
|
|
||||||
|
|
||||||
fun build(): TypeAdapter<CategoryImpl> {
|
|
||||||
return typeAdapter {
|
|
||||||
write {
|
|
||||||
beginArray()
|
|
||||||
value(it.name)
|
|
||||||
value(it.order)
|
|
||||||
endArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
read {
|
|
||||||
beginArray()
|
|
||||||
val category = CategoryImpl()
|
|
||||||
category.name = nextString()
|
|
||||||
category.order = nextInt()
|
|
||||||
endArray()
|
|
||||||
category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.serializer
|
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.typeAdapter
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import com.google.gson.stream.JsonToken
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [ChapterImpl] to / from json
|
|
||||||
*/
|
|
||||||
object ChapterTypeAdapter {
|
|
||||||
|
|
||||||
private const val URL = "u"
|
|
||||||
private const val READ = "r"
|
|
||||||
private const val BOOKMARK = "b"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
|
|
||||||
fun build(): TypeAdapter<ChapterImpl> {
|
|
||||||
return typeAdapter {
|
|
||||||
write {
|
|
||||||
if (it.read || it.bookmark || it.last_page_read != 0) {
|
|
||||||
beginObject()
|
|
||||||
name(URL)
|
|
||||||
value(it.url)
|
|
||||||
if (it.read) {
|
|
||||||
name(READ)
|
|
||||||
value(1)
|
|
||||||
}
|
|
||||||
if (it.bookmark) {
|
|
||||||
name(BOOKMARK)
|
|
||||||
value(1)
|
|
||||||
}
|
|
||||||
if (it.last_page_read != 0) {
|
|
||||||
name(LAST_READ)
|
|
||||||
value(it.last_page_read)
|
|
||||||
}
|
|
||||||
endObject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
read {
|
|
||||||
val chapter = ChapterImpl()
|
|
||||||
beginObject()
|
|
||||||
while (hasNext()) {
|
|
||||||
if (peek() == JsonToken.NAME) {
|
|
||||||
val name = nextName()
|
|
||||||
|
|
||||||
when (name) {
|
|
||||||
URL -> chapter.url = nextString()
|
|
||||||
READ -> chapter.read = nextInt() == 1
|
|
||||||
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
|
||||||
LAST_READ -> chapter.last_page_read = nextInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endObject()
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.serializer
|
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.typeAdapter
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [DHistory] to / from json
|
|
||||||
*/
|
|
||||||
object HistoryTypeAdapter {
|
|
||||||
|
|
||||||
fun build(): TypeAdapter<DHistory> {
|
|
||||||
return typeAdapter {
|
|
||||||
write {
|
|
||||||
if (it.lastRead != 0L) {
|
|
||||||
beginArray()
|
|
||||||
value(it.url)
|
|
||||||
value(it.lastRead)
|
|
||||||
endArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
read {
|
|
||||||
beginArray()
|
|
||||||
val url = nextString()
|
|
||||||
val lastRead = nextLong()
|
|
||||||
endArray()
|
|
||||||
DHistory(url, lastRead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.serializer
|
||||||
|
|
||||||
|
import com.google.gson.ExclusionStrategy
|
||||||
|
import com.google.gson.FieldAttributes
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
|
|
||||||
|
class IdExclusion : ExclusionStrategy {
|
||||||
|
|
||||||
|
private val categoryExclusions = listOf("id")
|
||||||
|
private val mangaExclusions = listOf("id")
|
||||||
|
private val chapterExclusions = listOf("id", "manga_id")
|
||||||
|
private val syncExclusions = listOf("id", "manga_id", "update")
|
||||||
|
|
||||||
|
override fun shouldSkipField(f: FieldAttributes) = when (f.declaringClass) {
|
||||||
|
Manga::class.java -> mangaExclusions.contains(f.name)
|
||||||
|
Chapter::class.java -> chapterExclusions.contains(f.name)
|
||||||
|
MangaSync::class.java -> syncExclusions.contains(f.name)
|
||||||
|
Category::class.java -> categoryExclusions.contains(f.name)
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldSkipClass(clazz: Class<*>) = false
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup.serializer
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSerializationContext
|
||||||
|
import com.google.gson.JsonSerializer
|
||||||
|
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
class IntegerSerializer : JsonSerializer<Int> {
|
||||||
|
|
||||||
|
override fun serialize(value: Int?, type: Type, context: JsonSerializationContext): JsonElement? {
|
||||||
|
if (value != null && value !== 0)
|
||||||
|
return JsonPrimitive(value)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.serializer
|
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.typeAdapter
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [MangaImpl] to / from json
|
|
||||||
*/
|
|
||||||
object MangaTypeAdapter {
|
|
||||||
|
|
||||||
fun build(): TypeAdapter<MangaImpl> {
|
|
||||||
return typeAdapter {
|
|
||||||
write {
|
|
||||||
beginArray()
|
|
||||||
value(it.url)
|
|
||||||
value(it.title)
|
|
||||||
value(it.source)
|
|
||||||
value(it.viewer)
|
|
||||||
value(it.chapter_flags)
|
|
||||||
endArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
read {
|
|
||||||
beginArray()
|
|
||||||
val manga = MangaImpl()
|
|
||||||
manga.url = nextString()
|
|
||||||
manga.title = nextString()
|
|
||||||
manga.source = nextLong()
|
|
||||||
manga.viewer = nextInt()
|
|
||||||
manga.chapter_flags = nextInt()
|
|
||||||
endArray()
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.serializer
|
|
||||||
|
|
||||||
import android.telecom.DisconnectCause.REMOTE
|
|
||||||
import com.github.salomonbrys.kotson.typeAdapter
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import com.google.gson.stream.JsonToken
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [TrackImpl] to / from json
|
|
||||||
*/
|
|
||||||
object TrackTypeAdapter {
|
|
||||||
|
|
||||||
private const val SYNC = "s"
|
|
||||||
private const val MEDIA = "r"
|
|
||||||
private const val LIBRARY = "ml"
|
|
||||||
private const val TITLE = "t"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
private const val TRACKING_URL = "u"
|
|
||||||
|
|
||||||
fun build(): TypeAdapter<TrackImpl> {
|
|
||||||
return typeAdapter {
|
|
||||||
write {
|
|
||||||
beginObject()
|
|
||||||
name(TITLE)
|
|
||||||
value(it.title)
|
|
||||||
name(SYNC)
|
|
||||||
value(it.sync_id)
|
|
||||||
name(MEDIA)
|
|
||||||
value(it.media_id)
|
|
||||||
name(LIBRARY)
|
|
||||||
value(it.library_id)
|
|
||||||
name(LAST_READ)
|
|
||||||
value(it.last_chapter_read)
|
|
||||||
name(TRACKING_URL)
|
|
||||||
value(it.tracking_url)
|
|
||||||
endObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
read {
|
|
||||||
val track = TrackImpl()
|
|
||||||
beginObject()
|
|
||||||
while (hasNext()) {
|
|
||||||
if (peek() == JsonToken.NAME) {
|
|
||||||
val name = nextName()
|
|
||||||
|
|
||||||
when (name) {
|
|
||||||
TITLE -> track.title = nextString()
|
|
||||||
SYNC -> track.sync_id = nextInt()
|
|
||||||
MEDIA -> track.media_id = nextInt()
|
|
||||||
LIBRARY -> track.library_id = nextLong()
|
|
||||||
LAST_READ -> track.last_chapter_read = nextInt()
|
|
||||||
TRACKING_URL -> track.tracking_url = nextString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endObject()
|
|
||||||
track
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,19 +2,18 @@ package eu.kanade.tachiyomi.data.cache
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.util.DiskUtils
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.saveImageTo
|
||||||
import eu.kanade.tachiyomi.util.saveTo
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.Okio
|
import okio.Okio
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to create chapter cache
|
* Class used to create chapter cache
|
||||||
@ -27,6 +26,15 @@ import java.io.IOException
|
|||||||
*/
|
*/
|
||||||
class ChapterCache(private val context: Context) {
|
class ChapterCache(private val context: Context) {
|
||||||
|
|
||||||
|
/** Google Json class used for parsing JSON files. */
|
||||||
|
private val gson: Gson = Gson()
|
||||||
|
|
||||||
|
/** Cache class used for cache management. */
|
||||||
|
private val diskCache: DiskLruCache
|
||||||
|
|
||||||
|
/** Page list collection used for deserializing from JSON. */
|
||||||
|
private val pageListCollection: Type = object : TypeToken<List<Page>>() {}.type
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** Name of cache directory. */
|
/** Name of cache directory. */
|
||||||
const val PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache"
|
const val PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache"
|
||||||
@ -41,36 +49,38 @@ class ChapterCache(private val context: Context) {
|
|||||||
const val PARAMETER_CACHE_SIZE = 75L * 1024 * 1024
|
const val PARAMETER_CACHE_SIZE = 75L * 1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Google Json class used for parsing JSON files. */
|
init {
|
||||||
private val gson: Gson by injectLazy()
|
// Open cache in default cache directory.
|
||||||
|
diskCache = DiskLruCache.open(
|
||||||
/** Cache class used for cache management. */
|
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||||
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
PARAMETER_APP_VERSION,
|
||||||
PARAMETER_APP_VERSION,
|
PARAMETER_VALUE_COUNT,
|
||||||
PARAMETER_VALUE_COUNT,
|
PARAMETER_CACHE_SIZE)
|
||||||
PARAMETER_CACHE_SIZE)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns directory of cache.
|
* Returns directory of cache.
|
||||||
|
* @return directory of cache.
|
||||||
*/
|
*/
|
||||||
val cacheDir: File
|
val cacheDir: File
|
||||||
get() = diskCache.directory
|
get() = diskCache.directory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns real size of directory.
|
* Returns real size of directory.
|
||||||
|
* @return real size of directory.
|
||||||
*/
|
*/
|
||||||
private val realSize: Long
|
private val realSize: Long
|
||||||
get() = DiskUtil.getDirectorySize(cacheDir)
|
get() = DiskUtils.getDirectorySize(cacheDir)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns real size of directory in human readable format.
|
* Returns real size of directory in human readable format.
|
||||||
|
* @return real size of directory.
|
||||||
*/
|
*/
|
||||||
val readableSize: String
|
val readableSize: String
|
||||||
get() = Formatter.formatFileSize(context, realSize)
|
get() = Formatter.formatFileSize(context, realSize)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove file from cache.
|
* Remove file from cache.
|
||||||
*
|
|
||||||
* @param file name of file "md5.0".
|
* @param file name of file "md5.0".
|
||||||
* @return status of deletion for the file.
|
* @return status of deletion for the file.
|
||||||
*/
|
*/
|
||||||
@ -81,39 +91,37 @@ class ChapterCache(private val context: Context) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Remove the extension from the file to get the key of the cache
|
// Remove the extension from the file to get the key of the cache
|
||||||
val key = file.substringBeforeLast(".")
|
val key = file.substring(0, file.lastIndexOf("."))
|
||||||
// Remove file from cache.
|
// Remove file from cache.
|
||||||
return diskCache.remove(key)
|
return diskCache.remove(key)
|
||||||
} catch (e: Exception) {
|
} catch (e: IOException) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get page list from cache.
|
* Get page list from cache.
|
||||||
*
|
* @param chapterUrl the url of the chapter.
|
||||||
* @param chapter the chapter.
|
|
||||||
* @return an observable of the list of pages.
|
* @return an observable of the list of pages.
|
||||||
*/
|
*/
|
||||||
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
|
fun getPageListFromCache(chapterUrl: String): Observable<List<Page>> {
|
||||||
return Observable.fromCallable {
|
return Observable.fromCallable<List<Page>> {
|
||||||
// Get the key for the chapter.
|
// Get the key for the chapter.
|
||||||
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
val key = DiskUtils.hashKeyForDisk(chapterUrl)
|
||||||
|
|
||||||
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
||||||
diskCache.get(key).use {
|
diskCache.get(key).use {
|
||||||
gson.fromJson<List<Page>>(it.getString(0))
|
gson.fromJson(it.getString(0), pageListCollection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add page list to disk cache.
|
* Add page list to disk cache.
|
||||||
*
|
* @param chapterUrl the url of the chapter.
|
||||||
* @param chapter the chapter.
|
|
||||||
* @param pages list of pages.
|
* @param pages list of pages.
|
||||||
*/
|
*/
|
||||||
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
fun putPageListToCache(chapterUrl: String, pages: List<Page>) {
|
||||||
// Convert list of pages to json string.
|
// Convert list of pages to json string.
|
||||||
val cachedValue = gson.toJson(pages)
|
val cachedValue = gson.toJson(pages)
|
||||||
|
|
||||||
@ -122,7 +130,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Get editor from md5 key.
|
// Get editor from md5 key.
|
||||||
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
val key = DiskUtils.hashKeyForDisk(chapterUrl)
|
||||||
editor = diskCache.edit(key) ?: return
|
editor = diskCache.edit(key) ?: return
|
||||||
|
|
||||||
// Write chapter urls to cache.
|
// Write chapter urls to cache.
|
||||||
@ -143,61 +151,59 @@ class ChapterCache(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if image is in cache.
|
* Check if image is in cache.
|
||||||
*
|
|
||||||
* @param imageUrl url of image.
|
* @param imageUrl url of image.
|
||||||
* @return true if in cache otherwise false.
|
* @return true if in cache otherwise false.
|
||||||
*/
|
*/
|
||||||
fun isImageInCache(imageUrl: String): Boolean {
|
fun isImageInCache(imageUrl: String): Boolean {
|
||||||
try {
|
try {
|
||||||
return diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)) != null
|
return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get image file from url.
|
* Get image path from url.
|
||||||
*
|
|
||||||
* @param imageUrl url of image.
|
* @param imageUrl url of image.
|
||||||
* @return path of image.
|
* @return path of image.
|
||||||
*/
|
*/
|
||||||
fun getImageFile(imageUrl: String): File {
|
fun getImagePath(imageUrl: String): String? {
|
||||||
// Get file from md5 key.
|
try {
|
||||||
val imageName = DiskUtil.hashKeyForDisk(imageUrl) + ".0"
|
// Get file from md5 key.
|
||||||
return File(diskCache.directory, imageName)
|
val imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0"
|
||||||
|
return File(diskCache.directory, imageName).canonicalPath
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add image to cache.
|
* Add image to cache.
|
||||||
*
|
|
||||||
* @param imageUrl url of image.
|
* @param imageUrl url of image.
|
||||||
* @param response http response from page.
|
* @param response http response from page.
|
||||||
* @throws IOException image error.
|
* @throws IOException image error.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun putImageToCache(imageUrl: String, response: Response) {
|
fun putImageToCache(imageUrl: String, response: Response, reencode: Boolean) {
|
||||||
// Initialize editor (edits the values for an entry).
|
// Initialize editor (edits the values for an entry).
|
||||||
var editor: DiskLruCache.Editor? = null
|
var editor: DiskLruCache.Editor? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get editor from md5 key.
|
// Get editor from md5 key.
|
||||||
val key = DiskUtil.hashKeyForDisk(imageUrl)
|
val key = DiskUtils.hashKeyForDisk(imageUrl)
|
||||||
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
||||||
|
|
||||||
// Get OutputStream and write image with Okio.
|
// Get OutputStream and write image with Okio.
|
||||||
response.body()!!.source().saveTo(editor.newOutputStream(0))
|
response.body().source().saveImageTo(editor.newOutputStream(0), reencode)
|
||||||
|
|
||||||
diskCache.flush()
|
diskCache.flush()
|
||||||
editor.commit()
|
editor.commit()
|
||||||
} finally {
|
} finally {
|
||||||
response.body()?.close()
|
response.body().close()
|
||||||
editor?.abortUnlessCommitted()
|
editor?.abortUnlessCommitted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKey(chapter: Chapter): String {
|
|
||||||
return "${chapter.manga_id}${chapter.url}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.DiskUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -20,8 +20,7 @@ class CoverCache(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Cache directory used for cache management.
|
* Cache directory used for cache management.
|
||||||
*/
|
*/
|
||||||
private val cacheDir = context.getExternalFilesDir("covers") ?:
|
private val cacheDir: File = File(context.externalCacheDir, "cover_disk_cache")
|
||||||
File(context.filesDir, "covers").also { it.mkdirs() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cover from cache.
|
* Returns the cover from cache.
|
||||||
@ -30,7 +29,7 @@ class CoverCache(private val context: Context) {
|
|||||||
* @return cover image.
|
* @return cover image.
|
||||||
*/
|
*/
|
||||||
fun getCoverFile(thumbnailUrl: String): File {
|
fun getCoverFile(thumbnailUrl: String): File {
|
||||||
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +59,7 @@ class CoverCache(private val context: Context) {
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
// Remove file.
|
// Remove file.
|
||||||
val file = getCoverFile(thumbnailUrl)
|
val file = getCoverFile(thumbnailUrl!!)
|
||||||
return file.exists() && file.delete()
|
return file.exists() && file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +1,25 @@
|
|||||||
package eu.kanade.tachiyomi.data.database
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
import android.arch.persistence.db.SupportSQLiteOpenHelper
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.*
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import eu.kanade.tachiyomi.data.database.queries.*
|
import eu.kanade.tachiyomi.data.database.queries.*
|
||||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides operations to manage the database through its interfaces.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context)
|
open class DatabaseHelper(context: Context)
|
||||||
: MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
|
: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries {
|
||||||
|
|
||||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
|
||||||
.callback(DbOpenCallback())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override val db = DefaultStorIOSQLite.builder()
|
override val db = DefaultStorIOSQLite.builder()
|
||||||
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
.sqliteOpenHelper(DbOpenHelper(context))
|
||||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
.addTypeMapping(Manga::class.java, MangaSQLiteTypeMapping())
|
||||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
.addTypeMapping(Chapter::class.java, ChapterSQLiteTypeMapping())
|
||||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
.addTypeMapping(MangaSync::class.java, MangaSyncSQLiteTypeMapping())
|
||||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
.addTypeMapping(Category::class.java, CategorySQLiteTypeMapping())
|
||||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
.addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping())
|
||||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||||
|
|
||||||
fun lowLevel() = db.lowLevel()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,23 @@ package eu.kanade.tachiyomi.data.database
|
|||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
|
||||||
inline fun StorIOSQLite.inTransaction(block: () -> Unit) {
|
inline fun StorIOSQLite.inTransaction(block: () -> Unit) {
|
||||||
lowLevel().beginTransaction()
|
internal().beginTransaction()
|
||||||
try {
|
try {
|
||||||
block()
|
block()
|
||||||
lowLevel().setTransactionSuccessful()
|
internal().setTransactionSuccessful()
|
||||||
} finally {
|
} finally {
|
||||||
lowLevel().endTransaction()
|
internal().endTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
|
inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
|
||||||
lowLevel().beginTransaction()
|
internal().beginTransaction()
|
||||||
try {
|
try {
|
||||||
val result = block()
|
val result = block()
|
||||||
lowLevel().setTransactionSuccessful()
|
internal().setTransactionSuccessful()
|
||||||
return result
|
return result
|
||||||
} finally {
|
} finally {
|
||||||
lowLevel().endTransaction()
|
internal().endTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database
|
|
||||||
|
|
||||||
import android.arch.persistence.db.SupportSQLiteDatabase
|
|
||||||
import android.arch.persistence.db.SupportSQLiteOpenHelper
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.*
|
|
||||||
|
|
||||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Name of the database file.
|
|
||||||
*/
|
|
||||||
const val DATABASE_NAME = "tachiyomi.db"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of the database.
|
|
||||||
*/
|
|
||||||
const val DATABASE_VERSION = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
|
||||||
execSQL(MangaTable.createTableQuery)
|
|
||||||
execSQL(ChapterTable.createTableQuery)
|
|
||||||
execSQL(TrackTable.createTableQuery)
|
|
||||||
execSQL(CategoryTable.createTableQuery)
|
|
||||||
execSQL(MangaCategoryTable.createTableQuery)
|
|
||||||
execSQL(HistoryTable.createTableQuery)
|
|
||||||
|
|
||||||
// DB indexes
|
|
||||||
execSQL(MangaTable.createUrlIndexQuery)
|
|
||||||
execSQL(MangaTable.createLibraryIndexQuery)
|
|
||||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
|
||||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
|
||||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
|
||||||
|
|
||||||
// Fix kissmanga covers after supporting cloudflare
|
|
||||||
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
|
||||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
|
||||||
}
|
|
||||||
if (oldVersion < 3) {
|
|
||||||
// Initialize history tables
|
|
||||||
db.execSQL(HistoryTable.createTableQuery)
|
|
||||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 4) {
|
|
||||||
db.execSQL(ChapterTable.bookmarkUpdateQuery)
|
|
||||||
}
|
|
||||||
if (oldVersion < 5) {
|
|
||||||
db.execSQL(ChapterTable.addScanlator)
|
|
||||||
}
|
|
||||||
if (oldVersion < 6) {
|
|
||||||
db.execSQL(TrackTable.addTrackingUrl)
|
|
||||||
}
|
|
||||||
if (oldVersion < 7) {
|
|
||||||
db.execSQL(TrackTable.addLibraryId)
|
|
||||||
}
|
|
||||||
if (oldVersion < 8) {
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS mangas_favorite_index")
|
|
||||||
db.execSQL(MangaTable.createLibraryIndexQuery)
|
|
||||||
db.execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
|
||||||
db.setForeignKeyConstraintsEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,50 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.*
|
||||||
|
|
||||||
|
class DbOpenHelper(context: Context)
|
||||||
|
: SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Name of the database file.
|
||||||
|
*/
|
||||||
|
const val DATABASE_NAME = "tachiyomi.db"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of the database.
|
||||||
|
*/
|
||||||
|
const val DATABASE_VERSION = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(db: SQLiteDatabase) = with(db) {
|
||||||
|
execSQL(MangaTable.createTableQuery)
|
||||||
|
execSQL(ChapterTable.createTableQuery)
|
||||||
|
execSQL(MangaSyncTable.createTableQuery)
|
||||||
|
execSQL(CategoryTable.createTableQuery)
|
||||||
|
execSQL(MangaCategoryTable.createTableQuery)
|
||||||
|
|
||||||
|
// DB indexes
|
||||||
|
execSQL(MangaTable.createUrlIndexQuery)
|
||||||
|
execSQL(MangaTable.createFavoriteIndexQuery)
|
||||||
|
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||||
|
|
||||||
|
// Fix kissmanga covers after supporting cloudflare
|
||||||
|
db.execSQL("""UPDATE mangas SET thumbnail_url =
|
||||||
|
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigure(db: SQLiteDatabase) {
|
||||||
|
db.setForeignKeyConstraintsEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
|
||||||
|
|
||||||
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
|
|
||||||
CategoryPutResolver(),
|
|
||||||
CategoryGetResolver(),
|
|
||||||
CategoryDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
class CategoryPutResolver : DefaultPutResolver<Category>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_NAME, obj.name)
|
|
||||||
put(COL_ORDER, obj.order)
|
|
||||||
put(COL_FLAGS, obj.flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CategoryGetResolver : DefaultGetResolver<Category>() {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
|
|
||||||
id = cursor.getInt(cursor.getColumnIndex(COL_ID))
|
|
||||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
|
||||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
|
||||||
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_BOOKMARK
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_CHAPTER_NUMBER
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_FETCH
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_LAST_PAGE_READ
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_MANGA_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_NAME
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_READ
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SCANLATOR
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SOURCE_ORDER
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
|
|
||||||
|
|
||||||
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
|
|
||||||
ChapterPutResolver(),
|
|
||||||
ChapterGetResolver(),
|
|
||||||
ChapterDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_MANGA_ID, obj.manga_id)
|
|
||||||
put(COL_URL, obj.url)
|
|
||||||
put(COL_NAME, obj.name)
|
|
||||||
put(COL_READ, obj.read)
|
|
||||||
put(COL_SCANLATOR, obj.scanlator)
|
|
||||||
put(COL_BOOKMARK, obj.bookmark)
|
|
||||||
put(COL_DATE_FETCH, obj.date_fetch)
|
|
||||||
put(COL_DATE_UPLOAD, obj.date_upload)
|
|
||||||
put(COL_LAST_PAGE_READ, obj.last_page_read)
|
|
||||||
put(COL_CHAPTER_NUMBER, obj.chapter_number)
|
|
||||||
put(COL_SOURCE_ORDER, obj.source_order)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
|
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
|
||||||
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
|
||||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
|
||||||
scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR))
|
|
||||||
read = cursor.getInt(cursor.getColumnIndex(COL_READ)) == 1
|
|
||||||
bookmark = cursor.getInt(cursor.getColumnIndex(COL_BOOKMARK)) == 1
|
|
||||||
date_fetch = cursor.getLong(cursor.getColumnIndex(COL_DATE_FETCH))
|
|
||||||
date_upload = cursor.getLong(cursor.getColumnIndex(COL_DATE_UPLOAD))
|
|
||||||
last_page_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_PAGE_READ))
|
|
||||||
chapter_number = cursor.getFloat(cursor.getColumnIndex(COL_CHAPTER_NUMBER))
|
|
||||||
source_order = cursor.getInt(cursor.getColumnIndex(COL_SOURCE_ORDER))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.HistoryImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_CHAPTER_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_LAST_READ
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
|
|
||||||
|
|
||||||
class HistoryTypeMapping : SQLiteTypeMapping<History>(
|
|
||||||
HistoryPutResolver(),
|
|
||||||
HistoryGetResolver(),
|
|
||||||
HistoryDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
open class HistoryPutResolver : DefaultPutResolver<History>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: History) = ContentValues(4).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_CHAPTER_ID, obj.chapter_id)
|
|
||||||
put(COL_LAST_READ, obj.last_read)
|
|
||||||
put(COL_TIME_READ, obj.time_read)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HistoryGetResolver : DefaultGetResolver<History>() {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
|
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
|
||||||
chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
|
|
||||||
last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))
|
|
||||||
time_read = cursor.getLong(cursor.getColumnIndex(COL_TIME_READ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_CATEGORY_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
|
|
||||||
|
|
||||||
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
|
|
||||||
MangaCategoryPutResolver(),
|
|
||||||
MangaCategoryGetResolver(),
|
|
||||||
MangaCategoryDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_MANGA_ID, obj.manga_id)
|
|
||||||
put(COL_CATEGORY_ID, obj.category_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
|
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
|
||||||
category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_TITLE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_URL
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
|
|
||||||
|
|
||||||
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
|
|
||||||
MangaPutResolver(),
|
|
||||||
MangaGetResolver(),
|
|
||||||
MangaDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_SOURCE, obj.source)
|
|
||||||
put(COL_URL, obj.url)
|
|
||||||
put(COL_ARTIST, obj.artist)
|
|
||||||
put(COL_AUTHOR, obj.author)
|
|
||||||
put(COL_DESCRIPTION, obj.description)
|
|
||||||
put(COL_GENRE, obj.genre)
|
|
||||||
put(COL_TITLE, obj.title)
|
|
||||||
put(COL_STATUS, obj.status)
|
|
||||||
put(COL_THUMBNAIL_URL, obj.thumbnail_url)
|
|
||||||
put(COL_FAVORITE, obj.favorite)
|
|
||||||
put(COL_LAST_UPDATE, obj.last_update)
|
|
||||||
put(COL_INITIALIZED, obj.initialized)
|
|
||||||
put(COL_VIEWER, obj.viewer)
|
|
||||||
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BaseMangaGetResolver {
|
|
||||||
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
|
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
|
||||||
source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE))
|
|
||||||
url = cursor.getString(cursor.getColumnIndex(COL_URL))
|
|
||||||
artist = cursor.getString(cursor.getColumnIndex(COL_ARTIST))
|
|
||||||
author = cursor.getString(cursor.getColumnIndex(COL_AUTHOR))
|
|
||||||
description = cursor.getString(cursor.getColumnIndex(COL_DESCRIPTION))
|
|
||||||
genre = cursor.getString(cursor.getColumnIndex(COL_GENRE))
|
|
||||||
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
|
||||||
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
|
||||||
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
|
|
||||||
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
|
|
||||||
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
|
|
||||||
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
|
||||||
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
|
||||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Manga {
|
|
||||||
return mapBaseFromCursor(MangaImpl(), cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.mappers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LAST_CHAPTER_READ
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LIBRARY_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MANGA_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MEDIA_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SCORE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_STATUS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SYNC_ID
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TITLE
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TOTAL_CHAPTERS
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
|
|
||||||
|
|
||||||
class TrackTypeMapping : SQLiteTypeMapping<Track>(
|
|
||||||
TrackPutResolver(),
|
|
||||||
TrackGetResolver(),
|
|
||||||
TrackDeleteResolver()
|
|
||||||
)
|
|
||||||
|
|
||||||
class TrackPutResolver : DefaultPutResolver<Track>() {
|
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun mapToContentValues(obj: Track) = ContentValues(10).apply {
|
|
||||||
put(COL_ID, obj.id)
|
|
||||||
put(COL_MANGA_ID, obj.manga_id)
|
|
||||||
put(COL_SYNC_ID, obj.sync_id)
|
|
||||||
put(COL_MEDIA_ID, obj.media_id)
|
|
||||||
put(COL_LIBRARY_ID, obj.library_id)
|
|
||||||
put(COL_TITLE, obj.title)
|
|
||||||
put(COL_LAST_CHAPTER_READ, obj.last_chapter_read)
|
|
||||||
put(COL_TOTAL_CHAPTERS, obj.total_chapters)
|
|
||||||
put(COL_STATUS, obj.status)
|
|
||||||
put(COL_TRACKING_URL, obj.tracking_url)
|
|
||||||
put(COL_SCORE, obj.score)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrackGetResolver : DefaultGetResolver<Track>() {
|
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
|
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
|
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
|
|
||||||
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
|
|
||||||
media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID))
|
|
||||||
library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID))
|
|
||||||
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
|
|
||||||
last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ))
|
|
||||||
total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS))
|
|
||||||
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
|
|
||||||
score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE))
|
|
||||||
tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
|
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
|
|
||||||
.table(TABLE)
|
|
||||||
.where("$COL_ID = ?")
|
|
||||||
.whereArgs(obj.id)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
|
||||||
|
|
||||||
|
@StorIOSQLiteType(table = CategoryTable.TABLE)
|
||||||
|
public class Category implements Serializable {
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = CategoryTable.COL_ID, key = true)
|
||||||
|
public Integer id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = CategoryTable.COL_NAME)
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = CategoryTable.COL_ORDER)
|
||||||
|
public int order;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = CategoryTable.COL_FLAGS)
|
||||||
|
public int flags;
|
||||||
|
|
||||||
|
public Category() {}
|
||||||
|
|
||||||
|
public static Category create(String name) {
|
||||||
|
Category c = new Category();
|
||||||
|
c.name = name;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Category createDefault() {
|
||||||
|
Category c = create("Default");
|
||||||
|
c.id = 0;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameLower() {
|
||||||
|
return name.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Category category = (Category) o;
|
||||||
|
|
||||||
|
return name.equals(category.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface Category : Serializable {
|
|
||||||
|
|
||||||
var id: Int?
|
|
||||||
|
|
||||||
var name: String
|
|
||||||
|
|
||||||
var order: Int
|
|
||||||
|
|
||||||
var flags: Int
|
|
||||||
|
|
||||||
val nameLower: String
|
|
||||||
get() = name.toLowerCase()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun create(name: String): Category = CategoryImpl().apply {
|
|
||||||
this.name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createDefault(): Category = create("Default").apply { id = 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class CategoryImpl : Category {
|
|
||||||
|
|
||||||
override var id: Int? = null
|
|
||||||
|
|
||||||
override lateinit var name: String
|
|
||||||
|
|
||||||
override var order: Int = 0
|
|
||||||
|
|
||||||
override var flags: Int = 0
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
|
||||||
|
|
||||||
val category = other as Category
|
|
||||||
|
|
||||||
return name == category.name
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return name.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,94 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
|
import eu.kanade.tachiyomi.util.UrlUtil;
|
||||||
|
|
||||||
|
@StorIOSQLiteType(table = ChapterTable.TABLE)
|
||||||
|
public class Chapter implements Serializable {
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_ID, key = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_MANGA_ID)
|
||||||
|
public Long manga_id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_URL)
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_NAME)
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_READ)
|
||||||
|
public boolean read;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_LAST_PAGE_READ)
|
||||||
|
public int last_page_read;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_DATE_FETCH)
|
||||||
|
public long date_fetch;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_DATE_UPLOAD)
|
||||||
|
public long date_upload;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_CHAPTER_NUMBER)
|
||||||
|
public float chapter_number;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = ChapterTable.COL_SOURCE_ORDER)
|
||||||
|
public int source_order;
|
||||||
|
|
||||||
|
public int status;
|
||||||
|
|
||||||
|
private transient List<Page> pages;
|
||||||
|
|
||||||
|
public Chapter() {}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = UrlUtil.getPath(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Chapter chapter = (Chapter) o;
|
||||||
|
|
||||||
|
return url.equals(chapter.url);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return url.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Chapter create() {
|
||||||
|
Chapter chapter = new Chapter();
|
||||||
|
chapter.chapter_number = -1;
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Page> getPages() {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPages(List<Page> pages) {
|
||||||
|
this.pages = pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDownloaded() {
|
||||||
|
return status == Download.DOWNLOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecognizedNumber() {
|
||||||
|
return chapter_number >= 0f;
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface Chapter : SChapter, Serializable {
|
|
||||||
|
|
||||||
var id: Long?
|
|
||||||
|
|
||||||
var manga_id: Long?
|
|
||||||
|
|
||||||
var read: Boolean
|
|
||||||
|
|
||||||
var bookmark: Boolean
|
|
||||||
|
|
||||||
var last_page_read: Int
|
|
||||||
|
|
||||||
var date_fetch: Long
|
|
||||||
|
|
||||||
var source_order: Int
|
|
||||||
|
|
||||||
val isRecognizedNumber: Boolean
|
|
||||||
get() = chapter_number >= 0f
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun create(): Chapter = ChapterImpl().apply {
|
|
||||||
chapter_number = -1f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class ChapterImpl : Chapter {
|
|
||||||
|
|
||||||
override var id: Long? = null
|
|
||||||
|
|
||||||
override var manga_id: Long? = null
|
|
||||||
|
|
||||||
override lateinit var url: String
|
|
||||||
|
|
||||||
override lateinit var name: String
|
|
||||||
|
|
||||||
override var scanlator: String? = null
|
|
||||||
|
|
||||||
override var read: Boolean = false
|
|
||||||
|
|
||||||
override var bookmark: Boolean = false
|
|
||||||
|
|
||||||
override var last_page_read: Int = 0
|
|
||||||
|
|
||||||
override var date_fetch: Long = 0
|
|
||||||
|
|
||||||
override var date_upload: Long = 0
|
|
||||||
|
|
||||||
override var chapter_number: Float = 0f
|
|
||||||
|
|
||||||
override var source_order: Int = 0
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
|
||||||
|
|
||||||
val chapter = other as Chapter
|
|
||||||
return url == chapter.url
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return url.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing the history statistics of a chapter
|
|
||||||
*/
|
|
||||||
interface History : Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of history object.
|
|
||||||
*/
|
|
||||||
var id: Long?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chapter id of history object.
|
|
||||||
*/
|
|
||||||
var chapter_id: Long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last time chapter was read in time long format
|
|
||||||
*/
|
|
||||||
var last_read: Long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total time chapter was read - todo not yet implemented
|
|
||||||
*/
|
|
||||||
var time_read: Long
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* History constructor
|
|
||||||
*
|
|
||||||
* @param chapter chapter object
|
|
||||||
* @return history object
|
|
||||||
*/
|
|
||||||
fun create(chapter: Chapter): History = HistoryImpl().apply {
|
|
||||||
this.chapter_id = chapter.id!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing the history statistics of a chapter
|
|
||||||
*/
|
|
||||||
class HistoryImpl : History {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of history object.
|
|
||||||
*/
|
|
||||||
override var id: Long? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chapter id of history object.
|
|
||||||
*/
|
|
||||||
override var chapter_id: Long = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last time chapter was read in time long format
|
|
||||||
*/
|
|
||||||
override var last_read: Long = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total time chapter was read - todo not yet implemented
|
|
||||||
*/
|
|
||||||
override var time_read: Long = 0
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class LibraryManga : MangaImpl() {
|
|
||||||
|
|
||||||
var unread: Int = 0
|
|
||||||
|
|
||||||
var category: Int = 0
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,213 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable;
|
||||||
|
import eu.kanade.tachiyomi.util.UrlUtil;
|
||||||
|
|
||||||
|
@StorIOSQLiteType(table = MangaTable.TABLE)
|
||||||
|
public class Manga implements Serializable {
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_ID, key = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_SOURCE)
|
||||||
|
public int source;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_URL)
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_ARTIST)
|
||||||
|
public String artist;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_AUTHOR)
|
||||||
|
public String author;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_DESCRIPTION)
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_GENRE)
|
||||||
|
public String genre;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_TITLE)
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_STATUS)
|
||||||
|
public int status;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_THUMBNAIL_URL)
|
||||||
|
public String thumbnail_url;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_FAVORITE)
|
||||||
|
public boolean favorite;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_LAST_UPDATE)
|
||||||
|
public long last_update;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_INITIALIZED)
|
||||||
|
public boolean initialized;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_VIEWER)
|
||||||
|
public int viewer;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaTable.COL_CHAPTER_FLAGS)
|
||||||
|
public int chapter_flags;
|
||||||
|
|
||||||
|
public transient int unread;
|
||||||
|
|
||||||
|
public transient int category;
|
||||||
|
|
||||||
|
public static final int UNKNOWN = 0;
|
||||||
|
public static final int ONGOING = 1;
|
||||||
|
public static final int COMPLETED = 2;
|
||||||
|
public static final int LICENSED = 3;
|
||||||
|
|
||||||
|
public static final int SORT_DESC = 0x00000000;
|
||||||
|
public static final int SORT_ASC = 0x00000001;
|
||||||
|
public static final int SORT_MASK = 0x00000001;
|
||||||
|
|
||||||
|
// Generic filter that does not filter anything
|
||||||
|
public static final int SHOW_ALL = 0x00000000;
|
||||||
|
|
||||||
|
public static final int SHOW_UNREAD = 0x00000002;
|
||||||
|
public static final int SHOW_READ = 0x00000004;
|
||||||
|
public static final int READ_MASK = 0x00000006;
|
||||||
|
|
||||||
|
public static final int SHOW_DOWNLOADED = 0x00000008;
|
||||||
|
public static final int SHOW_NOT_DOWNLOADED = 0x00000010;
|
||||||
|
public static final int DOWNLOADED_MASK = 0x00000018;
|
||||||
|
|
||||||
|
public static final int SORTING_SOURCE = 0x00000000;
|
||||||
|
public static final int SORTING_NUMBER = 0x00000100;
|
||||||
|
public static final int SORTING_MASK = 0x00000100;
|
||||||
|
|
||||||
|
public static final int DISPLAY_NAME = 0x00000000;
|
||||||
|
public static final int DISPLAY_NUMBER = 0x00100000;
|
||||||
|
public static final int DISPLAY_MASK = 0x00100000;
|
||||||
|
|
||||||
|
public Manga() {}
|
||||||
|
|
||||||
|
public static Manga create(String pathUrl) {
|
||||||
|
Manga m = new Manga();
|
||||||
|
m.url = pathUrl;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Manga create(String pathUrl, int source) {
|
||||||
|
Manga m = new Manga();
|
||||||
|
m.url = pathUrl;
|
||||||
|
m.source = source;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = UrlUtil.getPath(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(Manga other) {
|
||||||
|
if (other.title != null)
|
||||||
|
title = other.title;
|
||||||
|
|
||||||
|
if (other.author != null)
|
||||||
|
author = other.author;
|
||||||
|
|
||||||
|
if (other.artist != null)
|
||||||
|
artist = other.artist;
|
||||||
|
|
||||||
|
if (other.url != null)
|
||||||
|
url = other.url;
|
||||||
|
|
||||||
|
if (other.description != null)
|
||||||
|
description = other.description;
|
||||||
|
|
||||||
|
if (other.genre != null)
|
||||||
|
genre = other.genre;
|
||||||
|
|
||||||
|
if (other.thumbnail_url != null)
|
||||||
|
thumbnail_url = other.thumbnail_url;
|
||||||
|
|
||||||
|
status = other.status;
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus(Context context) {
|
||||||
|
switch (status) {
|
||||||
|
case ONGOING:
|
||||||
|
return context.getString(R.string.ongoing);
|
||||||
|
case COMPLETED:
|
||||||
|
return context.getString(R.string.completed);
|
||||||
|
case LICENSED:
|
||||||
|
return context.getString(R.string.licensed);
|
||||||
|
default:
|
||||||
|
return context.getString(R.string.unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChapterOrder(int order) {
|
||||||
|
setFlags(order, SORT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayMode(int mode) {
|
||||||
|
setFlags(mode, DISPLAY_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadFilter(int filter) {
|
||||||
|
setFlags(filter, READ_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadedFilter(int filter) {
|
||||||
|
setFlags(filter, DOWNLOADED_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSorting(int sort) {
|
||||||
|
setFlags(sort, SORTING_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFlags(int flag, int mask) {
|
||||||
|
chapter_flags = (chapter_flags & ~mask) | (flag & mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sortDescending() {
|
||||||
|
return (chapter_flags & SORT_MASK) == SORT_DESC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to display the chapter's title one way or another
|
||||||
|
public int getDisplayMode() {
|
||||||
|
return chapter_flags & DISPLAY_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReadFilter() {
|
||||||
|
return chapter_flags & READ_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloadedFilter() {
|
||||||
|
return chapter_flags & DOWNLOADED_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSorting() {
|
||||||
|
return chapter_flags & SORTING_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Manga manga = (Manga) o;
|
||||||
|
|
||||||
|
return url.equals(manga.url);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return url.hashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -1,92 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
|
|
||||||
interface Manga : SManga {
|
|
||||||
|
|
||||||
var id: Long?
|
|
||||||
|
|
||||||
var source: Long
|
|
||||||
|
|
||||||
var favorite: Boolean
|
|
||||||
|
|
||||||
var last_update: Long
|
|
||||||
|
|
||||||
var viewer: Int
|
|
||||||
|
|
||||||
var chapter_flags: Int
|
|
||||||
|
|
||||||
fun setChapterOrder(order: Int) {
|
|
||||||
setFlags(order, SORT_MASK)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setFlags(flag: Int, mask: Int) {
|
|
||||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sortDescending(): Boolean {
|
|
||||||
return chapter_flags and SORT_MASK == SORT_DESC
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
|
||||||
var displayMode: Int
|
|
||||||
get() = chapter_flags and DISPLAY_MASK
|
|
||||||
set(mode) = setFlags(mode, DISPLAY_MASK)
|
|
||||||
|
|
||||||
var readFilter: Int
|
|
||||||
get() = chapter_flags and READ_MASK
|
|
||||||
set(filter) = setFlags(filter, READ_MASK)
|
|
||||||
|
|
||||||
var downloadedFilter: Int
|
|
||||||
get() = chapter_flags and DOWNLOADED_MASK
|
|
||||||
set(filter) = setFlags(filter, DOWNLOADED_MASK)
|
|
||||||
|
|
||||||
var bookmarkedFilter: Int
|
|
||||||
get() = chapter_flags and BOOKMARKED_MASK
|
|
||||||
set(filter) = setFlags(filter, BOOKMARKED_MASK)
|
|
||||||
|
|
||||||
var sorting: Int
|
|
||||||
get() = chapter_flags and SORTING_MASK
|
|
||||||
set(sort) = setFlags(sort, SORTING_MASK)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val SORT_DESC = 0x00000000
|
|
||||||
const val SORT_ASC = 0x00000001
|
|
||||||
const val SORT_MASK = 0x00000001
|
|
||||||
|
|
||||||
// Generic filter that does not filter anything
|
|
||||||
const val SHOW_ALL = 0x00000000
|
|
||||||
|
|
||||||
const val SHOW_UNREAD = 0x00000002
|
|
||||||
const val SHOW_READ = 0x00000004
|
|
||||||
const val READ_MASK = 0x00000006
|
|
||||||
|
|
||||||
const val SHOW_DOWNLOADED = 0x00000008
|
|
||||||
const val SHOW_NOT_DOWNLOADED = 0x00000010
|
|
||||||
const val DOWNLOADED_MASK = 0x00000018
|
|
||||||
|
|
||||||
const val SHOW_BOOKMARKED = 0x00000020
|
|
||||||
const val SHOW_NOT_BOOKMARKED = 0x00000040
|
|
||||||
const val BOOKMARKED_MASK = 0x00000060
|
|
||||||
|
|
||||||
const val SORTING_SOURCE = 0x00000000
|
|
||||||
const val SORTING_NUMBER = 0x00000100
|
|
||||||
const val SORTING_MASK = 0x00000100
|
|
||||||
|
|
||||||
const val DISPLAY_NAME = 0x00000000
|
|
||||||
const val DISPLAY_NUMBER = 0x00100000
|
|
||||||
const val DISPLAY_MASK = 0x00100000
|
|
||||||
|
|
||||||
fun create(source: Long): Manga = MangaImpl().apply {
|
|
||||||
this.source = source
|
|
||||||
}
|
|
||||||
|
|
||||||
fun create(pathUrl: String, title: String, source: Long = 0): Manga = MangaImpl().apply {
|
|
||||||
url = pathUrl
|
|
||||||
this.title = title
|
|
||||||
this.source = source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,29 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
|
||||||
|
|
||||||
|
@StorIOSQLiteType(table = MangaCategoryTable.TABLE)
|
||||||
|
public class MangaCategory {
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_ID, key = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_MANGA_ID)
|
||||||
|
public long manga_id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaCategoryTable.COL_CATEGORY_ID)
|
||||||
|
public int category_id;
|
||||||
|
|
||||||
|
public MangaCategory() {}
|
||||||
|
|
||||||
|
public static MangaCategory create(Manga manga, Category category) {
|
||||||
|
MangaCategory mc = new MangaCategory();
|
||||||
|
mc.manga_id = manga.id;
|
||||||
|
mc.category_id = category.id;
|
||||||
|
return mc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class MangaCategory {
|
|
||||||
|
|
||||||
var id: Long? = null
|
|
||||||
|
|
||||||
var manga_id: Long = 0
|
|
||||||
|
|
||||||
var category_id: Int = 0
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun create(manga: Manga, category: Category): MangaCategory {
|
|
||||||
val mc = MangaCategory()
|
|
||||||
mc.manga_id = manga.id!!
|
|
||||||
mc.category_id = category.id!!
|
|
||||||
return mc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,12 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
public class MangaChapter {
|
||||||
|
|
||||||
|
public Manga manga;
|
||||||
|
public Chapter chapter;
|
||||||
|
|
||||||
|
public MangaChapter(Manga manga, Chapter chapter) {
|
||||||
|
this.manga = manga;
|
||||||
|
this.chapter = chapter;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class MangaChapter(val manga: Manga, val chapter: Chapter)
|
|
@ -1,10 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing manga, chapter and history
|
|
||||||
*
|
|
||||||
* @param manga object containing manga
|
|
||||||
* @param chapter object containing chater
|
|
||||||
* @param history object containing history
|
|
||||||
*/
|
|
||||||
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History)
|
|
@ -1,49 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
open class MangaImpl : Manga {
|
|
||||||
|
|
||||||
override var id: Long? = null
|
|
||||||
|
|
||||||
override var source: Long = -1
|
|
||||||
|
|
||||||
override lateinit var url: String
|
|
||||||
|
|
||||||
override lateinit var title: String
|
|
||||||
|
|
||||||
override var artist: String? = null
|
|
||||||
|
|
||||||
override var author: String? = null
|
|
||||||
|
|
||||||
override var description: String? = null
|
|
||||||
|
|
||||||
override var genre: String? = null
|
|
||||||
|
|
||||||
override var status: Int = 0
|
|
||||||
|
|
||||||
override var thumbnail_url: String? = null
|
|
||||||
|
|
||||||
override var favorite: Boolean = false
|
|
||||||
|
|
||||||
override var last_update: Long = 0
|
|
||||||
|
|
||||||
override var initialized: Boolean = false
|
|
||||||
|
|
||||||
override var viewer: Int = 0
|
|
||||||
|
|
||||||
override var chapter_flags: Int = 0
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
|
||||||
|
|
||||||
val manga = other as Manga
|
|
||||||
|
|
||||||
return url == manga.url
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return url.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,78 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.models;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
||||||
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
|
||||||
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService;
|
||||||
|
|
||||||
|
@StorIOSQLiteType(table = MangaSyncTable.TABLE)
|
||||||
|
public class MangaSync implements Serializable {
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_ID, key = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_MANGA_ID)
|
||||||
|
public long manga_id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_SYNC_ID)
|
||||||
|
public int sync_id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_REMOTE_ID)
|
||||||
|
public int remote_id;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_TITLE)
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_LAST_CHAPTER_READ)
|
||||||
|
public int last_chapter_read;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_TOTAL_CHAPTERS)
|
||||||
|
public int total_chapters;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_SCORE)
|
||||||
|
public float score;
|
||||||
|
|
||||||
|
@StorIOSQLiteColumn(name = MangaSyncTable.COL_STATUS)
|
||||||
|
public int status;
|
||||||
|
|
||||||
|
public boolean update;
|
||||||
|
|
||||||
|
public static MangaSync create() {
|
||||||
|
return new MangaSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MangaSync create(MangaSyncService service) {
|
||||||
|
MangaSync mangasync = new MangaSync();
|
||||||
|
mangasync.sync_id = service.getId();
|
||||||
|
return mangasync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyPersonalFrom(MangaSync other) {
|
||||||
|
last_chapter_read = other.last_chapter_read;
|
||||||
|
score = other.score;
|
||||||
|
status = other.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
MangaSync mangaSync = (MangaSync) o;
|
||||||
|
|
||||||
|
if (manga_id != mangaSync.manga_id) return false;
|
||||||
|
if (sync_id != mangaSync.sync_id) return false;
|
||||||
|
return remote_id == mangaSync.remote_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (manga_id ^ (manga_id >>> 32));
|
||||||
|
result = 31 * result + sync_id;
|
||||||
|
result = 31 * result + remote_id;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface Track : Serializable {
|
|
||||||
|
|
||||||
var id: Long?
|
|
||||||
|
|
||||||
var manga_id: Long
|
|
||||||
|
|
||||||
var sync_id: Int
|
|
||||||
|
|
||||||
var media_id: Int
|
|
||||||
|
|
||||||
var library_id: Long?
|
|
||||||
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
var last_chapter_read: Int
|
|
||||||
|
|
||||||
var total_chapters: Int
|
|
||||||
|
|
||||||
var score: Float
|
|
||||||
|
|
||||||
var status: Int
|
|
||||||
|
|
||||||
var tracking_url: String
|
|
||||||
|
|
||||||
fun copyPersonalFrom(other: Track) {
|
|
||||||
last_chapter_read = other.last_chapter_read
|
|
||||||
score = other.score
|
|
||||||
status = other.status
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun create(serviceId: Int): Track = TrackImpl().apply {
|
|
||||||
sync_id = serviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.models
|
|
||||||
|
|
||||||
class TrackImpl : Track {
|
|
||||||
|
|
||||||
override var id: Long? = null
|
|
||||||
|
|
||||||
override var manga_id: Long = 0
|
|
||||||
|
|
||||||
override var sync_id: Int = 0
|
|
||||||
|
|
||||||
override var media_id: Int = 0
|
|
||||||
|
|
||||||
override var library_id: Long? = null
|
|
||||||
|
|
||||||
override lateinit var title: String
|
|
||||||
|
|
||||||
override var last_chapter_read: Int = 0
|
|
||||||
|
|
||||||
override var total_chapters: Int = 0
|
|
||||||
|
|
||||||
override var score: Float = 0f
|
|
||||||
|
|
||||||
override var status: Int = 0
|
|
||||||
|
|
||||||
override var tracking_url: String = ""
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
|
||||||
|
|
||||||
other as Track
|
|
||||||
|
|
||||||
if (manga_id != other.manga_id) return false
|
|
||||||
if (sync_id != other.sync_id) return false
|
|
||||||
return media_id == other.media_id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
|
||||||
result = 31 * result + sync_id
|
|
||||||
result = 31 * result + media_id
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterBackupPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
||||||
@ -34,24 +34,79 @@ interface ChapterQueries : DbProvider {
|
|||||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getChapter(id: Long) = db.get()
|
fun getNextChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
||||||
|
// Add a delta to the chapter number, because binary decimal representation
|
||||||
|
// can retrieve the same chapter again
|
||||||
|
val chapterNumber = chapter.chapter_number + 0.00001
|
||||||
|
|
||||||
|
return db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COL_CHAPTER_NUMBER} > ? AND " +
|
||||||
|
"${ChapterTable.COL_CHAPTER_NUMBER} <= ?")
|
||||||
|
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
|
||||||
|
.orderBy(ChapterTable.COL_CHAPTER_NUMBER)
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextChapterBySource(chapter: Chapter) = db.get()
|
||||||
.`object`(Chapter::class.java)
|
.`object`(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_ID} = ?")
|
.where("""${ChapterTable.COL_MANGA_ID} = ? AND
|
||||||
.whereArgs(id)
|
${ChapterTable.COL_SOURCE_ORDER} < ?""")
|
||||||
|
.whereArgs(chapter.manga_id, chapter.source_order)
|
||||||
|
.orderBy("${ChapterTable.COL_SOURCE_ORDER} DESC")
|
||||||
|
.limit(1)
|
||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getChapter(url: String) = db.get()
|
fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
||||||
|
// Add a delta to the chapter number, because binary decimal representation
|
||||||
|
// can retrieve the same chapter again
|
||||||
|
val chapterNumber = chapter.chapter_number - 0.00001
|
||||||
|
|
||||||
|
return db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder().table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COL_CHAPTER_NUMBER} < ? AND " +
|
||||||
|
"${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
|
||||||
|
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
|
||||||
|
.orderBy("${ChapterTable.COL_CHAPTER_NUMBER} DESC")
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPreviousChapterBySource(chapter: Chapter) = db.get()
|
||||||
.`object`(Chapter::class.java)
|
.`object`(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("""${ChapterTable.COL_MANGA_ID} = ? AND
|
||||||
.whereArgs(url)
|
${ChapterTable.COL_SOURCE_ORDER} > ?""")
|
||||||
|
.whereArgs(chapter.manga_id, chapter.source_order)
|
||||||
|
.orderBy(ChapterTable.COL_SOURCE_ORDER)
|
||||||
|
.limit(1)
|
||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun getNextUnreadChapter(manga: Manga) = db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COL_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COL_READ} = ? AND " +
|
||||||
|
"${ChapterTable.COL_CHAPTER_NUMBER} >= ?")
|
||||||
|
.whereArgs(manga.id, 0, 0)
|
||||||
|
.orderBy(ChapterTable.COL_CHAPTER_NUMBER)
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||||
|
|
||||||
@ -61,11 +116,6 @@ interface ChapterQueries : DbProvider {
|
|||||||
|
|
||||||
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
||||||
|
|
||||||
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
|
||||||
.objects(chapters)
|
|
||||||
.withPutResolver(ChapterBackupPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateChapterProgress(chapter: Chapter) = db.put()
|
fun updateChapterProgress(chapter: Chapter) = db.put()
|
||||||
.`object`(chapter)
|
.`object`(chapter)
|
||||||
.withPutResolver(ChapterProgressPutResolver())
|
.withPutResolver(ChapterProgressPutResolver())
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
interface HistoryQueries : DbProvider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert history into database
|
|
||||||
* @param history object containing history information
|
|
||||||
*/
|
|
||||||
fun insertHistory(history: History) = db.put().`object`(history).prepare()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns history of recent manga containing last read chapter
|
|
||||||
* @param date recent date range
|
|
||||||
*/
|
|
||||||
fun getRecentManga(date: Date) = db.get()
|
|
||||||
.listOfObjects(MangaChapterHistory::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getRecentMangasQuery())
|
|
||||||
.args(date.time)
|
|
||||||
.observesTables(HistoryTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getHistoryByMangaId(mangaId: Long) = db.get()
|
|
||||||
.listOfObjects(History::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getHistoryByMangaId())
|
|
||||||
.args(mangaId)
|
|
||||||
.observesTables(HistoryTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
|
|
||||||
.`object`(History::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getHistoryByChapterUrl())
|
|
||||||
.args(chapterUrl)
|
|
||||||
.observesTables(HistoryTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the history last read.
|
|
||||||
* Inserts history object if not yet in database
|
|
||||||
* @param history history object
|
|
||||||
*/
|
|
||||||
fun updateHistoryLastRead(history: History) = db.put()
|
|
||||||
.`object`(history)
|
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the history last read.
|
|
||||||
* Inserts history object if not yet in database
|
|
||||||
* @param historyList history object list
|
|
||||||
*/
|
|
||||||
fun updateHistoryLastRead(historyList: List<History>) = db.put()
|
|
||||||
.objects(historyList)
|
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun deleteHistory() = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun deleteHistoryNoLastRead() = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
|
||||||
.whereArgs(0)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
@ -4,10 +4,9 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.*
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
@ -22,15 +21,15 @@ interface MangaQueries : DbProvider {
|
|||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getLibraryMangas() = db.get()
|
fun getLibraryMangas() = db.get()
|
||||||
.listOfObjects(LibraryManga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(RawQuery.builder()
|
||||||
.query(libraryQuery)
|
.query(libraryQuery)
|
||||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
|
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE)
|
||||||
.build())
|
.build())
|
||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getFavoriteMangas() = db.get()
|
open fun getFavoriteMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(Query.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
@ -40,7 +39,7 @@ interface MangaQueries : DbProvider {
|
|||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getManga(url: String, sourceId: Long) = db.get()
|
fun getManga(url: String, sourceId: Int) = db.get()
|
||||||
.`object`(Manga::class.java)
|
.`object`(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(Query.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
@ -67,26 +66,6 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaFlagsPutResolver())
|
.withPutResolver(MangaFlagsPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun updateLastUpdated(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateMangaFavorite(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaFavoritePutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateMangaViewer(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaViewerPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun updateMangaTitle(manga: Manga) = db.put()
|
|
||||||
.`object`(manga)
|
|
||||||
.withPutResolver(MangaTitlePutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
@ -99,20 +78,4 @@ interface MangaQueries : DbProvider {
|
|||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteMangas() = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getLastReadManga() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getLastReadMangaQuery())
|
|
||||||
.observesTables(MangaTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare();
|
|
||||||
}
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable
|
||||||
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
|
||||||
|
|
||||||
|
interface MangaSyncQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getMangaSync(manga: Manga, sync: MangaSyncService) = db.get()
|
||||||
|
.`object`(MangaSync::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COL_MANGA_ID} = ? AND " +
|
||||||
|
"${MangaSyncTable.COL_SYNC_ID} = ?")
|
||||||
|
.whereArgs(manga.id, sync.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getMangasSync(manga: Manga) = db.get()
|
||||||
|
.listOfObjects(MangaSync::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COL_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertMangaSync(manga: MangaSync) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun insertMangasSync(mangas: List<MangaSync>) = db.put().objects(mangas).prepare()
|
||||||
|
|
||||||
|
fun deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun deleteMangaSyncForManga(manga: Manga) = db.delete()
|
||||||
|
.byQuery(DeleteQuery.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COL_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.database.queries
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||||
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.MangaCategoryTable as MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||||
|
|
||||||
@ -40,67 +39,6 @@ fun getRecentsQuery() = """
|
|||||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
/**
|
|
||||||
* Query to get the recently read chapters of manga from the library up to a date.
|
|
||||||
* The max_last_read table contains the most recent chapters grouped by manga
|
|
||||||
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
|
||||||
* and are read after the given time period
|
|
||||||
* @return return limit is 25
|
|
||||||
*/
|
|
||||||
fun getRecentMangasQuery() = """
|
|
||||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
|
||||||
FROM ${Manga.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
|
||||||
JOIN ${History.TABLE}
|
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
|
||||||
JOIN (
|
|
||||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
|
||||||
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
|
||||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
|
||||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
|
||||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
|
||||||
LIMIT 25
|
|
||||||
"""
|
|
||||||
|
|
||||||
fun getHistoryByMangaId() = """
|
|
||||||
SELECT ${History.TABLE}.*
|
|
||||||
FROM ${History.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
|
||||||
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
|
||||||
"""
|
|
||||||
|
|
||||||
fun getHistoryByChapterUrl() = """
|
|
||||||
SELECT ${History.TABLE}.*
|
|
||||||
FROM ${History.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
|
||||||
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
|
||||||
"""
|
|
||||||
|
|
||||||
fun getLastReadMangaQuery() = """
|
|
||||||
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
|
||||||
FROM ${Manga.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
|
||||||
JOIN ${History.TABLE}
|
|
||||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
|
||||||
WHERE ${Manga.TABLE}.${Manga.COL_FAVORITE} = 1
|
|
||||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
|
||||||
ORDER BY max DESC
|
|
||||||
"""
|
|
||||||
|
|
||||||
fun getTotalChapterMangaQuery()= """
|
|
||||||
SELECT ${Manga.TABLE}.*
|
|
||||||
FROM ${Manga.TABLE}
|
|
||||||
JOIN ${Chapter.TABLE}
|
|
||||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
|
||||||
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
|
||||||
ORDER by COUNT(*)
|
|
||||||
"""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the categories for a manga.
|
* Query to get the categories for a manga.
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
|
|
||||||
interface TrackQueries : DbProvider {
|
|
||||||
|
|
||||||
fun getTracks(manga: Manga) = db.get()
|
|
||||||
.listOfObjects(Track::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(TrackTable.TABLE)
|
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertTrack(track: Track) = db.put().`object`(track).prepare()
|
|
||||||
|
|
||||||
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
|
|
||||||
|
|
||||||
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(TrackTable.TABLE)
|
|
||||||
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
|
|
||||||
.whereArgs(manga.id, sync.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
|
||||||
|
|
||||||
class ChapterBackupPutResolver : PutResolver<Chapter>() {
|
|
||||||
|
|
||||||
override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(chapter)
|
|
||||||
val contentValues = mapToContentValues(chapter)
|
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
|
||||||
.whereArgs(chapter.url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
|
||||||
put(ChapterTable.COL_READ, chapter.read)
|
|
||||||
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
|
||||||
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,7 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
|
|||||||
val updateQuery = mapToUpdateQuery(chapter)
|
val updateQuery = mapToUpdateQuery(chapter)
|
||||||
val contentValues = mapToContentValues(chapter)
|
val contentValues = mapToContentValues(chapter)
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues)
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,9 +25,8 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
|
|||||||
.whereArgs(chapter.id)
|
.whereArgs(chapter.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
|
fun mapToContentValues(chapter: Chapter) = ContentValues(2).apply {
|
||||||
put(ChapterTable.COL_READ, chapter.read)
|
put(ChapterTable.COL_READ, chapter.read)
|
||||||
put(ChapterTable.COL_BOOKMARK, chapter.bookmark)
|
|
||||||
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class ChapterSourceOrderPutResolver : PutResolver<Chapter>() {
|
|||||||
val updateQuery = mapToUpdateQuery(chapter)
|
val updateQuery = mapToUpdateQuery(chapter)
|
||||||
val contentValues = mapToContentValues(chapter)
|
val contentValues = mapToContentValues(chapter)
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues)
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.support.annotation.NonNull
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
|
||||||
|
|
||||||
class HistoryLastReadPutResolver : HistoryPutResolver() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates last_read time of chapter
|
|
||||||
*/
|
|
||||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(history)
|
|
||||||
|
|
||||||
val cursor = db.lowLevel().query(Query.builder()
|
|
||||||
.table(updateQuery.table())
|
|
||||||
.where(updateQuery.where())
|
|
||||||
.whereArgs(updateQuery.whereArgs())
|
|
||||||
.build())
|
|
||||||
|
|
||||||
val putResult: PutResult
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (cursor.count == 0) {
|
|
||||||
val insertQuery = mapToInsertQuery(history)
|
|
||||||
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
|
|
||||||
putResult = PutResult.newInsertResult(insertedId, insertQuery.table())
|
|
||||||
} else {
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history))
|
|
||||||
putResult = PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
putResult
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates update query
|
|
||||||
* @param obj history object
|
|
||||||
*/
|
|
||||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
|
||||||
.whereArgs(obj.chapter_id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create content query
|
|
||||||
* @param history object
|
|
||||||
*/
|
|
||||||
fun mapToUpdateContentValues(history: History) = ContentValues(1).apply {
|
|
||||||
put(HistoryTable.COL_LAST_READ, history.last_read)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +1,25 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.BaseMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGetResolver {
|
class LibraryMangaGetResolver : MangaStorIOSQLiteGetResolver() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val INSTANCE = LibraryMangaGetResolver()
|
val INSTANCE = LibraryMangaGetResolver()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): LibraryManga {
|
override fun mapFromCursor(cursor: Cursor): Manga {
|
||||||
val manga = LibraryManga()
|
val manga = super.mapFromCursor(cursor)
|
||||||
|
|
||||||
mapBaseFromCursor(manga, cursor)
|
val unreadColumn = cursor.getColumnIndex(MangaTable.COL_UNREAD)
|
||||||
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
|
manga.unread = cursor.getInt(unreadColumn)
|
||||||
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY))
|
|
||||||
|
val categoryColumn = cursor.getColumnIndex(MangaTable.COL_CATEGORY)
|
||||||
|
manga.category = cursor.getInt(categoryColumn)
|
||||||
|
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.data.database.resolvers
|
|||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
|
import eu.kanade.tachiyomi.data.database.models.ChapterStorIOSQLiteGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver
|
||||||
|
|
||||||
class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
||||||
|
|
||||||
@ -12,15 +12,15 @@ class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
|
|||||||
val INSTANCE = MangaChapterGetResolver()
|
val INSTANCE = MangaChapterGetResolver()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mangaGetResolver = MangaGetResolver()
|
private val mangaGetResolver = MangaStorIOSQLiteGetResolver()
|
||||||
|
|
||||||
private val chapterGetResolver = ChapterGetResolver()
|
private val chapterGetResolver = ChapterStorIOSQLiteGetResolver()
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): MangaChapter {
|
override fun mapFromCursor(cursor: Cursor): MangaChapter {
|
||||||
val manga = mangaGetResolver.mapFromCursor(cursor)
|
val manga = mangaGetResolver.mapFromCursor(cursor)
|
||||||
val chapter = chapterGetResolver.mapFromCursor(cursor)
|
val chapter = chapterGetResolver.mapFromCursor(cursor)
|
||||||
manga.id = chapter.manga_id
|
manga.id = chapter.manga_id
|
||||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"));
|
||||||
|
|
||||||
return MangaChapter(manga, chapter)
|
return MangaChapter(manga, chapter)
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.database.Cursor
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
|
||||||
|
|
||||||
class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>() {
|
|
||||||
companion object {
|
|
||||||
val INSTANCE = MangaChapterHistoryGetResolver()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manga get resolver
|
|
||||||
*/
|
|
||||||
private val mangaGetResolver = MangaGetResolver()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chapter get resolver
|
|
||||||
*/
|
|
||||||
private val chapterResolver = ChapterGetResolver()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* History get resolver
|
|
||||||
*/
|
|
||||||
private val historyGetResolver = HistoryGetResolver()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map correct objects from cursor result
|
|
||||||
*/
|
|
||||||
override fun mapFromCursor(cursor: Cursor): MangaChapterHistory {
|
|
||||||
// Get manga object
|
|
||||||
val manga = mangaGetResolver.mapFromCursor(cursor)
|
|
||||||
|
|
||||||
// Get chapter object
|
|
||||||
val chapter = chapterResolver.mapFromCursor(cursor)
|
|
||||||
|
|
||||||
// Get history object
|
|
||||||
val history = historyGetResolver.mapFromCursor(cursor)
|
|
||||||
|
|
||||||
// Make certain column conflicts are dealt with
|
|
||||||
manga.id = chapter.manga_id
|
|
||||||
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
|
|
||||||
chapter.id = history.chapter_id
|
|
||||||
|
|
||||||
// Return result
|
|
||||||
return MangaChapterHistory(manga, chapter, history)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
|
||||||
|
|
||||||
class MangaFavoritePutResolver : PutResolver<Manga>() {
|
|
||||||
|
|
||||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(manga)
|
|
||||||
val contentValues = mapToContentValues(manga)
|
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
|
||||||
put(MangaTable.COL_FAVORITE, manga.favorite)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,7 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
|
|||||||
val updateQuery = mapToUpdateQuery(manga)
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
val contentValues = mapToContentValues(manga)
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues)
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
|
||||||
|
|
||||||
class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
|
|
||||||
|
|
||||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(manga)
|
|
||||||
val contentValues = mapToContentValues(manga)
|
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
|
||||||
put(MangaTable.COL_LAST_UPDATE, manga.last_update)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
|
||||||
|
|
||||||
class MangaTitlePutResolver : PutResolver<Manga>() {
|
|
||||||
|
|
||||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(manga)
|
|
||||||
val contentValues = mapToContentValues(manga)
|
|
||||||
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
|
||||||
put(MangaTable.COL_TITLE, manga.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|