Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a54b76bf | ||
|
|
a09940eb86 | ||
|
|
159b8c13b0 | ||
|
|
498d51ec22 | ||
|
|
24e50da7c9 | ||
|
|
fcc595d9a3 | ||
|
|
d2aecb5eb5 | ||
|
|
e2cfa30052 |
No files matched your search
@@ -1,32 +1,19 @@
|
||||
name: Bug 反馈 / Bug report
|
||||
description: 提交一个问题报告 / Create a bug report
|
||||
description: 提交一个问题报告 / Create a bug report to help us improve
|
||||
labels:
|
||||
- "bug"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您愿意为 EhViewer 做出贡献!
|
||||
提交问题报告前,还请首先完成文末的自查步骤
|
||||
|
||||
Thanks for your contribution on EhViewer Project!
|
||||
Please finish verify steps which list in the end first before create bug report
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: 复现步骤 / Step to reproduce
|
||||
description: |
|
||||
请从全新安装应用开始,在此处写下复现的方式。请尽量附带录屏
|
||||
如果不能稳定复现,请在下方上传应用日志
|
||||
Describe how to reproduce here with fresh installation, with screen recording if possible.
|
||||
If you can't reproduce it consistently, please upload app logs below
|
||||
在此处写下复现的方式
|
||||
Describe how to reproduce here
|
||||
placeholder: |
|
||||
0. 重新安装应用 / Fresh install
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
[录屏] / [Screen recording]
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -57,34 +44,12 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: 应用日志 / App logs
|
||||
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
|
||||
placeholder: 06-15 17:44:53.704 23382 23382 E ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 备注 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
placeholder: |
|
||||
其他有用的信息与附件
|
||||
Additional details and attachments
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 1.6.9.13
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -92,8 +57,8 @@ body:
|
||||
id: ci
|
||||
attributes:
|
||||
label: EhViewer CI 版本 / EhViewer CI Version
|
||||
description: 位于设置 - 关于中的版本号后 / Next to the version code in Settings - About
|
||||
placeholder: 26f05a0
|
||||
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
|
||||
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -124,6 +89,15 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: 应用日志 / App logs
|
||||
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
|
||||
placeholder: 06-15 17:44:53.704 23382 23382 E ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: check
|
||||
attributes:
|
||||
@@ -135,11 +109,14 @@ body:
|
||||
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
|
||||
required: false
|
||||
|
||||
- label: 您已搜索过 [Issue Tracker](https://github.com/FooIbar/EhViewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/FooIbar/EhViewer/issues), No duplicate or related open issue has been found
|
||||
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
|
||||
required: true
|
||||
|
||||
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
|
||||
required: true
|
||||
|
||||
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 [CI 版本](https://github.com/FooIbar/EhViewer/actions)测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest [CI version](https://github.com/FooIbar/EhViewer/actions)
|
||||
required: true
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
|
||||
required: true
|
||||
@@ -1 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 所有其他问题 / All other questions
|
||||
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
|
||||
about: 转到 Discussions / Turn to discussions
|
||||
@@ -0,0 +1,85 @@
|
||||
name: 功能请求 / Feature request
|
||||
description: 提出一个功能建议 / Suggest an idea for this project
|
||||
labels:
|
||||
- "enhancement"
|
||||
body:
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: 需求 / Requirement
|
||||
description: |
|
||||
在此处描述您的需求。这通常会是一个您想要的功能
|
||||
Ddescribe what you need here.
|
||||
placeholder: |
|
||||
我需要 XXX 功能 ...
|
||||
I want ABC feature ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: impl
|
||||
attributes:
|
||||
label: 建议实现 / Suggested implements
|
||||
description: |
|
||||
在此处表述您建议的实现方式。如有可能,UI 类功能请求还请尽量附上图示
|
||||
Describe your suggested implements here, It's recommend to add a photo if you are making a UI feature request.
|
||||
placeholder: |
|
||||
建议在 XX 处添加 XX ...
|
||||
I recommend add ABC feature to DEF...
|
||||
图片(如果有)/ Photos (if exists)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: more
|
||||
attributes:
|
||||
label: 其他细节 / Addition details
|
||||
description: |
|
||||
在此处写下其他您想说的内容
|
||||
Describe addition details here
|
||||
placeholder: |
|
||||
其他有用的信息与附件
|
||||
Additional details and attachments
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: EhViewer 版本号 / EhViewer version code
|
||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
||||
placeholder: 四位版本号 / 4 digits version code
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: ci
|
||||
attributes:
|
||||
label: EhViewer CI 版本 / EhViewer CI Version
|
||||
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
|
||||
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: check
|
||||
attributes:
|
||||
label: 自查步骤 / Verify steps
|
||||
description: |
|
||||
请确认您已经遵守所有必选项
|
||||
Please ensure you have obtained all needed options
|
||||
options:
|
||||
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
|
||||
required: false
|
||||
|
||||
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
|
||||
required: true
|
||||
|
||||
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
|
||||
required: true
|
||||
|
||||
- label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
|
||||
required: true
|
||||
|
||||
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
|
||||
required: true
|
||||
+13
-51
@@ -1,12 +1,6 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- 'renovate/**'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -14,51 +8,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java 17
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
java-version: 11
|
||||
|
||||
- name: Install Rust Toolchain
|
||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./app/src/main/rust"
|
||||
|
||||
- name: Rustfmt Check
|
||||
working-directory: ./app/src/main/rust
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy Check
|
||||
working-directory: ./app/src/main/rust
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Spotless Check
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: CMake Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
app/.cxx
|
||||
app/src/main/cpp/nettle/nettle
|
||||
key: ${{ runner.os }}-cmake-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-cmake-
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
run: ./gradlew assembleRelease
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -83,10 +52,3 @@ jobs:
|
||||
with:
|
||||
name: x86-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-x86-release.apk
|
||||
|
||||
- name: Update mapping
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release/mapping.txt
|
||||
retention-days: 5
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Pull Request Check
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
|
||||
- name: Update x86_64
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: x86_64-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-x86_64-release.apk
|
||||
|
||||
- name: Update ARM32
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: armeabi-v7a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
|
||||
|
||||
- name: Update x86
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: x86-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-x86-release.apk
|
||||
@@ -11,54 +11,34 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java 17
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
java-version: 11
|
||||
|
||||
- name: Install Rust Toolchain
|
||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./app/src/main/rust"
|
||||
|
||||
- name: CMake Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
app/.cxx
|
||||
app/src/main/cpp/nettle/nettle
|
||||
key: ${{ runner.os }}-cmake-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-cmake-
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Rename Apks
|
||||
run: |
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- name: Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
with:
|
||||
body: Bump Version
|
||||
discussion_category_name: Announcements
|
||||
files: |
|
||||
app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
app/build/outputs/mapping/release/mapping.txt
|
||||
app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
app/build/outputs/apk/release/app-x86_64-release.apk
|
||||
app/build/outputs/apk/release/app-x86-release.apk
|
||||
app/build/outputs/apk/release/app-armeabi-v7a-release.apk
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
|
||||
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
|
||||
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
|
||||
[submodule "native/src/main/jni/image/giflib/giflib"]
|
||||
path = native/src/main/jni/image/giflib/giflib
|
||||
url = https://github.com/Ehviewer-Overhauled/giflib
|
||||
[submodule "native/src/main/jni/image/libpng/libpng"]
|
||||
path = native/src/main/jni/image/libpng/libpng
|
||||
url = https://github.com/Ehviewer-Overhauled/libpng
|
||||
[submodule "native/src/main/jni/libarchive"]
|
||||
path = native/src/main/jni/libarchive
|
||||
url = https://github.com/Ehviewer-Overhauled/libarchive
|
||||
[submodule "native/src/main/jni/liblzma"]
|
||||
path = native/src/main/jni/liblzma
|
||||
url = https://github.com/Ehviewer-Overhauled/xz
|
||||
[submodule "native/src/main/jni/nettle/nettle"]
|
||||
path = native/src/main/jni/nettle/nettle
|
||||
url = https://github.com/Ehviewer-Overhauled/nettle
|
||||
@@ -1,111 +1,77 @@
|
||||
# Maintenance Mode
|
||||
|
||||
The project is in maintenance mode, will only receive bugfixes.
|
||||
|
||||
<p align="right">
|
||||
<strong>English</strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
|
||||
<img src="art/launcher_icon-web.png" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/blob/1.8.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/FooIbar/EhViewer" alt="LICENSE">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/FooIbar/EhViewer">
|
||||
<img src="https://www.codefactor.io/repository/github/FooIbar/EhViewer/badge" alt="CodeFactor">
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||
<img src="https://img.shields.io/github/downloads/FooIbar/EhViewer/total.svg" alt="Downloads">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/FooIbar/EhViewer" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/FooIbar/EhViewer" alt="Issues">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/FooIbar/EhViewer#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/FooIbar/EhViewer#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
# Description
|
||||
|
||||
致力于轻量化与良好性能表现的 EhViewer 分支
|
||||
|
||||
已针对性适配 Material Design 3 及其动态取色系统
|
||||
|
||||
|
||||
EhViewer fork dedicated to lightweight and high-performance
|
||||
|
||||
with [Material Design 3](https://m3.material.io/)
|
||||
and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview) Support
|
||||
with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
|
||||
</a>
|
||||
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
|
||||
|
||||
[CI 版本下载(需登录)](https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml)
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Thanks
|
||||
|
||||
本项目受到了诸多开源项目的帮助
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [Arrow](https://arrow-kt.io/)
|
||||
- [AOSP & AndroidX](http://source.android.com/)
|
||||
- [Kotlin & KotlinX](https://kotlinlang.org/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [MCA](https://github.com/material-components/material-components-android)
|
||||
- [AOSP](http://source.android.com/)
|
||||
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Okhttp](https://square.github.io/okhttp/)
|
||||
- [RikkaX](https://github.com/RikkaApps/RikkaX)
|
||||
- [Libarchive](http://www.libarchive.org/)
|
||||
- [Coil](https://coil-kt.github.io/coil/)
|
||||
- [Jsoup](https://jsoup.org/)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
- [apng](http://apng.sourceforge.net/)
|
||||
- [giflib](http://giflib.sourceforge.net)
|
||||
- [greenDAO](https://github.com/greenrobot/greenDAO)
|
||||
- [jsoup](https://github.com/jhy/jsoup)
|
||||
- [libjpeg-turbo](https://libjpeg-turbo.org/)
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementation!
|
||||
|
||||
# License
|
||||
|
||||
Copyright 2014-2019 Hippo Seven
|
||||
Copyright 2020-2022 NekoInverter
|
||||
Copyright 2022-2023 Tarsin Norbin
|
||||
Copyright 2023 Foolbar
|
||||
Copyright 2022 Tarsin Norbin
|
||||
|
||||
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
|
||||
+1
-2
@@ -4,5 +4,4 @@ manifest-merger-release-report.txt
|
||||
/src/main/libs
|
||||
/src/main/obj
|
||||
# Intellij
|
||||
*.iml
|
||||
/.cxx
|
||||
*.iml
|
||||
@@ -0,0 +1,166 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
|
||||
Properties localProperties = new Properties()
|
||||
if (project.rootProject.file('local.properties').exists()) {
|
||||
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable = true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
sign {
|
||||
storeFile file(signStoreFile)
|
||||
storePassword signStorePassword
|
||||
keyAlias signkeyAlias
|
||||
keyPassword signkeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
versionCode 114535
|
||||
versionName "1.7.29.3"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError true
|
||||
checkReleaseBuilds false
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/**"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo.includeInApk false
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.sign
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/java-gen'
|
||||
}
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
task -> task.dependsOn ":daogenerator:executeDaoGenerator"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-alpha05"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.4.0"
|
||||
implementation "androidx.annotation:annotation-experimental:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
|
||||
|
||||
def arch_core_version = "2.1.0"
|
||||
implementation "androidx.arch.core:core-common:$arch_core_version"
|
||||
implementation "androidx.arch.core:core-runtime:$arch_core_version"
|
||||
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
|
||||
def collection_version = "1.3.0-alpha02"
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
def core_version = "1.9.0-alpha05"
|
||||
implementation "androidx.core:core:$core_version"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
implementation "androidx.customview:customview:1.2.0-alpha01"
|
||||
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.5.1"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha01"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
|
||||
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation "androidx.transition:transition-ktx:1.4.1"
|
||||
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
|
||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0-beta01'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0-alpha03'
|
||||
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'org.greenrobot:greendao:3.3.0'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation projects.native
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
|
||||
exclude group: 'dev.rikka.rikkax.material', module: 'material'
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
import com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
||||
import com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
val isRelease: Boolean
|
||||
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.google.devtools.ksp")
|
||||
id("com.diffplug.spotless")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
id("org.mozilla.rust-android-gradle.rust-android")
|
||||
id("dev.shreyaspatil.compose-compiler-report-generator")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
buildToolsVersion = "34.0.0"
|
||||
ndkVersion = "26.0.10792818"
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
if (isRelease) {
|
||||
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
|
||||
isUniversalApk = true
|
||||
} else {
|
||||
include("arm64-v8a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val signConfig = signingConfigs.create("release") {
|
||||
storeFile = File(projectDir.path + "/keystore/androidkey.jks")
|
||||
storePassword = "000000"
|
||||
keyAlias = "key0"
|
||||
keyPassword = "000000"
|
||||
enableV3Signing = true
|
||||
enableV4Signing = true
|
||||
}
|
||||
|
||||
val commitSha = providers.exec {
|
||||
commandLine = "git rev-parse --short=7 HEAD".split(' ')
|
||||
}.standardOutput.asText.get().trim()
|
||||
|
||||
val buildTime by lazy {
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm").withZone(ZoneOffset.UTC)
|
||||
formatter.format(Instant.now())
|
||||
}
|
||||
|
||||
val repoName = providers.exec {
|
||||
commandLine = "git remote get-url origin".split(' ')
|
||||
}.standardOutput.asText.get().trim().removePrefix("https://github.com/").removePrefix("git@github.com:")
|
||||
.removeSuffix(".git")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 180043
|
||||
versionName = "1.8.9.7"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
"zh-rCN",
|
||||
"zh-rHK",
|
||||
"zh-rTW",
|
||||
"es",
|
||||
"ja",
|
||||
"ko",
|
||||
"fr",
|
||||
"de",
|
||||
"th",
|
||||
"tr",
|
||||
"nb-rNO",
|
||||
),
|
||||
)
|
||||
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
|
||||
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = File("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive",
|
||||
"-Xjvm-default=all",
|
||||
"-Xlambdas=indy",
|
||||
"-XXLanguage:+BreakContinueInInlineLambdas",
|
||||
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.paging.ExperimentalPagingApi",
|
||||
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"-opt-in=splitties.experimental.ExperimentalSplittiesApi",
|
||||
"-opt-in=splitties.preferences.DataStorePreferencesPreview",
|
||||
)
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = true
|
||||
checkReleaseBuilds = false
|
||||
disable.add("MissingTranslation")
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/**"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo.includeInApk = false
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
signingConfig = signConfig
|
||||
buildConfigField("String", "BUILD_TIME", "\"$buildTime\"")
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
buildConfigField("String", "BUILD_TIME", "\"\"")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
compose = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.3"
|
||||
}
|
||||
|
||||
namespace = "com.hippo.ehviewer"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||
implementation("androidx.activity:activity-compose:1.8.0-rc01")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
|
||||
implementation("androidx.browser:browser:1.6.0")
|
||||
implementation("androidx.collection:collection-ktx:1.3.0-rc01")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
||||
// api(platform("androidx.compose:compose-bom:2023.05.00"))
|
||||
api(platform("dev.chrisbanes.compose:compose-bom:2023.07.00-alpha02"))
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material3:material3-window-size-class")
|
||||
implementation("androidx.compose.ui:ui-util")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
|
||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha12")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.7.0-alpha05")
|
||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.6.2")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
val navigation = "2.7.3"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:$navigation")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:$navigation")
|
||||
implementation("androidx.navigation:navigation-compose:$navigation")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||
val paging = "3.2.1"
|
||||
implementation("androidx.paging:paging-compose:$paging")
|
||||
implementation("androidx.paging:paging-runtime-ktx:$paging")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
||||
implementation("androidx.recyclerview:recyclerview-selection:1.2.0-alpha01")
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/room
|
||||
val room = "2.6.0-rc01"
|
||||
ksp("androidx.room:room-compiler:$room")
|
||||
implementation("androidx.room:room-paging:$room")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("com.drakeet.drawer:drawer:1.0.3")
|
||||
implementation("com.github.chrisbanes:PhotoView:2.3.0") // Dead Dependency
|
||||
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") // Dead Dependency
|
||||
// https://github.com/google/accompanist/releases
|
||||
val accompanist = "0.32.0"
|
||||
implementation("com.google.accompanist:accompanist-themeadapter-material3:$accompanist")
|
||||
implementation("com.google.accompanist:accompanist-webview:$accompanist")
|
||||
implementation("com.google.android.material:material:1.11.0-alpha03")
|
||||
|
||||
val splitties = "3.0.0"
|
||||
implementation("com.louiscad.splitties:splitties-appctx:$splitties")
|
||||
implementation("com.louiscad.splitties:splitties-systemservices:$splitties")
|
||||
implementation("com.louiscad.splitties:splitties-preferences:$splitties")
|
||||
implementation("com.louiscad.splitties:splitties-arch-room:$splitties")
|
||||
|
||||
// https://square.github.io/okhttp/changelogs/changelog/
|
||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11"))
|
||||
implementation("com.squareup.okhttp3:okhttp-android")
|
||||
implementation("com.squareup.okhttp3:okhttp-coroutines")
|
||||
|
||||
implementation("com.squareup.okio:okio-jvm:3.5.0")
|
||||
|
||||
implementation("com.mikepenz:aboutlibraries-core:10.9.1")
|
||||
|
||||
implementation("dev.chrisbanes.insetter:insetter:0.6.1") // Dead Dependency
|
||||
implementation("dev.rikka.rikkax.core:core-ktx:1.4.1")
|
||||
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
|
||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
|
||||
|
||||
implementation(platform("io.arrow-kt:arrow-stack:1.2.1"))
|
||||
implementation("io.arrow-kt:arrow-fx-coroutines")
|
||||
|
||||
// https://coil-kt.github.io/coil/changelog/
|
||||
implementation(platform("io.coil-kt:coil-bom:2.4.0"))
|
||||
implementation("io.coil-kt:coil-compose")
|
||||
implementation("io.coil-kt:coil-gif")
|
||||
|
||||
val ktor = "2.3.4"
|
||||
implementation("io.ktor:ktor-io-jvm:$ktor")
|
||||
implementation("io.ktor:ktor-utils-jvm:$ktor")
|
||||
|
||||
val serialization = "1.6.0"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:$serialization")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:$serialization")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||
implementation("org.jsoup:jsoup:1.16.1")
|
||||
|
||||
val chunker = "4.0.0"
|
||||
debugImplementation("com.github.chuckerteam.chucker:library:$chunker")
|
||||
releaseImplementation("com.github.chuckerteam.chucker:library-no-op:$chunker")
|
||||
|
||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
arg("room.generateKotlin", "true")
|
||||
}
|
||||
|
||||
aboutLibraries {
|
||||
duplicationMode = MERGE
|
||||
duplicationRule = GROUP
|
||||
exclusionPatterns = listOf(
|
||||
"androidx\\..*".toPattern(),
|
||||
".*annotations".toPattern(),
|
||||
"com\\.google\\.code\\..*".toPattern(),
|
||||
"com\\.google\\.guava:listenablefuture".toPattern(),
|
||||
"com\\.google\\.protobuf:protobuf-javalite".toPattern(),
|
||||
"org\\.checkerframework:checker-qual".toPattern(),
|
||||
"org\\.chromium\\.net:cronet-embedded".toPattern(),
|
||||
"org\\.slf4j:slf4j-api".toPattern(),
|
||||
)
|
||||
}
|
||||
|
||||
cargo {
|
||||
module = "src/main/rust"
|
||||
libname = "ehviewer_rust"
|
||||
targets = if (isRelease) listOf("arm", "x86", "arm64", "x86_64") else listOf("arm64")
|
||||
if (isRelease) profile = "release"
|
||||
}
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
ktlint()
|
||||
}
|
||||
kotlinGradle {
|
||||
ktlint().editorConfigOverride(
|
||||
mapOf(
|
||||
"ktlint_standard_multiline-expression-wrapping" to "disabled",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.configureEach {
|
||||
if ((name == "mergeDebugJniLibFolders" || name == "mergeReleaseJniLibFolders")) {
|
||||
dependsOn("cargoBuild")
|
||||
// fix mergeDebugJniLibFolders UP-TO-DATE
|
||||
inputs.dir(layout.buildDirectory.dir("rustJniLibs/android"))
|
||||
}
|
||||
}
|
||||
Vendored
+22
-18
@@ -2,29 +2,33 @@
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static void check*(...);
|
||||
public static void throw*(...);
|
||||
}
|
||||
|
||||
# https://issuetracker.google.com/issues/190382641
|
||||
-keepclassmembers class kotlin.SafePublicationLazyImpl {
|
||||
java.lang.Object _value;
|
||||
}
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
|
||||
-keep class com.hippo.ehviewer.client.parser.Torrent { *; }
|
||||
-keep class com.hippo.ehviewer.client.parser.GalleryListResult { *; }
|
||||
-keep class com.hippo.ehviewer.client.parser.Limits { *; }
|
||||
-keep class com.hippo.ehviewer.client.data.BaseGalleryInfo { *; }
|
||||
-keep class com.hippo.ehviewer.ui.scene.SearchBarScene { getBinding(); }
|
||||
|
||||
# Ktor logger
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
|
||||
-keepattributes LineNumberTable,SourceFile
|
||||
-renamesourcefileattribute SourceFile
|
||||
-keep class com.hippo.ehviewer.dao.* { *; }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-overloadaggressively
|
||||
|
||||
-dontwarn net.sqlcipher.database.**
|
||||
-dontwarn rx.**
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.conscrypt.Conscrypt$Version
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn net.sqlcipher.Cursor
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.conscrypt.Conscrypt
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "c801ee62729ba20a203b150211234d5c",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "OK_HTTP_3_COOKIE",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NAME` TEXT, `VALUE` TEXT, `EXPIRES_AT` INTEGER NOT NULL, `DOMAIN` TEXT, `PATH` TEXT, `SECURE` INTEGER NOT NULL, `HTTP_ONLY` INTEGER NOT NULL, `PERSISTENT` INTEGER NOT NULL, `HOST_ONLY` INTEGER NOT NULL, `_id` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "VALUE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "expiresAt",
|
||||
"columnName": "EXPIRES_AT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domain",
|
||||
"columnName": "DOMAIN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "PATH",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "secure",
|
||||
"columnName": "SECURE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "httpOnly",
|
||||
"columnName": "HTTP_ONLY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "persistent",
|
||||
"columnName": "PERSISTENT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hostOnly",
|
||||
"columnName": "HOST_ONLY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c801ee62729ba20a203b150211234d5c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "1b80cd29939b0f43934721f1289ba94d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "OK_HTTP_3_COOKIE",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NAME` TEXT NOT NULL, `VALUE` TEXT NOT NULL, `EXPIRES_AT` INTEGER NOT NULL, `DOMAIN` TEXT NOT NULL, `PATH` TEXT NOT NULL, `SECURE` INTEGER NOT NULL, `HTTP_ONLY` INTEGER NOT NULL, `PERSISTENT` INTEGER NOT NULL, `HOST_ONLY` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "VALUE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "expiresAt",
|
||||
"columnName": "EXPIRES_AT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domain",
|
||||
"columnName": "DOMAIN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "PATH",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "secure",
|
||||
"columnName": "SECURE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "httpOnly",
|
||||
"columnName": "HTTP_ONLY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "persistent",
|
||||
"columnName": "PERSISTENT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hostOnly",
|
||||
"columnName": "HOST_ONLY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1b80cd29939b0f43934721f1289ba94d')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "613604f93bf815adeb87f03e2166e983",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '613604f93bf815adeb87f03e2166e983')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,469 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 11,
|
||||
"identityHash": "92915e937cb7ccc536d352fda5cf287f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL DEFAULT 0, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL DEFAULT 0, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '92915e937cb7ccc536d352fda5cf287f')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 12,
|
||||
"identityHash": "d40b38b0ba3b1c53b773728cdee328c8",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd40b38b0ba3b1c53b773728cdee328c8')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 13,
|
||||
"identityHash": "df15d8f70f45047a738c8911212ea73c",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "GALLERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `FAVORITE_SLOT` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'df15d8f70f45047a738c8911212ea73c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 14,
|
||||
"identityHash": "54e4f2704cc47c5a6a2f4b96de978cce",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "GALLERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '54e4f2704cc47c5a6a2f4b96de978cce')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 15,
|
||||
"identityHash": "991a3a3ddc4fe8d4dbcb39b975ab73b8",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "GALLERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"LABEL"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '991a3a3ddc4fe8d4dbcb39b975ab73b8')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 16,
|
||||
"identityHash": "06e5ff8fd6797d5801e86753fa5831b2",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "GALLERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"LABEL"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_DOWNLOADS_LABEL",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"LABEL"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "DOWNLOAD_LABELS",
|
||||
"onDelete": "SET NULL",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"LABEL"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"LABEL"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '06e5ff8fd6797d5801e86753fa5831b2')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "f5752deda842dccabded1cdbf1c78384",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BOOKMARKS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "page",
|
||||
"columnName": "PAGE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f5752deda842dccabded1cdbf1c78384')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "f5752deda842dccabded1cdbf1c78384",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BOOKMARKS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "page",
|
||||
"columnName": "PAGE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f5752deda842dccabded1cdbf1c78384')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "e45f4380ef769c127604cb2a502a942b",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BOOKMARKS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`PAGE` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "page",
|
||||
"columnName": "PAGE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e45f4380ef769c127604cb2a502a942b')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "9563c2017be4341dc1bada115426df77",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT, `ENABLE` INTEGER, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9563c2017be4341dc1bada115426df77')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 8,
|
||||
"identityHash": "f09bae277bbdab54ecbb0d698c83e734",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `LABEL` TEXT, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f09bae277bbdab54ecbb0d698c83e734')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 9,
|
||||
"identityHash": "64f51a531e13d2b0b971f3d8da0876b0",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `TIME` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `MODE` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlotBackingField",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`TIME` INTEGER NOT NULL, `GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64f51a531e13d2b0b971f3d8da0876b0')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "69230710633494b4b331e10dae436fb7",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "suggestions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `date` LONG, `query` TEXT, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "LONG",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "query",
|
||||
"columnName": "query",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '69230710633494b4b331e10dae436fb7')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "bf4a5b5f3cb9bc7383488bae14f5d4bd",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "suggestions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`date` INTEGER NOT NULL, `query` TEXT NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "query",
|
||||
"columnName": "query",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bf4a5b5f3cb9bc7383488bae14f5d4bd')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
@@ -9,17 +23,20 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<!-- Required to maintain app compatibility. -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
<application
|
||||
android:name=".EhApplication"
|
||||
android:name="com.hippo.ehviewer.EhApplication"
|
||||
android:allowBackup="true"
|
||||
android:appCategory="image"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
@@ -27,32 +44,26 @@
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locale_config"
|
||||
android:largeHeap="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:targetApi="tiramisu">
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".ui.ConfigureActivity"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:name="com.hippo.ehviewer.ui.MainActivity"
|
||||
android:banner="@mipmap/ic_leanback_banner"
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -70,27 +81,29 @@
|
||||
<data android:scheme="https" />
|
||||
<data android:host="exhentai.org" />
|
||||
<data android:host="e-hentai.org" />
|
||||
<data android:host="g.e-hentai.org" />
|
||||
<data android:host="lofi.e-hentai.org" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/keyword_search">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/image_search">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
|
||||
android:name="com.hippo.ehviewer.ui.GalleryActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -124,29 +137,22 @@
|
||||
<data android:mimeType="application/x-xz-compressed-tar" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.SecurityActivity"
|
||||
android:name="com.hippo.ehviewer.ui.SettingsActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:label="@string/settings" />
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".shortcuts.ShortcutsActivity"
|
||||
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
<activity
|
||||
android:name=".ui.WebViewActivity" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".download.DownloadService"
|
||||
android:name="com.hippo.ehviewer.download.DownloadService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:label="@string/download_service_label" />
|
||||
|
||||
@@ -155,10 +161,11 @@
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -1,80 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(ehviewer C ASM)
|
||||
include(FetchContent)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -funroll-loops -flto \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
-mllvm -polly-isl-arg=--no-schedule-serialize-sccs \
|
||||
-mllvm -polly-ast-use-context \
|
||||
-mllvm -polly-detect-keep-going \
|
||||
-mllvm -polly-position=before-vectorizer \
|
||||
-mllvm -polly-vectorizer=stripmine \
|
||||
-mllvm -polly-detect-profitability-min-per-loop-insts=40 \
|
||||
-mllvm -polly-invariant-load-hoisting")
|
||||
endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
||||
# Change the default path of fetch content to avoid downloading the same dependencies per architecture
|
||||
get_filename_component(deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
set(FETCHCONTENT_BASE_DIR ${deps})
|
||||
|
||||
set(BUILD_TESTING 0)
|
||||
|
||||
FetchContent_Declare(liblzma
|
||||
GIT_REPOSITORY https://github.com/FooIbar/xz.git
|
||||
GIT_TAG v5.4.4
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(liblzma)
|
||||
include_directories(${liblzma_SOURCE_DIR}/src/liblzma/api)
|
||||
|
||||
# Build GNUTLS libnettle
|
||||
FetchContent_Declare(nettle
|
||||
URL https://ftp.gnu.org/gnu/nettle/nettle-3.9.1.tar.gz
|
||||
URL_MD5 29fcd2dec6bf5b48e5e3ffb3cbc4779e
|
||||
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/nettle/nettle
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(nettle)
|
||||
add_subdirectory(nettle)
|
||||
|
||||
# Configure libnettle support for libarchive
|
||||
include_directories(nettle)
|
||||
include_directories(.)
|
||||
set(HAVE_LIBNETTLE 1)
|
||||
set(HAVE_NETTLE_AES_H 1)
|
||||
set(HAVE_NETTLE_HMAC_H 1)
|
||||
set(HAVE_NETTLE_MD5_H 1)
|
||||
set(HAVE_NETTLE_PBKDF2_H 1)
|
||||
set(HAVE_NETTLE_RIPEMD160_H 1)
|
||||
set(HAVE_NETTLE_SHA_H 1)
|
||||
|
||||
# Configure lzma support for libarchive
|
||||
SET(HAVE_LIBLZMA 1)
|
||||
SET(HAVE_LZMA_H 1)
|
||||
SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
|
||||
SET(HAVE_LZMADEC_H 1)
|
||||
SET(HAVE_LIBLZMADEC 1)
|
||||
|
||||
option(ENABLE_TAR OFF)
|
||||
option(ENABLE_CPIO OFF)
|
||||
option(ENABLE_CAT OFF)
|
||||
option(ENABLE_UNZIP OFF)
|
||||
option(ENABLE_TEST OFF)
|
||||
|
||||
# Configure libarchive link's static lib
|
||||
SET(LIBARCHIVE_CUSTOM_LIBS "nettle" "liblzma")
|
||||
|
||||
FetchContent_Declare(libarchive
|
||||
GIT_REPOSITORY https://github.com/FooIbar/libarchive.git
|
||||
GIT_TAG v3.7.2
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libarchive)
|
||||
include_directories(${libarchive_SOURCE_DIR}/libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED archive.c gifutils.c natsort/strnatcmp.c)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log)
|
||||
@@ -1,486 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
|
||||
#include "natsort/strnatcmp.h"
|
||||
#include "ehviewer.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
typedef struct {
|
||||
int using;
|
||||
int next_index;
|
||||
struct archive *arc;
|
||||
struct archive_entry *entry;
|
||||
} archive_ctx;
|
||||
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
int index;
|
||||
size_t size;
|
||||
} entry;
|
||||
|
||||
_Atomic mcs_lock ctx_lock;
|
||||
static archive_ctx **ctx_pool;
|
||||
#define CTX_POOL_SIZE 20
|
||||
|
||||
static void *mempool = NULL;
|
||||
static size_t *mempoolofs = NULL;
|
||||
|
||||
#define PAGE_ALIGN(x) ((x + ~PAGE_MASK) & PAGE_MASK)
|
||||
|
||||
#define MEMPOOL_ADDR_BY_SORTED_IDX(x) (mempool + (index ? mempoolofs[index - 1] : 0))
|
||||
#define MEMPOOL_SIZE (mempoolofs[entryCount - 1])
|
||||
#define MEMPOOL_ENTITY_SIZE(x) PAGE_ALIGN(entries[x].size)
|
||||
|
||||
#define PROT_RW (PROT_WRITE | PROT_READ)
|
||||
#define MAP_ANON_POOL (MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_UNINITIALIZED)
|
||||
|
||||
static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static size_t archiveSize = 0;
|
||||
static entry *entries = NULL;
|
||||
static size_t entryCount = 0;
|
||||
|
||||
const char supportExt[10][6] = {
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
"bmp",
|
||||
"ico",
|
||||
"wbmp",
|
||||
"heif",
|
||||
"avif"
|
||||
};
|
||||
|
||||
static inline int filename_is_playable_file(const char *name) {
|
||||
const char *dotptr = strrchr(name, '.');
|
||||
if (!dotptr++)
|
||||
return false;
|
||||
int i;
|
||||
for (i = 0; i < 10; i++)
|
||||
if (strcmp(dotptr, supportExt[i]) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int compare_entries(const void *a, const void *b) {
|
||||
const char *fa = ((entry *) a)->filename;
|
||||
const char *fb = ((entry *) b)->filename;
|
||||
return strnatcmp(fa, fb);
|
||||
}
|
||||
|
||||
static long archive_map_entries_index(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||
const char *name = archive_entry_pathname(ctx->entry);
|
||||
if (filename_is_playable_file(name)) {
|
||||
entries[count].filename = strdup(name);
|
||||
entries[count].index = count;
|
||||
entries[count].size = archive_entry_size(ctx->entry);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (!count)
|
||||
LOGE("%s", archive_error_string(ctx->arc));
|
||||
qsort(entries, entryCount, sizeof(entry), compare_entries);
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool archive_prealloc_mempool() {
|
||||
mempoolofs = calloc(entryCount, sizeof(size_t));
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
if (!i)
|
||||
mempoolofs[i] = PAGE_ALIGN(entries[i].size);
|
||||
else
|
||||
mempoolofs[i] = PAGE_ALIGN(entries[i].size) + mempoolofs[i - 1];
|
||||
}
|
||||
mempool = mmap(0, MEMPOOL_SIZE, PROT_RW, MAP_ANON_POOL, -1, 0);
|
||||
if (mempool == MAP_FAILED) {
|
||||
LOGE("%s%s", "mmap failed with error ", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Mincore does not support anon mapping yet
|
||||
// Now that we use MADV_FREE, how can we detect whether pages is reclaimed?
|
||||
static bool is_mempool_pages_present_and_lock(int index) {
|
||||
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
|
||||
size_t size = MEMPOOL_ENTITY_SIZE(index);
|
||||
mlock(addr, size);
|
||||
#if 0
|
||||
unsigned char vec[size / PAGE_SIZE];
|
||||
unsigned char r = 1;
|
||||
mincore(addr, size, vec);
|
||||
for (int i = 0; i < size / PAGE_SIZE; ++i) r &= vec[i];
|
||||
return r;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#define MEMPOOL_IN_CORE_AND_LOCK(x) is_mempool_pages_present_and_lock(x)
|
||||
|
||||
static void mempool_release_pages(void *addr, size_t size) {
|
||||
size = PAGE_ALIGN(size);
|
||||
munlock(addr, size);
|
||||
int ret = madvise(addr, size, MADV_FREE);
|
||||
if (ret == -EINVAL) madvise(addr, size, MADV_DONTNEED);
|
||||
}
|
||||
|
||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
||||
if (filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
||||
count++;
|
||||
if (!count)
|
||||
LOGE("%s", archive_error_string(ctx->arc));
|
||||
return count;
|
||||
}
|
||||
|
||||
static void archive_release_ctx(archive_ctx *ctx) {
|
||||
if (ctx) {
|
||||
archive_read_close(ctx->arc);
|
||||
archive_read_free(ctx->arc);
|
||||
free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
||||
archive_ctx *ctx;
|
||||
int err;
|
||||
ctx = calloc(1, sizeof(archive_ctx));
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
ctx->arc = archive_read_new();
|
||||
ctx->using = 1;
|
||||
if (!ctx->arc) {
|
||||
free(ctx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
archive_read_support_format_tar(ctx->arc);
|
||||
archive_read_support_format_7zip(ctx->arc);
|
||||
archive_read_support_format_rar5(ctx->arc);
|
||||
archive_read_support_format_zip(ctx->arc);
|
||||
archive_read_support_filter_gzip(ctx->arc);
|
||||
archive_read_support_filter_xz(ctx->arc);
|
||||
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||
if (passwd)
|
||||
archive_read_add_passphrase(ctx->arc, passwd);
|
||||
err = archive_read_open_memory(ctx->arc, archiveAddr, archiveSize);
|
||||
if (err) {
|
||||
archive_read_free(ctx->arc);
|
||||
free(ctx);
|
||||
return err;
|
||||
}
|
||||
*ctxptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int archive_skip_to_index(archive_ctx *ctx, int index) {
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
||||
continue;
|
||||
if (ctx->next_index++ == index) {
|
||||
return ctx->next_index - 1;
|
||||
}
|
||||
}
|
||||
return ARCHIVE_FATAL;
|
||||
}
|
||||
|
||||
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||
int ret;
|
||||
archive_ctx *ctx = NULL;
|
||||
mcs_lock_t local_lock;
|
||||
lock_mcs(&ctx_lock, &local_lock);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||
if (!ctx_pool[i])
|
||||
continue;
|
||||
if (ctx_pool[i]->using)
|
||||
continue;
|
||||
if (ctx_pool[i]->next_index > idx)
|
||||
continue;
|
||||
if (!ctx || ctx_pool[i]->next_index > ctx->next_index)
|
||||
ctx = ctx_pool[i];
|
||||
if (ctx->next_index == idx)
|
||||
break;
|
||||
}
|
||||
if (ctx)
|
||||
ctx->using = 1;
|
||||
unlock_mcs(&ctx_lock, &local_lock);
|
||||
|
||||
if (!ctx) {
|
||||
archive_ctx *victimCtx = NULL;
|
||||
int victimIdx = 0;
|
||||
int replace = 1;
|
||||
ret = archive_alloc_ctx(&ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
lock_mcs(&ctx_lock, &local_lock);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||
if (!ctx_pool[i]) {
|
||||
ctx_pool[i] = ctx;
|
||||
replace = 0;
|
||||
break;
|
||||
}
|
||||
if (ctx_pool[i]->using)
|
||||
continue;
|
||||
if (!victimCtx || ctx_pool[i]->next_index > victimCtx->next_index) {
|
||||
victimCtx = ctx_pool[i];
|
||||
victimIdx = i;
|
||||
}
|
||||
}
|
||||
if (replace) ctx_pool[victimIdx] = ctx;
|
||||
unlock_mcs(&ctx_lock, &local_lock);
|
||||
if (replace) archive_release_ctx(victimCtx);
|
||||
}
|
||||
ret = archive_skip_to_index(ctx, idx);
|
||||
if (ret != idx) {
|
||||
ret = archive_errno(ctx->arc);
|
||||
LOGE("Skip to index failed:%s", archive_error_string(ctx->arc));
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
*ctxptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jclass thiz, jint fd,
|
||||
jlong size) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int mmap_flags = MAP_PRIVATE;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
|
||||
if (archiveAddr == MAP_FAILED) {
|
||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
archiveSize = size;
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||
if (!ctx_pool) {
|
||||
LOGE("Allocate archive ctx pool failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
long r = archive_alloc_ctx(&ctx);
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
entryCount = archive_list_all_entries(ctx);
|
||||
LOGI("%s%zu%s", "Found ", entryCount, " image entries in archive");
|
||||
|
||||
// We must read through the file|vm then we can know whether it is encrypted
|
||||
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
|
||||
switch (encryptRet) {
|
||||
case 1: // At lease 1 encrypted entry
|
||||
need_encrypt = true;
|
||||
break;
|
||||
case 0: // format supports but no encrypted entry found
|
||||
default:
|
||||
need_encrypt = false;
|
||||
}
|
||||
|
||||
int format = archive_format(ctx->arc);
|
||||
switch (format) {
|
||||
case ARCHIVE_FORMAT_ZIP:
|
||||
case ARCHIVE_FORMAT_RAR_V5:
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
|
||||
break;
|
||||
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
|
||||
r = archive_alloc_ctx(&ctx);
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
entries = calloc(entryCount, sizeof(entry));
|
||||
r = archive_map_entries_index(ctx);
|
||||
if (!archive_prealloc_mempool()) {
|
||||
r = 0;
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
free((void *) entries[i].filename);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "ConstantConditionsOC"
|
||||
#pragma ide diagnostic ignored "UnreachableCode"
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToByteBuffer(JNIEnv *env, jclass thiz,
|
||||
jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
|
||||
size_t size = entries[index].size;
|
||||
if (!MEMPOOL_IN_CORE_AND_LOCK(index)) {
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret) return 0;
|
||||
ret = archive_read_data(ctx->arc, addr, size);
|
||||
ctx->using = 0;
|
||||
if (ret == size)
|
||||
return (*env)->NewDirectByteBuffer(env, addr, size);
|
||||
if (ret != size)
|
||||
LOGE("%s", "No enough data read, WTF?");
|
||||
if (ret < 0)
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
mempool_release_pages(addr, size);
|
||||
} else {
|
||||
return (*env)->NewDirectByteBuffer(env, addr, size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_closeArchive(JNIEnv *env, jclass thiz) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||
archive_release_ctx(ctx_pool[i]);
|
||||
free(passwd);
|
||||
free(ctx_pool);
|
||||
passwd = NULL;
|
||||
need_encrypt = false;
|
||||
munmap(archiveAddr, archiveSize);
|
||||
munmap(mempool, MEMPOOL_SIZE);
|
||||
free(mempoolofs);
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
free((void *) entries[i].filename);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_needPassword(JNIEnv *env, jclass thiz) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env, jclass thiz,
|
||||
jstring str) {
|
||||
EH_UNUSED(thiz);
|
||||
struct archive_entry *entry;
|
||||
archive_ctx *ctx;
|
||||
jboolean ret = true;
|
||||
int len = (*env)->GetStringUTFLength(env, str);
|
||||
passwd = realloc(passwd, len * sizeof(char));
|
||||
if (!passwd) {
|
||||
LOGE("Allocate passwd buffer failed");
|
||||
return false;
|
||||
}
|
||||
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
|
||||
archive_alloc_ctx(&ctx);
|
||||
void *tmpBuf = alloca(4096);
|
||||
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(entry)))
|
||||
continue;
|
||||
if (!archive_entry_is_encrypted(entry))
|
||||
continue;
|
||||
if (archive_read_data(ctx->arc, tmpBuf, 4096) < ARCHIVE_OK) {
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_getFilename(JNIEnv *env, jclass thiz,
|
||||
jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret)
|
||||
return NULL;
|
||||
jstring str = (*env)->NewStringUTF(env, archive_entry_pathname(ctx->entry));
|
||||
ctx->using = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToFd(JNIEnv *env, jclass thiz,
|
||||
jint index, jint fd) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (!ret) {
|
||||
archive_read_data_into_fd(ctx->arc, fd);
|
||||
ctx->using = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_releaseByteBuffer(JNIEnv *env, jclass thiz,
|
||||
jobject buffer) {
|
||||
EH_UNUSED(thiz);
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||
mempool_release_pages(addr, size);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EHVIEWER_EHVIEWER_H
|
||||
#define EHVIEWER_EHVIEWER_H
|
||||
|
||||
#define EH_UNUSED(x) (void)x
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
#define madvise_log_if_error(addr, len, advice) \
|
||||
if (madvise(addr, len, advice)) \
|
||||
LOGE("%s%p%s%zu%s%d%s%s%s", "madvise addr:", addr, "len:", len, "with advice ", advice, " failed with error: ", strerror(errno), ", Ignored")
|
||||
|
||||
#endif /* EHVIEWER_EHVIEWER_H */
|
||||
@@ -1,61 +0,0 @@
|
||||
#include <jni.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <android/log.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define LOG_TAG "gifUtils"
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
#define GIF_HEADER_87A "GIF87a"
|
||||
#define GIF_HEADER_89A "GIF89a"
|
||||
|
||||
static int FRAME_DELAY_START_MARKER = 0x0021F904;
|
||||
|
||||
typedef signed char byte;
|
||||
|
||||
#define FRAME_DELAY_START_MARKER ((byte*)(&FRAME_DELAY_START_MARKER))
|
||||
#define MINIMUM_FRAME_DELAY 2
|
||||
#define DEFAULT_FRAME_DELAY 10
|
||||
|
||||
static inline bool isGif(void *addr) {
|
||||
return !memcmp(addr, GIF_HEADER_87A, 6) || !memcmp(addr, GIF_HEADER_89A, 6);
|
||||
}
|
||||
|
||||
static void doRewrite(byte *addr, size_t size) {
|
||||
if (size < 7 || !isGif(addr)) return;
|
||||
for (size_t i = 0; i < size - 8; i++) {
|
||||
// TODO: Optimize this hex find with SIMD?
|
||||
if (addr[i] == FRAME_DELAY_START_MARKER[3] && addr[i + 1] == FRAME_DELAY_START_MARKER[2] &&
|
||||
addr[i + 2] == FRAME_DELAY_START_MARKER[1] &&
|
||||
addr[i + 3] == FRAME_DELAY_START_MARKER[0]) {
|
||||
byte *end = addr + i + 4;
|
||||
if (end[4] != 0) continue;
|
||||
int frameDelay = end[2] << 8 | end[1];
|
||||
if (frameDelay > MINIMUM_FRAME_DELAY)
|
||||
break; // Quit if the first block looks normal, for performance
|
||||
end[1] = DEFAULT_FRAME_DELAY;
|
||||
end[2] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
|
||||
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||
doRewrite(addr, size);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource2(JNIEnv *env, jclass clazz, jint fd) {
|
||||
struct stat64 st;
|
||||
fstat64(fd, &st);
|
||||
size_t size = st.st_size;
|
||||
byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) return;
|
||||
doRewrite(addr, size);
|
||||
munmap(addr, size);
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/* -*- mode: c; c-file-style: "k&r" -*-
|
||||
|
||||
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
|
||||
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
/* partial change history:
|
||||
*
|
||||
* 2004-10-10 mbp: Lift out character type dependencies into macros.
|
||||
*
|
||||
* Eric Sosman pointed out that ctype functions take a parameter whose
|
||||
* value must be that of an unsigned int, even on platforms that have
|
||||
* negative chars in their default char type.
|
||||
*/
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <ctype.h>
|
||||
|
||||
#include "strnatcmp.h"
|
||||
|
||||
|
||||
/* These are defined as macros to make it easier to adapt this code to
|
||||
* different characters types or comparison functions. */
|
||||
static inline int
|
||||
nat_isdigit(nat_char a)
|
||||
{
|
||||
return isdigit((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
static inline int
|
||||
nat_isspace(nat_char a)
|
||||
{
|
||||
return isspace((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
static inline nat_char
|
||||
nat_toupper(nat_char a)
|
||||
{
|
||||
return toupper((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
compare_right(nat_char const *a, nat_char const *b)
|
||||
{
|
||||
int bias = 0;
|
||||
|
||||
/* The longest run of digits wins. That aside, the greatest
|
||||
value wins, but we can't know that it will until we've scanned
|
||||
both numbers to know that they have the same magnitude, so we
|
||||
remember it in BIAS. */
|
||||
for (;; a++, b++) {
|
||||
if (!nat_isdigit(*a) && !nat_isdigit(*b))
|
||||
return bias;
|
||||
if (!nat_isdigit(*a))
|
||||
return -1;
|
||||
if (!nat_isdigit(*b))
|
||||
return +1;
|
||||
if (*a < *b) {
|
||||
if (!bias)
|
||||
bias = -1;
|
||||
} else if (*a > *b) {
|
||||
if (!bias)
|
||||
bias = +1;
|
||||
} else if (!*a && !*b)
|
||||
return bias;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
compare_left(nat_char const *a, nat_char const *b)
|
||||
{
|
||||
/* Compare two left-aligned numbers: the first to have a
|
||||
different value wins. */
|
||||
for (;; a++, b++) {
|
||||
if (!nat_isdigit(*a) && !nat_isdigit(*b))
|
||||
return 0;
|
||||
if (!nat_isdigit(*a))
|
||||
return -1;
|
||||
if (!nat_isdigit(*b))
|
||||
return +1;
|
||||
if (*a < *b)
|
||||
return -1;
|
||||
if (*a > *b)
|
||||
return +1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
strnatcmp0(nat_char const *a, nat_char const *b, int fold_case)
|
||||
{
|
||||
int ai, bi;
|
||||
nat_char ca, cb;
|
||||
int fractional, result;
|
||||
|
||||
ai = bi = 0;
|
||||
while (1) {
|
||||
ca = a[ai]; cb = b[bi];
|
||||
|
||||
/* skip over leading spaces or zeros */
|
||||
while (nat_isspace(ca))
|
||||
ca = a[++ai];
|
||||
|
||||
while (nat_isspace(cb))
|
||||
cb = b[++bi];
|
||||
|
||||
/* process run of digits */
|
||||
if (nat_isdigit(ca) && nat_isdigit(cb)) {
|
||||
fractional = (ca == '0' || cb == '0');
|
||||
|
||||
if (fractional) {
|
||||
if ((result = compare_left(a+ai, b+bi)) != 0)
|
||||
return result;
|
||||
} else {
|
||||
if ((result = compare_right(a+ai, b+bi)) != 0)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ca && !cb) {
|
||||
/* The strings compare the same. Perhaps the caller
|
||||
will want to call strcmp to break the tie. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fold_case) {
|
||||
ca = nat_toupper(ca);
|
||||
cb = nat_toupper(cb);
|
||||
}
|
||||
|
||||
if (ca < cb)
|
||||
return -1;
|
||||
|
||||
if (ca > cb)
|
||||
return +1;
|
||||
|
||||
++ai; ++bi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
strnatcmp(nat_char const *a, nat_char const *b) {
|
||||
return strnatcmp0(a, b, 0);
|
||||
}
|
||||
|
||||
|
||||
/* Compare, recognizing numeric string and ignoring case. */
|
||||
int
|
||||
strnatcasecmp(nat_char const *a, nat_char const *b) {
|
||||
return strnatcmp0(a, b, 1);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* -*- mode: c; c-file-style: "k&r" -*-
|
||||
|
||||
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
|
||||
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* CUSTOMIZATION SECTION
|
||||
*
|
||||
* You can change this typedef, but must then also change the inline
|
||||
* functions in strnatcmp.c */
|
||||
typedef char nat_char;
|
||||
|
||||
int strnatcmp(nat_char const *a, nat_char const *b);
|
||||
int strnatcasecmp(nat_char const *a, nat_char const *b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1 +0,0 @@
|
||||
/nettle
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef EHVIEWER_SPINLOCK_H
|
||||
#define EHVIEWER_SPINLOCK_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))
|
||||
|
||||
#define barrier() __asm__ __volatile__("":::"memory")
|
||||
|
||||
#if defined(__aarch64__) || defined(__arm__)
|
||||
#define cpu_relax() __asm__ volatile ("wfe");
|
||||
#else
|
||||
#define cpu_relax() __asm__ volatile ("pause" ::: "memory");
|
||||
#endif
|
||||
|
||||
typedef struct mcs_lock_t mcs_lock_t;
|
||||
struct mcs_lock_t {
|
||||
mcs_lock_t *next;
|
||||
int spin;
|
||||
};
|
||||
typedef struct mcs_lock_t *mcs_lock;
|
||||
|
||||
static inline void lock_mcs(_Atomic mcs_lock *m, mcs_lock_t *me) {
|
||||
mcs_lock_t *tail;
|
||||
me->next = NULL;
|
||||
me->spin = 0;
|
||||
tail = atomic_exchange(m, me);
|
||||
if (!tail) return;
|
||||
tail->next = me;
|
||||
barrier();
|
||||
while (!me->spin) cpu_relax();
|
||||
return;
|
||||
}
|
||||
|
||||
static inline void unlock_mcs(mcs_lock *m, mcs_lock_t *me) {
|
||||
if (!me->next) {
|
||||
if (cmpxchg(m, me, NULL) == me) return;
|
||||
while (!me->next) cpu_relax();
|
||||
}
|
||||
me->next->spin = 1;
|
||||
}
|
||||
|
||||
#endif //EHVIEWER_SPINLOCK_H
|
||||
@@ -1,41 +0,0 @@
|
||||
package androidx.navigation.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavOptions
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
||||
if (
|
||||
navController.currentDestination!!.parent!!.findNode(item.itemId)
|
||||
is ActivityNavigator.Destination
|
||||
) {
|
||||
builder.setEnterAnim(R.anim.nav_default_enter_anim)
|
||||
.setExitAnim(R.anim.nav_default_exit_anim)
|
||||
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
|
||||
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
|
||||
} else {
|
||||
builder.setEnterAnim(androidx.fragment.R.animator.fragment_open_enter)
|
||||
.setExitAnim(androidx.fragment.R.animator.fragment_open_exit)
|
||||
.setPopEnterAnim(androidx.fragment.R.animator.fragment_close_enter)
|
||||
.setPopExitAnim(androidx.fragment.R.animator.fragment_close_exit)
|
||||
}
|
||||
val options = builder.build()
|
||||
return try {
|
||||
// TODO provide proper API instead of using Exceptions as Control-Flow.
|
||||
navController.navigate(item.itemId, null, options)
|
||||
// Return true only if the destination we've navigated to matches the MenuItem
|
||||
navController.currentDestination?.matchDestination(item.itemId) == true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
|
||||
hierarchy.any { it.id == destId }
|
||||
@@ -1,25 +0,0 @@
|
||||
package androidx.swiperefreshlayout.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
) : SwipeRefreshLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
val surfaceColor =
|
||||
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
|
||||
val overlayColor =
|
||||
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
|
||||
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
|
||||
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
|
||||
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
|
||||
/**
|
||||
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
||||
* use of a bitmap, asset, resource, external file or any other URI.
|
||||
*
|
||||
*
|
||||
* When you are using a preview image, you must set the dimensions of the full size image on the
|
||||
* ImageSource object for the full size image using the [.dimensions] method.
|
||||
*/
|
||||
class ImageSource {
|
||||
val bitmap: Bitmap?
|
||||
val provider: InputProvider?
|
||||
|
||||
private var sWidth = 0
|
||||
private var sHeight = 0
|
||||
|
||||
var sRegion = Rect(0, 0, 0, 0)
|
||||
private set
|
||||
var isCached = false
|
||||
private set
|
||||
|
||||
private constructor(bitmap: Bitmap, cached: Boolean) {
|
||||
this.bitmap = bitmap
|
||||
provider = null
|
||||
sWidth = bitmap.width
|
||||
sHeight = bitmap.height
|
||||
isCached = cached
|
||||
}
|
||||
|
||||
private constructor(provider: InputProvider) {
|
||||
bitmap = null
|
||||
this.provider = provider
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
||||
* you are using one.
|
||||
*
|
||||
* @param sRegion the region of the source image to be displayed.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun region(sRegion: Rect): ImageSource {
|
||||
this.sRegion = sRegion
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
||||
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
||||
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
||||
*
|
||||
* @param sWidth width of the source image.
|
||||
* @param sHeight height of the source image.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
||||
if (bitmap == null) {
|
||||
this.sWidth = sWidth
|
||||
this.sHeight = sHeight
|
||||
}
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setInvariants() {
|
||||
if (!sRegion.isEmpty) {
|
||||
sWidth = sRegion.width()
|
||||
sHeight = sRegion.height()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create an instance from an input provider.
|
||||
*
|
||||
* @param provider input stream provider.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun provider(provider: InputProvider): ImageSource {
|
||||
return ImageSource(provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded bitmap for display.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun bitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
||||
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
||||
* or Volley.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.graphics.PointF
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
||||
*/
|
||||
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
||||
private val centerX: Float = center.x
|
||||
private val centerY: Float = center.y
|
||||
|
||||
val center: PointF
|
||||
get() = PointF(centerX, centerY)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,50 +0,0 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes.
|
||||
*/
|
||||
interface Decoder {
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
fun init(context: Context, provider: InputProvider): Point
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
val isReady: Boolean
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
fun recycle()
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
|
||||
class ImageDecoder(private val cropBorders: Boolean, bitmapConfig: Bitmap.Config? = null) : Decoder {
|
||||
init {
|
||||
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
override fun init(context: Context, provider: InputProvider): Point {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
override val isReady: Boolean = TODO()
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
override fun recycle() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
interface InputProvider
|
||||
+11
-12
@@ -13,17 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
package com.hippo.annotation;
|
||||
|
||||
@Serializable
|
||||
data class VoteCommentResult(
|
||||
@SerialName("comment_id")
|
||||
val id: Long,
|
||||
@SerialName("comment_score")
|
||||
val score: Int,
|
||||
@SerialName("comment_vote")
|
||||
val vote: Int,
|
||||
)
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface Implemented {
|
||||
Class value();
|
||||
}
|
||||
+7
-6
@@ -13,20 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class CheckBoxDialogBuilder(
|
||||
class CheckBoxDialogBuilder constructor(
|
||||
context: Context,
|
||||
message: String?,
|
||||
checkText: String?,
|
||||
checked: Boolean,
|
||||
) : BaseDialogBuilder(
|
||||
context,
|
||||
checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
val isChecked: Boolean
|
||||
@@ -41,4 +42,4 @@ class CheckBoxDialogBuilder(
|
||||
mCheckBox.isChecked = checked
|
||||
view.setOnClickListener { mCheckBox.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
@@ -23,17 +23,17 @@ import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class EditTextDialogBuilder(
|
||||
class EditTextDialogBuilder constructor(
|
||||
context: Context,
|
||||
text: String?,
|
||||
hint: String?,
|
||||
) : BaseDialogBuilder(
|
||||
context,
|
||||
),
|
||||
OnEditorActionListener {
|
||||
hint: String?
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
), OnEditorActionListener {
|
||||
private val mTextInputLayout: TextInputLayout
|
||||
val editText: EditText
|
||||
private var mDialog: AlertDialog? = null
|
||||
@@ -69,4 +69,4 @@ class EditTextDialogBuilder(
|
||||
editText.setOnEditorActionListener(this)
|
||||
mTextInputLayout.hint = hint
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
class ListCheckBoxDialogBuilder constructor(
|
||||
context: Context, items: Array<CharSequence>,
|
||||
listener: OnItemClickListener?, checkText: String?, checked: Boolean
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
private var mDialog: AlertDialog? = null
|
||||
val isChecked: Boolean
|
||||
get() = mCheckBox.isChecked
|
||||
|
||||
override fun create(): AlertDialog {
|
||||
mDialog = super.create()
|
||||
return mDialog as AlertDialog
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(builder: ListCheckBoxDialogBuilder?, dialog: AlertDialog?, position: Int)
|
||||
}
|
||||
|
||||
init {
|
||||
val view =
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null)
|
||||
setView(view)
|
||||
val listView = ViewUtils.`$$`(view, R.id.list_view) as ListView
|
||||
mCheckBox = ViewUtils.`$$`(view, R.id.checkbox) as CheckBox
|
||||
listView.adapter = ArrayAdapter(getContext(), R.layout.item_select_dialog, items)
|
||||
listView.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
|
||||
listener?.onItemClick(this@ListCheckBoxDialogBuilder, mDialog, position)
|
||||
mDialog?.dismiss()
|
||||
}
|
||||
mCheckBox.text = checkText
|
||||
mCheckBox.isChecked = checked
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class BeerBelly<V> {
|
||||
|
||||
private static final String TAG = BeerBelly.class.getSimpleName();
|
||||
private final boolean mHasMemoryCache;
|
||||
private final boolean mHasDiskCache;
|
||||
@Nullable
|
||||
private MemoryCache<V> mMemoryCache;
|
||||
@Nullable
|
||||
private DiskCache<V> mDiskCache;
|
||||
|
||||
public BeerBelly(BeerBellyParams params) {
|
||||
params.isValid();
|
||||
mHasMemoryCache = params.hasMemoryCache;
|
||||
mHasDiskCache = params.hasDiskCache;
|
||||
|
||||
if (mHasMemoryCache) {
|
||||
initMemoryCache(params.memoryCacheMaxSize);
|
||||
}
|
||||
|
||||
if (mHasDiskCache) {
|
||||
initDiskCache(params.diskCacheDir, params.diskCacheMaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMemoryCache(int maxSize) {
|
||||
mMemoryCache = new MemoryCache<>(maxSize, this);
|
||||
}
|
||||
|
||||
private void initDiskCache(File cacheDir, int maxSize) {
|
||||
// Set up disk cache
|
||||
try {
|
||||
mDiskCache = new DiskCache<>(cacheDir, maxSize, this);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can't create disk cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int sizeOf(String key, V value);
|
||||
|
||||
protected abstract void memoryEntryAdded(V value);
|
||||
|
||||
protected abstract void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue);
|
||||
|
||||
protected abstract V read(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
protected abstract boolean write(OutputStream os, V value);
|
||||
|
||||
@Nullable
|
||||
public LruCache<String, V> getMemoryCache() {
|
||||
return mMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SimpleDiskCache getDiskCache() {
|
||||
if (mDiskCache != null) {
|
||||
return mDiskCache.mDiskCache;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have memory cache
|
||||
*
|
||||
* @return true if have memory cache
|
||||
*/
|
||||
public boolean hasMemoryCache() {
|
||||
return mHasMemoryCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have disk cache
|
||||
*
|
||||
* @return true if have disk cache
|
||||
*/
|
||||
public boolean hasDiskCache() {
|
||||
return mHasDiskCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache usage size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memorySize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.size();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache max size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memoryMaxSize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.maxSize();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache usage size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.size();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache max size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskMaxSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.getMaxSize();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache
|
||||
*/
|
||||
public V getFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from disk cache. Override {@link #read(InputStreamPipe)} to do it
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache or get error
|
||||
*/
|
||||
public V getFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache and disk cache. If miss in memory cache and
|
||||
* get in disk cache, it will put value from disk cache to memory cache.
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get
|
||||
*/
|
||||
public V get(@NonNull String key) {
|
||||
V value = getFromMemory(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in memory cache
|
||||
return value;
|
||||
}
|
||||
|
||||
value = getFromDisk(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in disk cache
|
||||
putToMemory(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no memory cache
|
||||
*/
|
||||
public boolean putToMemory(@NonNull String key, @NonNull V value) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
memoryEntryAdded(value);
|
||||
mMemoryCache.put(key, value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putToDisk(@NonNull String key, @NonNull V value) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.put(key, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache and disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
*/
|
||||
public void put(@NonNull String key, @NonNull V value) {
|
||||
putToMemory(key, value);
|
||||
putToDisk(key, value);
|
||||
}
|
||||
|
||||
public void removeFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(@NonNull String key) {
|
||||
removeFromMemory(key);
|
||||
removeFromDisk(key);
|
||||
}
|
||||
|
||||
public boolean pullFromDiskCache(@NonNull String key, @NonNull OutputStream os) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.pull(key, os);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the key
|
||||
* @param is the input stream to store
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.putRaw(key, is);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache
|
||||
*/
|
||||
public void clearMemory() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear disk cache
|
||||
*/
|
||||
public void clearDisk() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache and lets the system know
|
||||
* now would be a good time to garbage collect
|
||||
*/
|
||||
public void clear() {
|
||||
clearMemory();
|
||||
clearDisk();
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder class that contains cache parameters.
|
||||
*/
|
||||
public static class BeerBellyParams {
|
||||
|
||||
/**
|
||||
* is memory cache available
|
||||
*/
|
||||
public boolean hasMemoryCache = false;
|
||||
/**
|
||||
* the maximum number of bytes the memory cache should use to store
|
||||
*/
|
||||
public int memoryCacheMaxSize = 0;
|
||||
/**
|
||||
* is disk cache available
|
||||
*/
|
||||
public boolean hasDiskCache = false;
|
||||
/**
|
||||
* the dir to store disk cache
|
||||
*/
|
||||
public File diskCacheDir = null;
|
||||
/**
|
||||
* the maximum number of bytes the disk cache should use to store
|
||||
*/
|
||||
public int diskCacheMaxSize = 0;
|
||||
|
||||
/**
|
||||
* Check BeerBellyParams is valid
|
||||
*/
|
||||
public void isValid() throws IllegalStateException {
|
||||
if (!hasMemoryCache && !hasDiskCache) {
|
||||
throw new IllegalStateException("No memory cache and no disk cache. What can I do for you?");
|
||||
}
|
||||
|
||||
if (hasMemoryCache && memoryCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Memory cache max size must be bigger than 0.");
|
||||
}
|
||||
|
||||
if (hasDiskCache) {
|
||||
if (diskCacheDir == null) {
|
||||
throw new IllegalStateException("Disk cache dir can't be null.");
|
||||
}
|
||||
if (diskCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Disk cache max size must be bigger than 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiskCache<E> {
|
||||
|
||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
private final SimpleDiskCache mDiskCache;
|
||||
private final BeerBelly<E> mParent;
|
||||
|
||||
private final File mCacheDir;
|
||||
private final int mMaxSize;
|
||||
|
||||
public DiskCache(File cacheDir, int size, BeerBelly<E> parent) throws IOException {
|
||||
mDiskCache = new SimpleDiskCache(cacheDir, size);
|
||||
mParent = parent;
|
||||
|
||||
mCacheDir = cacheDir;
|
||||
mMaxSize = size;
|
||||
}
|
||||
|
||||
public File getCacheDir() {
|
||||
return mCacheDir;
|
||||
}
|
||||
|
||||
public int getMaxSize() {
|
||||
return mMaxSize;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return mDiskCache.size();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
|
||||
public E get(String key) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return null;
|
||||
} else {
|
||||
return mParent.read(isPipe);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean put(String key, E value) {
|
||||
OutputStreamPipe osPipe = mDiskCache.getOutputStreamPipe(key);
|
||||
BufferedOutputStream buffOut = null;
|
||||
try {
|
||||
osPipe.obtain();
|
||||
OutputStream os = osPipe.open();
|
||||
buffOut = new BufferedOutputStream(os, IO_BUFFER_SIZE);
|
||||
return mParent.write(os, value);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
Util.closeQuietly(buffOut);
|
||||
osPipe.close();
|
||||
osPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putRaw(String key, InputStream is) {
|
||||
return mDiskCache.put(key, is);
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
|
||||
public boolean pull(@NonNull String key, @NonNull OutputStream os) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
InputStream is = isPipe.open();
|
||||
Util.copy(is, os);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryCache<E> extends LruCache<String, E> {
|
||||
|
||||
public BeerBelly<E> mParent;
|
||||
|
||||
public MemoryCache(int maxSize, BeerBelly<E> parent) {
|
||||
super(maxSize);
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, E value) {
|
||||
return mParent.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) {
|
||||
mParent.memoryEntryRemoved(evicted, key, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,984 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache
|
||||
* entry has a string key and a fixed number of values. Each key must match
|
||||
* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
|
||||
* accessible as streams or files. Each value must be between {@code 0} and
|
||||
* {@code Integer.MAX_VALUE} bytes in length.
|
||||
*
|
||||
* <p>The cache stores its data in a directory on the filesystem. This
|
||||
* directory must be exclusive to the cache; the cache may delete or overwrite
|
||||
* files from its directory. It is an error for multiple processes to use the
|
||||
* same cache directory at the same time.
|
||||
*
|
||||
* <p>This cache limits the number of bytes that it will store on the
|
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will
|
||||
* remove entries in the background until the limit is satisfied. The limit is
|
||||
* not strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache
|
||||
* journal so space-sensitive applications should set a conservative limit.
|
||||
*
|
||||
* <p>Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to
|
||||
* supply a full set of values; the empty value should be used as a
|
||||
* placeholder if necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary
|
||||
* to supply data for every value; values default to their previous
|
||||
* value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
*
|
||||
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
*
|
||||
* <p>This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If
|
||||
* an error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
public final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
static final String JOURNAL_FILE_TEMP = "journal.tmp";
|
||||
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
static final String VERSION_1 = "1";
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
|
||||
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
|
||||
private static final String CLEAN = "CLEAN";
|
||||
private static final String DIRTY = "DIRTY";
|
||||
private static final String REMOVE = "REMOVE";
|
||||
private static final String READ = "READ";
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this:
|
||||
* libcore.io.DiskLruCache
|
||||
* 1
|
||||
* 100
|
||||
* 2
|
||||
*
|
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
|
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
|
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
|
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
|
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a
|
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
|
||||
* READ 335c4c6028171cfddfbaae1a9c313c52
|
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
|
||||
*
|
||||
* The first five lines of the journal form its header. They are the
|
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version,
|
||||
* the application's version, the value count, and a blank line.
|
||||
*
|
||||
* Each of the subsequent lines in the file is a record of the state of a
|
||||
* cache entry. Each line contains space-separated values: a state, a key,
|
||||
* and optional state-specific values.
|
||||
* o DIRTY lines track that an entry is actively being created or updated.
|
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
|
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
|
||||
* temporary files may need to be deleted.
|
||||
* o CLEAN lines track a cache entry that has been successfully published
|
||||
* and may be read. A publish line is followed by the lengths of each of
|
||||
* its values.
|
||||
* o READ lines track accesses for LRU.
|
||||
* o REMOVE lines track entries that have been deleted.
|
||||
*
|
||||
* The journal file is appended to as cache operations occur. The journal may
|
||||
* occasionally be compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted if
|
||||
* it exists when the cache is opened.
|
||||
*/
|
||||
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) {
|
||||
// Eat all writes silently. Nom nom.
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This cache uses a single background thread to evict entries.
|
||||
*/
|
||||
final ThreadPoolExecutor executorService =
|
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
||||
private final File directory;
|
||||
private final File journalFile;
|
||||
private final File journalFileTmp;
|
||||
private final File journalFileBackup;
|
||||
private final int appVersion;
|
||||
private final int valueCount;
|
||||
private final LinkedHashMap<String, Entry> lruEntries =
|
||||
new LinkedHashMap<>(0, 0.75f, true);
|
||||
private long maxSize;
|
||||
private long size = 0;
|
||||
private Writer journalWriter;
|
||||
private int redundantOpCount;
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given
|
||||
* a sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
this.journalFile = new File(directory, JOURNAL_FILE);
|
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
|
||||
throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// If a bkp file exists, use it instead.
|
||||
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
if (backupFile.exists()) {
|
||||
File journalFile = new File(directory, JOURNAL_FILE);
|
||||
// If journal file also exists just delete backup file.
|
||||
if (journalFile.exists()) {
|
||||
backupFile.delete();
|
||||
} else {
|
||||
renameTo(backupFile, journalFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer to pick up where we left off.
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
return cache;
|
||||
} catch (IOException journalIsCorrupt) {
|
||||
System.out
|
||||
.println("DiskLruCache "
|
||||
+ directory
|
||||
+ " is corrupt: "
|
||||
+ journalIsCorrupt.getMessage()
|
||||
+ ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new empty cache.
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
try {
|
||||
String magic = reader.readLine();
|
||||
String version = reader.readLine();
|
||||
String appVersionString = reader.readLine();
|
||||
String valueCountString = reader.readLine();
|
||||
String blank = reader.readLine();
|
||||
if (!MAGIC.equals(magic)
|
||||
|| !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString)
|
||||
|| !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
|
||||
+ valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
int lineCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(reader.readLine());
|
||||
lineCount++;
|
||||
} catch (EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
redundantOpCount = lineCount - lruEntries.size();
|
||||
|
||||
// If we ended on a truncated line, rebuild the journal before appending to it.
|
||||
if (reader.hasUnterminatedLine()) {
|
||||
rebuildJournal();
|
||||
} else {
|
||||
journalWriter = new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(journalFile, true), Util.US_ASCII));
|
||||
}
|
||||
} finally {
|
||||
Util.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine(String line) throws IOException {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
int keyBegin = firstSpace + 1;
|
||||
int secondSpace = line.indexOf(' ', keyBegin);
|
||||
final String key;
|
||||
if (secondSpace == -1) {
|
||||
key = line.substring(keyBegin);
|
||||
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = line.substring(keyBegin, secondSpace);
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
|
||||
String[] parts = line.substring(secondSpace + 1).split(" ");
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(parts);
|
||||
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
|
||||
// This work was already done by calling lruEntries.get().
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
|
||||
Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
try (Writer writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII))) {
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (journalFile.exists()) {
|
||||
renameTo(journalFile, journalFileBackup, true);
|
||||
}
|
||||
renameTo(journalFileTmp, journalFile, false);
|
||||
journalFileBackup.delete();
|
||||
|
||||
journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if it contains the key
|
||||
*/
|
||||
public synchronized boolean contain(String key) {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
return entry != null && entry.readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open all streams eagerly to guarantee that we see a single published
|
||||
// snapshot. If we opened streams lazily then the streams could come
|
||||
// from different edits.
|
||||
InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
ins[i] = new FileInputStream(entry.getCleanFile(i));
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// A file must have been deleted manually!
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (ins[i] != null) {
|
||||
Util.closeQuietly(ins[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ').append(key).append(String.valueOf('\n'));
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit(String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|
||||
|| entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // Snapshot is stale.
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // Another edit is in progress.
|
||||
}
|
||||
|
||||
Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// Flush the journal before creating files to prevent file leaks.
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory where this cache stores its data.
|
||||
*/
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public synchronized long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum number of bytes the cache can store and queues a job
|
||||
* to trim the existing store, if necessary.
|
||||
*/
|
||||
public synchronized void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
|
||||
Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// If this edit is creating the entry for the first time, every index must have a value.
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!editor.written[i]) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
|
||||
}
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
long oldLength = entry.lengths[i];
|
||||
long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
journalWriter.flush();
|
||||
|
||||
if (size > maxSize || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int redundantOpCompactThreshold = 2000;
|
||||
return redundantOpCount >= redundantOpCompactThreshold //
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File file = entry.getCleanFile(i);
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ').append(key).append(String.valueOf('\n'));
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this cache has been closed.
|
||||
*/
|
||||
public synchronized boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force buffered operations to the filesystem.
|
||||
*/
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this cache. Stored values will remain on the filesystem.
|
||||
*/
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // Already closed.
|
||||
}
|
||||
for (Entry entry : new ArrayList<>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
Util.deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey(String key) {
|
||||
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("keys must match regex "
|
||||
+ STRING_KEY_PATTERN + ": \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A snapshot of the values for an entry.
|
||||
*/
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
private final long sequenceNumber;
|
||||
private final InputStream[] ins;
|
||||
private final long[] lengths;
|
||||
|
||||
private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.ins = ins;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unbuffered stream with the value for {@code index}.
|
||||
*/
|
||||
public InputStream getInputStream(int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value for {@code index}.
|
||||
*/
|
||||
public String getString(int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte length of the value for {@code index}.
|
||||
*/
|
||||
public long getLength(int index) {
|
||||
return lengths[index];
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (InputStream in : ins) {
|
||||
Util.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the values for an entry.
|
||||
*/
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
private final boolean[] written;
|
||||
private boolean hasErrors;
|
||||
private boolean committed;
|
||||
|
||||
private Editor(Entry entry) {
|
||||
this.entry = entry;
|
||||
this.written = (entry.readable) ? null : new boolean[valueCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream(int index) {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value
|
||||
* has been committed.
|
||||
*/
|
||||
public String getString(int index) throws IOException {
|
||||
InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors
|
||||
* when writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream(int index) throws IOException {
|
||||
if (index < 0 || index >= valueCount) {
|
||||
throw new IllegalArgumentException("Expected index " + index + " to "
|
||||
+ "be greater than 0 and less than the maximum value count "
|
||||
+ "of " + valueCount);
|
||||
}
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
written[index] = true;
|
||||
}
|
||||
File dirtyFile = entry.getDirtyFile(index);
|
||||
FileOutputStream outputStream;
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Attempt to recreate the cache directory.
|
||||
directory.mkdirs();
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e2) {
|
||||
// We are unable to recover. Silently eat the writes.
|
||||
return NULL_OUTPUT_STREAM;
|
||||
}
|
||||
}
|
||||
return new FaultHidingOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at {@code index} to {@code value}.
|
||||
*/
|
||||
public void set(int index, String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
Util.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the
|
||||
* edit lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // The previous entry is stale.
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
committed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
public void abortUnlessCommitted() {
|
||||
if (!committed) {
|
||||
try {
|
||||
abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/**
|
||||
* Lengths of this entry's files.
|
||||
*/
|
||||
private final long[] lengths;
|
||||
|
||||
/**
|
||||
* True if this entry has ever been published.
|
||||
*/
|
||||
private boolean readable;
|
||||
|
||||
/**
|
||||
* The ongoing edit or null if this entry is not being edited.
|
||||
*/
|
||||
private Editor currentEditor;
|
||||
|
||||
/**
|
||||
* The sequence number of the most recently committed edit to this entry.
|
||||
*/
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry(String key) {
|
||||
this.key = key;
|
||||
this.lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lengths using decimal numbers like "10123".
|
||||
*/
|
||||
private void setLengths(String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths(String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile(int i) {
|
||||
return new File(directory, key + "." + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile(int i) {
|
||||
return new File(directory, key + "." + i + ".tmp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class SimpleDiskCache {
|
||||
|
||||
private static final String TAG = SimpleDiskCache.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Disk cache is free
|
||||
*/
|
||||
private static final int STATE_DISK_CACHE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Disk cache is flushing or clearing
|
||||
*/
|
||||
private static final int STATE_DISK_CACHE_IN_USE = 1;
|
||||
|
||||
/**
|
||||
* Disk cache is reading or writing
|
||||
*/
|
||||
private static final int STATE_DISK_CACHE_BUSY = 2;
|
||||
|
||||
private final File mCacheDir;
|
||||
private final int mSize;
|
||||
@NonNull
|
||||
private final Map<String, CounterLock> mLockMap = new HashMap<>();
|
||||
@NonNull
|
||||
private final LockPool mLockPool = new LockPool();
|
||||
private int mDiskCacheState;
|
||||
@Nullable
|
||||
private DiskLruCache mDiskLruCache;
|
||||
|
||||
public SimpleDiskCache(File cacheDir, int size) {
|
||||
mCacheDir = cacheDir;
|
||||
mSize = size;
|
||||
|
||||
try {
|
||||
init();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing method that changes a string (like a URL) into a hash suitable
|
||||
* for using as a disk filename.
|
||||
*
|
||||
* @param key The key used to store the file
|
||||
*/
|
||||
private static String hashKeyForDisk(final String key) {
|
||||
String cacheKey;
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(key.getBytes());
|
||||
cacheKey = bytesToHexString(digest.digest());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
cacheKey = String.valueOf(key.hashCode());
|
||||
}
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/332079
|
||||
*
|
||||
* @param bytes The bytes to convert.
|
||||
* @return A {@link String} converted from the bytes of a hashable key used
|
||||
* to store a filename on the disk, to hex digits.
|
||||
*/
|
||||
private static String bytesToHexString(final byte[] bytes) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final byte b : bytes) {
|
||||
final String hex = Integer.toHexString(0xFF & b);
|
||||
if (hex.length() == 1) {
|
||||
builder.append('0');
|
||||
}
|
||||
builder.append(hex);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public synchronized boolean isValid() {
|
||||
return mDiskLruCache != null;
|
||||
}
|
||||
|
||||
private synchronized void init() throws IOException {
|
||||
if (!isValid()) {
|
||||
mDiskLruCache = DiskLruCache.open(mCacheDir, 1, 1, mSize);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized long size() {
|
||||
if (null != mDiskLruCache) {
|
||||
return mDiskLruCache.size();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void flush() {
|
||||
// Wait for cache available
|
||||
while (mDiskCacheState != STATE_DISK_CACHE_NONE) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
mDiskCacheState = STATE_DISK_CACHE_BUSY;
|
||||
|
||||
if (null != mDiskLruCache) {
|
||||
try {
|
||||
mDiskLruCache.flush();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "SimpleDiskCache flush", e);
|
||||
}
|
||||
}
|
||||
|
||||
mDiskCacheState = STATE_DISK_CACHE_NONE;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized boolean clear() {
|
||||
boolean result = true;
|
||||
if (null == mDiskLruCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for cache available
|
||||
while (mDiskCacheState != STATE_DISK_CACHE_NONE) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
mDiskCacheState = STATE_DISK_CACHE_BUSY;
|
||||
|
||||
try {
|
||||
mDiskLruCache.delete();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "SimpleDiskCache clearCache", e);
|
||||
}
|
||||
mDiskLruCache = null;
|
||||
try {
|
||||
init();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "SimpleDiskCache init", e);
|
||||
result = false;
|
||||
}
|
||||
|
||||
mDiskCacheState = STATE_DISK_CACHE_NONE;
|
||||
notifyAll();
|
||||
return result;
|
||||
}
|
||||
|
||||
private synchronized CounterLock obtainLock(String key) {
|
||||
// Wait for clear over
|
||||
while (mDiskCacheState == STATE_DISK_CACHE_BUSY) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
mDiskCacheState = STATE_DISK_CACHE_IN_USE;
|
||||
|
||||
// Get lock from key
|
||||
CounterLock lock;
|
||||
lock = mLockMap.get(key);
|
||||
if (lock == null) {
|
||||
lock = mLockPool.get();
|
||||
mLockMap.put(key, lock);
|
||||
}
|
||||
lock.obtain();
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
private synchronized void releaseLock(String key, CounterLock lock) {
|
||||
lock.release();
|
||||
if (lock.isFree()) {
|
||||
mLockMap.remove(key);
|
||||
mLockPool.push(lock);
|
||||
}
|
||||
|
||||
if (mLockMap.isEmpty()) {
|
||||
mDiskCacheState = STATE_DISK_CACHE_NONE;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contain(@NonNull String key) {
|
||||
String diskKey = hashKeyForDisk(key);
|
||||
CounterLock lock = obtainLock(diskKey);
|
||||
if (null == mDiskLruCache) {
|
||||
releaseLock(diskKey, lock);
|
||||
return false;
|
||||
}
|
||||
lock.readLock().lock();
|
||||
|
||||
boolean result = mDiskLruCache.contain(diskKey);
|
||||
|
||||
lock.readLock().unlock();
|
||||
releaseLock(diskKey, lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean remove(@NonNull String key) {
|
||||
String diskKey = hashKeyForDisk(key);
|
||||
CounterLock lock = obtainLock(diskKey);
|
||||
if (null == mDiskLruCache) {
|
||||
releaseLock(diskKey, lock);
|
||||
return false;
|
||||
}
|
||||
lock.writeLock().lock();
|
||||
|
||||
boolean result;
|
||||
try {
|
||||
result = mDiskLruCache.remove(diskKey);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
result = false;
|
||||
}
|
||||
|
||||
lock.writeLock().unlock();
|
||||
releaseLock(diskKey, lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean put(@NonNull String key, @NonNull InputStream is) {
|
||||
String diskKey = hashKeyForDisk(key);
|
||||
CounterLock lock = obtainLock(diskKey);
|
||||
if (null == mDiskLruCache) {
|
||||
releaseLock(diskKey, lock);
|
||||
return false;
|
||||
}
|
||||
lock.writeLock().lock();
|
||||
|
||||
boolean result = false;
|
||||
if (isValid()) {
|
||||
result = putToDisk(diskKey, is);
|
||||
}
|
||||
|
||||
lock.writeLock().unlock();
|
||||
releaseLock(diskKey, lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean putToDisk(String key, InputStream is) {
|
||||
DiskLruCache.Editor editor = null;
|
||||
OutputStream os = null;
|
||||
boolean completeEdit = false;
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
editor = mDiskLruCache.edit(key);
|
||||
if (editor == null) {
|
||||
// The editor is in progress
|
||||
return false;
|
||||
}
|
||||
|
||||
os = editor.newOutputStream(0);
|
||||
if (os != null) {
|
||||
Util.copy(is, os);
|
||||
completeEdit = true;
|
||||
editor.commit();
|
||||
return true;
|
||||
} else {
|
||||
// Can't get OutputStream
|
||||
completeEdit = true;
|
||||
editor.abort();
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Util.closeQuietly(os);
|
||||
try {
|
||||
if (!completeEdit && editor != null) {
|
||||
editor.abort();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the key of the target
|
||||
* @return the InputStreamPipe, <code>null</code> for missing
|
||||
*/
|
||||
@Nullable
|
||||
public InputStreamPipe getInputStreamPipe(@NonNull String key) {
|
||||
if (contain(key)) {
|
||||
String diskKey = hashKeyForDisk(key);
|
||||
return new CacheInputStreamPipe(diskKey);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public OutputStreamPipe getOutputStreamPipe(@NonNull String key) {
|
||||
String diskKey = hashKeyForDisk(key);
|
||||
return new CacheOutputStreamPipe(diskKey);
|
||||
}
|
||||
|
||||
private class CacheInputStreamPipe implements InputStreamPipe {
|
||||
|
||||
private final String mKey;
|
||||
private CounterLock mLock;
|
||||
private DiskLruCache.Snapshot mCurrentSnapshot;
|
||||
|
||||
private CacheInputStreamPipe(String key) {
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void obtain() {
|
||||
if (mLock == null) {
|
||||
mLock = obtainLock(mKey);
|
||||
mLock.readLock().lock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (mCurrentSnapshot != null) {
|
||||
throw new IllegalStateException("Please close it first");
|
||||
}
|
||||
|
||||
if (mLock != null) {
|
||||
mLock.readLock().unlock();
|
||||
releaseLock(mKey, mLock);
|
||||
mLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
InputStream open() throws IOException {
|
||||
if (mLock == null) {
|
||||
throw new IllegalStateException("Please obtain it first");
|
||||
}
|
||||
if (mCurrentSnapshot != null) {
|
||||
throw new IllegalStateException("Please close it before reopen");
|
||||
}
|
||||
if (null == mDiskLruCache) {
|
||||
throw new IOException("Can't find disk lru cache");
|
||||
}
|
||||
|
||||
DiskLruCache.Snapshot snapshot;
|
||||
snapshot = mDiskLruCache.get(mKey);
|
||||
if (snapshot == null) {
|
||||
throw new IOException("Miss the key " + mKey);
|
||||
}
|
||||
mCurrentSnapshot = snapshot;
|
||||
return snapshot.getInputStream(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Util.closeQuietly(mCurrentSnapshot);
|
||||
mCurrentSnapshot = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class CacheOutputStreamPipe implements OutputStreamPipe {
|
||||
|
||||
private final String mKey;
|
||||
private CounterLock mLock;
|
||||
private DiskLruCache.Editor mCurrentEditor;
|
||||
|
||||
private CacheOutputStreamPipe(String key) {
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void obtain() {
|
||||
if (mLock == null) {
|
||||
mLock = obtainLock(mKey);
|
||||
mLock.writeLock().lock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (mCurrentEditor != null) {
|
||||
throw new IllegalStateException("Please close it first");
|
||||
}
|
||||
|
||||
if (mLock != null) {
|
||||
mLock.writeLock().unlock();
|
||||
releaseLock(mKey, mLock);
|
||||
mLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
OutputStream open() throws IOException {
|
||||
if (mLock == null) {
|
||||
throw new IllegalStateException("Please obtain it first");
|
||||
}
|
||||
if (mCurrentEditor != null) {
|
||||
throw new IllegalStateException("Please close it before reopen");
|
||||
}
|
||||
if (null == mDiskLruCache) {
|
||||
throw new IOException("Can't find disk lru cache");
|
||||
}
|
||||
|
||||
DiskLruCache.Editor editor = mDiskLruCache.edit(mKey);
|
||||
if (editor == null) {
|
||||
throw new IOException("Miss the key " + mKey);
|
||||
}
|
||||
|
||||
OutputStream os;
|
||||
try {
|
||||
os = editor.newOutputStream(0);
|
||||
mCurrentEditor = editor;
|
||||
return os;
|
||||
} catch (IOException e) {
|
||||
// Get trouble
|
||||
try {
|
||||
editor.abort();
|
||||
} catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mCurrentEditor != null) {
|
||||
try {
|
||||
mCurrentEditor.commit();
|
||||
} catch (IOException e) {
|
||||
// Empty
|
||||
}
|
||||
mCurrentEditor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CounterLock extends ReentrantReadWriteLock {
|
||||
|
||||
private int mCount;
|
||||
|
||||
public boolean isFree() {
|
||||
return mCount == 0;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (--mCount < 0) {
|
||||
throw new IllegalStateException("Release time is more than occupy time");
|
||||
}
|
||||
}
|
||||
|
||||
public void obtain() {
|
||||
++mCount;
|
||||
}
|
||||
}
|
||||
|
||||
private class LockPool {
|
||||
|
||||
private static final int MAX_SIZE = 10;
|
||||
|
||||
private final CounterLock[] mArray = new CounterLock[MAX_SIZE];
|
||||
private final int mMaxSize = MAX_SIZE;
|
||||
private int mSize;
|
||||
|
||||
public void push(CounterLock lock) {
|
||||
if (lock != null && mSize < mMaxSize) {
|
||||
mArray[mSize++] = lock;
|
||||
}
|
||||
}
|
||||
|
||||
public CounterLock get() {
|
||||
if (mSize > 0) {
|
||||
CounterLock lock = mArray[--mSize];
|
||||
mArray[mSize] = null;
|
||||
return lock;
|
||||
} else {
|
||||
return new CounterLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Buffers input from an {@link InputStream} for reading lines.
|
||||
*
|
||||
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
|
||||
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
|
||||
* line at end of input is invalid and will be ignored, the caller may use {@code
|
||||
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
|
||||
*
|
||||
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
|
||||
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
|
||||
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
|
||||
* end-of-input reporting and a more restrictive definition of a line.
|
||||
*
|
||||
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
|
||||
* and 10, respectively, and the representation of no other character contains these values.
|
||||
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
|
||||
* The default charset is US_ASCII.
|
||||
*/
|
||||
class StrictLineReader implements Closeable {
|
||||
private static final byte CR = (byte) '\r';
|
||||
private static final byte LF = (byte) '\n';
|
||||
|
||||
private final InputStream in;
|
||||
private final Charset charset;
|
||||
|
||||
/*
|
||||
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
|
||||
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
|
||||
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
|
||||
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
|
||||
*/
|
||||
private byte[] buf;
|
||||
private int pos;
|
||||
private int end;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, Charset charset) {
|
||||
this(in, 8192, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code LineReader} with the specified capacity and charset.
|
||||
*
|
||||
* @param in the {@code InputStream} to read data from.
|
||||
* @param capacity the capacity of the buffer.
|
||||
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
|
||||
* supported.
|
||||
* @throws NullPointerException if {@code in} or {@code charset} is null.
|
||||
* @throws IllegalArgumentException if {@code capacity} is negative or zero
|
||||
* or the specified charset is not supported.
|
||||
*/
|
||||
public StrictLineReader(InputStream in, int capacity, Charset charset) {
|
||||
if (in == null || charset == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (capacity < 0) {
|
||||
throw new IllegalArgumentException("capacity <= 0");
|
||||
}
|
||||
if (!(charset.equals(Util.US_ASCII))) {
|
||||
throw new IllegalArgumentException("Unsupported encoding");
|
||||
}
|
||||
|
||||
this.in = in;
|
||||
this.charset = charset;
|
||||
buf = new byte[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the reader by closing the underlying {@code InputStream} and
|
||||
* marking this reader as closed.
|
||||
*
|
||||
* @throws IOException for errors when closing the underlying {@code InputStream}.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf != null) {
|
||||
buf = null;
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
|
||||
* this end of line marker is not included in the result.
|
||||
*
|
||||
* @return the next line from the input.
|
||||
* @throws IOException for underlying {@code InputStream} errors.
|
||||
* @throws EOFException for the end of source stream.
|
||||
*/
|
||||
public String readLine() throws IOException {
|
||||
synchronized (in) {
|
||||
if (buf == null) {
|
||||
throw new IOException("LineReader is closed");
|
||||
}
|
||||
|
||||
// Read more data if we are at the end of the buffered data.
|
||||
// Though it's an error to read after an exception, we will let {@code fillBuf()}
|
||||
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
|
||||
if (pos >= end) {
|
||||
fillBuf();
|
||||
}
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
||||
pos = i + 1;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's anticipate up to 80 characters on top of those already read.
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
out.write(buf, pos, end - pos);
|
||||
// Mark unterminated line in case fillBuf throws EOFException or IOException.
|
||||
end = -1;
|
||||
fillBuf();
|
||||
// Try to find LF in the buffered data and return the line if successful.
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
if (i != pos) {
|
||||
out.write(buf, pos, i - pos);
|
||||
}
|
||||
pos = i + 1;
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasUnterminatedLine() {
|
||||
return end == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads new input data into the buffer. Call only with pos == end or end == -1,
|
||||
* depending on the desired outcome if the function throws.
|
||||
*/
|
||||
private void fillBuf() throws IOException {
|
||||
int result = in.read(buf, 0, buf.length);
|
||||
if (result == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
pos = 0;
|
||||
end = result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Junk drawer of utility methods.
|
||||
*/
|
||||
final class Util {
|
||||
static final Charset US_ASCII = StandardCharsets.US_ASCII;
|
||||
static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
private static final int EOF = -1;
|
||||
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
static String readFully(Reader reader) throws IOException {
|
||||
try (reader) {
|
||||
StringWriter writer = new StringWriter();
|
||||
char[] buffer = new char[1024];
|
||||
int count;
|
||||
while ((count = reader.read(buffer)) != -1) {
|
||||
writer.write(buffer, 0, count);
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the contents of {@code dir}. Throws an IOException if any file
|
||||
* could not be deleted, or if {@code dir} is not a readable directory.
|
||||
*/
|
||||
static void deleteContents(File dir) throws IOException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IOException("not a readable directory: " + dir);
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(/*Auto*/Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy bytes from an <code>InputStream</code> to an
|
||||
* <code>OutputStream</code>.
|
||||
*
|
||||
* @param input the InputStream
|
||||
* @param output the OutputStream
|
||||
* @return the number of bytes copied
|
||||
*/
|
||||
public static long copy(InputStream input, OutputStream output)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
long count = 0;
|
||||
int n;
|
||||
while (EOF != (n = input.read(buffer))) {
|
||||
output.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.thread.SerialThreadExecutor;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final class Conaco<V> {
|
||||
|
||||
public static final int SOURCE_MEMORY = 0;
|
||||
public static final int SOURCE_DISK = 1;
|
||||
public static final int SOURCE_NETWORK = 2;
|
||||
private static final String TAG = Conaco.class.getSimpleName();
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Register<V> mRegister;
|
||||
private final SerialThreadExecutor mDiskExecutor;
|
||||
private final ThreadPoolExecutor mNetworkExecutor;
|
||||
private final AtomicInteger mIdGenerator;
|
||||
private final boolean mDebug;
|
||||
|
||||
private Conaco(Builder<V> builder) {
|
||||
mHelper = builder.objectHelper;
|
||||
|
||||
BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams();
|
||||
beerBellyParams.hasMemoryCache = builder.hasMemoryCache;
|
||||
beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize;
|
||||
beerBellyParams.hasDiskCache = builder.hasDiskCache;
|
||||
beerBellyParams.diskCacheDir = builder.diskCacheDir;
|
||||
beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize;
|
||||
|
||||
mCache = new ValueCache<>(beerBellyParams, mHelper);
|
||||
mOkHttpClient = builder.okHttpClient;
|
||||
|
||||
mRegister = new Register<>();
|
||||
|
||||
mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(),
|
||||
new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
|
||||
mIdGenerator = new AtomicInteger();
|
||||
|
||||
mDebug = builder.debug;
|
||||
}
|
||||
|
||||
public BeerBelly getBeerBelly() {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
private void onUnregisterConacoTask(ConacoTask<V> task) {
|
||||
ConacoTask<V> next = mRegister.getByKey(task.getKey());
|
||||
if (next != null) {
|
||||
startConacoTask(next);
|
||||
}
|
||||
}
|
||||
|
||||
private void startConacoTask(ConacoTask<V> task) {
|
||||
Unikery<V> unikery = task.getUnikery();
|
||||
|
||||
// Can't unikery is gone, finish the task now
|
||||
if (unikery == null) {
|
||||
finishConacoTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
String key = task.getKey();
|
||||
V value = null;
|
||||
|
||||
// Get from memory
|
||||
if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) {
|
||||
value = mCache.getFromMemory(key);
|
||||
}
|
||||
|
||||
if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) {
|
||||
unikery.onMiss(SOURCE_MEMORY);
|
||||
task.start();
|
||||
} else {
|
||||
// Get the object, finish the task
|
||||
finishConacoTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void load(ConacoTask.Builder<V> builder) {
|
||||
builder.isValid();
|
||||
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "Key " + builder.getKey());
|
||||
Log.d(TAG, "Url " + builder.getUrl());
|
||||
}
|
||||
|
||||
Unikery<V> unikery = builder.getUnikery();
|
||||
|
||||
// Cancel first
|
||||
cancel(unikery);
|
||||
|
||||
// Build conaco task
|
||||
int id;
|
||||
//noinspection StatementWithEmptyBody
|
||||
while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ;
|
||||
unikery.setTaskId(id);
|
||||
builder.setId(id)
|
||||
.setHelper(mHelper)
|
||||
.setCache(mCache)
|
||||
.setOkHttpClient(mOkHttpClient)
|
||||
.setDiskExecutor(mDiskExecutor)
|
||||
.setNetworkExecutor(mNetworkExecutor)
|
||||
.setConaco(this);
|
||||
ConacoTask<V> task = builder.build();
|
||||
|
||||
if (!mRegister.register(id, task)) {
|
||||
startConacoTask(task);
|
||||
} else {
|
||||
// The key is repeated, wait
|
||||
unikery.onWait();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void cancel(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
if (id != Unikery.INVALID_ID) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
ConacoTask<V> task = mRegister.unregister(id);
|
||||
if (task != null) {
|
||||
task.stop();
|
||||
// Don't need unikery anymore
|
||||
task.clearUnikery();
|
||||
// Callback
|
||||
onUnregisterConacoTask(task);
|
||||
} else {
|
||||
Log.e(TAG, "Can't find conaco task by id " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoading(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
return id != Unikery.INVALID_ID && mRegister.contain(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister task, reset unikery id, clear unikery in task, call next
|
||||
*/
|
||||
void finishConacoTask(ConacoTask<V> task) {
|
||||
mRegister.unregister(task.getId());
|
||||
Unikery unikery = task.getUnikery();
|
||||
if (unikery != null) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
}
|
||||
task.clearUnikery();
|
||||
|
||||
onUnregisterConacoTask(task);
|
||||
}
|
||||
|
||||
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Source {
|
||||
}
|
||||
|
||||
public static class Builder<T> extends BeerBelly.BeerBellyParams {
|
||||
/**
|
||||
* The client to get image from internet
|
||||
*/
|
||||
public OkHttpClient okHttpClient = null;
|
||||
|
||||
/**
|
||||
* Decode, get size and others
|
||||
*/
|
||||
public ValueHelper<T> objectHelper = null;
|
||||
|
||||
public boolean debug = false;
|
||||
|
||||
@Override
|
||||
public void isValid() throws IllegalStateException {
|
||||
super.isValid();
|
||||
|
||||
if (okHttpClient == null) {
|
||||
throw new IllegalStateException("No http client? How can I load image via url?");
|
||||
}
|
||||
}
|
||||
|
||||
public Conaco<T> build() {
|
||||
isValid();
|
||||
return new Conaco<>(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.okhttp.ChromeRequestBuilder;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public final class ConacoTask<V> {
|
||||
|
||||
private static final String TAG = ConacoTask.class.getSimpleName();
|
||||
|
||||
private final int mId;
|
||||
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
||||
private final String mKey;
|
||||
private final String mUrl;
|
||||
private final DataContainer mDataContainer;
|
||||
private final boolean mUseMemoryCache;
|
||||
private final boolean mUseDiskCache;
|
||||
private final boolean mUseNetwork;
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Executor mDiskExecutor;
|
||||
private final Executor mNetworkExecutor;
|
||||
private final Conaco<V> mConaco;
|
||||
|
||||
private DiskLoadTask mDiskLoadTask;
|
||||
private NetworkLoadTask mNetworkLoadTask;
|
||||
private Call mCall;
|
||||
private boolean mStart;
|
||||
private volatile boolean mStop;
|
||||
|
||||
private ConacoTask(Builder<V> builder) {
|
||||
mId = builder.mId;
|
||||
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
||||
mKey = builder.mKey;
|
||||
mUrl = builder.mUrl;
|
||||
mDataContainer = builder.mDataContainer;
|
||||
mUseMemoryCache = builder.mUseMemoryCache;
|
||||
mUseDiskCache = builder.mUseDiskCache;
|
||||
mUseNetwork = builder.mUseNetwork;
|
||||
mHelper = builder.mHelper;
|
||||
mCache = builder.mCache;
|
||||
mOkHttpClient = builder.mOkHttpClient;
|
||||
mDiskExecutor = builder.mDiskExecutor;
|
||||
mNetworkExecutor = builder.mNetworkExecutor;
|
||||
mConaco = builder.mConaco;
|
||||
}
|
||||
|
||||
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
|
||||
SimpleDiskCache diskCache = cache.getDiskCache();
|
||||
if (diskCache != null) {
|
||||
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
container.save(pipe.open(), -1L, null, null);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Can't save value from disk cache to data container");
|
||||
e.printStackTrace();
|
||||
container.remove();
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
|
||||
InputStreamPipe pipe = container.get();
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.obtain();
|
||||
cache.putRawToDisk(key, pipe.open());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Can't save value from data container to disk cache", e);
|
||||
cache.removeFromDisk(key);
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
boolean useMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Unikery<V> getUnikery() {
|
||||
return mUnikeryWeakReference.get();
|
||||
}
|
||||
|
||||
void clearUnikery() {
|
||||
mUnikeryWeakReference.clear();
|
||||
}
|
||||
|
||||
private void onFinish() {
|
||||
if (!mStop) {
|
||||
mConaco.finishConacoTask(this);
|
||||
}/* else {
|
||||
// It is done by Conaco
|
||||
}*/
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void start() {
|
||||
if (mStop || mStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStart = true;
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (mUseDiskCache) {
|
||||
mDiskLoadTask = new DiskLoadTask();
|
||||
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
|
||||
return;
|
||||
} else if (mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onMiss(Conaco.SOURCE_NETWORK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void stop() {
|
||||
if (mStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStop = true;
|
||||
|
||||
// Stop jobs
|
||||
if (mDiskLoadTask != null) { // Getting from disk
|
||||
mDiskLoadTask.cancel(false);
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall.cancel();
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null) {
|
||||
unikery.onCancel();
|
||||
}
|
||||
|
||||
// Conaco handle the clean up
|
||||
}
|
||||
|
||||
private boolean isNotNecessary(AsyncTask asyncTask) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
|
||||
public static class Builder<T> {
|
||||
|
||||
private int mId;
|
||||
private Unikery<T> mUnikery;
|
||||
private String mKey;
|
||||
private String mUrl;
|
||||
private DataContainer mDataContainer;
|
||||
private boolean mUseMemoryCache = true;
|
||||
private boolean mUseDiskCache = true;
|
||||
private boolean mUseNetwork = true;
|
||||
private ValueHelper<T> mHelper;
|
||||
private ValueCache<T> mCache;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Executor mDiskExecutor;
|
||||
private Executor mNetworkExecutor;
|
||||
private Conaco<T> mConaco;
|
||||
|
||||
public Builder<T> setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Unikery<T> getUnikery() {
|
||||
return mUnikery;
|
||||
}
|
||||
|
||||
public Builder<T> setUnikery(Unikery<T> unikery) {
|
||||
mUnikery = unikery;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public Builder<T> setKey(String key) {
|
||||
mKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
public Builder<T> setUrl(String url) {
|
||||
mUrl = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setDataContainer(DataContainer dataContainer) {
|
||||
mDataContainer = dataContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
|
||||
mUseMemoryCache = useMemoryCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseDiskCache() {
|
||||
return mUseDiskCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseDiskCache(boolean useDiskCache) {
|
||||
mUseDiskCache = useDiskCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseNetwork() {
|
||||
return mUseNetwork;
|
||||
}
|
||||
|
||||
public Builder<T> setUseNetwork(boolean useNetwork) {
|
||||
mUseNetwork = useNetwork;
|
||||
return this;
|
||||
}
|
||||
|
||||
ValueHelper<T> getHelper() {
|
||||
return mHelper;
|
||||
}
|
||||
|
||||
Builder<T> setHelper(ValueHelper<T> helper) {
|
||||
mHelper = helper;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setCache(ValueCache<T> cache) {
|
||||
mCache = cache;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
|
||||
mOkHttpClient = okHttpClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setDiskExecutor(Executor diskExecutor) {
|
||||
mDiskExecutor = diskExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setNetworkExecutor(Executor networkExecutor) {
|
||||
mNetworkExecutor = networkExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setConaco(Conaco<T> conaco) {
|
||||
mConaco = conaco;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void isValid() {
|
||||
if (mUnikery == null) {
|
||||
throw new IllegalStateException("Must set unikery");
|
||||
}
|
||||
if (mKey == null && mUrl == null && mDataContainer == null) {
|
||||
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
||||
}
|
||||
}
|
||||
|
||||
public ConacoTask<T> build() {
|
||||
return new ConacoTask<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
} else {
|
||||
V value = null;
|
||||
|
||||
// First check data container
|
||||
if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp != null) {
|
||||
value = mHelper.decode(isp);
|
||||
}
|
||||
}
|
||||
|
||||
// Then check disk cache
|
||||
if (mKey != null) {
|
||||
if (value == null && mUseDiskCache) {
|
||||
value = mCache.getFromDisk(mKey);
|
||||
// Put back to data container
|
||||
if (value != null && mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V value) {
|
||||
mDiskLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(value);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
boolean getValue = false;
|
||||
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else if (!getValue) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V holder) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
|
||||
|
||||
@Override
|
||||
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
if (!isNotNecessary(this)) {
|
||||
publishProgress(singleReceivedSize, receivedSize, totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDiskCache(InputStream is, long length) {
|
||||
SimpleDiskCache diskCache = mCache.getDiskCache();
|
||||
if (diskCache == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
|
||||
try {
|
||||
pipe.obtain();
|
||||
OutputStream os = pipe.open();
|
||||
|
||||
final byte[] buffer = new byte[1024 * 4];
|
||||
long receivedSize = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
receivedSize += bytesRead;
|
||||
notifyProgress(bytesRead, receivedSize, length);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDataContainer(InputStream is, ResponseBody body) {
|
||||
// Get media type
|
||||
String mediaType;
|
||||
MediaType mt = body.contentType();
|
||||
if (mt != null) {
|
||||
mediaType = mt.type() + '/' + mt.subtype();
|
||||
} else {
|
||||
mediaType = null;
|
||||
}
|
||||
return mDataContainer.save(is, body.contentLength(), mediaType, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
V value;
|
||||
InputStream is = null;
|
||||
try {
|
||||
Log.d("TAG", "Conaco " + mUrl);
|
||||
|
||||
// Load it from internet
|
||||
Request request = new ChromeRequestBuilder(mUrl).build();
|
||||
mCall = mOkHttpClient.newCall(request);
|
||||
|
||||
Response response = mCall.execute();
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
is = body.byteStream();
|
||||
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((mDataContainer == null || !mDataContainer.isEnabled()) && mKey != null) {
|
||||
if (putToDiskCache(is, body.contentLength())) {
|
||||
// Get object from disk cache
|
||||
value = mCache.getFromDisk(mKey);
|
||||
if (value == null) {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
return null;
|
||||
}
|
||||
} else if (mDataContainer != null && mDataContainer.isEnabled()) {
|
||||
// Check url Moved
|
||||
HttpUrl requestHttpUrl = request.url();
|
||||
HttpUrl responseHttpUrl = response.request().url();
|
||||
if (!responseHttpUrl.equals(requestHttpUrl)) {
|
||||
mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString());
|
||||
}
|
||||
|
||||
// Put to data container
|
||||
if (!putToDataContainer(is, body)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get value from data container
|
||||
InputStreamPipe isp = mDataContainer.get();
|
||||
if (isp == null) {
|
||||
return null;
|
||||
}
|
||||
value = mHelper.decode(isp);
|
||||
if (value == null) {
|
||||
mDataContainer.remove();
|
||||
} else if (mKey != null) {
|
||||
// Put to disk cache
|
||||
putFromDataContainerToDiskCache(mKey, mCache, mDataContainer);
|
||||
|
||||
if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
mCall = null;
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V holder) {
|
||||
mNetworkLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(holder);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V value) {
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Long... values) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
|
||||
unikery.onProgress(values[0], values[1], values[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface DataContainer {
|
||||
|
||||
/**
|
||||
* Returns the enabled status for this DataContainer.
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Get 304 or something like that
|
||||
*/
|
||||
void onUrlMoved(String requestUrl, String responseUrl);
|
||||
|
||||
/**
|
||||
* Save the {@code InputStream}
|
||||
*/
|
||||
boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify);
|
||||
|
||||
/**
|
||||
* Get {@code InputStreamPipe} for saved before
|
||||
*/
|
||||
@Nullable
|
||||
InputStreamPipe get();
|
||||
|
||||
/**
|
||||
* Remove saved stuff
|
||||
*/
|
||||
void remove();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
public interface ProgressNotifier {
|
||||
|
||||
void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import com.hippo.yorozuya.collect.Multimap;
|
||||
import com.hippo.yorozuya.collect.SparseILArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Register<V> {
|
||||
|
||||
private final SparseILArray<ConacoTask<V>> mIdMap = new SparseILArray<>();
|
||||
private final Multimap<String, ConacoTask<V>> mKeyMap = Multimap.create();
|
||||
|
||||
/**
|
||||
* @return true for the key is already registered
|
||||
*/
|
||||
public synchronized boolean register(int id, ConacoTask<V> task) {
|
||||
boolean repeatedKey = false;
|
||||
String taskKey = task.getKey();
|
||||
|
||||
// For no-key task, there no need to check
|
||||
if (taskKey != null) {
|
||||
repeatedKey = mKeyMap.countElements(taskKey) != 0;
|
||||
}
|
||||
|
||||
// Append task
|
||||
mKeyMap.putElement(taskKey, task);
|
||||
mIdMap.append(id, task);
|
||||
|
||||
return repeatedKey;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> unregister(int id) {
|
||||
ConacoTask<V> task = mIdMap.remove(id);
|
||||
if (task != null) {
|
||||
mKeyMap.removeElement(task.getKey(), task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public synchronized boolean contain(int id) {
|
||||
return mIdMap.indexOfKey(id) >= 0;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> getByKey(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ConacoTask<V>> list = mKeyMap.get(key);
|
||||
if (list != null && list.size() > 0) {
|
||||
return list.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface Unikery<V> {
|
||||
|
||||
int INVALID_ID = -1;
|
||||
|
||||
/**
|
||||
* Get the conaco task id
|
||||
*
|
||||
* @return the conaco task id
|
||||
*/
|
||||
int getTaskId();
|
||||
|
||||
/**
|
||||
* Store the conaco task id
|
||||
*
|
||||
* @param id the conaco task id
|
||||
*/
|
||||
void setTaskId(int id);
|
||||
|
||||
/**
|
||||
* On miss in the source
|
||||
*/
|
||||
void onMiss(@Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On start http request
|
||||
*/
|
||||
void onRequest();
|
||||
|
||||
/**
|
||||
* On http request progress
|
||||
*/
|
||||
void onProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
|
||||
/**
|
||||
* On start to wait because repeated key
|
||||
*/
|
||||
void onWait();
|
||||
|
||||
/**
|
||||
* On get the value
|
||||
*
|
||||
* @return {@code true} for the {@code Unikery} accepts the value
|
||||
* and the task ends. {@code false} for the {@code Unikery}
|
||||
* reject the value and the task goes on or failed
|
||||
*/
|
||||
boolean onGetValue(@NonNull V value, @Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On failed to get value
|
||||
*/
|
||||
void onFailure();
|
||||
|
||||
/**
|
||||
* On user cancel the conaco task
|
||||
*/
|
||||
void onCancel();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
class ValueCache<V> extends BeerBelly<V> {
|
||||
|
||||
private final ValueHelper<V> mHelper;
|
||||
|
||||
public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper<V> helper) {
|
||||
super(params);
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, V value) {
|
||||
return mHelper.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryAdded(V value) {
|
||||
mHelper.onAddToMemoryCache(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) {
|
||||
if (oldValue != null) {
|
||||
mHelper.onRemoveFromMemoryCache(key, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V read(@NonNull InputStreamPipe isPipe) {
|
||||
return mHelper.decode(isPipe);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean write(OutputStream os, V value) {
|
||||
throw new UnsupportedOperationException("Not support write object");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
public interface ValueHelper<V> {
|
||||
|
||||
/**
|
||||
* Decode object for the {@code InputStreamPipe}
|
||||
*
|
||||
* @param isPipe the {@code InputStreamPipe}
|
||||
* @return the decoded object
|
||||
*/
|
||||
@Nullable
|
||||
V decode(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
/**
|
||||
* Get the size of the object
|
||||
*/
|
||||
int sizeOf(@NonNull String key, @NonNull V value);
|
||||
|
||||
/**
|
||||
* Called when the object added to memory cache
|
||||
*/
|
||||
void onAddToMemoryCache(@NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Called when the object removed from memory cache
|
||||
*/
|
||||
void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Use cache memory or not. Sometimes large object
|
||||
* should not be stored in memory cache.
|
||||
*/
|
||||
boolean useMemoryCache(@NonNull String key, V holder);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2017 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.util.SparseArray
|
||||
|
||||
class MSQLiteBuilder {
|
||||
companion object {
|
||||
const val COLUMN_ID = "_id"
|
||||
private val JAVA_TYPE_TO_SQLITE_TYPE: MutableMap<Class<*>?, String> = HashMap()
|
||||
private fun javaTypeToSQLiteType(clazz: Class<*>?): String {
|
||||
return JAVA_TYPE_TO_SQLITE_TYPE[clazz]
|
||||
?: throw IllegalStateException("Unknown type: $clazz")
|
||||
}
|
||||
|
||||
init {
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Boolean::class.javaPrimitiveType] =
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Byte::class.javaPrimitiveType] =
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Short::class.javaPrimitiveType] =
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Int::class.javaPrimitiveType] = "INTEGER NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Long::class.javaPrimitiveType] =
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Float::class.javaPrimitiveType] =
|
||||
"REAL NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[Double::class.javaPrimitiveType] = "REAL NOT NULL DEFAULT 0"
|
||||
JAVA_TYPE_TO_SQLITE_TYPE[String::class.java] = "TEXT"
|
||||
}
|
||||
}
|
||||
|
||||
private val statementsMap = SparseArray<List<String>?>()
|
||||
private var version = 0
|
||||
private var statements: MutableList<String>? = null
|
||||
|
||||
/**
|
||||
* Bump database version.
|
||||
*/
|
||||
fun version(version: Int): MSQLiteBuilder {
|
||||
check(version > this.version) {
|
||||
("New version must be bigger than current version. "
|
||||
+ "current version: " + this.version + ", new version: " + version + ".")
|
||||
}
|
||||
this.version = version
|
||||
statements = ArrayList()
|
||||
statementsMap.put(version, statements)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Creates a table.
|
||||
*/
|
||||
/**
|
||||
* Creates a table with int [.COLUMN_ID] primary key.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createTable(
|
||||
table: String,
|
||||
column: String = COLUMN_ID,
|
||||
clazz: Class<*>? = Int::class.javaPrimitiveType
|
||||
): MSQLiteBuilder {
|
||||
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);")
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table.
|
||||
*/
|
||||
fun dropTable(table: String): MSQLiteBuilder {
|
||||
return statement("DROP TABLE $table;")
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a column to the table.
|
||||
*/
|
||||
fun insertColumn(table: String, column: String, clazz: Class<*>?): MSQLiteBuilder {
|
||||
return statement(
|
||||
"ALTER TABLE $table ADD COLUMN $column " + javaTypeToSQLiteType(
|
||||
clazz
|
||||
) + ";"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement.
|
||||
*/
|
||||
fun statement(statement: String): MSQLiteBuilder {
|
||||
check(!(version == 0 || statements == null)) { "Call version() first!" }
|
||||
statements!!.add(statement)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SQLiteOpenHelper from it.
|
||||
*/
|
||||
fun build(context: Context?, name: String?, version: Int): SQLiteOpenHelper {
|
||||
return MSQLiteOpenHelper(context, name, version, this)
|
||||
}
|
||||
|
||||
fun getStatements(oldVersion: Int, newVersion: Int): List<String> {
|
||||
val result: MutableList<String> = ArrayList()
|
||||
for (i in oldVersion + 1..newVersion) {
|
||||
val list = statementsMap[i]
|
||||
if (list != null) {
|
||||
result.addAll(list)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2017 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import com.hippo.util.SqlUtils
|
||||
|
||||
internal class MSQLiteOpenHelper(
|
||||
context: Context?,
|
||||
name: String?,
|
||||
private val version: Int,
|
||||
private val builder: MSQLiteBuilder
|
||||
) : SQLiteOpenHelper(context, name, null, version) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
onUpgrade(db, 0, version)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
for (command in builder.getStatements(oldVersion, newVersion)) {
|
||||
db.execSQL(command)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
SqlUtils.dropAllTable(db)
|
||||
onCreate(db)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class AddDeleteDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint = new Paint();
|
||||
private final Path mPath = new Path();
|
||||
|
||||
private final int mSize;
|
||||
|
||||
private float mProgress;
|
||||
|
||||
private boolean mAutoUpdateMirror = false;
|
||||
private boolean mVerticalMirror = false;
|
||||
|
||||
/**
|
||||
* @param context used to get the configuration for the drawable from
|
||||
*/
|
||||
public AddDeleteDrawable(Context context, int color) {
|
||||
Resources resources = context.getResources();
|
||||
|
||||
mSize = resources.getDimensionPixelSize(R.dimen.add_size);
|
||||
float barThickness = Math.round(resources.getDimension(R.dimen.add_thickness));
|
||||
|
||||
mPaint.setColor(color);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeJoin(Paint.Join.MITER);
|
||||
mPaint.setStrokeCap(Paint.Cap.BUTT);
|
||||
mPaint.setStrokeWidth(barThickness);
|
||||
|
||||
float halfSize = mSize / 2;
|
||||
mPath.moveTo(0f, -halfSize);
|
||||
mPath.lineTo(0, halfSize);
|
||||
mPath.moveTo(-halfSize, 0);
|
||||
mPath.lineTo(halfSize, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = getBounds();
|
||||
float canvasRotate;
|
||||
if (mVerticalMirror) {
|
||||
canvasRotate = MathUtils.lerp(270, 135f, mProgress);
|
||||
} else {
|
||||
canvasRotate = MathUtils.lerp(0f, 135f, mProgress);
|
||||
}
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(bounds.centerX(), bounds.centerY());
|
||||
canvas.rotate(canvasRotate);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mSize * 6 / 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mSize * 6 / 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
||||
*/
|
||||
protected void setVerticalMirror(boolean verticalMirror) {
|
||||
mVerticalMirror = verticalMirror;
|
||||
}
|
||||
|
||||
public void setAutoUpdateMirror(boolean autoUpdateMirror) {
|
||||
mAutoUpdateMirror = autoUpdateMirror;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setProgress(float progress) {
|
||||
if (mAutoUpdateMirror) {
|
||||
if (progress == 1f) {
|
||||
setVerticalMirror(true);
|
||||
} else if (progress == 0f) {
|
||||
setVerticalMirror(false);
|
||||
}
|
||||
}
|
||||
mProgress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setAdd(long duration) {
|
||||
setShape(false, duration);
|
||||
}
|
||||
|
||||
public void setDelete(long duration) {
|
||||
setShape(true, duration);
|
||||
}
|
||||
|
||||
public void setShape(boolean delete, long duration) {
|
||||
if (!((!delete && mProgress == 0f) || (delete && mProgress == 1f))) {
|
||||
float endProgress = delete ? 1f : 0f;
|
||||
if (duration <= 0) {
|
||||
setProgress(endProgress);
|
||||
} else {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
|
||||
oa.setDuration(duration);
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class BatteryDrawable extends Drawable {
|
||||
|
||||
public static final int WARN_LIMIT = 15;
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = BatteryDrawable.class.getSimpleName();
|
||||
private final Paint mPaint;
|
||||
private final Rect mTopRect;
|
||||
private final Rect mBottomRect;
|
||||
private final Rect mRightRect;
|
||||
private final Rect mHeadRect;
|
||||
private final Rect mElectRect;
|
||||
private int mColor = Color.WHITE;
|
||||
private int mWarningColor = Color.RED;
|
||||
private int mElect = -1;
|
||||
private int mStart;
|
||||
private int mStop;
|
||||
|
||||
public BatteryDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mTopRect = new Rect();
|
||||
mBottomRect = new Rect();
|
||||
mRightRect = new Rect();
|
||||
mHeadRect = new Rect();
|
||||
mElectRect = new Rect();
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
int width = bounds.width();
|
||||
int height = bounds.height();
|
||||
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
|
||||
int turn1 = width * 6 / 7;
|
||||
int turn2 = height / 3;
|
||||
int secBottom = height - strokeWidth;
|
||||
mStart = strokeWidth;
|
||||
mStop = turn1 - strokeWidth;
|
||||
|
||||
mTopRect.set(0, 0, turn1, strokeWidth);
|
||||
mBottomRect.set(0, secBottom, turn1, height);
|
||||
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
|
||||
mHeadRect.set(turn1, turn2, width, height - turn2);
|
||||
mElectRect.set(0, strokeWidth, mStop, secBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* How to draw:<br>
|
||||
* |------------------------------|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mElect == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
|
||||
|
||||
canvas.drawRect(mTopRect, mPaint);
|
||||
canvas.drawRect(mBottomRect, mPaint);
|
||||
canvas.drawRect(mRightRect, mPaint);
|
||||
canvas.drawRect(mHeadRect, mPaint);
|
||||
canvas.drawRect(mElectRect, mPaint);
|
||||
}
|
||||
|
||||
private boolean isWarn() {
|
||||
return mElect <= WARN_LIMIT;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mColor = color;
|
||||
if (!isWarn()) {
|
||||
mPaint.setColor(mColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setWarningColor(int color) {
|
||||
if (mWarningColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWarningColor = color;
|
||||
if (isWarn()) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setElect(int elect) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
public void setElect(int elect, boolean warn) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint(warn);
|
||||
}
|
||||
|
||||
private void updatePaint() {
|
||||
updatePaint(isWarn());
|
||||
}
|
||||
|
||||
private void updatePaint(boolean warn) {
|
||||
if (warn) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
} else {
|
||||
mPaint.setColor(mColor);
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2018 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
public class DrawableWrapper extends Drawable implements Drawable.Callback {
|
||||
private Drawable mDrawable;
|
||||
|
||||
public DrawableWrapper(Drawable drawable) {
|
||||
this.setWrappedDrawable(drawable);
|
||||
}
|
||||
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
this.mDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
this.mDrawable.setBounds(bounds);
|
||||
}
|
||||
|
||||
public int getChangingConfigurations() {
|
||||
return this.mDrawable.getChangingConfigurations();
|
||||
}
|
||||
|
||||
public void setChangingConfigurations(int configs) {
|
||||
this.mDrawable.setChangingConfigurations(configs);
|
||||
}
|
||||
|
||||
public void setDither(boolean dither) {
|
||||
this.mDrawable.setDither(dither);
|
||||
}
|
||||
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
this.mDrawable.setFilterBitmap(filter);
|
||||
}
|
||||
|
||||
public void setAlpha(int alpha) {
|
||||
this.mDrawable.setAlpha(alpha);
|
||||
}
|
||||
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
this.mDrawable.setColorFilter(cf);
|
||||
}
|
||||
|
||||
public boolean isStateful() {
|
||||
return this.mDrawable.isStateful();
|
||||
}
|
||||
|
||||
public boolean setState(@NonNull int[] stateSet) {
|
||||
return this.mDrawable.setState(stateSet);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public int[] getState() {
|
||||
return this.mDrawable.getState();
|
||||
}
|
||||
|
||||
public void jumpToCurrentState() {
|
||||
this.mDrawable.jumpToCurrentState();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Drawable getCurrent() {
|
||||
return this.mDrawable.getCurrent();
|
||||
}
|
||||
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);
|
||||
}
|
||||
|
||||
public int getOpacity() {
|
||||
return this.mDrawable.getOpacity();
|
||||
}
|
||||
|
||||
public Region getTransparentRegion() {
|
||||
return this.mDrawable.getTransparentRegion();
|
||||
}
|
||||
|
||||
public int getIntrinsicWidth() {
|
||||
return this.mDrawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
public int getIntrinsicHeight() {
|
||||
return this.mDrawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
public int getMinimumWidth() {
|
||||
return this.mDrawable.getMinimumWidth();
|
||||
}
|
||||
|
||||
public int getMinimumHeight() {
|
||||
return this.mDrawable.getMinimumHeight();
|
||||
}
|
||||
|
||||
public boolean getPadding(@NonNull Rect padding) {
|
||||
return this.mDrawable.getPadding(padding);
|
||||
}
|
||||
|
||||
public void invalidateDrawable(@NonNull Drawable who) {
|
||||
this.invalidateSelf();
|
||||
}
|
||||
|
||||
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
|
||||
this.scheduleSelf(what, when);
|
||||
}
|
||||
|
||||
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
|
||||
this.unscheduleSelf(what);
|
||||
}
|
||||
|
||||
protected boolean onLevelChange(int level) {
|
||||
return this.mDrawable.setLevel(level);
|
||||
}
|
||||
|
||||
public boolean isAutoMirrored() {
|
||||
return DrawableCompat.isAutoMirrored(this.mDrawable);
|
||||
}
|
||||
|
||||
public void setAutoMirrored(boolean mirrored) {
|
||||
DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);
|
||||
}
|
||||
|
||||
public void setTint(int tint) {
|
||||
DrawableCompat.setTint(this.mDrawable, tint);
|
||||
}
|
||||
|
||||
public void setTintList(ColorStateList tint) {
|
||||
DrawableCompat.setTintList(this.mDrawable, tint);
|
||||
}
|
||||
|
||||
public void setTintMode(@NonNull PorterDuff.Mode tintMode) {
|
||||
DrawableCompat.setTintMode(this.mDrawable, tintMode);
|
||||
}
|
||||
|
||||
public void setHotspot(float x, float y) {
|
||||
DrawableCompat.setHotspot(this.mDrawable, x, y);
|
||||
}
|
||||
|
||||
public void setHotspotBounds(int left, int top, int right, int bottom) {
|
||||
DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public Drawable getWrappedDrawable() {
|
||||
return this.mDrawable;
|
||||
}
|
||||
|
||||
public void setWrappedDrawable(Drawable drawable) {
|
||||
if (this.mDrawable != null) {
|
||||
this.mDrawable.setCallback(null);
|
||||
}
|
||||
|
||||
this.mDrawable = drawable;
|
||||
if (drawable != null) {
|
||||
drawable.setCallback(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
/**
|
||||
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
|
||||
*/
|
||||
public class DrawerArrowDrawable extends Drawable {
|
||||
|
||||
// The angle in degrees that the arrow head is inclined at.
|
||||
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
|
||||
private final Paint mPaint = new Paint();
|
||||
private final float mBarThickness;
|
||||
// The length of top and bottom bars when they merge into an arrow
|
||||
private final float mTopBottomArrowSize;
|
||||
// The length of middle bar
|
||||
private final float mBarSize;
|
||||
// The length of the middle bar when arrow is shaped
|
||||
private final float mMiddleArrowSize;
|
||||
// The space between bars when they are parallel
|
||||
private final float mBarGap;
|
||||
// Whether bars should spin or not during progress
|
||||
private final boolean mSpin;
|
||||
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
|
||||
// wont look different
|
||||
private final Path mPath = new Path();
|
||||
// The reported intrinsic size of the drawable.
|
||||
private final int mSize;
|
||||
// the amount that overlaps w/ bar size when rotation is max
|
||||
private final float mMaxCutForBarSize;
|
||||
// Whether we should mirror animation when animation is reversed.
|
||||
private boolean mVerticalMirror = false;
|
||||
// The interpolated version of the original progress
|
||||
private float mProgress;
|
||||
|
||||
/**
|
||||
* @param context used to get the configuration for the drawable from
|
||||
*/
|
||||
public DrawerArrowDrawable(Context context, int color) {
|
||||
Resources resources = context.getResources();
|
||||
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(color);
|
||||
mSize = resources.getDimensionPixelSize(R.dimen.dad_drawable_size);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarSize = Math.round(resources.getDimension(R.dimen.dad_bar_size));
|
||||
// round this because having this floating may cause bad measurements
|
||||
mTopBottomArrowSize = Math.round(resources.getDimension(R.dimen.dad_top_bottom_bar_arrow_size));
|
||||
mBarThickness = resources.getDimension(R.dimen.dad_thickness);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarGap = Math.round(resources.getDimension(R.dimen.dad_gap_between_bars));
|
||||
mSpin = resources.getBoolean(R.bool.dad_spin_bars);
|
||||
mMiddleArrowSize = resources.getDimension(R.dimen.dad_middle_bar_arrow_size);
|
||||
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeJoin(Paint.Join.MITER);
|
||||
mPaint.setStrokeCap(Paint.Cap.BUTT);
|
||||
mPaint.setStrokeWidth(mBarThickness);
|
||||
|
||||
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
||||
*/
|
||||
protected void setVerticalMirror(boolean verticalMirror) {
|
||||
mVerticalMirror = verticalMirror;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = getBounds();
|
||||
// Interpolated widths of arrow bars
|
||||
final float arrowSize = MathUtils.lerp(mBarSize, mTopBottomArrowSize, mProgress);
|
||||
final float middleBarSize = MathUtils.lerp(mBarSize, mMiddleArrowSize, mProgress);
|
||||
// Interpolated size of middle bar
|
||||
final float middleBarCut = Math.round(MathUtils.lerp(0, mMaxCutForBarSize, mProgress));
|
||||
// The rotation of the top and bottom bars (that make the arrow head)
|
||||
final float rotation = MathUtils.lerp(0, ARROW_HEAD_ANGLE, mProgress);
|
||||
|
||||
// The whole canvas rotates as the transition happens
|
||||
final float canvasRotate = MathUtils.lerp(-180, 0, mProgress);
|
||||
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
|
||||
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
|
||||
|
||||
mPath.rewind();
|
||||
final float topBottomBarOffset = MathUtils.lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
|
||||
mProgress);
|
||||
|
||||
final float arrowEdge = -middleBarSize / 2;
|
||||
// draw middle bar
|
||||
mPath.moveTo(arrowEdge + middleBarCut, 0);
|
||||
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
|
||||
|
||||
// bottom bar
|
||||
mPath.moveTo(arrowEdge, topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, arrowHeight);
|
||||
|
||||
// top bar
|
||||
mPath.moveTo(arrowEdge, -topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, -arrowHeight);
|
||||
|
||||
mPath.close();
|
||||
|
||||
canvas.save();
|
||||
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
|
||||
// the arrow pointing the other way for RTL.
|
||||
canvas.translate(bounds.centerX(), bounds.centerY());
|
||||
if (mSpin) {
|
||||
canvas.rotate(canvasRotate * (mVerticalMirror ? -1 : 1));
|
||||
}
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void setColor(@ColorInt int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) {
|
||||
mPaint.setAlpha(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setProgress(float progress) {
|
||||
if (progress == 1f) {
|
||||
setVerticalMirror(true);
|
||||
} else if (progress == 0f) {
|
||||
setVerticalMirror(false);
|
||||
}
|
||||
mProgress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setMenu(long duration) {
|
||||
setShape(false, duration);
|
||||
}
|
||||
|
||||
public void setArrow(long duration) {
|
||||
setShape(true, duration);
|
||||
}
|
||||
|
||||
public void setShape(boolean arrow, long duration) {
|
||||
if (!((!arrow && mProgress == 0f) || (arrow && mProgress == 1f))) {
|
||||
float endProgress = arrow ? 1f : 0f;
|
||||
if (duration <= 0) {
|
||||
setProgress(endProgress);
|
||||
} else {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
|
||||
oa.setDuration(duration);
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
/**
|
||||
* Show a part of the original drawable
|
||||
*/
|
||||
public class PreciselyClipDrawable extends DrawableWrapper {
|
||||
|
||||
private final boolean mClip;
|
||||
private RectF mScale;
|
||||
private Rect mTemp;
|
||||
|
||||
public PreciselyClipDrawable(Drawable drawable, int offsetX, int offsetY, int width, int height) {
|
||||
super(drawable);
|
||||
float originWidth = drawable.getIntrinsicWidth();
|
||||
float originHeight = drawable.getIntrinsicHeight();
|
||||
|
||||
if (originWidth <= 0 || originHeight <= 0) {
|
||||
// Can not clip
|
||||
mClip = false;
|
||||
} else {
|
||||
mClip = true;
|
||||
mScale = new RectF();
|
||||
mScale.set(MathUtils.clamp(offsetX / originWidth, 0.0f, 1.0f),
|
||||
MathUtils.clamp(offsetY / originHeight, 0.0f, 1.0f),
|
||||
MathUtils.clamp((offsetX + width) / originWidth, 0.0f, 1.0f),
|
||||
MathUtils.clamp((offsetY + height) / originHeight, 0.0f, 1.0f));
|
||||
mTemp = new Rect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
if (mClip) {
|
||||
if (!mScale.isEmpty()) {
|
||||
mTemp.left = (int) ((mScale.left * bounds.right - mScale.right * bounds.left) /
|
||||
(mScale.left * (1 - mScale.right) - mScale.right * (1 - mScale.left)));
|
||||
mTemp.right = (int) (((1 - mScale.right) * bounds.left - (1 - mScale.left) * bounds.right) /
|
||||
(mScale.left * (1 - mScale.right) - mScale.right * (1 - mScale.left)));
|
||||
mTemp.top = (int) ((mScale.top * bounds.bottom - mScale.bottom * bounds.top) /
|
||||
(mScale.top * (1 - mScale.bottom) - mScale.bottom * (1 - mScale.top)));
|
||||
mTemp.bottom = (int) (((1 - mScale.bottom) * bounds.top - (1 - mScale.top) * bounds.bottom) /
|
||||
(mScale.top * (1 - mScale.bottom) - mScale.bottom * (1 - mScale.top)));
|
||||
super.onBoundsChange(mTemp);
|
||||
}
|
||||
} else {
|
||||
super.onBoundsChange(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
if (mClip) {
|
||||
return (int) (super.getIntrinsicWidth() * mScale.width());
|
||||
} else {
|
||||
return super.getIntrinsicWidth();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
if (mClip) {
|
||||
return (int) (super.getIntrinsicHeight() * mScale.height());
|
||||
} else {
|
||||
return super.getIntrinsicHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mClip) {
|
||||
if (!mScale.isEmpty()) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.clipRect(getBounds());
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
} else {
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class TriangleDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint;
|
||||
private final Path mPath;
|
||||
|
||||
public TriangleDrawable(int color) {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setColor(color);
|
||||
mPath = new Path();
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mPath.reset();
|
||||
mPath.moveTo(bounds.left, bounds.top);
|
||||
mPath.lineTo(bounds.right, bounds.top);
|
||||
mPath.lineTo(bounds.right, bounds.bottom);
|
||||
mPath.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
||||
ObservedTextView.OnWindowAttachListener {
|
||||
|
||||
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
private String mUrl;
|
||||
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
load(mUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
mConaco.cancel(this);
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
public void load(String url) {
|
||||
if (url != null) {
|
||||
mUrl = url;
|
||||
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
((ImageDrawable) drawable).recycle();
|
||||
}
|
||||
setDrawable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDrawable(Drawable drawable) {
|
||||
// Remove old callback
|
||||
Drawable oldDrawable = getDrawable();
|
||||
if (oldDrawable != null) {
|
||||
oldDrawable.setCallback(null);
|
||||
}
|
||||
|
||||
super.setDrawable(drawable);
|
||||
|
||||
if (drawable != null) {
|
||||
drawable.setCallback(mTextView);
|
||||
}
|
||||
|
||||
updateBounds();
|
||||
if (drawable != null) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskId() {
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskId(int id) {
|
||||
mTaskId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateSelf() {
|
||||
CharSequence cs = mTextView.getText();
|
||||
mTextView.setText(cs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiss(int source) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWait() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
ImageDrawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
drawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class WrapDrawable extends Drawable {
|
||||
|
||||
private Drawable mDrawable;
|
||||
|
||||
public Drawable getDrawable() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
public void setDrawable(Drawable drawable) {
|
||||
mDrawable = drawable;
|
||||
}
|
||||
|
||||
public void updateBounds() {
|
||||
setBounds(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mDrawable != null) {
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(int left, int top, int right, int bottom) {
|
||||
super.setBounds(left, top, right, bottom);
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setBounds(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(Rect bounds) {
|
||||
super.setBounds(bounds);
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setBounds(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
if (mDrawable != null) {
|
||||
return mDrawable.getChangingConfigurations();
|
||||
} else {
|
||||
return super.getChangingConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangingConfigurations(int configs) {
|
||||
super.setChangingConfigurations(configs);
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setChangingConfigurations(configs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDither(boolean dither) {
|
||||
super.setDither(dither);
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setDither(dither);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
super.setFilterBitmap(filter);
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setFilterBitmap(filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setColorFilter(cf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
if (mDrawable != null) {
|
||||
return mDrawable.getOpacity();
|
||||
} else {
|
||||
return PixelFormat.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
if (mDrawable != null) {
|
||||
return mDrawable.getIntrinsicWidth();
|
||||
} else {
|
||||
return super.getIntrinsicWidth();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
if (mDrawable != null) {
|
||||
return mDrawable.getIntrinsicHeight();
|
||||
} else {
|
||||
return super.getIntrinsicHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.collection.LongSparseArray;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
|
||||
/**
|
||||
* Add setChoiceMode for RecyclerView
|
||||
*/
|
||||
// Get some code from twoway-view and AbsListView.
|
||||
public class EasyRecyclerView extends RecyclerView {
|
||||
|
||||
/**
|
||||
* Normal list that does not indicate choices
|
||||
*/
|
||||
public static final int CHOICE_MODE_NONE = 0;
|
||||
|
||||
/**
|
||||
* The list allows up to one choice
|
||||
*/
|
||||
public static final int CHOICE_MODE_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* The list allows multiple choices
|
||||
*/
|
||||
public static final int CHOICE_MODE_MULTIPLE = 2;
|
||||
|
||||
/**
|
||||
* The list allows multiple choices in a modal selection mode
|
||||
*/
|
||||
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
|
||||
|
||||
/**
|
||||
* The list allows multiple choices in custom action
|
||||
*/
|
||||
public static final int CHOICE_MODE_MULTIPLE_CUSTOM = 4;
|
||||
// Fling friction
|
||||
public float pageScrollThreshold;
|
||||
/**
|
||||
* Wrapper for the multiple choice mode callback; AbsListView needs to perform
|
||||
* a few extra actions around what application code does.
|
||||
*/
|
||||
MultiChoiceModeWrapper mMultiChoiceModeCallback;
|
||||
/**
|
||||
* Controls if/how the user may choose/check items in the list
|
||||
*/
|
||||
private int mChoiceMode = CHOICE_MODE_NONE;
|
||||
/**
|
||||
* Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
|
||||
*/
|
||||
private ActionMode mChoiceActionMode;
|
||||
/**
|
||||
* Listener for custom multiple choices
|
||||
*/
|
||||
private CustomChoiceListener mCustomChoiceListener;
|
||||
|
||||
private boolean mCustomChoice = false;
|
||||
|
||||
/**
|
||||
* A lock, avoid OutOfCustomChoiceMode when doing OutOfCustomChoiceMode
|
||||
*/
|
||||
private boolean mOutOfCustomChoiceModing = false;
|
||||
|
||||
private SparseBooleanArray mTempCheckStates;
|
||||
|
||||
/**
|
||||
* Running count of how many items are currently checked
|
||||
*/
|
||||
private int mCheckedItemCount;
|
||||
|
||||
/**
|
||||
* Running state of which positions are currently checked
|
||||
*/
|
||||
private SparseBooleanArray mCheckStates;
|
||||
|
||||
/**
|
||||
* Running state of which IDs are currently checked.
|
||||
* If there is a value for a given key, the checked state for that ID is true
|
||||
* and the value holds the last known position in the adapter for that id.
|
||||
*/
|
||||
private LongSparseArray<Integer> mCheckedIdStates;
|
||||
|
||||
private Adapter<?> mAdapter;
|
||||
|
||||
public EasyRecyclerView(Context context) {
|
||||
super(context);
|
||||
EasyRecyclerView.this.init(context);
|
||||
}
|
||||
|
||||
public EasyRecyclerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
EasyRecyclerView.this.init(context);
|
||||
}
|
||||
|
||||
public EasyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
EasyRecyclerView.this.init(context);
|
||||
}
|
||||
|
||||
public static void setViewChecked(View view, boolean checked) {
|
||||
if (view instanceof Checkable) {
|
||||
((Checkable) view).setChecked(checked);
|
||||
} else {
|
||||
view.setActivated(checked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setAdapter(RecyclerView.Adapter adapter) {
|
||||
|
||||
super.setAdapter(adapter);
|
||||
|
||||
mAdapter = adapter;
|
||||
if (adapter != null) {
|
||||
if (mChoiceMode != CHOICE_MODE_NONE && adapter.hasStableIds() &&
|
||||
mCheckedIdStates == null) {
|
||||
mCheckedIdStates = new LongSparseArray<>();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCheckStates != null) {
|
||||
mCheckStates.clear();
|
||||
}
|
||||
|
||||
if (mCheckedIdStates != null) {
|
||||
mCheckedIdStates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildAttachedToWindow(@NonNull View child) {
|
||||
super.onChildAttachedToWindow(child);
|
||||
|
||||
if (mCheckStates != null) {
|
||||
int position = getChildAdapterPosition(child);
|
||||
if (position >= 0) {
|
||||
setViewChecked(child, mCheckStates.get(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getCheckedItemCount() {
|
||||
return mCheckedItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checked state of the specified position. The result is only
|
||||
* valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
|
||||
* or {@link #CHOICE_MODE_MULTIPLE}.
|
||||
*
|
||||
* @param position The item whose checked state to return
|
||||
* @return The item's checked state or <code>false</code> if choice mode
|
||||
* is invalid
|
||||
* @see #setChoiceMode(int)
|
||||
*/
|
||||
public boolean isItemChecked(int position) {
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
|
||||
return mCheckStates.get(position);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of checked items in the list. The result is only valid if
|
||||
* the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
|
||||
*
|
||||
* @return A SparseBooleanArray which will return true for each call to
|
||||
* get(int position) where position is a checked position in the
|
||||
* list and false otherwise, or <code>null</code> if the choice
|
||||
* mode is set to {@link #CHOICE_MODE_NONE}.
|
||||
*/
|
||||
public SparseBooleanArray getCheckedItemPositions() {
|
||||
if (mChoiceMode != CHOICE_MODE_NONE) {
|
||||
return mCheckStates;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInCustomChoice() {
|
||||
return mCustomChoice;
|
||||
}
|
||||
|
||||
public void intoCustomChoiceMode() {
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !mCustomChoice) {
|
||||
mCustomChoice = true;
|
||||
|
||||
mCustomChoiceListener.onIntoCustomChoice(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void outOfCustomChoiceMode() {
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && mCustomChoice && !mOutOfCustomChoiceModing) {
|
||||
mOutOfCustomChoiceModing = true;
|
||||
|
||||
// Copy mCheckStates
|
||||
mTempCheckStates.clear();
|
||||
for (int i = 0, n = mCheckStates.size(); i < n; i++) {
|
||||
mTempCheckStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i));
|
||||
}
|
||||
// Uncheck remain checked items
|
||||
for (int i = 0, n = mTempCheckStates.size(); i < n; i++) {
|
||||
if (mTempCheckStates.valueAt(i)) {
|
||||
setItemChecked(mTempCheckStates.keyAt(i), false);
|
||||
}
|
||||
}
|
||||
|
||||
mCustomChoice = false;
|
||||
mCustomChoiceListener.onOutOfCustomChoice(this);
|
||||
|
||||
mOutOfCustomChoiceModing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any choices previously set
|
||||
*/
|
||||
private void clearChoices() {
|
||||
if (mCheckStates != null) {
|
||||
mCheckStates.clear();
|
||||
}
|
||||
if (mCheckedIdStates != null) {
|
||||
mCheckedIdStates.clear();
|
||||
}
|
||||
mCheckedItemCount = 0;
|
||||
updateOnScreenCheckedViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all items checked.
|
||||
*/
|
||||
public void checkAll() {
|
||||
if (mChoiceMode == CHOICE_MODE_NONE || mChoiceMode == CHOICE_MODE_SINGLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check is intoCheckMode
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !mCustomChoice) {
|
||||
throw new IllegalStateException("Call intoCheckMode first");
|
||||
}
|
||||
|
||||
// Start selection mode if needed. We don't need to if we're unchecking something.
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
|
||||
if (mMultiChoiceModeCallback == null ||
|
||||
!mMultiChoiceModeCallback.hasWrappedCallback()) {
|
||||
throw new IllegalStateException("EasyRecyclerView: attempted to start selection mode " +
|
||||
"for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
|
||||
"supplied. Call setMultiChoiceModeListener to set a callback.");
|
||||
}
|
||||
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
|
||||
}
|
||||
|
||||
for (int i = 0, n = mAdapter.getItemCount(); i < n; i++) {
|
||||
boolean oldValue = mCheckStates.get(i);
|
||||
mCheckStates.put(i, true);
|
||||
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
|
||||
mCheckedIdStates.put(mAdapter.getItemId(i), i);
|
||||
}
|
||||
if (!oldValue) {
|
||||
mCheckedItemCount++;
|
||||
}
|
||||
if (mChoiceActionMode != null) {
|
||||
final long id = mAdapter.getItemId(i);
|
||||
mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, i, id, true);
|
||||
}
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
final long id = mAdapter.getItemId(i);
|
||||
mCustomChoiceListener.onItemCheckedStateChanged(this, i, id, true);
|
||||
}
|
||||
}
|
||||
|
||||
updateOnScreenCheckedViews();
|
||||
}
|
||||
|
||||
public void toggleItemChecked(int position) {
|
||||
if (mCheckStates != null) {
|
||||
setItemChecked(position, !mCheckStates.get(position));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked state of the specified position. The is only valid if
|
||||
* the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
|
||||
* {@link #CHOICE_MODE_MULTIPLE}.
|
||||
*
|
||||
* @param position The item whose checked state is to be checked
|
||||
* @param value The new checked state for the item
|
||||
*/
|
||||
public void setItemChecked(int position, boolean value) {
|
||||
if (mChoiceMode == CHOICE_MODE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check is intoCheckMode
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !mCustomChoice) {
|
||||
throw new IllegalStateException("Call intoCheckMode first");
|
||||
}
|
||||
|
||||
// Start selection mode if needed. We don't need to if we're unchecking something.
|
||||
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
|
||||
if (mMultiChoiceModeCallback == null ||
|
||||
!mMultiChoiceModeCallback.hasWrappedCallback()) {
|
||||
throw new IllegalStateException("EasyRecyclerView: attempted to start selection mode " +
|
||||
"for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
|
||||
"supplied. Call setMultiChoiceModeListener to set a callback.");
|
||||
}
|
||||
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
|
||||
}
|
||||
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL || mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
boolean oldValue = mCheckStates.get(position);
|
||||
mCheckStates.put(position, value);
|
||||
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
|
||||
if (value) {
|
||||
mCheckedIdStates.put(mAdapter.getItemId(position), position);
|
||||
} else {
|
||||
mCheckedIdStates.remove(mAdapter.getItemId(position));
|
||||
}
|
||||
}
|
||||
if (oldValue != value) {
|
||||
if (value) {
|
||||
mCheckedItemCount++;
|
||||
} else {
|
||||
mCheckedItemCount--;
|
||||
}
|
||||
}
|
||||
if (mChoiceActionMode != null) {
|
||||
final long id = mAdapter.getItemId(position);
|
||||
mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
|
||||
position, id, value);
|
||||
}
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
final long id = mAdapter.getItemId(position);
|
||||
mCustomChoiceListener.onItemCheckedStateChanged(this, position, id, value);
|
||||
}
|
||||
} else {
|
||||
boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
|
||||
// Clear all values if we're checking something, or unchecking the currently
|
||||
// selected item
|
||||
if (value || isItemChecked(position)) {
|
||||
mCheckStates.clear();
|
||||
if (updateIds) {
|
||||
mCheckedIdStates.clear();
|
||||
}
|
||||
}
|
||||
// this may end up selecting the value we just cleared but this way
|
||||
// we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
|
||||
if (value) {
|
||||
mCheckStates.put(position, true);
|
||||
if (updateIds) {
|
||||
mCheckedIdStates.put(mAdapter.getItemId(position), position);
|
||||
}
|
||||
mCheckedItemCount = 1;
|
||||
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
|
||||
mCheckedItemCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
updateOnScreenCheckedViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the choice behavior for the List. By default, Lists do not have any choice behavior
|
||||
* ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
|
||||
* List allows up to one item to be in a chosen state. By setting the choiceMode to
|
||||
* {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
|
||||
*
|
||||
* @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
|
||||
* {@link #CHOICE_MODE_MULTIPLE}
|
||||
*/
|
||||
public void setChoiceMode(int choiceMode) {
|
||||
mChoiceMode = choiceMode;
|
||||
if (mChoiceActionMode != null) {
|
||||
mChoiceActionMode.finish();
|
||||
mChoiceActionMode = null;
|
||||
}
|
||||
if (mChoiceMode != CHOICE_MODE_NONE) {
|
||||
if (mCheckStates == null) {
|
||||
mCheckStates = new SparseBooleanArray(0);
|
||||
}
|
||||
if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
|
||||
mCheckedIdStates = new LongSparseArray<>(0);
|
||||
}
|
||||
// Modal multi-choice mode only has choices when the mode is active. Clear them.
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
|
||||
clearChoices();
|
||||
setLongClickable(true);
|
||||
} else if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
if (mTempCheckStates == null) {
|
||||
mTempCheckStates = new SparseBooleanArray(0);
|
||||
}
|
||||
clearChoices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCustomCheckedListener(CustomChoiceListener listener) {
|
||||
mCustomChoiceListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a quick, in-place update of the checked or activated state
|
||||
* on all visible item views. This should only be called when a valid
|
||||
* choice mode is active.
|
||||
*/
|
||||
private void updateOnScreenCheckedViews() {
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View child = getChildAt(i);
|
||||
final int position = getChildAdapterPosition(child);
|
||||
setViewChecked(child, mCheckStates.get(position));
|
||||
}
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
// g (m/s^2)
|
||||
// inch/meter
|
||||
// A context-specific coefficient adjusted to physical values.
|
||||
// look and feel tuning
|
||||
pageScrollThreshold = scale * 80;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final SavedState ss = new SavedState(super.onSaveInstanceState());
|
||||
|
||||
ss.choiceMode = mChoiceMode;
|
||||
ss.customChoice = mCustomChoice;
|
||||
ss.checkedItemCount = mCheckedItemCount;
|
||||
ss.checkState = mCheckStates;
|
||||
ss.checkIdState = mCheckedIdStates;
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
SavedState ss = (SavedState) state;
|
||||
super.onRestoreInstanceState(ss.getSuperState());
|
||||
|
||||
setChoiceMode(ss.choiceMode);
|
||||
mCustomChoice = ss.customChoice;
|
||||
mCheckedItemCount = ss.checkedItemCount;
|
||||
if (ss.checkState != null) {
|
||||
mCheckStates = ss.checkState;
|
||||
}
|
||||
if (ss.checkIdState != null) {
|
||||
mCheckedIdStates = ss.checkIdState;
|
||||
}
|
||||
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mCheckedItemCount > 0) {
|
||||
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
|
||||
}
|
||||
updateOnScreenCheckedViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* A MultiChoiceModeListener receives events for {@link android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
|
||||
* It acts as the {@link android.view.ActionMode.Callback} for the selection mode and also receives
|
||||
* {@link #onItemCheckedStateChanged(android.view.ActionMode, int, long, boolean)} events when the user
|
||||
* selects and deselects list items.
|
||||
*/
|
||||
public interface MultiChoiceModeListener extends ActionMode.Callback {
|
||||
/**
|
||||
* Called when an item is checked or unchecked during selection mode.
|
||||
*
|
||||
* @param mode The {@link android.view.ActionMode} providing the selection mode
|
||||
* @param position Adapter position of the item that was checked or unchecked
|
||||
* @param id Adapter ID of the item that was checked or unchecked
|
||||
* @param checked <code>true</code> if the item is now checked, <code>false</code>
|
||||
* if the item is now unchecked.
|
||||
*/
|
||||
void onItemCheckedStateChanged(ActionMode mode,
|
||||
int position, long id, boolean checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom checked
|
||||
*/
|
||||
public interface CustomChoiceListener {
|
||||
|
||||
void onIntoCustomChoice(EasyRecyclerView view);
|
||||
|
||||
void onOutOfCustomChoice(EasyRecyclerView view);
|
||||
|
||||
void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* This saved state class is a Parcelable and should not extend
|
||||
* {@link android.view.View.BaseSavedState} nor {@link android.view.AbsSavedState}
|
||||
* because its super class AbsSavedState's constructor
|
||||
* currently passes null
|
||||
* as a class loader to read its superstate from Parcelable.
|
||||
* This causes {@link android.os.BadParcelableException} when restoring saved states.
|
||||
* <p/>
|
||||
* The super class "RecyclerView" is a part of the support library,
|
||||
* and restoring its saved state requires the class loader that loaded the RecyclerView.
|
||||
* It seems that the class loader is not required when restoring from RecyclerView itself,
|
||||
* but it is required when restoring from RecyclerView's subclasses.
|
||||
*/
|
||||
static class SavedState implements Parcelable {
|
||||
|
||||
public static final SavedState EMPTY_STATE = new SavedState() {
|
||||
};
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
int choiceMode;
|
||||
boolean customChoice;
|
||||
int checkedItemCount;
|
||||
SparseBooleanArray checkState;
|
||||
LongSparseArray<Integer> checkIdState;
|
||||
// This keeps the parent(RecyclerView)'s state
|
||||
Parcelable mSuperState;
|
||||
|
||||
SavedState() {
|
||||
mSuperState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called from {@link #onSaveInstanceState()}
|
||||
*/
|
||||
SavedState(Parcelable superState) {
|
||||
mSuperState = superState != EMPTY_STATE ? superState : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called from {@link #CREATOR}
|
||||
*/
|
||||
private SavedState(Parcel in) {
|
||||
// Parcel 'in' has its parent(RecyclerView)'s saved state.
|
||||
// To restore it, class loader that loaded RecyclerView is required.
|
||||
Parcelable superState = in.readParcelable(RecyclerView.class.getClassLoader());
|
||||
mSuperState = superState != null ? superState : EMPTY_STATE;
|
||||
|
||||
choiceMode = in.readInt();
|
||||
customChoice = NumberUtils.int2boolean(in.readInt());
|
||||
checkedItemCount = in.readInt();
|
||||
checkState = in.readSparseBooleanArray();
|
||||
final int N = in.readInt();
|
||||
if (N > 0) {
|
||||
checkIdState = new LongSparseArray<>();
|
||||
for (int i = 0; i < N; i++) {
|
||||
final long key = in.readLong();
|
||||
final int value = in.readInt();
|
||||
checkIdState.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeParcelable(mSuperState, flags);
|
||||
|
||||
out.writeInt(choiceMode);
|
||||
out.writeInt(NumberUtils.boolean2int(customChoice));
|
||||
out.writeInt(checkedItemCount);
|
||||
out.writeSparseBooleanArray(checkState);
|
||||
final int N = checkIdState != null ? checkIdState.size() : 0;
|
||||
out.writeInt(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
out.writeLong(checkIdState.keyAt(i));
|
||||
out.writeInt(checkIdState.valueAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
public Parcelable getSuperState() {
|
||||
return mSuperState;
|
||||
}
|
||||
}
|
||||
|
||||
class MultiChoiceModeWrapper implements MultiChoiceModeListener {
|
||||
private MultiChoiceModeListener mWrapped;
|
||||
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean hasWrappedCallback() {
|
||||
return mWrapped != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
if (mWrapped.onCreateActionMode(mode, menu)) {
|
||||
// Initialize checked graphic state?
|
||||
setLongClickable(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return mWrapped.onPrepareActionMode(mode, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
return mWrapped.onActionItemClicked(mode, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
mWrapped.onDestroyActionMode(mode);
|
||||
mChoiceActionMode = null;
|
||||
|
||||
// Ending selection mode means deselecting everything.
|
||||
clearChoices();
|
||||
|
||||
requestLayout();
|
||||
|
||||
setLongClickable(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode,
|
||||
int position, long id, boolean checked) {
|
||||
mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
|
||||
|
||||
// If there are no items selected we no longer need the selection mode.
|
||||
if (getCheckedItemCount() == 0) {
|
||||
mode.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
public class FastScroller extends View {
|
||||
|
||||
private static final int INVALID = -1;
|
||||
|
||||
private static final int SCROLL_BAR_FADE_DURATION = 500;
|
||||
private static final int SCROLL_BAR_DELAY = 1000;
|
||||
|
||||
private static final int MIN_HANDLER_HEIGHT_DP = 32;
|
||||
|
||||
private Handler mSimpleHandler;
|
||||
|
||||
private boolean mDraggable;
|
||||
|
||||
private int mMinHandlerHeight;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private RecyclerView.OnScrollListener mOnScrollChangeListener;
|
||||
private RecyclerView.Adapter<?> mAdapter;
|
||||
private RecyclerView.AdapterDataObserver mAdapterDataObserver;
|
||||
|
||||
private Drawable mHandler;
|
||||
private int mHandlerOffset = INVALID;
|
||||
private int mHandlerHeight = INVALID;
|
||||
|
||||
private float mDownX = INVALID;
|
||||
private float mDownY = INVALID;
|
||||
|
||||
private float mLastMotionY = INVALID;
|
||||
|
||||
private boolean mDragged = false;
|
||||
|
||||
private boolean mCantDrag = false;
|
||||
|
||||
private int mTouchSlop;
|
||||
|
||||
private OnDragHandlerListener mListener;
|
||||
|
||||
private ObjectAnimator mShowAnimator;
|
||||
private ObjectAnimator mHideAnimator;
|
||||
|
||||
private final Runnable mHideRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mHideAnimator.start();
|
||||
}
|
||||
};
|
||||
|
||||
public FastScroller(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
}
|
||||
|
||||
public FastScroller(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
mSimpleHandler = SimpleHandler.getInstance();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr, 0);
|
||||
mHandler = a.getDrawable(R.styleable.FastScroller_handler);
|
||||
mDraggable = a.getBoolean(R.styleable.FastScroller_draggable, true);
|
||||
a.recycle();
|
||||
|
||||
setAlpha(0.0f);
|
||||
setVisibility(INVISIBLE);
|
||||
|
||||
mMinHandlerHeight = LayoutUtils.dp2pix(context, MIN_HANDLER_HEIGHT_DP);
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
mShowAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f);
|
||||
mShowAnimator.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
mShowAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
|
||||
|
||||
mHideAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f);
|
||||
mHideAnimator.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
|
||||
mHideAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
|
||||
mHideAnimator.addListener(new SimpleAnimatorListener() {
|
||||
|
||||
private boolean mCancel;
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mCancel = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (mCancel) {
|
||||
mCancel = false;
|
||||
} else {
|
||||
setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnDragHandlerListener(OnDragHandlerListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void updatePosition(boolean show) {
|
||||
if (mRecyclerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int paddingTop = getPaddingTop();
|
||||
int paddingBottom = getPaddingBottom();
|
||||
int height = getHeight() - paddingTop - paddingBottom;
|
||||
int offset = mRecyclerView.computeVerticalScrollOffset();
|
||||
int extent = mRecyclerView.computeVerticalScrollExtent();
|
||||
int range = mRecyclerView.computeVerticalScrollRange();
|
||||
|
||||
if (height <= 0 || extent >= range || extent <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int endOffset = height * offset / range;
|
||||
int endHeight = height * extent / range;
|
||||
|
||||
endHeight = Math.max(endHeight, mMinHandlerHeight);
|
||||
endOffset = Math.min(endOffset, height - endHeight);
|
||||
|
||||
mHandlerOffset = endOffset + paddingTop;
|
||||
mHandlerHeight = endHeight;
|
||||
|
||||
if (show) {
|
||||
if (mHideAnimator.isRunning()) {
|
||||
mHideAnimator.cancel();
|
||||
mShowAnimator.start();
|
||||
} else if (getVisibility() != VISIBLE && !mShowAnimator.isRunning()) {
|
||||
setVisibility(VISIBLE);
|
||||
mShowAnimator.start();
|
||||
}
|
||||
|
||||
Handler handler = mSimpleHandler;
|
||||
handler.removeCallbacks(mHideRunnable);
|
||||
|
||||
if (!mDragged) {
|
||||
handler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setHandlerDrawable(Drawable drawable) {
|
||||
mHandler = drawable;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isDraggable() {
|
||||
return mDraggable;
|
||||
}
|
||||
|
||||
public void setDraggable(boolean draggable) {
|
||||
mDraggable = draggable;
|
||||
if (mDragged) {
|
||||
mDragged = false;
|
||||
}
|
||||
mSimpleHandler.removeCallbacks(mHideRunnable);
|
||||
mHideRunnable.run();
|
||||
}
|
||||
|
||||
public boolean isAttached() {
|
||||
return mRecyclerView != null;
|
||||
}
|
||||
|
||||
public void attachToRecyclerView(RecyclerView recyclerView) {
|
||||
if (recyclerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
throw new IllegalStateException("The FastScroller is already attached to a RecyclerView, " +
|
||||
"call detachedFromRecyclerView first");
|
||||
}
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
mOnScrollChangeListener = new RecyclerView.OnScrollListener() {
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
updatePosition(true);
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
recyclerView.addOnScrollListener(mOnScrollChangeListener);
|
||||
|
||||
mAdapter = recyclerView.getAdapter();
|
||||
if (mAdapter != null) {
|
||||
mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
super.onItemRangeChanged(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
super.onItemRangeInserted(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public void detachedFromRecyclerView() {
|
||||
if (mRecyclerView != null && mOnScrollChangeListener != null) {
|
||||
mRecyclerView.removeOnScrollListener(mOnScrollChangeListener);
|
||||
}
|
||||
mRecyclerView = null;
|
||||
mOnScrollChangeListener = null;
|
||||
|
||||
if (mAdapter != null && mAdapterDataObserver != null) {
|
||||
mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
|
||||
}
|
||||
mAdapter = null;
|
||||
mAdapterDataObserver = null;
|
||||
|
||||
setAlpha(0.0f);
|
||||
setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mRecyclerView == null || mHandler == null) {
|
||||
return;
|
||||
}
|
||||
if (mHandlerHeight == INVALID) {
|
||||
updatePosition(false);
|
||||
}
|
||||
if (mHandlerHeight == INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
int paddingLeft = getPaddingLeft();
|
||||
int saved = canvas.save();
|
||||
canvas.translate(paddingLeft, mHandlerOffset);
|
||||
mHandler.setBounds(0, 0, getWidth() - paddingLeft - getPaddingRight(), mHandlerHeight);
|
||||
mHandler.draw(canvas);
|
||||
canvas.restoreToCount(saved);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!mDraggable || getVisibility() != VISIBLE || mRecyclerView == null || mHandlerHeight == INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mCantDrag = false;
|
||||
}
|
||||
|
||||
if (mCantDrag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mDragged = false;
|
||||
mDownX = event.getX();
|
||||
mDownY = event.getY();
|
||||
if (mDownY < mHandlerOffset || mDownY > mHandlerOffset + mHandlerHeight) {
|
||||
mCantDrag = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (!mDragged) {
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
// Check touch slop
|
||||
if (MathUtils.dist(x, y, mDownX, mDownY) < mTouchSlop) {
|
||||
return true;
|
||||
}
|
||||
if (Math.abs(x - mDownX) > Math.abs(y - mDownY) ||
|
||||
y < mHandlerOffset || y > mHandlerOffset + mHandlerHeight) {
|
||||
mCantDrag = true;
|
||||
return false;
|
||||
} else {
|
||||
mDragged = true;
|
||||
mSimpleHandler.removeCallbacks(mHideRunnable);
|
||||
// Update mLastMotionY
|
||||
if (mDownY < mHandlerOffset || mDownY >= mHandlerOffset + mHandlerHeight) {
|
||||
// the point out of handler, make the point in handler center
|
||||
mLastMotionY = mHandlerOffset + mHandlerHeight / 2;
|
||||
} else {
|
||||
mLastMotionY = mDownY;
|
||||
}
|
||||
// Notify
|
||||
if (mListener != null) {
|
||||
mListener.onStartDragHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int range = mRecyclerView.computeVerticalScrollRange();
|
||||
if (range <= 0) {
|
||||
break;
|
||||
}
|
||||
float y = event.getY();
|
||||
int scroll = (int) (range * (y - mLastMotionY) / (getHeight() - getPaddingTop() - getPaddingBottom()));
|
||||
mRecyclerView.scrollBy(0, scroll);
|
||||
mLastMotionY = y;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
// Notify
|
||||
if (mDragged && mListener != null) {
|
||||
mListener.onEndDragHandler();
|
||||
}
|
||||
mDragged = false;
|
||||
mSimpleHandler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public interface OnDragHandlerListener {
|
||||
void onStartDragHandler();
|
||||
|
||||
void onEndDragHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class HandlerDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint;
|
||||
private final RectF mTemp = new RectF();
|
||||
private int mColor = Color.BLACK;
|
||||
|
||||
public HandlerDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setColor(mColor);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
int width = getBounds().width();
|
||||
int height = getBounds().height();
|
||||
|
||||
if (width > height) {
|
||||
canvas.drawRect(0, 0, width, height, mPaint);
|
||||
} else {
|
||||
mTemp.set(0, 0, width, width);
|
||||
canvas.drawArc(mTemp, -180, 180, true, mPaint);
|
||||
mTemp.set(0, height - width, width, height);
|
||||
canvas.drawArc(mTemp, 0, 180, true, mPaint);
|
||||
float halfWidth = (float) width / 2.0f;
|
||||
canvas.drawRect(0, halfWidth, width, height - halfWidth, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor != color) {
|
||||
mColor = color;
|
||||
mPaint.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
int alpha = Color.alpha(mColor);
|
||||
if (alpha == 0xff) {
|
||||
return PixelFormat.OPAQUE;
|
||||
} else if (alpha == 0x00) {
|
||||
return PixelFormat.TRANSPARENT;
|
||||
} else {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class LayoutManagerUtils {
|
||||
|
||||
private static Method sCsdfp = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
sCsdfp = StaggeredGridLayoutManager.class.getDeclaredMethod("calculateScrollDirectionForPosition", int.class);
|
||||
sCsdfp.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Ignore
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void scrollToPositionWithOffset(
|
||||
RecyclerView.LayoutManager layoutManager, int position, int offset) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset);
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
((StaggeredGridLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do scrollToPositionWithOffset for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static void smoothScrollToPosition(
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position) {
|
||||
smoothScrollToPosition(layoutManager, context, position, -1);
|
||||
}
|
||||
|
||||
public static void smoothScrollToPosition(
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||
int millisecondsPerInch) {
|
||||
SimpleSmoothScroller smoothScroller;
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
return linearLayoutManager.computeScrollVectorForPosition(targetPosition);
|
||||
}
|
||||
};
|
||||
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
int direction = 0;
|
||||
try {
|
||||
direction = (Integer) sCsdfp.invoke(staggeredGridLayoutManager, targetPosition);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (direction == 0) {
|
||||
return null;
|
||||
}
|
||||
if (staggeredGridLayoutManager.getOrientation() == StaggeredGridLayoutManager.HORIZONTAL) {
|
||||
return new PointF(direction, 0);
|
||||
} else {
|
||||
return new PointF(0, direction);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do smoothScrollToPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
smoothScroller.setTargetPosition(position);
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
public static void scrollToPositionProperly(final RecyclerView.LayoutManager layoutManager,
|
||||
final Context context, final int position, final OnScrollToPositionListener listener) {
|
||||
SimpleHandler.getInstance().postDelayed(() -> {
|
||||
int first = getFirstVisibleItemPosition(layoutManager);
|
||||
int last = getLastVisibleItemPosition(layoutManager);
|
||||
int offset = Math.abs(position - first);
|
||||
int max = last - first;
|
||||
if (offset < max && max > 0) {
|
||||
smoothScrollToPosition(layoutManager, context, position,
|
||||
MathUtils.lerp(100, 25, (offset / max)));
|
||||
} else {
|
||||
scrollToPositionWithOffset(layoutManager, position, 0);
|
||||
if (listener != null) {
|
||||
listener.onScrollToPosition(position);
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public static int getFirstVisibleItemPosition(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null);
|
||||
return MathUtils.min(positions);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do getFirstVisibleItemPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null);
|
||||
return MathUtils.max(positions);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do getLastVisibleItemPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnScrollToPositionListener {
|
||||
void onScrollToPosition(int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class MarginItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private int mMargin;
|
||||
private int mPaddingLeft;
|
||||
private int mPaddingTop;
|
||||
private int mPaddingRight;
|
||||
private int mPaddingBottom;
|
||||
|
||||
public MarginItemDecoration(int margin, int paddingLeft, int paddingTop,
|
||||
int paddingRight, int paddingBottom) {
|
||||
setMargin(margin, paddingLeft, paddingTop, paddingRight, paddingBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view,
|
||||
RecyclerView parent, RecyclerView.State state) {
|
||||
outRect.set(mMargin, mMargin, mMargin, mMargin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param margin gap between two item
|
||||
* @param paddingLeft gap between RecyclerView left and left item left
|
||||
* @param paddingTop gap between RecyclerView top and top item top
|
||||
* @param paddingRight gap between RecyclerView right and right item right
|
||||
* @param paddingBottom gap between RecyclerView bottom and bottom item bottom
|
||||
*/
|
||||
public void setMargin(int margin, int paddingLeft, int paddingTop,
|
||||
int paddingRight, int paddingBottom) {
|
||||
int halfMargin = margin / 2;
|
||||
mMargin = halfMargin;
|
||||
mPaddingLeft = paddingLeft - halfMargin;
|
||||
mPaddingTop = paddingTop - halfMargin;
|
||||
mPaddingRight = paddingRight - halfMargin;
|
||||
mPaddingBottom = paddingBottom - halfMargin;
|
||||
}
|
||||
|
||||
public void applyPaddings(View view) {
|
||||
view.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SimpleHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public SimpleHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
|
||||
public abstract class SimpleSmoothScroller extends LinearSmoothScroller {
|
||||
|
||||
private final float mMillisecondsPerPx;
|
||||
|
||||
public SimpleSmoothScroller(Context context, float millisecondsPerInch) {
|
||||
super(context);
|
||||
mMillisecondsPerPx = millisecondsPerInch / context.getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
if (mMillisecondsPerPx <= 0) {
|
||||
return super.calculateTimeForScrolling(dx);
|
||||
} else {
|
||||
return (int) Math.ceil(Math.abs(dx) * mMillisecondsPerPx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.exception.ParseException;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class AppConfig {
|
||||
|
||||
public static final String APP_DIRNAME = "EhViewer";
|
||||
|
||||
private static final String DOWNLOAD = "download";
|
||||
private static final String TEMP = "temp";
|
||||
private static final String PARSE_ERROR = "parse_error";
|
||||
private static final String CRASH = "crash";
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static Context sContext;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getExternalAppDir() {
|
||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
File dir = sContext.getExternalFilesDir(null);
|
||||
return FileUtils.ensureDirectory(dir) ? dir : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* mkdirs and get
|
||||
*/
|
||||
@Nullable
|
||||
public static File getDirInExternalAppDir(String filename) {
|
||||
File appFolder = getExternalAppDir();
|
||||
if (appFolder != null) {
|
||||
File dir = new File(appFolder, filename);
|
||||
return FileUtils.ensureDirectory(dir) ? dir : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getFileInExternalAppDir(String filename) {
|
||||
File appFolder = getExternalAppDir();
|
||||
if (appFolder != null) {
|
||||
File file = new File(appFolder, filename);
|
||||
return FileUtils.ensureFile(file) ? file : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getDefaultDownloadDir() {
|
||||
return getDirInExternalAppDir(DOWNLOAD);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getExternalTempDir() {
|
||||
File dir = sContext.getExternalCacheDir();
|
||||
File file;
|
||||
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, TEMP))) {
|
||||
return file;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getExternalCopyTempDir() {
|
||||
File dir = sContext.getExternalCacheDir();
|
||||
File file;
|
||||
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, "copy"))) {
|
||||
return file;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getExternalParseErrorDir() {
|
||||
return getDirInExternalAppDir(PARSE_ERROR);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getExternalCrashDir() {
|
||||
return getDirInExternalAppDir(CRASH);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getTempDir() {
|
||||
File dir = sContext.getCacheDir();
|
||||
File file;
|
||||
if (null != dir && FileUtils.ensureDirectory(file = new File(dir, TEMP))) {
|
||||
return file;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File createTempFile() {
|
||||
return FileUtils.createTempFile(getTempDir(), null);
|
||||
}
|
||||
|
||||
public static void saveParseErrorBody(ParseException e) {
|
||||
File dir = getExternalParseErrorDir();
|
||||
if (null == dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = new File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".txt");
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new FileOutputStream(file);
|
||||
String message = e.getMessage();
|
||||
String body = e.getBody();
|
||||
if (null != message) {
|
||||
os.write(message.getBytes(StandardCharsets.UTF_8));
|
||||
os.write('\n');
|
||||
}
|
||||
if (null != body) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
os.flush();
|
||||
} catch (IOException e1) {
|
||||
// Ignore
|
||||
} finally {
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getFilesDir(String name) {
|
||||
File dir = sContext.getFilesDir();
|
||||
if (dir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
dir = new File(dir, name);
|
||||
if (dir.isDirectory() || dir.mkdirs()) {
|
||||
return dir;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.PackageUtils;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class Crash {
|
||||
private Crash() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String avoidNull(String str) {
|
||||
return null != str ? str : "null";
|
||||
}
|
||||
|
||||
private static void collectInfo(Context context, FileWriter fw) throws IOException {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
|
||||
if (pi != null) {
|
||||
String versionName = pi.versionName == null ? "null" : pi.versionName;
|
||||
String versionCode = String.valueOf(pi.versionCode);
|
||||
fw.write("======== PackageInfo ========\r\n");
|
||||
fw.write("PackageName=");
|
||||
fw.write(pi.packageName);
|
||||
fw.write("\r\n");
|
||||
fw.write("VersionName=");
|
||||
fw.write(versionName);
|
||||
fw.write("\r\n");
|
||||
fw.write("VersionCode=");
|
||||
fw.write(versionCode);
|
||||
fw.write("\r\n");
|
||||
String signature = PackageUtils.getSignature(context, pi.packageName);
|
||||
fw.write("Signature=");
|
||||
fw.write(null != signature ? signature : "null");
|
||||
fw.write("\r\n");
|
||||
fw.write("\r\n");
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
fw.write("======== PackageInfo ========\r\n");
|
||||
fw.write("Can't get package information\r\n");
|
||||
fw.write("\r\n");
|
||||
}
|
||||
|
||||
// Runtime
|
||||
String topActivityClazzName = "null";
|
||||
String topSceneClazzName = "null";
|
||||
try {
|
||||
Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity();
|
||||
if (null != topActivity) {
|
||||
topActivityClazzName = topActivity.getClass().getName();
|
||||
if (topActivity instanceof StageActivity) {
|
||||
Class<?> clazz = ((StageActivity) topActivity).getTopSceneClass();
|
||||
if (clazz != null) {
|
||||
topSceneClazzName = clazz.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Ignore
|
||||
}
|
||||
fw.write("======== Runtime ========\r\n");
|
||||
fw.write("TopActivity=");
|
||||
fw.write(avoidNull(topActivityClazzName));
|
||||
fw.write("\r\n");
|
||||
fw.write("TopScene=");
|
||||
fw.write(avoidNull(topSceneClazzName));
|
||||
fw.write("\r\n");
|
||||
fw.write("\r\n");
|
||||
|
||||
// Device info
|
||||
fw.write("======== DeviceInfo ========\r\n");
|
||||
fw.write("BOARD=");
|
||||
fw.write(Build.BOARD);
|
||||
fw.write("\r\n");
|
||||
fw.write("BOOTLOADER=");
|
||||
fw.write(Build.BOOTLOADER);
|
||||
fw.write("\r\n");
|
||||
fw.write("SUPPORTED_ABIS=");
|
||||
fw.write(Arrays.toString(Build.SUPPORTED_ABIS));
|
||||
fw.write("\r\n");
|
||||
fw.write("DEVICE=");
|
||||
fw.write(Build.DEVICE);
|
||||
fw.write("\r\n");
|
||||
fw.write("DISPLAY=");
|
||||
fw.write(Build.DISPLAY);
|
||||
fw.write("\r\n");
|
||||
fw.write("FINGERPRINT=");
|
||||
fw.write(Build.FINGERPRINT);
|
||||
fw.write("\r\n");
|
||||
fw.write("HARDWARE=");
|
||||
fw.write(Build.HARDWARE);
|
||||
fw.write("\r\n");
|
||||
fw.write("HOST=");
|
||||
fw.write(Build.HOST);
|
||||
fw.write("\r\n");
|
||||
fw.write("ID=");
|
||||
fw.write(Build.ID);
|
||||
fw.write("\r\n");
|
||||
fw.write("MANUFACTURER=");
|
||||
fw.write(Build.MANUFACTURER);
|
||||
fw.write("\r\n");
|
||||
fw.write("MODEL=");
|
||||
fw.write(Build.MODEL);
|
||||
fw.write("\r\n");
|
||||
fw.write("PRODUCT=");
|
||||
fw.write(Build.PRODUCT);
|
||||
fw.write("\r\n");
|
||||
fw.write("RADIO=");
|
||||
fw.write(Build.getRadioVersion());
|
||||
fw.write("\r\n");
|
||||
fw.write("TAGS=");
|
||||
fw.write(Build.TAGS);
|
||||
fw.write("\r\n");
|
||||
fw.write("TYPE=");
|
||||
fw.write(Build.TYPE);
|
||||
fw.write("\r\n");
|
||||
fw.write("USER=");
|
||||
fw.write(Build.USER);
|
||||
fw.write("\r\n");
|
||||
fw.write("CODENAME=");
|
||||
fw.write(Build.VERSION.CODENAME);
|
||||
fw.write("\r\n");
|
||||
fw.write("INCREMENTAL=");
|
||||
fw.write(Build.VERSION.INCREMENTAL);
|
||||
fw.write("\r\n");
|
||||
fw.write("RELEASE=");
|
||||
fw.write(Build.VERSION.RELEASE);
|
||||
fw.write("\r\n");
|
||||
fw.write("SDK=");
|
||||
fw.write(Integer.toString(Build.VERSION.SDK_INT));
|
||||
fw.write("\r\n");
|
||||
fw.write("MEMORY=");
|
||||
fw.write(
|
||||
FileUtils.humanReadableByteCount(OSUtils.getAppAllocatedMemory(), false));
|
||||
fw.write("\r\n");
|
||||
fw.write("MEMORY_NATIVE=");
|
||||
fw.write(FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));
|
||||
fw.write("\r\n");
|
||||
fw.write("MEMORY_MAX=");
|
||||
fw.write(FileUtils.humanReadableByteCount(OSUtils.getAppMaxMemory(), false));
|
||||
fw.write("\r\n");
|
||||
fw.write("MEMORY_TOTAL=");
|
||||
fw.write(FileUtils.humanReadableByteCount(OSUtils.getTotalMemory(), false));
|
||||
fw.write("\r\n");
|
||||
fw.write("\r\n");
|
||||
}
|
||||
|
||||
private static void getThrowableInfo(Throwable t, FileWriter fw) {
|
||||
PrintWriter printWriter = new PrintWriter(fw);
|
||||
t.printStackTrace(printWriter);
|
||||
Throwable cause = t.getCause();
|
||||
while (cause != null) {
|
||||
cause.printStackTrace(printWriter);
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveCrashLog(Context context, Throwable t) {
|
||||
File dir = AppConfig.getExternalCrashDir();
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String nowString = ReadableTime.getFilenamableTime(System.currentTimeMillis());
|
||||
String fileName = "crash-" + nowString + ".log";
|
||||
File file = new File(dir, fileName);
|
||||
|
||||
FileWriter fw = null;
|
||||
try {
|
||||
fw = new FileWriter(file);
|
||||
fw.write("TIME=");
|
||||
fw.write(nowString);
|
||||
fw.write("\r\n");
|
||||
fw.write("\r\n");
|
||||
collectInfo(context, fw);
|
||||
fw.write("======== CrashInfo ========\r\n");
|
||||
getThrowableInfo(t, fw);
|
||||
fw.write("\r\n");
|
||||
fw.flush();
|
||||
} catch (Exception e) {
|
||||
file.delete();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Debug;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.collection.LruCache;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.Native;
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhDns;
|
||||
import com.hippo.ehviewer.client.EhEngine;
|
||||
import com.hippo.ehviewer.client.EhRequestBuilder;
|
||||
import com.hippo.ehviewer.client.EhSSLSocketFactory;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhX509TrustManager;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.parser.EventPaneParser;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.BitmapUtils;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class EhApplication extends SceneApplication {
|
||||
|
||||
public static final boolean BETA = false;
|
||||
private static final String TAG = EhApplication.class.getSimpleName();
|
||||
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
|
||||
private static final boolean DEBUG_CONACO = false;
|
||||
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
|
||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = false;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
|
||||
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
|
||||
private final List<Activity> mActivityList = new ArrayList<>();
|
||||
private EhCookieStore mEhCookieStore;
|
||||
private EhClient mEhClient;
|
||||
private EhProxySelector mEhProxySelector;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Cache mOkHttpCache;
|
||||
private ImageBitmapHelper mImageBitmapHelper;
|
||||
private Conaco<ImageBitmap> mConaco;
|
||||
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
|
||||
private SimpleDiskCache mSpiderInfoCache;
|
||||
private DownloadManager mDownloadManager;
|
||||
private Hosts mHosts;
|
||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
||||
private boolean initialized = false;
|
||||
|
||||
public static EhApplication getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhCookieStore == null) {
|
||||
application.mEhCookieStore = new EhCookieStore(context);
|
||||
}
|
||||
return application.mEhCookieStore;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhClient getEhClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhClient == null) {
|
||||
application.mEhClient = new EhClient(application);
|
||||
}
|
||||
return application.mEhClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhProxySelector getEhProxySelector(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhProxySelector == null) {
|
||||
application.mEhProxySelector = new EhProxySelector();
|
||||
}
|
||||
return application.mEhProxySelector;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpClient == null) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.callTimeout(10, TimeUnit.SECONDS)
|
||||
.cookieJar(getEhCookieStore(application))
|
||||
.cache(getOkHttpCache(application))
|
||||
.dns(new EhDns(application))
|
||||
.addNetworkInterceptor(chain -> {
|
||||
try {
|
||||
return chain.proceed(chain.request());
|
||||
} catch (NullPointerException e) {
|
||||
// crash on meizu devices due to old Android version
|
||||
// https://github.com/square/okhttp/issues/3301#issuecomment-348415095
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
})
|
||||
.proxySelector(getEhProxySelector(application));
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), new EhX509TrustManager());
|
||||
}
|
||||
application.mOkHttpClient = builder.build();
|
||||
}
|
||||
return application.mOkHttpClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Cache getOkHttpCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpCache == null) {
|
||||
application.mOkHttpCache = new Cache(new File(application.getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
|
||||
}
|
||||
return application.mOkHttpCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mImageBitmapHelper == null) {
|
||||
application.mImageBitmapHelper = new ImageBitmapHelper();
|
||||
}
|
||||
return application.mImageBitmapHelper;
|
||||
}
|
||||
|
||||
private static int getMemoryCacheMaxSize() {
|
||||
return Math.min(20 * 1024 * 1024, (int) OSUtils.getAppMaxMemory());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mConaco == null) {
|
||||
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
|
||||
builder.hasMemoryCache = true;
|
||||
builder.memoryCacheMaxSize = getMemoryCacheMaxSize();
|
||||
builder.hasDiskCache = true;
|
||||
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
|
||||
builder.diskCacheMaxSize = 320 * 1024 * 1024; // 320MB
|
||||
builder.okHttpClient = getOkHttpClient(context);
|
||||
builder.objectHelper = getImageBitmapHelper(context);
|
||||
builder.debug = DEBUG_CONACO;
|
||||
application.mConaco = builder.build();
|
||||
}
|
||||
return application.mConaco;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LruCache<Long, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mGalleryDetailCache == null) {
|
||||
// Max size 25, 3 min timeout
|
||||
application.mGalleryDetailCache = new LruCache<>(25);
|
||||
getFavouriteStatusRouter().addListener((gid, slot) -> {
|
||||
GalleryDetail gd = application.mGalleryDetailCache.get(gid);
|
||||
if (gd != null) {
|
||||
gd.favoriteSlot = slot;
|
||||
}
|
||||
});
|
||||
}
|
||||
return application.mGalleryDetailCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SimpleDiskCache getSpiderInfoCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (null == application.mSpiderInfoCache) {
|
||||
application.mSpiderInfoCache = new SimpleDiskCache(
|
||||
new File(context.getCacheDir(), "spider_info"), 20 * 1024 * 1024); // 20M
|
||||
}
|
||||
return application.mSpiderInfoCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager() {
|
||||
return getDownloadManager(instance);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mDownloadManager == null) {
|
||||
application.mDownloadManager = new DownloadManager(application);
|
||||
}
|
||||
return application.mDownloadManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Hosts getHosts(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mHosts == null) {
|
||||
application.mHosts = new Hosts(application, "hosts.db");
|
||||
}
|
||||
return application.mHosts;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter() {
|
||||
return getFavouriteStatusRouter(getInstance());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mFavouriteStatusRouter == null) {
|
||||
application.mFavouriteStatusRouter = new FavouriteStatusRouter();
|
||||
}
|
||||
return application.mFavouriteStatusRouter;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
|
||||
instance = this;
|
||||
|
||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
try {
|
||||
// Always save crash file if onCreate() is not done
|
||||
if (!initialized || Settings.getSaveCrashLog()) {
|
||||
Crash.saveCrashLog(instance, e);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
handler.uncaughtException(t, e);
|
||||
}
|
||||
});
|
||||
|
||||
super.onCreate();
|
||||
|
||||
Native.initialize();
|
||||
ClipboardUtil.initialize(this);
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
Settings.initialize(this);
|
||||
ReadableTime.initialize(this);
|
||||
AppConfig.initialize(this);
|
||||
SpiderDen.initialize(this);
|
||||
EhDB.initialize(this);
|
||||
EhEngine.initialize();
|
||||
BitmapUtils.initialize(this);
|
||||
|
||||
if (EhDB.needMerge()) {
|
||||
EhDB.mergeOldDB(this);
|
||||
}
|
||||
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
||||
|
||||
// Do io tasks in new thread
|
||||
//noinspection deprecation
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
// Check no media file
|
||||
try {
|
||||
UniFile downloadLocation = Settings.getDownloadLocation();
|
||||
if (Settings.getMediaScan()) {
|
||||
CommonOperations.removeNoMediaFile(downloadLocation);
|
||||
} else {
|
||||
CommonOperations.ensureNoMediaFile(downloadLocation);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
// Clear temp files
|
||||
try {
|
||||
clearTempDir();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
|
||||
|
||||
// Check app update
|
||||
update();
|
||||
|
||||
// Update version code
|
||||
try {
|
||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
Settings.putVersionCode(pi.versionCode);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
|
||||
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
|
||||
debugPrint();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
theDawnOfNewDay();
|
||||
}
|
||||
|
||||
private void theDawnOfNewDay() {
|
||||
if (!Settings.getRequestNews()) {
|
||||
return;
|
||||
}
|
||||
EhCookieStore store = getEhCookieStore(this);
|
||||
HttpUrl eh = HttpUrl.get(EhUrl.HOST_E);
|
||||
|
||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(eh, EhCookieStore.KEY_IPD_PASS_HASH)) {
|
||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
||||
String referer = EhUrl.REFERER_E;
|
||||
Request request = new EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build();
|
||||
Call call = getOkHttpClient(this).newCall(request);
|
||||
try {
|
||||
Response response = call.execute();
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
return;
|
||||
}
|
||||
String body = responseBody.string();
|
||||
String html = EventPaneParser.parse(body);
|
||||
if (html != null) {
|
||||
showEventPane(html);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void showEventPane(String html) {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
dialog.setOnShowListener(d -> {
|
||||
final View messageView = dialog.findViewById(android.R.id.message);
|
||||
if (messageView instanceof TextView) {
|
||||
((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
});
|
||||
try {
|
||||
dialog.show();
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void clearTempDir() {
|
||||
File dir = AppConfig.getTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
dir = AppConfig.getExternalTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
int version = Settings.getVersionCode();
|
||||
if (version < 52) {
|
||||
Settings.putGuideGallery(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearMemoryCache() {
|
||||
if (null != mConaco) {
|
||||
mConaco.getBeerBelly().clearMemory();
|
||||
}
|
||||
if (null != mGalleryDetailCache) {
|
||||
mGalleryDetailCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
clearMemoryCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void debugPrint() {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY) {
|
||||
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
|
||||
Debug.getNativeHeapAllocatedSize(), false));
|
||||
}
|
||||
if (DEBUG_PRINT_IMAGE_COUNT) {
|
||||
Log.i(TAG, "Image count: " + Image.getImageCount());
|
||||
}
|
||||
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
|
||||
public int putGlobalStuff(@NonNull Object o) {
|
||||
int id = mIdGenerator.nextId();
|
||||
mGlobalStuffMap.put(id, o);
|
||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean containGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.containsKey(id);
|
||||
}
|
||||
|
||||
public Object getGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.get(id);
|
||||
}
|
||||
|
||||
public Object removeGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.remove(id);
|
||||
}
|
||||
|
||||
public void removeGlobalStuff(Object o) {
|
||||
mGlobalStuffMap.values().removeAll(Collections.singleton(o));
|
||||
}
|
||||
|
||||
public void registerActivity(Activity activity) {
|
||||
mActivityList.add(activity);
|
||||
}
|
||||
|
||||
public void unregisterActivity(Activity activity) {
|
||||
mActivityList.remove(activity);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Activity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startService(Intent service) {
|
||||
try {
|
||||
return super.startService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
||||
try {
|
||||
return super.bindService(service, conn, flags);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public void unbindService(ServiceConnection conn) {
|
||||
try {
|
||||
super.unbindService(conn);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startForegroundService(Intent service) {
|
||||
try {
|
||||
return super.startForegroundService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentCallbacks2
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.LruCache
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import coil.ImageLoaderFactory
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.util.DebugLogger
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.coil.MergeInterceptor
|
||||
import com.hippo.ehviewer.coil.installCronetHttpUriFetcher
|
||||
import com.hippo.ehviewer.dailycheck.checkDawn
|
||||
import com.hippo.ehviewer.dao.SearchDatabase
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ktbuilder.cache
|
||||
import com.hippo.ehviewer.ktbuilder.chunker
|
||||
import com.hippo.ehviewer.ktbuilder.diskCache
|
||||
import com.hippo.ehviewer.ktbuilder.httpClient
|
||||
import com.hippo.ehviewer.ktbuilder.imageLoader
|
||||
import com.hippo.ehviewer.legacy.cleanObsoleteCache
|
||||
import com.hippo.ehviewer.legacy.migrateCookies
|
||||
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
|
||||
import com.hippo.ehviewer.ui.lockObserver
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.Crash
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.util.isCronetSupported
|
||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.kt.unreachable
|
||||
import okhttp3.AsyncDns
|
||||
import okhttp3.android.AndroidAsyncDns
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import splitties.arch.room.roomDb
|
||||
import splitties.init.appCtx
|
||||
|
||||
private val lifecycle = ProcessLifecycleOwner.get().lifecycle
|
||||
private val lifecycleScope = lifecycle.coroutineScope
|
||||
|
||||
class EhApplication : Application(), ImageLoaderFactory {
|
||||
override fun onCreate() {
|
||||
lifecycle.addObserver(lockObserver)
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
try {
|
||||
if (Settings.saveCrashLog) {
|
||||
Crash.saveCrashLog(e)
|
||||
}
|
||||
} catch (ignored: Throwable) {
|
||||
}
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super.onCreate()
|
||||
System.loadLibrary("ehviewer")
|
||||
System.loadLibrary("ehviewer_rust")
|
||||
ReadableTime.initialize(this)
|
||||
lifecycleScope.launchIO {
|
||||
val theme = Settings.theme
|
||||
withUIContext {
|
||||
AppCompatDelegate.setDefaultNightMode(theme)
|
||||
}
|
||||
}
|
||||
lifecycleScope.launchIO {
|
||||
launchIO {
|
||||
migrateCookies()
|
||||
}
|
||||
launchIO {
|
||||
EhTagDatabase.update()
|
||||
}
|
||||
launchIO {
|
||||
EhDB
|
||||
}
|
||||
launchIO {
|
||||
DownloadManager.isIdle
|
||||
}
|
||||
launchIO {
|
||||
cleanupDownload()
|
||||
}
|
||||
if (Settings.requestNews) {
|
||||
launchIO {
|
||||
checkDawn()
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanObsoleteCache(this)
|
||||
}
|
||||
|
||||
private suspend fun cleanupDownload() {
|
||||
runCatching {
|
||||
keepNoMediaFileStatus()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
runCatching {
|
||||
clearTempDir()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearTempDir() {
|
||||
var dir = AppConfig.tempDir
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
dir = AppConfig.externalTempDir
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
galleryDetailCache.evictAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun newImageLoader() = imageLoader {
|
||||
components {
|
||||
if (isCronetSupported) {
|
||||
callFactory { unreachable() }
|
||||
installCronetHttpUriFetcher()
|
||||
} else {
|
||||
callFactory(baseOkHttpClient)
|
||||
}
|
||||
add { result, options, _ -> ImageDecoderDecoder(result.source, options, false) }
|
||||
add(MergeInterceptor)
|
||||
}
|
||||
diskCache(imageCache)
|
||||
crossfade(300)
|
||||
error(R.drawable.image_failed)
|
||||
if (BuildConfig.DEBUG) logger(DebugLogger())
|
||||
respectCacheHeaders(false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val baseOkHttpClient by lazy {
|
||||
httpClient {
|
||||
cookieJar(EhCookieStore)
|
||||
if (isAtLeastQ) {
|
||||
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
|
||||
}
|
||||
chunker { alwaysReadResponseBody(false) }
|
||||
addInterceptor(CloudflareInterceptor(appCtx))
|
||||
}
|
||||
}
|
||||
|
||||
val noRedirectOkHttpClient by lazy {
|
||||
httpClient(baseOkHttpClient) {
|
||||
followRedirects(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Never use this okhttp client to download large blobs!!!
|
||||
val okHttpClient by lazy {
|
||||
httpClient(baseOkHttpClient) {
|
||||
cache(
|
||||
appCtx.cacheDir.toOkioPath() / "http_cache",
|
||||
20L * 1024L * 1024L,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val galleryDetailCache by lazy {
|
||||
LruCache<Long, GalleryDetail>(25).also {
|
||||
lifecycleScope.launch {
|
||||
FavouriteStatusRouter.globalFlow.collect { (gid, slot) -> it[gid]?.favoriteSlot = slot }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val imageCache by lazy {
|
||||
diskCache {
|
||||
directory(appCtx.cacheDir.toOkioPath() / "image_cache")
|
||||
maxSizeBytes(Settings.readCacheSize.coerceIn(320, 5120).toLong() * 1024 * 1024)
|
||||
}
|
||||
}
|
||||
|
||||
val searchDatabase by lazy { roomDb<SearchDatabase>("search_database.db") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,841 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.dao.DaoMaster;
|
||||
import com.hippo.ehviewer.dao.DaoSession;
|
||||
import com.hippo.ehviewer.dao.DownloadDirname;
|
||||
import com.hippo.ehviewer.dao.DownloadDirnameDao;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.DownloadLabelDao;
|
||||
import com.hippo.ehviewer.dao.DownloadsDao;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.dao.FilterDao;
|
||||
import com.hippo.ehviewer.dao.HistoryDao;
|
||||
import com.hippo.ehviewer.dao.HistoryInfo;
|
||||
import com.hippo.ehviewer.dao.LocalFavoriteInfo;
|
||||
import com.hippo.ehviewer.dao.LocalFavoritesDao;
|
||||
import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.dao.QuickSearchDao;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.SqlUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import org.greenrobot.greendao.AbstractDao;
|
||||
import org.greenrobot.greendao.query.CloseableListIterator;
|
||||
import org.greenrobot.greendao.query.LazyList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EhDB {
|
||||
|
||||
private static final String TAG = EhDB.class.getSimpleName();
|
||||
|
||||
private static final int MAX_HISTORY_COUNT = 200;
|
||||
|
||||
private static DaoSession sDaoSession;
|
||||
|
||||
private static boolean sHasOldDB;
|
||||
private static boolean sNewDB;
|
||||
|
||||
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
|
||||
switch (oldVersion) {
|
||||
case 1: // 1 to 2, add FILTER
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS \"FILTER\" (" + //
|
||||
"\"_id\" INTEGER PRIMARY KEY ," + // 0: id
|
||||
"\"MODE\" INTEGER NOT NULL ," + // 1: mode
|
||||
"\"TEXT\" TEXT," + // 2: text
|
||||
"\"ENABLE\" INTEGER);"); // 3: enable
|
||||
case 2: // 2 to 3, add ENABLE column to table FILTER
|
||||
db.execSQL("CREATE TABLE " + "\"FILTER2\" (" +
|
||||
"\"_id\" INTEGER PRIMARY KEY ," +
|
||||
"\"MODE\" INTEGER NOT NULL ," +
|
||||
"\"TEXT\" TEXT," +
|
||||
"\"ENABLE\" INTEGER);");
|
||||
db.execSQL("INSERT INTO \"FILTER2\" (" +
|
||||
"_id, MODE, TEXT, ENABLE)" +
|
||||
"SELECT _id, MODE, TEXT, 1 FROM FILTER;");
|
||||
db.execSQL("DROP TABLE FILTER");
|
||||
db.execSQL("ALTER TABLE FILTER2 RENAME TO FILTER");
|
||||
case 3: // 3 to 4, add PAGE_FROM and PAGE_TO column to QUICK_SEARCH
|
||||
db.execSQL("CREATE TABLE " + "\"QUICK_SEARCH2\" (" +
|
||||
"\"_id\" INTEGER PRIMARY KEY ," +
|
||||
"\"NAME\" TEXT," +
|
||||
"\"MODE\" INTEGER NOT NULL ," +
|
||||
"\"CATEGORY\" INTEGER NOT NULL ," +
|
||||
"\"KEYWORD\" TEXT," +
|
||||
"\"ADVANCE_SEARCH\" INTEGER NOT NULL ," +
|
||||
"\"MIN_RATING\" INTEGER NOT NULL ," +
|
||||
"\"PAGE_FROM\" INTEGER NOT NULL ," +
|
||||
"\"PAGE_TO\" INTEGER NOT NULL ," +
|
||||
"\"TIME\" INTEGER NOT NULL );");
|
||||
db.execSQL("INSERT INTO \"QUICK_SEARCH2\" (" +
|
||||
"_id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, PAGE_FROM, PAGE_TO, TIME)" +
|
||||
"SELECT _id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, -1, -1, TIME FROM QUICK_SEARCH;");
|
||||
db.execSQL("DROP TABLE QUICK_SEARCH");
|
||||
db.execSQL("ALTER TABLE QUICK_SEARCH2 RENAME TO QUICK_SEARCH");
|
||||
}
|
||||
}
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sHasOldDB = context.getDatabasePath("data").exists();
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(
|
||||
context.getApplicationContext(), "eh.db", null);
|
||||
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
|
||||
sDaoSession = daoMaster.newSession();
|
||||
}
|
||||
|
||||
public static boolean needMerge() {
|
||||
return sNewDB && sHasOldDB;
|
||||
}
|
||||
|
||||
public static void mergeOldDB(Context context) {
|
||||
sNewDB = false;
|
||||
|
||||
OldDBHelper oldDBHelper = new OldDBHelper(context);
|
||||
SQLiteDatabase oldDB;
|
||||
try {
|
||||
oldDB = oldDBHelper.getReadableDatabase();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get GalleryInfo list
|
||||
SparseJLArray<GalleryInfo> map = new SparseJLArray<>();
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_GALLERY, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
GalleryInfo gi = new GalleryInfo();
|
||||
gi.gid = cursor.getInt(0);
|
||||
gi.token = cursor.getString(1);
|
||||
gi.title = cursor.getString(2);
|
||||
gi.posted = cursor.getString(3);
|
||||
gi.category = cursor.getInt(4);
|
||||
gi.thumb = cursor.getString(5);
|
||||
gi.uploader = cursor.getString(6);
|
||||
try {
|
||||
// In 0.6.x version, NaN is stored
|
||||
gi.rating = cursor.getFloat(7);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
gi.rating = -1.0f;
|
||||
}
|
||||
|
||||
map.put(gi.gid, gi);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge local favorites
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_LOCAL_FAVOURITE, null);
|
||||
if (cursor != null) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalFavoriteInfo info = new LocalFavoriteInfo(gi);
|
||||
info.setTime(i);
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
|
||||
// Merge quick search
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_TAG, null);
|
||||
if (cursor != null) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
QuickSearch quickSearch = new QuickSearch();
|
||||
|
||||
int mode = cursor.getInt(2);
|
||||
String search = cursor.getString(4);
|
||||
String tag = cursor.getString(7);
|
||||
if (mode == ListUrlBuilder.MODE_UPLOADER && search != null &&
|
||||
search.startsWith("uploader:")) {
|
||||
search = search.substring("uploader:".length());
|
||||
}
|
||||
|
||||
quickSearch.setTime(cursor.getInt(0));
|
||||
quickSearch.setName(cursor.getString(1));
|
||||
quickSearch.setMode(mode);
|
||||
quickSearch.setCategory(cursor.getInt(3));
|
||||
quickSearch.setKeyword(mode == ListUrlBuilder.MODE_TAG ? tag : search);
|
||||
quickSearch.setAdvanceSearch(cursor.getInt(5));
|
||||
quickSearch.setMinRating(cursor.getInt(6));
|
||||
|
||||
dao.insert(quickSearch);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge download info
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_DOWNLOAD, null);
|
||||
if (cursor != null) {
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
DownloadInfo info = new DownloadInfo(gi);
|
||||
int state = cursor.getInt(2);
|
||||
int legacy = cursor.getInt(3);
|
||||
if (state == DownloadInfo.STATE_FINISH && legacy > 0) {
|
||||
state = DownloadInfo.STATE_FAILED;
|
||||
}
|
||||
info.setState(state);
|
||||
info.setLegacy(legacy);
|
||||
if (cursor.getColumnCount() == 5) {
|
||||
info.setTime(cursor.getLong(4));
|
||||
} else {
|
||||
info.setTime(i);
|
||||
}
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
// Merge history info
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_HISTORY, null);
|
||||
if (cursor != null) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
HistoryInfo info = new HistoryInfo(gi);
|
||||
info.setMode(cursor.getInt(1));
|
||||
info.setTime(cursor.getLong(2));
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
oldDBHelper.close();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
List<DownloadInfo> list = dao.queryBuilder().orderDesc(DownloadsDao.Properties.Time).list();
|
||||
// Fix state
|
||||
for (DownloadInfo info : list) {
|
||||
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
info.state = DownloadInfo.STATE_NONE;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// Insert or update
|
||||
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (null != dao.load(downloadInfo.gid)) {
|
||||
// Update
|
||||
dao.update(downloadInfo);
|
||||
} else {
|
||||
// Insert
|
||||
dao.insert(downloadInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadInfo(long gid) {
|
||||
sDaoSession.getDownloadsDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static synchronized String getDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) {
|
||||
return raw.getDirname();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update
|
||||
*/
|
||||
public static synchronized void putDownloadDirname(long gid, String dirname) {
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) { // Update
|
||||
raw.setDirname(dirname);
|
||||
dao.update(raw);
|
||||
} else { // Insert
|
||||
raw = new DownloadDirname();
|
||||
raw.setGid(gid);
|
||||
raw.setDirname(dirname);
|
||||
dao.insert(raw);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void clearDownloadDirname() {
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized List<DownloadLabel> getAllDownloadLabelList() {
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
return dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized DownloadLabel addDownloadLabel(String label) {
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
DownloadLabel raw = new DownloadLabel();
|
||||
raw.setLabel(label);
|
||||
raw.setTime(System.currentTimeMillis());
|
||||
raw.setId(dao.insert(raw));
|
||||
return raw;
|
||||
}
|
||||
|
||||
public static synchronized DownloadLabel addDownloadLabel(DownloadLabel raw) {
|
||||
// Reset id
|
||||
raw.setId(null);
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
raw.setId(dao.insert(raw));
|
||||
return raw;
|
||||
}
|
||||
|
||||
public static synchronized void updateDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.update(raw);
|
||||
}
|
||||
|
||||
public static synchronized void moveDownloadLabel(int fromPosition, int toPosition) {
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean reverse = fromPosition > toPosition;
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
List<DownloadLabel> list = dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
int end = reverse ? 0 : limit - 1;
|
||||
long toTime = list.get(end).getTime();
|
||||
for (int i = end; reverse ? i < start : i > start; i += step) {
|
||||
list.get(i).setTime(list.get(i + step).getTime());
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.delete(raw);
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> getAllLocalFavorites() {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||
query = SqlUtils.sqlEscapeString("%" + query + "%");
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time)
|
||||
.where(LocalFavoritesDao.Properties.Title.like(query)).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long gid) {
|
||||
sDaoSession.getLocalFavoritesDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long[] gidArray) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
for (long gid : gidArray) {
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized boolean containLocalFavorites(long gid) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
return null != dao.load(gid);
|
||||
}
|
||||
|
||||
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (null == dao.load(galleryInfo.gid)) {
|
||||
LocalFavoriteInfo info;
|
||||
if (galleryInfo instanceof LocalFavoriteInfo) {
|
||||
info = (LocalFavoriteInfo) galleryInfo;
|
||||
} else {
|
||||
info = new LocalFavoriteInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
}
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void putLocalFavorites(List<GalleryInfo> galleryInfoList) {
|
||||
for (GalleryInfo gi : galleryInfoList) {
|
||||
putLocalFavorites(gi);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized List<QuickSearch> getAllQuickSearch() {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
return dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
quickSearch.id = null;
|
||||
quickSearch.time = System.currentTimeMillis();
|
||||
quickSearch.id = dao.insert(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
dao.insert(quickSearch);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.update(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.delete(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void moveQuickSearch(int fromPosition, int toPosition) {
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean reverse = fromPosition > toPosition;
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
List<QuickSearch> list = dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
int end = reverse ? 0 : limit - 1;
|
||||
long toTime = list.get(end).getTime();
|
||||
for (int i = end; reverse ? i < start : i > start; i += step) {
|
||||
list.get(i).setTime(list.get(i + step).getTime());
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized LazyList<HistoryInfo> getHistoryLazyList() {
|
||||
return sDaoSession.getHistoryDao().queryBuilder().orderDesc(HistoryDao.Properties.Time).listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
if (null != info) {
|
||||
// Update time
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.update(info);
|
||||
} else {
|
||||
// New history
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
for (HistoryInfo info : historyInfoList) {
|
||||
if (null == dao.load(info.gid)) {
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void deleteHistoryInfo(HistoryInfo info) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.delete(info);
|
||||
}
|
||||
|
||||
public static synchronized void clearHistoryInfo() {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
public static synchronized List<Filter> getAllFilter() {
|
||||
return sDaoSession.getFilterDao().queryBuilder().list();
|
||||
}
|
||||
|
||||
public static synchronized boolean addFilter(Filter filter) {
|
||||
Filter existFilter;
|
||||
try {
|
||||
existFilter = sDaoSession.getFilterDao().queryBuilder().where(FilterDao.Properties.Text.eq(filter.text), FilterDao.Properties.Mode.eq(filter.mode)).unique();
|
||||
} catch (Exception e) {
|
||||
existFilter = null;
|
||||
}
|
||||
if (existFilter == null) {
|
||||
filter.setId(null);
|
||||
filter.setId(sDaoSession.getFilterDao().insert(filter));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void deleteFilter(Filter filter) {
|
||||
sDaoSession.getFilterDao().delete(filter);
|
||||
}
|
||||
|
||||
public static synchronized void triggerFilter(Filter filter) {
|
||||
filter.setEnable(!filter.enable);
|
||||
sDaoSession.getFilterDao().update(filter);
|
||||
}
|
||||
|
||||
private static <T> boolean copyDao(AbstractDao<T, ?> from, AbstractDao<T, ?> to) {
|
||||
try (CloseableListIterator<T> iterator = from.queryBuilder().listIterator()) {
|
||||
while (iterator.hasNext()) {
|
||||
to.insert(iterator.next());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||
final String ehExportName = "eh.export.db";
|
||||
|
||||
// Delete old export db
|
||||
context.deleteDatabase(ehExportName);
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(context.getApplicationContext(), ehExportName, null);
|
||||
|
||||
try {
|
||||
// Copy data to a export db
|
||||
try (SQLiteDatabase db = helper.getWritableDatabase()) {
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession exportSession = daoMaster.newSession();
|
||||
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getBookmarksDao(), exportSession.getBookmarksDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy export db to data dir
|
||||
File dbFile = context.getDatabasePath(ehExportName);
|
||||
if (dbFile == null || !dbFile.isFile()) {
|
||||
return false;
|
||||
}
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
is = new FileInputStream(dbFile);
|
||||
os = context.getContentResolver().openOutputStream(uri);
|
||||
IOUtils.copy(is, os);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
// Delete failed file
|
||||
return false;
|
||||
} finally {
|
||||
context.deleteDatabase(ehExportName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return error string, null for no error
|
||||
*/
|
||||
public static synchronized String importDB(Context context, Uri uri) {
|
||||
try {
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
File file = File.createTempFile("importDatabase", "");
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
byte[] buff = new byte[1024];
|
||||
int read;
|
||||
if (inputStream != null) {
|
||||
while ((read = inputStream.read(buff, 0, buff.length)) > 0) {
|
||||
outputStream.write(buff, 0, read);
|
||||
}
|
||||
} else {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(
|
||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
int newVersion = DaoMaster.SCHEMA_VERSION;
|
||||
int oldVersion = db.getVersion();
|
||||
if (oldVersion < newVersion) {
|
||||
upgradeDB(db, oldVersion);
|
||||
db.setVersion(newVersion);
|
||||
} else if (oldVersion > newVersion) {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession session = daoMaster.newSession();
|
||||
|
||||
// Downloads
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
List<DownloadInfo> downloadInfoList = session.getDownloadsDao().queryBuilder().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
|
||||
// Download label
|
||||
List<DownloadLabel> downloadLabelList = session.getDownloadLabelDao().queryBuilder().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
|
||||
// Download dirname
|
||||
List<DownloadDirname> downloadDirnameList = session.getDownloadDirnameDao().queryBuilder().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
|
||||
// History
|
||||
List<HistoryInfo> historyInfoList = session.getHistoryDao().queryBuilder().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
|
||||
// QuickSearch
|
||||
List<QuickSearch> quickSearchList = session.getQuickSearchDao().queryBuilder().list();
|
||||
List<QuickSearch> currentQuickSearchList = sDaoSession.getQuickSearchDao().queryBuilder().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
|
||||
// LocalFavorites
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = session.getLocalFavoritesDao().queryBuilder().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
|
||||
// Bookmarks
|
||||
// TODO
|
||||
|
||||
// Filter
|
||||
List<Filter> filterList = session.getFilterDao().queryBuilder().list();
|
||||
List<Filter> currentFilterList = sDaoSession.getFilterDao().queryBuilder().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DBOpenHelper extends DaoMaster.OpenHelper {
|
||||
|
||||
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
|
||||
super(context, name, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
super.onCreate(db);
|
||||
sNewDB = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
upgradeDB(db, oldVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OldDBHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DB_NAME = "data";
|
||||
private static final int VERSION = 4;
|
||||
|
||||
private static final String TABLE_GALLERY = "gallery";
|
||||
private static final String TABLE_LOCAL_FAVOURITE = "local_favourite";
|
||||
private static final String TABLE_TAG = "tag";
|
||||
private static final String TABLE_DOWNLOAD = "download";
|
||||
private static final String TABLE_HISTORY = "history";
|
||||
|
||||
public OldDBHelper(Context context) {
|
||||
super(context, DB_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
|
||||
import arrow.fx.coroutines.release
|
||||
import arrow.fx.coroutines.resource
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadDirname
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.DownloadLabel
|
||||
import com.hippo.ehviewer.dao.EhDatabase
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.dao.LocalFavoriteInfo
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import splitties.arch.room.roomDb
|
||||
|
||||
object EhDB {
|
||||
private val db = roomDb<EhDatabase>("eh.db")
|
||||
|
||||
private suspend fun putGalleryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
db.galleryDao().upsert(galleryInfo)
|
||||
}
|
||||
|
||||
private suspend fun deleteGalleryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
runCatching { db.galleryDao().delete(galleryInfo) }
|
||||
}
|
||||
|
||||
suspend fun getAllDownloadInfo() = db.downloadsDao().joinList().onEach {
|
||||
if (it.state == DownloadInfo.STATE_WAIT || it.state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
it.state = DownloadInfo.STATE_NONE
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateDownloadInfo(downloadInfo: List<DownloadInfo>) {
|
||||
val dao = db.downloadsDao()
|
||||
dao.update(downloadInfo.map(DownloadInfo::downloadInfo))
|
||||
}
|
||||
|
||||
suspend fun putDownloadInfo(downloadInfo: DownloadInfo) {
|
||||
putGalleryInfo(downloadInfo.galleryInfo)
|
||||
db.downloadsDao().upsert(downloadInfo.downloadInfo)
|
||||
}
|
||||
|
||||
suspend fun removeDownloadInfo(downloadInfo: DownloadInfo) {
|
||||
val dao = db.downloadsDao()
|
||||
dao.delete(downloadInfo.downloadInfo)
|
||||
dao.fill(downloadInfo.position)
|
||||
deleteGalleryInfo(downloadInfo.galleryInfo)
|
||||
}
|
||||
|
||||
suspend fun removeDownloadInfo(downloadInfo: List<DownloadInfo>) {
|
||||
val dao = db.downloadsDao()
|
||||
downloadInfo.forEach {
|
||||
dao.delete(it.downloadInfo)
|
||||
deleteGalleryInfo(it.galleryInfo)
|
||||
}
|
||||
dao.fill(downloadInfo.first().position, downloadInfo.size)
|
||||
}
|
||||
|
||||
suspend fun getDownloadDirname(gid: Long): String? {
|
||||
val dao = db.downloadDirnameDao()
|
||||
val raw = dao.load(gid)
|
||||
return raw?.dirname
|
||||
}
|
||||
|
||||
suspend fun putDownloadDirname(gid: Long, dirname: String) {
|
||||
val dao = db.downloadDirnameDao()
|
||||
dao.upsert(DownloadDirname(gid, dirname))
|
||||
}
|
||||
|
||||
suspend fun removeDownloadDirname(gid: Long) {
|
||||
val dao = db.downloadDirnameDao()
|
||||
dao.deleteByKey(gid)
|
||||
}
|
||||
|
||||
private suspend fun importDownloadDirname(downloadDirnameList: List<DownloadDirname>) {
|
||||
val dao = db.downloadDirnameDao()
|
||||
dao.insertOrIgnore(downloadDirnameList)
|
||||
}
|
||||
|
||||
suspend fun getAllDownloadLabelList() = db.downloadLabelDao().list()
|
||||
|
||||
suspend fun addDownloadLabel(raw: DownloadLabel): DownloadLabel {
|
||||
// Reset id
|
||||
raw.id = null
|
||||
val dao = db.downloadLabelDao()
|
||||
raw.id = dao.insert(raw)
|
||||
return raw
|
||||
}
|
||||
|
||||
suspend fun updateDownloadLabel(raw: DownloadLabel) {
|
||||
val dao = db.downloadLabelDao()
|
||||
dao.update(raw)
|
||||
}
|
||||
|
||||
suspend fun updateDownloadLabel(downloadLabels: List<DownloadLabel>) {
|
||||
val dao = db.downloadLabelDao()
|
||||
dao.update(downloadLabels)
|
||||
}
|
||||
|
||||
suspend fun removeDownloadLabel(raw: DownloadLabel) {
|
||||
val dao = db.downloadLabelDao()
|
||||
dao.delete(raw)
|
||||
dao.fill(raw.position)
|
||||
}
|
||||
|
||||
suspend fun removeLocalFavorites(galleryInfo: BaseGalleryInfo) {
|
||||
db.localFavoritesDao().deleteByKey(galleryInfo.gid)
|
||||
deleteGalleryInfo(galleryInfo)
|
||||
}
|
||||
|
||||
suspend fun removeLocalFavorites(galleryInfoList: List<BaseGalleryInfo>) {
|
||||
galleryInfoList.forEach {
|
||||
removeLocalFavorites(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun containLocalFavorites(gid: Long): Boolean {
|
||||
val dao = db.localFavoritesDao()
|
||||
return dao.contains(gid)
|
||||
}
|
||||
|
||||
suspend fun putLocalFavorites(galleryInfo: BaseGalleryInfo) {
|
||||
putGalleryInfo(galleryInfo)
|
||||
db.localFavoritesDao().upsert(LocalFavoriteInfo(galleryInfo.gid))
|
||||
}
|
||||
|
||||
suspend fun putLocalFavorites(galleryInfoList: List<BaseGalleryInfo>) {
|
||||
galleryInfoList.forEach {
|
||||
putLocalFavorites(it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun importLocalFavorites(localFavorites: List<LocalFavoriteInfo>) {
|
||||
db.localFavoritesDao().insertOrIgnore(localFavorites)
|
||||
}
|
||||
|
||||
suspend fun getAllQuickSearch() = db.quickSearchDao().list()
|
||||
|
||||
suspend fun insertQuickSearch(quickSearch: QuickSearch) {
|
||||
val dao = db.quickSearchDao()
|
||||
quickSearch.id = dao.insert(quickSearch)
|
||||
}
|
||||
|
||||
private suspend fun importQuickSearch(quickSearchList: List<QuickSearch>) {
|
||||
val dao = db.quickSearchDao()
|
||||
dao.insert(quickSearchList)
|
||||
}
|
||||
|
||||
suspend fun deleteQuickSearch(quickSearch: QuickSearch) {
|
||||
val dao = db.quickSearchDao()
|
||||
dao.delete(quickSearch)
|
||||
dao.fill(quickSearch.position)
|
||||
}
|
||||
|
||||
suspend fun updateQuickSearch(quickSearchList: List<QuickSearch>) {
|
||||
val dao = db.quickSearchDao()
|
||||
dao.update(quickSearchList)
|
||||
}
|
||||
|
||||
val historyLazyList
|
||||
get() = db.historyDao().joinListLazy()
|
||||
|
||||
val localFavLazyList
|
||||
get() = db.localFavoritesDao().joinListLazy()
|
||||
|
||||
val localFavCount: Flow<Int>
|
||||
get() = db.localFavoritesDao().count()
|
||||
|
||||
fun searchLocalFav(keyword: String) = db.localFavoritesDao().joinListLazy("%$keyword%")
|
||||
|
||||
suspend fun putHistoryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
putGalleryInfo(galleryInfo)
|
||||
db.historyDao().upsert(HistoryInfo(galleryInfo.gid))
|
||||
}
|
||||
|
||||
suspend fun modifyHistoryInfoFavslotNonRefresh(gid: Long, slot: Int) {
|
||||
val dao = db.galleryDao()
|
||||
dao.load(gid)?.let {
|
||||
it.favoriteSlot = slot
|
||||
dao.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun importHistoryInfo(historyInfoList: List<HistoryInfo>) {
|
||||
val dao = db.historyDao()
|
||||
dao.insertOrIgnore(historyInfoList)
|
||||
}
|
||||
|
||||
suspend fun deleteHistoryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
val dao = db.historyDao()
|
||||
dao.deleteByKey(galleryInfo.gid)
|
||||
deleteGalleryInfo(galleryInfo)
|
||||
}
|
||||
|
||||
suspend fun clearHistoryInfo() {
|
||||
val dao = db.historyDao()
|
||||
val historyList = dao.list()
|
||||
dao.deleteAll()
|
||||
historyList.forEach { runCatching { db.galleryDao().deleteByKey(it.gid) } }
|
||||
}
|
||||
|
||||
suspend fun getAllFilter() = db.filterDao().list()
|
||||
|
||||
suspend fun addFilter(filter: Filter): Boolean {
|
||||
val existFilter = runCatching { db.filterDao().load(filter.text, filter.mode.field) }.getOrNull()
|
||||
return if (existFilter == null) {
|
||||
filter.id = null
|
||||
filter.id = db.filterDao().insert(filter)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteFilter(filter: Filter) {
|
||||
db.filterDao().delete(filter)
|
||||
}
|
||||
|
||||
suspend fun updateFilter(filter: Filter) {
|
||||
db.filterDao().update(filter)
|
||||
}
|
||||
|
||||
suspend fun exportDB(context: Context, uri: Uri) {
|
||||
val ehExportName = "eh.export.db"
|
||||
resource {
|
||||
context.deleteDatabase(ehExportName)
|
||||
roomDb<EhDatabase>(ehExportName)
|
||||
} release {
|
||||
it.close()
|
||||
context.deleteDatabase(ehExportName)
|
||||
} use { newDb ->
|
||||
db.galleryDao().list().let { newDb.galleryDao().insertOrIgnore(it) }
|
||||
db.downloadLabelDao().list().let { newDb.downloadLabelDao().insert(it) }
|
||||
db.downloadDirnameDao().list().let { newDb.downloadDirnameDao().insertOrIgnore(it) }
|
||||
db.downloadsDao().list().let { newDb.downloadsDao().insert(it) }
|
||||
db.historyDao().list().let { newDb.historyDao().insertOrIgnore(it) }
|
||||
db.quickSearchDao().list().let { newDb.quickSearchDao().insert(it) }
|
||||
db.localFavoritesDao().list().let { newDb.localFavoritesDao().insertOrIgnore(it) }
|
||||
db.filterDao().list().let { newDb.filterDao().insert(it) }
|
||||
newDb.close()
|
||||
val dbFile = context.getDatabasePath(ehExportName)
|
||||
context.contentResolver.openFileDescriptor(uri, "rw")!!.use { toFd ->
|
||||
ParcelFileDescriptor.open(dbFile, MODE_READ_ONLY).use { fromFd ->
|
||||
fromFd sendTo toFd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun importDB(context: Context, uri: Uri) {
|
||||
val tempDBName = "tmp.db"
|
||||
resource {
|
||||
context.deleteDatabase(tempDBName)
|
||||
roomDb<EhDatabase>(tempDBName) { createFromInputStream { context.contentResolver.openInputStream(uri) } }
|
||||
} release {
|
||||
it.close()
|
||||
context.deleteDatabase(tempDBName)
|
||||
} use { oldDB ->
|
||||
runCatching {
|
||||
db.galleryDao().insertOrIgnore(oldDB.galleryDao().list())
|
||||
}
|
||||
runCatching {
|
||||
val downloadLabelList = oldDB.downloadLabelDao().list()
|
||||
DownloadManager.addDownloadLabel(downloadLabelList)
|
||||
}
|
||||
runCatching {
|
||||
oldDB.downloadDirnameDao().list().let {
|
||||
importDownloadDirname(it)
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
val downloadInfoList = oldDB.downloadsDao().joinList().asReversed()
|
||||
DownloadManager.addDownload(downloadInfoList, false)
|
||||
}
|
||||
runCatching {
|
||||
val historyInfoList = oldDB.historyDao().list()
|
||||
importHistoryInfo(historyInfoList)
|
||||
}
|
||||
runCatching {
|
||||
val quickSearchList = oldDB.quickSearchDao().list()
|
||||
val currentQuickSearchList = db.quickSearchDao().list()
|
||||
val offset = currentQuickSearchList.size
|
||||
val importList = quickSearchList.filter { newQS ->
|
||||
currentQuickSearchList.none { it.name == newQS.name }
|
||||
}.onEachIndexed { index, q -> q.position = index + offset }
|
||||
importQuickSearch(importList)
|
||||
}
|
||||
runCatching {
|
||||
oldDB.localFavoritesDao().list().let {
|
||||
importLocalFavorites(it)
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
val filterList = oldDB.filterDao().list()
|
||||
val currentFilterList = db.filterDao().list()
|
||||
filterList.forEach {
|
||||
if (it !in currentFilterList) addFilter(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.network.InetValidator;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EhProxySelector extends ProxySelector {
|
||||
|
||||
public static final int TYPE_DIRECT = 0;
|
||||
public static final int TYPE_SYSTEM = 1;
|
||||
public static final int TYPE_HTTP = 2;
|
||||
public static final int TYPE_SOCKS = 3;
|
||||
|
||||
private ProxySelector delegation;
|
||||
private ProxySelector alternative;
|
||||
|
||||
EhProxySelector() {
|
||||
alternative = ProxySelector.getDefault();
|
||||
if (alternative == null) {
|
||||
alternative = new NullProxySelector();
|
||||
}
|
||||
|
||||
updateProxy();
|
||||
}
|
||||
|
||||
public void updateProxy() {
|
||||
switch (Settings.getProxyType()) {
|
||||
case TYPE_DIRECT:
|
||||
delegation = new NullProxySelector();
|
||||
break;
|
||||
default:
|
||||
case TYPE_SYSTEM:
|
||||
delegation = alternative;
|
||||
break;
|
||||
case TYPE_HTTP:
|
||||
case TYPE_SOCKS:
|
||||
delegation = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
int type = Settings.getProxyType();
|
||||
if (type == TYPE_HTTP || type == TYPE_SOCKS) {
|
||||
try {
|
||||
String ip = Settings.getProxyIp();
|
||||
int port = Settings.getProxyPort();
|
||||
if (!TextUtils.isEmpty(ip) && InetValidator.isValidInetPort(port)) {
|
||||
InetAddress inetAddress = InetAddress.getByName(ip);
|
||||
SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
|
||||
return Collections.singletonList(new Proxy(type == TYPE_HTTP ? Proxy.Type.HTTP : Proxy.Type.SOCKS, socketAddress));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (delegation != null) {
|
||||
return delegation.select(uri);
|
||||
}
|
||||
|
||||
return alternative.select(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||
if (delegation != null) {
|
||||
delegation.select(uri);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullProxySelector extends ProxySelector {
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
return Collections.singletonList(Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FavouriteStatusRouter {
|
||||
|
||||
private static final String KEY_DATA_MAP_NEXT_ID = "data_map_next_id";
|
||||
|
||||
private final IntIdGenerator idGenerator = new IntIdGenerator(Settings.getInt(KEY_DATA_MAP_NEXT_ID, 0));
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private final HashMap<Integer, Map<Long, GalleryInfo>> maps = new HashMap<>();
|
||||
|
||||
private final List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public int saveDataMap(Map<Long, GalleryInfo> map) {
|
||||
int id = idGenerator.nextId();
|
||||
maps.put(id, map);
|
||||
Settings.putInt(KEY_DATA_MAP_NEXT_ID, idGenerator.nextId());
|
||||
return id;
|
||||
}
|
||||
|
||||
public Map<Long, GalleryInfo> restoreDataMap(int id) {
|
||||
return maps.remove(id);
|
||||
}
|
||||
|
||||
public void modifyFavourites(long gid, int slot) {
|
||||
for (Map<Long, GalleryInfo> map : maps.values()) {
|
||||
GalleryInfo info = map.get(gid);
|
||||
if (info != null) {
|
||||
info.favoriteSlot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
for (Listener listener : listeners) {
|
||||
listener.onModifyFavourites(gid, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onModifyFavourites(long gid, int slot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class GetText {
|
||||
|
||||
private static Resources sResources;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sResources = context.getApplicationContext().getResources();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getString(@StringRes int id) {
|
||||
return sResources.getString(id);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getString(@StringRes int id, Object... formatArgs) {
|
||||
return sResources.getString(id, formatArgs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright 2018 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
/*
|
||||
* Created by Hippo on 2018/3/21.
|
||||
*/
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.database.MSQLiteBuilder;
|
||||
import com.hippo.util.SqlUtils;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Hosts {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
private static final String TABLE_HOSTS = "HOSTS";
|
||||
private static final String COLUMN_HOST = "HOST";
|
||||
private static final String COLUMN_IP = "IP";
|
||||
|
||||
private static final int DB_VERSION = VERSION_1;
|
||||
|
||||
private final SQLiteDatabase db;
|
||||
|
||||
public Hosts(Context context, String name) {
|
||||
SQLiteOpenHelper helper = new MSQLiteBuilder()
|
||||
.version(VERSION_1)
|
||||
.createTable(TABLE_HOSTS)
|
||||
.insertColumn(TABLE_HOSTS, COLUMN_HOST, String.class)
|
||||
.insertColumn(TABLE_HOSTS, COLUMN_IP, String.class)
|
||||
.build(context, name, DB_VERSION);
|
||||
db = helper.getWritableDatabase();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InetAddress toInetAddress(String host, String ip) {
|
||||
if (!isValidHost(host)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ip == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = parseV4(ip);
|
||||
if (bytes == null) {
|
||||
bytes = parseV6(ip);
|
||||
}
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return InetAddress.getByAddress(host, bytes);
|
||||
} catch (UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the host is valid.
|
||||
*/
|
||||
public static boolean isValidHost(String host) {
|
||||
if (host == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int labelLength = 0;
|
||||
for (int i = 0, n = host.length(); i < n; i++) {
|
||||
char ch = host.charAt(i);
|
||||
|
||||
if (ch == '.') {
|
||||
if (labelLength < 1 || labelLength > 63) {
|
||||
return false;
|
||||
}
|
||||
labelLength = 0;
|
||||
} else {
|
||||
labelLength++;
|
||||
}
|
||||
|
||||
if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '-' && ch != '.') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return labelLength >= 1 && labelLength <= 63;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if ips are valid.
|
||||
*/
|
||||
public static boolean isValidIp(String ips) {
|
||||
if (ips == null) {
|
||||
return false;
|
||||
}
|
||||
String[] list = ips.split("\\+");
|
||||
for (String ip : list) {
|
||||
if (parseV4(ip) == null && parseV6(ip) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// org.xbill.DNS.Address.parseV4
|
||||
@Nullable
|
||||
private static byte[] parseV4(String s) {
|
||||
int numDigits;
|
||||
int currentOctet;
|
||||
byte[] values = new byte[4];
|
||||
int currentValue;
|
||||
int length = s.length();
|
||||
|
||||
currentOctet = 0;
|
||||
currentValue = 0;
|
||||
numDigits = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c >= '0' && c <= '9') {
|
||||
/* Can't have more than 3 digits per octet. */
|
||||
if (numDigits == 3)
|
||||
return null;
|
||||
/* Octets shouldn't start with 0, unless they are 0. */
|
||||
if (numDigits > 0 && currentValue == 0)
|
||||
return null;
|
||||
numDigits++;
|
||||
currentValue *= 10;
|
||||
currentValue += (c - '0');
|
||||
/* 255 is the maximum value for an octet. */
|
||||
if (currentValue > 255)
|
||||
return null;
|
||||
} else if (c == '.') {
|
||||
/* Can't have more than 3 dots. */
|
||||
if (currentOctet == 3)
|
||||
return null;
|
||||
/* Two consecutive dots are bad. */
|
||||
if (numDigits == 0)
|
||||
return null;
|
||||
values[currentOctet++] = (byte) currentValue;
|
||||
currentValue = 0;
|
||||
numDigits = 0;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
/* Must have 4 octets. */
|
||||
if (currentOctet != 3)
|
||||
return null;
|
||||
/* The fourth octet can't be empty. */
|
||||
if (numDigits == 0)
|
||||
return null;
|
||||
values[currentOctet] = (byte) currentValue;
|
||||
return values;
|
||||
}
|
||||
|
||||
// org.xbill.DNS.Address.parseV6
|
||||
@Nullable
|
||||
private static byte[] parseV6(String s) {
|
||||
int range = -1;
|
||||
byte[] data = new byte[16];
|
||||
|
||||
String[] tokens = s.split(":", -1);
|
||||
|
||||
int first = 0;
|
||||
int last = tokens.length - 1;
|
||||
|
||||
if (tokens[0].length() == 0) {
|
||||
// If the first two tokens are empty, it means the string
|
||||
// started with ::, which is fine. If only the first is
|
||||
// empty, the string started with :, which is bad.
|
||||
if (last - first > 0 && tokens[1].length() == 0)
|
||||
first++;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tokens[last].length() == 0) {
|
||||
// If the last two tokens are empty, it means the string
|
||||
// ended with ::, which is fine. If only the last is
|
||||
// empty, the string ended with :, which is bad.
|
||||
if (last - first > 0 && tokens[last - 1].length() == 0)
|
||||
last--;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
if (last - first + 1 > 8)
|
||||
return null;
|
||||
|
||||
int i, j;
|
||||
for (i = first, j = 0; i <= last; i++) {
|
||||
if (tokens[i].length() == 0) {
|
||||
if (range >= 0)
|
||||
return null;
|
||||
range = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokens[i].indexOf('.') >= 0) {
|
||||
// An IPv4 address must be the last component
|
||||
if (i < last)
|
||||
return null;
|
||||
// There can't have been more than 6 components.
|
||||
if (i > 6)
|
||||
return null;
|
||||
byte[] v4addr = parseV4(tokens[i]);
|
||||
if (v4addr == null)
|
||||
return null;
|
||||
for (int k = 0; k < 4; k++)
|
||||
data[j++] = v4addr[k];
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int k = 0; k < tokens[i].length(); k++) {
|
||||
char c = tokens[i].charAt(k);
|
||||
if (Character.digit(c, 16) < 0)
|
||||
return null;
|
||||
}
|
||||
int x = Integer.parseInt(tokens[i], 16);
|
||||
if (x > 0xFFFF || x < 0)
|
||||
return null;
|
||||
data[j++] = (byte) (x >>> 8);
|
||||
data[j++] = (byte) (x & 0xFF);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (j < 16 && range < 0)
|
||||
return null;
|
||||
|
||||
if (range >= 0) {
|
||||
int empty = 16 - j;
|
||||
System.arraycopy(data, range, data, range + empty, j - range);
|
||||
for (i = range; i < range + empty; i++)
|
||||
data[i] = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a InetAddress with the host.
|
||||
*/
|
||||
@Nullable
|
||||
public List<InetAddress> get(String host) {
|
||||
if (!isValidHost(host)) {
|
||||
return null;
|
||||
}
|
||||
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host})) {
|
||||
if (cursor.moveToNext()) {
|
||||
String[] ips = SqlUtils.getString(cursor, COLUMN_IP, null).split("\\+");
|
||||
List<InetAddress> addresses = new ArrayList<>();
|
||||
for (String ip : ips) {
|
||||
addresses.add(toInetAddress(host, ip));
|
||||
}
|
||||
return addresses;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean contains(String host) {
|
||||
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host})) {
|
||||
return cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the host-ip pair into this hosts.
|
||||
*/
|
||||
public boolean put(String host, String ip) {
|
||||
if (!isValidHost(host) || !isValidIp(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_HOST, host);
|
||||
values.put(COLUMN_IP, ip);
|
||||
|
||||
if (contains(host)) {
|
||||
db.update(TABLE_HOSTS, values, COLUMN_HOST + " = ?", new String[]{host});
|
||||
} else {
|
||||
db.insert(TABLE_HOSTS, null, values);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts delete the entry with the host.
|
||||
*/
|
||||
public void delete(String host) {
|
||||
db.delete(TABLE_HOSTS, COLUMN_HOST + " = ?", new String[]{host});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data from this host.
|
||||
*/
|
||||
public List<Pair<String, String>> getAll() {
|
||||
List<Pair<String, String>> result = new ArrayList<>();
|
||||
|
||||
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + ";", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
String host = SqlUtils.getString(cursor, COLUMN_HOST, null);
|
||||
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
|
||||
result.add(new Pair<>(host, ip));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 512 * 512;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
return ImageBitmap.decode(isPipe.open());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
|
||||
return value.getWidth() * value.getHeight() * 4 /* value.getByteCount() TODO Update Image */;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
|
||||
oldValue.obtain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
|
||||
oldValue.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
|
||||
if (value != null) {
|
||||
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE
|
||||
/* value.getByteCount() <= MAX_CACHE_BYTE_COUNT TODO Update Image */;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1171
File diff suppressed because it is too large.
Load diff
@@ -1,117 +0,0 @@
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import splitties.preferences.DataStorePreferences
|
||||
import splitties.preferences.edit
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
object Settings : DataStorePreferences(null) {
|
||||
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
|
||||
|
||||
private val _favFlow = MutableSharedFlow<Unit>()
|
||||
val favChangesFlow = _favFlow.debounce(1000)
|
||||
var favCat by stringArrayPref("fav_cat", 10, "Favorites").emitTo(_favFlow)
|
||||
var favCount by intArrayPref("fav_count", 10).emitTo(_favFlow)
|
||||
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
|
||||
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
||||
var downloadPath by stringOrNullPref("image_path", null)
|
||||
var downloadQuery by stringOrNullPref("image_query", null)
|
||||
var downloadFragment by stringOrNullPref("image_fragment", null)
|
||||
var archivePasswds by stringSetOrNullPref("archive_passwds")
|
||||
var downloadDelay by intPref("download_delay_2", 0)
|
||||
var gallerySite by intPref("gallery_site_2", 0).observed { updateWhenGallerySiteChanges() }
|
||||
var multiThreadDownload by intPref("download_thread_2", 3)
|
||||
var preloadImage by intPref("preload_image_2", 5)
|
||||
var theme by intPref("theme_2", -1).observed { updateWhenThemeChanges() }
|
||||
var listMode by intPref("list_mode_2", 0)
|
||||
var detailSize by intPref("detail_size_2", 0)
|
||||
var thumbResolution by intPref("thumb_resolution_2", 0)
|
||||
var readCacheSize by intPref("read_cache_size_2", 640)
|
||||
var launchPage by intPref("launch_page_2", 0)
|
||||
var forceEhThumb by boolPref("force_eh_thumb", false)
|
||||
var showComments by boolPref("show_gallery_comments", true)
|
||||
var requestNews by boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
|
||||
var hideHvEvents by boolPref("hide_hv_events", false)
|
||||
var showJpnTitle by boolPref("show_jpn_title", false)
|
||||
var showGalleryPages by boolPref("show_gallery_pages", false)
|
||||
var showTagTranslations by boolPref(KEY_SHOW_TAG_TRANSLATIONS, false).observed { updateWhenTagTranslationChanges() }
|
||||
var meteredNetworkWarning by boolPref("cellular_network_warning", false)
|
||||
var appLinkVerifyTip by boolPref("app_link_verify_tip", false)
|
||||
var enabledSecurity by boolPref("enable_secure", false)
|
||||
var useCIUpdateChannel by boolPref("ci_update_channel", false)
|
||||
var mediaScan by boolPref("media_scan", false).observed { updateWhenKeepMediaStatusChanges() }
|
||||
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
|
||||
var qSSaveProgress by boolPref("qs_save_progress", true)
|
||||
var saveParseErrorBody by boolPref("save_parse_error_body", true)
|
||||
var saveCrashLog by boolPref("save_crash_log", true)
|
||||
var security by boolPref("require_unlock", false)
|
||||
var removeImageFiles by boolPref("include_pic", true)
|
||||
var needSignIn by boolPref("need_sign_in", true)
|
||||
var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() }
|
||||
var harmonizeCategoryColor by boolPref("harmonize_category_color", true)
|
||||
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
|
||||
var downloadOriginImage by boolPref("download_origin_image", false)
|
||||
var thumbSizeDp by intPref("thumb_size_", 120)
|
||||
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
|
||||
var defaultFavSlot by intPref("default_favorite_slot", -2)
|
||||
var securityDelay by intPref("require_unlock_delay", 0)
|
||||
var clipboardTextHashCode by intPref("clipboard_text_hash_code", 0)
|
||||
var listThumbSize by intPref("list_tile_size", 40)
|
||||
var searchCategory by intPref("search_pref", EhUtils.ALL_CATEGORY)
|
||||
var requestNewsTimerHour by intPref("request_news_timer_hour", -1)
|
||||
var requestNewsTimerMinute by intPref("request_news_timer_minute", -1)
|
||||
var updateIntervalDays by intPref("update_interval_days", 0)
|
||||
var recentDownloadLabel by stringOrNullPref("recent_download_label", null)
|
||||
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
|
||||
var displayName by stringOrNullPref("display_name", null)
|
||||
var avatar by stringOrNullPref("avatar", null)
|
||||
var language by stringPref("app_language", "system").observed { updateWhenLocaleChanges() }
|
||||
var lastDawnDay by longPref("last_dawn_day", 0)
|
||||
var lastUpdateDay by longPref("last_update_day", 0)
|
||||
|
||||
init {
|
||||
if ("CN" == Locale.getDefault().country) {
|
||||
edit {
|
||||
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Delegate<R> {
|
||||
val flowGetter: () -> Flow<Unit>
|
||||
operator fun getValue(thisRef: Any?, prop: KProperty<*>?): R
|
||||
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
|
||||
}
|
||||
|
||||
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
|
||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
||||
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
|
||||
check(value.size == count)
|
||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
||||
}
|
||||
}
|
||||
|
||||
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
|
||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
||||
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
|
||||
check(value.size == count)
|
||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.dailycheck.updateDailyCheckWork
|
||||
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.init.appCtx
|
||||
import splitties.preferences.PrefDelegate
|
||||
|
||||
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
fun <T, R : PrefDelegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { changesFlow().collect(func) } }
|
||||
fun <T, R : Settings.Delegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { flowGetter().collect(func) } }
|
||||
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flowGetter().collect { flow.emit(Unit) } } }
|
||||
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { changesFlow().collect { flow.emit(Unit) } } }
|
||||
|
||||
fun updateWhenLocaleChanges() {
|
||||
val newValue = Settings.language
|
||||
if ("system" == newValue) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
|
||||
} else {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateWhenKeepMediaStatusChanges() {
|
||||
collectScope.launchIO {
|
||||
runCatching {
|
||||
keepNoMediaFileStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateWhenThemeChanges() {
|
||||
collectScope.launch {
|
||||
delay(100) // Avoid recompose being cancelled
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.theme)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateWhenAmoledModeChanges() {
|
||||
collectScope.launch {
|
||||
delay(100) // Avoid recompose being cancelled
|
||||
AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_AUTO_BATTERY)
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.theme)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateWhenRequestNewsChanges() {
|
||||
updateDailyCheckWork(appCtx)
|
||||
}
|
||||
|
||||
fun updateWhenGallerySiteChanges() {
|
||||
if (!Settings.needSignIn && EhCookieStore.hasSignedIn()) {
|
||||
collectScope.launchIO {
|
||||
runCatching {
|
||||
EhEngine.getUConfig()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateWhenTagTranslationChanges() {
|
||||
collectScope.launchIO {
|
||||
runCatching {
|
||||
EhTagDatabase.update()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrlOpener;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.StageActivity;
|
||||
|
||||
public final class UrlOpener {
|
||||
|
||||
private UrlOpener() {
|
||||
}
|
||||
|
||||
public static void openUrl(@NonNull Context context, String url, boolean ehUrl) {
|
||||
openUrl(context, url, ehUrl, null);
|
||||
}
|
||||
|
||||
public static void openUrl(@NonNull Context context, String url, boolean ehUrl, GalleryDetail galleryDetail) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent;
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
if (ehUrl) {
|
||||
if (galleryDetail != null) {
|
||||
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
||||
if (result != null) {
|
||||
if (result.gid == galleryDetail.gid) {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
} else if (url.startsWith("#c")) {
|
||||
try {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
Announcer announcer = EhUrlOpener.parseUrl(url);
|
||||
if (null != announcer) {
|
||||
intent = new Intent(context, MainActivity.class);
|
||||
intent.setAction(StageActivity.ACTION_START_SCENE);
|
||||
intent.putExtra(StageActivity.KEY_SCENE_NAME, announcer.getClazz().getName());
|
||||
intent.putExtra(StageActivity.KEY_SCENE_ARGS, announcer.getArgs());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder();
|
||||
customTabsIntent.setShowTitle(true);
|
||||
try {
|
||||
customTabsIntent.build().launchUrl(context, uri);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(context, R.string.no_browser_installed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.WindowInsetsAnimationCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class WindowInsetsAnimationHelper extends WindowInsetsAnimationCompat.Callback {
|
||||
private final View[] views;
|
||||
private final HashMap<View, Integer> startPaddings = new HashMap<>();
|
||||
private final HashMap<View, Integer> endPaddings = new HashMap<>();
|
||||
WindowInsetsAnimationCompat animation;
|
||||
|
||||
public WindowInsetsAnimationHelper(int dispatchMode, View... views) {
|
||||
super(dispatchMode);
|
||||
this.views = views;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
|
||||
super.onPrepare(animation);
|
||||
this.animation = animation;
|
||||
for (View view : views) {
|
||||
if (view == null) {
|
||||
continue;
|
||||
}
|
||||
startPaddings.put(view, view.getPaddingBottom());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsetsAnimationCompat.BoundsCompat onStart(@NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds) {
|
||||
this.animation = animation;
|
||||
for (View view : views) {
|
||||
if (view == null) {
|
||||
continue;
|
||||
}
|
||||
endPaddings.put(view, view.getPaddingBottom());
|
||||
int startPadding = startPaddings.containsKey(view) ? startPaddings.get(view) : 0;
|
||||
view.setTranslationY(-(startPadding - view.getPaddingBottom()));
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
|
||||
if (animation == null) {
|
||||
return insets;
|
||||
}
|
||||
WindowInsetsAnimationCompat imeAnimation = null;
|
||||
for (WindowInsetsAnimationCompat animation : runningAnimations) {
|
||||
if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) {
|
||||
imeAnimation = animation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (imeAnimation != null) {
|
||||
for (View view : views) {
|
||||
if (view == null) {
|
||||
continue;
|
||||
}
|
||||
int startPadding = startPaddings.containsKey(view) ? startPaddings.get(view) : 0;
|
||||
int endPadding = endPaddings.containsKey(view) ? endPaddings.get(view) : 0;
|
||||
view.setTranslationY(MathUtils.lerp(endPadding - startPadding, 0, animation.getInterpolatedFraction()));
|
||||
}
|
||||
}
|
||||
return insets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
|
||||
super.onEnd(animation);
|
||||
startPaddings.clear();
|
||||
endPaddings.clear();
|
||||
this.animation = null;
|
||||
for (View view : views) {
|
||||
if (view == null) {
|
||||
continue;
|
||||
}
|
||||
view.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
public class EhCacheKeyFactory {
|
||||
|
||||
public static String getThumbKey(long gid) {
|
||||
return "preview:large:" + gid + ":" + 0; // "thumb:" + gid;
|
||||
}
|
||||
|
||||
public static String getNormalPreviewKey(long gid, int index) {
|
||||
return "preview:normal:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getLargePreviewKey(long gid, int index) {
|
||||
return "preview:large:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getLargePreviewSetKey(long gid, int index) {
|
||||
return "large_preview_set:" + gid + ":" + index;
|
||||
}
|
||||
|
||||
public static String getImageKey(long gid, int index) {
|
||||
return "image:" + gid + ":" + index;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.NormalGalleryPreview
|
||||
|
||||
// ExHentai Large Preview: https://s.exhentai.org/t/***
|
||||
// ExHentai Normal Preview: https://s.exhentai.org/m/***
|
||||
// EHentai Normal Preview: https://ehgt.org/m/***
|
||||
// EHentai Large Preview: https://ehgt.org/***
|
||||
|
||||
private const val URL_PREFIX_THUMB_E = "https://ehgt.org/"
|
||||
private const val URL_PREFIX_THUMB_EX = "https://s.exhentai.org/"
|
||||
private const val URL_PREFIX_LARGE_THUMB_EX = URL_PREFIX_THUMB_EX + "t/"
|
||||
|
||||
fun getImageKey(gid: Long, index: Int): String {
|
||||
return "image:$gid:$index"
|
||||
}
|
||||
|
||||
fun getThumbKey(url: String): String {
|
||||
return url.removePrefix(URL_PREFIX_THUMB_E).removePrefix(URL_PREFIX_LARGE_THUMB_EX).removePrefix(URL_PREFIX_THUMB_EX)
|
||||
}
|
||||
|
||||
val GalleryPreview.url
|
||||
get() = if (exThumb) {
|
||||
if (this is NormalGalleryPreview) URL_PREFIX_THUMB_EX else URL_PREFIX_LARGE_THUMB_EX
|
||||
} else {
|
||||
URL_PREFIX_THUMB_E
|
||||
} + imageKey
|
||||
|
||||
val GalleryInfo.thumbUrl
|
||||
get() = (if (exThumb) URL_PREFIX_LARGE_THUMB_EX else URL_PREFIX_THUMB_E) + thumbKey!!
|
||||
|
||||
val exThumb get() = EhUtils.isExHentai && !Settings.forceEhThumb
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.client.exception.CancelledException;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class EhClient {
|
||||
|
||||
public static final String TAG = EhClient.class.getSimpleName();
|
||||
|
||||
public static final int METHOD_SIGN_IN = 0;
|
||||
public static final int METHOD_GET_GALLERY_LIST = 1;
|
||||
public static final int METHOD_GET_GALLERY_DETAIL = 3;
|
||||
public static final int METHOD_GET_PREVIEW_SET = 4;
|
||||
public static final int METHOD_GET_RATE_GALLERY = 5;
|
||||
public static final int METHOD_GET_COMMENT_GALLERY = 6;
|
||||
public static final int METHOD_GET_GALLERY_TOKEN = 7;
|
||||
public static final int METHOD_GET_FAVORITES = 8;
|
||||
public static final int METHOD_ADD_FAVORITES = 9;
|
||||
public static final int METHOD_ADD_FAVORITES_RANGE = 10;
|
||||
public static final int METHOD_MODIFY_FAVORITES = 11;
|
||||
public static final int METHOD_GET_TORRENT_LIST = 12;
|
||||
public static final int METHOD_GET_PROFILE = 14;
|
||||
public static final int METHOD_VOTE_COMMENT = 15;
|
||||
public static final int METHOD_IMAGE_SEARCH = 16;
|
||||
public static final int METHOD_ARCHIVE_LIST = 17;
|
||||
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
|
||||
public static final int METHOD_VOTE_TAG = 19;
|
||||
|
||||
private final ThreadPoolExecutor mRequestThreadPool;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
|
||||
public EhClient(Context context) {
|
||||
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
||||
mOkHttpClient = EhApplication.getOkHttpClient(context);
|
||||
}
|
||||
|
||||
public void execute(EhRequest request) {
|
||||
if (!request.isCancelled()) {
|
||||
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
|
||||
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
|
||||
request.task = task;
|
||||
} else {
|
||||
request.getCallback().onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback<E> {
|
||||
|
||||
void onSuccess(E result);
|
||||
|
||||
void onFailure(Exception e);
|
||||
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
public class Task extends AsyncTask<Object, Void, Object> {
|
||||
|
||||
private final int mMethod;
|
||||
private final AtomicReference<Call> mCall = new AtomicReference<>();
|
||||
private final AtomicBoolean mStop = new AtomicBoolean();
|
||||
private Callback mCallback;
|
||||
private EhConfig mEhConfig;
|
||||
|
||||
public Task(int method, Callback callback, EhConfig ehConfig) {
|
||||
mMethod = method;
|
||||
mCallback = callback;
|
||||
mEhConfig = ehConfig;
|
||||
}
|
||||
|
||||
// Called in Job thread
|
||||
public void setCall(Call call) throws CancelledException {
|
||||
if (mStop.get()) {
|
||||
// Stopped Job thread
|
||||
throw new CancelledException();
|
||||
} else {
|
||||
mCall.lazySet(call);
|
||||
}
|
||||
}
|
||||
|
||||
public EhConfig getEhConfig() {
|
||||
return mEhConfig;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!mStop.get()) {
|
||||
mStop.lazySet(true);
|
||||
|
||||
if (mCallback != null) {
|
||||
// TODO Avoid new runnable
|
||||
SimpleHandler.getInstance().post(mCallback::onCancel);
|
||||
}
|
||||
|
||||
Status status = getStatus();
|
||||
if (status == Status.PENDING) {
|
||||
cancel(false);
|
||||
} else if (status == Status.RUNNING) {
|
||||
// It is running, cancel call if it is created
|
||||
Call call = mCall.get();
|
||||
if (call != null) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear
|
||||
mCallback = null;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
try {
|
||||
switch (mMethod) {
|
||||
case METHOD_SIGN_IN:
|
||||
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
||||
case METHOD_GET_GALLERY_LIST:
|
||||
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_GALLERY_DETAIL:
|
||||
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_PREVIEW_SET:
|
||||
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_RATE_GALLERY:
|
||||
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
||||
case METHOD_GET_COMMENT_GALLERY:
|
||||
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
||||
case METHOD_GET_GALLERY_TOKEN:
|
||||
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
||||
case METHOD_GET_FAVORITES:
|
||||
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
|
||||
case METHOD_ADD_FAVORITES:
|
||||
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
||||
case METHOD_ADD_FAVORITES_RANGE:
|
||||
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
||||
case METHOD_MODIFY_FAVORITES:
|
||||
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
|
||||
case METHOD_GET_TORRENT_LIST:
|
||||
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_GET_PROFILE:
|
||||
return EhEngine.getProfile(this, mOkHttpClient);
|
||||
case METHOD_VOTE_COMMENT:
|
||||
return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
|
||||
case METHOD_IMAGE_SEARCH:
|
||||
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
||||
case METHOD_ARCHIVE_LIST:
|
||||
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_DOWNLOAD_ARCHIVE:
|
||||
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
||||
case METHOD_VOTE_TAG:
|
||||
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
||||
default:
|
||||
return new IllegalStateException("Can't detect method " + mMethod);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
if (mCallback != null) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (!(result instanceof CancelledException)) {
|
||||
if (result instanceof Exception) {
|
||||
mCallback.onFailure((Exception) result);
|
||||
} else {
|
||||
mCallback.onSuccess(result);
|
||||
}
|
||||
} else {
|
||||
// onCancel is called in stop
|
||||
}
|
||||
}
|
||||
|
||||
// Clear
|
||||
mCallback = null;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,806 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import com.hippo.network.InetValidator;
|
||||
|
||||
/**
|
||||
* Some configurable stuff about EH. It ends up cookie.
|
||||
*/
|
||||
public class EhConfig implements Cloneable {
|
||||
|
||||
/**
|
||||
* The Cookie key of uconfig
|
||||
*/
|
||||
public static final String KEY_UCONFIG = "uconfig";
|
||||
/**
|
||||
* The Cookie key of lofi resolution
|
||||
*/
|
||||
public static final String KEY_LOFI_RESOLUTION = "xres";
|
||||
/**
|
||||
* The Cookie key of show warning
|
||||
*/
|
||||
public static final String KEY_CONTENT_WARNING = "nw";
|
||||
/**
|
||||
* load images through the Hentai@Home Network
|
||||
*/
|
||||
public static final String LOAD_FROM_HAH_YES = "y";
|
||||
/**
|
||||
* do not load images through the Hentai@Home Network
|
||||
*/
|
||||
public static final String LOAD_FROM_HAH_NO = "n";
|
||||
/**
|
||||
* Image Size Auto
|
||||
*/
|
||||
public static final String IMAGE_SIZE_AUTO = "a";
|
||||
/**
|
||||
* Image Size 780x
|
||||
*/
|
||||
public static final String IMAGE_SIZE_780X = "780";
|
||||
/**
|
||||
* Image Size 980x
|
||||
*/
|
||||
public static final String IMAGE_SIZE_980X = "980";
|
||||
/**
|
||||
* Image Size 1280x
|
||||
*/
|
||||
public static final String IMAGE_SIZE_1280X = "1280";
|
||||
/**
|
||||
* Image Size 1600x
|
||||
*/
|
||||
public static final String IMAGE_SIZE_1600X = "1600";
|
||||
/**
|
||||
* Image Size 2400x
|
||||
*/
|
||||
public static final String IMAGE_SIZE_2400X = "2400";
|
||||
/**
|
||||
* Manual Accept, Manual Start
|
||||
*/
|
||||
public static final String ARCHIVER_DOWNLOAD_MAMS = "0";
|
||||
/**
|
||||
* >Manual Accept, Auto Start
|
||||
*/
|
||||
public static final String ARCHIVER_DOWNLOAD_AAMS = "1";
|
||||
/**
|
||||
* Auto Accept, Manual Start
|
||||
*/
|
||||
public static final String ARCHIVER_DOWNLOAD_MAAS = "2";
|
||||
/**
|
||||
* Auto Accept, Auto Start
|
||||
*/
|
||||
public static final String ARCHIVER_DOWNLOAD_AAAS = "3";
|
||||
/**
|
||||
* List View on the front and search pages
|
||||
*/
|
||||
public static final String LAYOUT_MODE_LIST = "l";
|
||||
/**
|
||||
* Thumbnail View on the front and search pages
|
||||
*/
|
||||
public static final String LAYOUT_MODE_THUMB = "t";
|
||||
public static final int MISC = 0x1;
|
||||
public static final int DOUJINSHI = 0x2;
|
||||
public static final int MANGA = 0x4;
|
||||
public static final int ARTIST_CG = 0x8;
|
||||
public static final int GAME_CG = 0x10;
|
||||
public static final int IMAGE_SET = 0x20;
|
||||
public static final int COSPLAY = 0x40;
|
||||
public static final int ASIAN_PORN = 0x80;
|
||||
public static final int NON_H = 0x100;
|
||||
public static final int WESTERN = 0x200;
|
||||
public static final int ALL_CATEGORY = 0x3ff;
|
||||
public static final int NAMESPACES_RECLASS = 0x1;
|
||||
public static final int NAMESPACES_LANGUAGE = 0x2;
|
||||
public static final int NAMESPACES_PARODY = 0x4;
|
||||
public static final int NAMESPACES_CHARACTER = 0x8;
|
||||
public static final int NAMESPACES_GROUP = 0x10;
|
||||
public static final int NAMESPACES_ARTIST = 0x20;
|
||||
public static final int NAMESPACES_MALE = 0x40;
|
||||
public static final int NAMESPACES_FEMALE = 0x80;
|
||||
public static final String JAPANESE_ORIGINAL = "0";
|
||||
public static final String JAPANESE_TRANSLATED = "1024";
|
||||
public static final String JAPANESE_REWRITE = "2048";
|
||||
public static final String ENGLISH_ORIGINAL = "1";
|
||||
public static final String ENGLISH_TRANSLATED = "1025";
|
||||
public static final String ENGLISH_REWRITE = "2049";
|
||||
public static final String CHINESE_ORIGINAL = "10";
|
||||
public static final String CHINESE_TRANSLATED = "1034";
|
||||
public static final String CHINESE_REWRITE = "2058";
|
||||
public static final String DUTCH_ORIGINAL = "20";
|
||||
public static final String DUTCH_TRANSLATED = "1044";
|
||||
public static final String DUTCH_REWRITE = "2068";
|
||||
public static final String FRENCH_ORIGINAL = "30";
|
||||
public static final String FRENCH_TRANSLATED = "1054";
|
||||
public static final String FRENCH_REWRITE = "2078";
|
||||
public static final String GERMAN_ORIGINAL = "40";
|
||||
public static final String GERMAN_TRANSLATED = "1064";
|
||||
public static final String GERMAN_REWRITE = "2088";
|
||||
public static final String HUNGARIAN_ORIGINAL = "50";
|
||||
public static final String HUNGARIAN_TRANSLATED = "1074";
|
||||
public static final String HUNGARIAN_REWRITE = "2098";
|
||||
public static final String ITALIAN_ORIGINAL = "60";
|
||||
public static final String ITALIAN_TRANSLATED = "1084";
|
||||
public static final String ITALIAN_REWRITE = "2108";
|
||||
public static final String KOREAN_ORIGINAL = "70";
|
||||
public static final String KOREAN_TRANSLATED = "1094";
|
||||
public static final String KOREAN_REWRITE = "2118";
|
||||
public static final String POLISH_ORIGINAL = "80";
|
||||
public static final String POLISH_TRANSLATED = "1104";
|
||||
public static final String POLISH_REWRITE = "2128";
|
||||
public static final String PORTUGUESE_ORIGINAL = "90";
|
||||
public static final String PORTUGUESE_TRANSLATED = "1114";
|
||||
public static final String PORTUGUESE_REWRITE = "2138";
|
||||
public static final String RUSSIAN_ORIGINAL = "100";
|
||||
public static final String RUSSIAN_TRANSLATED = "1124";
|
||||
public static final String RUSSIAN_REWRITE = "2148";
|
||||
public static final String SPANISH_ORIGINAL = "110";
|
||||
public static final String SPANISH_TRANSLATED = "1134";
|
||||
public static final String SPANISH_REWRITE = "2158";
|
||||
public static final String THAI_ORIGINAL = "120";
|
||||
public static final String THAI_TRANSLATED = "1144";
|
||||
public static final String THAI_REWRITE = "2168";
|
||||
public static final String VIETNAMESE_ORIGINAL = "130";
|
||||
public static final String VIETNAMESE_TRANSLATED = "1154";
|
||||
public static final String VIETNAMESE_REWRITE = "2178";
|
||||
public static final String NA_ORIGINAL = "254";
|
||||
public static final String NA_TRANSLATED = "1278";
|
||||
public static final String NA_REWRITE = "2302";
|
||||
public static final String OTHER_ORIGINAL = "255";
|
||||
public static final String OTHER_TRANSLATED = "1279";
|
||||
public static final String OTHER_REWRITE = "2303";
|
||||
/**
|
||||
* 25 results per page for the index/search page and torrent search pages
|
||||
*/
|
||||
public static final String RESULT_COUNT_25 = "0";
|
||||
/**
|
||||
* 50 results per page for the index/search page and torrent search pages
|
||||
*/
|
||||
public static final String RESULT_COUNT_50 = "1";
|
||||
/**
|
||||
* 100 results per page for the index/search page and torrent search pages
|
||||
*/
|
||||
public static final String RESULT_COUNT_100 = "2";
|
||||
/**
|
||||
* 200 results per page for the index/search page and torrent search pages
|
||||
*/
|
||||
public static final String RESULT_COUNT_200 = "3";
|
||||
/**
|
||||
* On mouse-over
|
||||
*/
|
||||
public static final String MOUSE_OVER_YES = "m";
|
||||
/**
|
||||
* On page load
|
||||
*/
|
||||
public static final String MOUSE_OVER_NO = "p";
|
||||
/**
|
||||
* Preview normal size
|
||||
*/
|
||||
public static final String PREVIEW_SIZE_NORMAL = "m";
|
||||
/**
|
||||
* Preview large size
|
||||
*/
|
||||
public static final String PREVIEW_SIZE_LARGE = "l";
|
||||
/**
|
||||
* 4 row preview per page
|
||||
*/
|
||||
public static final String PREVIEW_ROW_4 = "2";
|
||||
/**
|
||||
* 10 row preview per page
|
||||
*/
|
||||
public static final String PREVIEW_ROW_10 = "5";
|
||||
/**
|
||||
* 20 row preview per page
|
||||
*/
|
||||
public static final String PREVIEW_ROW_20 = "10";
|
||||
/**
|
||||
* 40 row preview per page
|
||||
*/
|
||||
public static final String PREVIEW_ROW_40 = "20";
|
||||
/**
|
||||
* Oldest comments first
|
||||
*/
|
||||
public static final String COMMENTS_SORT_OLDEST_FIRST = "a";
|
||||
/**
|
||||
* Recent comments first
|
||||
*/
|
||||
public static final String COMMENTS_SORT_RECENT_FIRST = "d";
|
||||
/**
|
||||
* By highest score
|
||||
*/
|
||||
public static final String COMMENTS_SORT_HIGHEST_SCORE_FIRST = "s";
|
||||
/**
|
||||
* Show gallery comment votes On score hover or click
|
||||
*/
|
||||
public static final String COMMENTS_VOTES_POP = "0";
|
||||
/**
|
||||
* Always show gallery comment votes
|
||||
*/
|
||||
public static final String COMMENTS_VOTES_ALWAYS = "1";
|
||||
/**
|
||||
* Sort order for gallery tags alphabetically
|
||||
*/
|
||||
public static final String TAGS_SORT_ALPHABETICAL = "a";
|
||||
/**
|
||||
* Sort order for gallery tags by tag power
|
||||
*/
|
||||
public static final String TAGS_SORT_POWER = "p";
|
||||
/**
|
||||
* Show gallery page numbers
|
||||
*/
|
||||
public static final String SHOW_GALLERY_INDEX_YES = "1";
|
||||
/**
|
||||
* Do not show gallery page numbers
|
||||
*/
|
||||
public static final String SHOW_GALLERY_INDEX_NO = "0";
|
||||
/**
|
||||
* Enable Tag Flagging
|
||||
*/
|
||||
public static final String ENABLE_TAG_FLAGGING_YES = "y";
|
||||
/**
|
||||
* Do not enable Tag Flagging
|
||||
*/
|
||||
public static final String ENABLE_TAG_FLAGGING_NO = "n";
|
||||
/**
|
||||
* Always display the original images
|
||||
*/
|
||||
public static final String ALWAYS_ORIGINAL_YES = "y";
|
||||
/**
|
||||
* Do not Always display the original images
|
||||
*/
|
||||
public static final String ALWAYS_ORIGINAL_NO = "n";
|
||||
/**
|
||||
* Enable the Multi-Page Viewe
|
||||
*/
|
||||
public static final String MULTI_PAGE_YES = "y";
|
||||
/**
|
||||
* Do not enable the Multi-Page Viewe
|
||||
*/
|
||||
public static final String MULTI_PAGE_NO = "n";
|
||||
/**
|
||||
* Align left, only scale if image is larger than browser width
|
||||
*/
|
||||
public static final String MULTI_PAGE_STYLE_N = "n";
|
||||
/**
|
||||
* Align center, only scale if image is larger than browser width
|
||||
*/
|
||||
public static final String MULTI_PAGE_STYLE_C = "c";
|
||||
/**
|
||||
* Align center, Always scale images to fit browser width
|
||||
*/
|
||||
public static final String MULTI_PAGE_STYLE_Y = "y";
|
||||
/**
|
||||
* Show Multi-Page Viewer Thumbnail Pane
|
||||
*/
|
||||
public static final String MULTI_PAGE_THUMB_SHOW = "n";
|
||||
/**
|
||||
* Hide Multi-Page Viewer Thumbnail Pane
|
||||
*/
|
||||
public static final String MULTI_PAGE_THUMB_HIDE = "y";
|
||||
/**
|
||||
* 460x for lofi resolution
|
||||
*/
|
||||
public static final String LOFI_RESOLUTION_460X = "1";
|
||||
/**
|
||||
* 780X for lofi resolution
|
||||
*/
|
||||
public static final String LOFI_RESOLUTION_780X = "2";
|
||||
/**
|
||||
* 980X for lofi resolution
|
||||
*/
|
||||
public static final String LOFI_RESOLUTION_980X = "3";
|
||||
/**
|
||||
* show warning
|
||||
*/
|
||||
public static final String CONTENT_WARNING_SHOW = "0";
|
||||
/**
|
||||
* not show warning
|
||||
*/
|
||||
public static final String CONTENT_WARNING_NOT_SHOW = "1";
|
||||
/**
|
||||
* The key of load images through the Hentai@Home Network
|
||||
*
|
||||
* @see #LOAD_FROM_HAH_YES
|
||||
* @see #LOAD_FROM_HAH_NO
|
||||
*/
|
||||
private static final String KEY_LOAD_FROM_HAH = "uh";
|
||||
/**
|
||||
* The key of Image Size Settings
|
||||
*
|
||||
* @see #IMAGE_SIZE_AUTO
|
||||
* @see #IMAGE_SIZE_780X
|
||||
* @see #IMAGE_SIZE_980X
|
||||
* @see #IMAGE_SIZE_1280X
|
||||
* @see #IMAGE_SIZE_1600X
|
||||
* @see #IMAGE_SIZE_2400X
|
||||
*/
|
||||
private static final String KEY_IMAGE_SIZE = "xr";
|
||||
/**
|
||||
* The key of scale images width
|
||||
*/
|
||||
private static final String KEY_SCALE_WIDTH = "rx";
|
||||
/**
|
||||
* The key of scale images height
|
||||
*/
|
||||
private static final String KEY_SCALE_HEIGHT = "ry";
|
||||
/**
|
||||
* The key of Gallery Name Display
|
||||
*
|
||||
* @see #GALLERY_TITLE_DEFAULT
|
||||
* @see #GALLERY_TITLE_JAPANESE
|
||||
*/
|
||||
private static final String KEY_GALLERY_TITLE = "tl";
|
||||
/**
|
||||
* The key of the behavior for downloading archiver
|
||||
*
|
||||
* @see #ARCHIVER_DOWNLOAD_MAMS
|
||||
* @see #ARCHIVER_DOWNLOAD_MAAS
|
||||
* @see #ARCHIVER_DOWNLOAD_AAMS
|
||||
* @see #ARCHIVER_DOWNLOAD_AAAS
|
||||
*/
|
||||
private static final String KEY_ARCHIVER_DOWNLOAD = "ar";
|
||||
/**
|
||||
* The key of display mode would you like to use on the front and search pages
|
||||
*
|
||||
* @see #LAYOUT_MODE_LIST
|
||||
* @see #LAYOUT_MODE_THUMB
|
||||
*/
|
||||
private static final String KEY_LAYOUT_MODE = "dm";
|
||||
/**
|
||||
* The key for show popular
|
||||
*
|
||||
* @see #POPULAR_YES
|
||||
* @see #POPULAR_NO
|
||||
*/
|
||||
private static final String KEY_POPULAR = "prn";
|
||||
/**
|
||||
* The key of categories would you like to view as default on the front page
|
||||
*/
|
||||
private static final String KEY_DEFAULT_CATEGORIES = "cats";
|
||||
/**
|
||||
* The key for favorites sort
|
||||
*
|
||||
* @see #FAVORITES_SORT_GALLERY_UPDATE_TIME
|
||||
* @see #FAVORITES_SORT_FAVORITED_TIME
|
||||
*/
|
||||
private static final String KEY_FAVORITES_SORT = "fs";
|
||||
/**
|
||||
* The key of exclude certain namespaces from a default tag search
|
||||
*/
|
||||
private static final String KEY_EXCLUDED_NAMESPACES = "xns";
|
||||
/**
|
||||
* The key of hide galleries in certain languages from the gallery list and searches
|
||||
*/
|
||||
private static final String KEY_EXCLUDED_LANGUAGES = "xl";
|
||||
/**
|
||||
* The key of how many results would you like per page for the index/search page and torrent search pages
|
||||
*
|
||||
* @see #RESULT_COUNT_25
|
||||
* @see #RESULT_COUNT_50
|
||||
* @see #RESULT_COUNT_100
|
||||
* @see #RESULT_COUNT_200
|
||||
*/
|
||||
private static final String KEY_RESULT_COUNT = "rc";
|
||||
/**
|
||||
* The key of mouse-over thumb
|
||||
*
|
||||
* @see #MOUSE_OVER_YES
|
||||
* @see #MOUSE_OVER_NO
|
||||
*/
|
||||
private static final String KEY_MOUSE_OVER = "lt";
|
||||
/**
|
||||
* The key of preview size
|
||||
*
|
||||
* @see #PREVIEW_SIZE_NORMAL
|
||||
* @see #PREVIEW_SIZE_LARGE
|
||||
*/
|
||||
private static final String KEY_PREVIEW_SIZE = "ts";
|
||||
/**
|
||||
* The key of preview row per page
|
||||
*
|
||||
* @see #PREVIEW_ROW_4
|
||||
* @see #PREVIEW_ROW_10
|
||||
* @see #PREVIEW_ROW_20
|
||||
* @see #PREVIEW_ROW_40
|
||||
*/
|
||||
private static final String KEY_PREVIEW_ROW = "tr";
|
||||
/**
|
||||
* The key of sort order for gallery comments
|
||||
*
|
||||
* @see #COMMENTS_SORT_OLDEST_FIRST
|
||||
* @see #COMMENTS_SORT_RECENT_FIRST
|
||||
* @see #COMMENTS_SORT_HIGHEST_SCORE_FIRST
|
||||
*/
|
||||
private static final String KEY_COMMENTS_SORT = "cs";
|
||||
/**
|
||||
* The key of show gallery comment votes
|
||||
*
|
||||
* @see #COMMENTS_VOTES_POP
|
||||
* @see #COMMENTS_VOTES_ALWAYS
|
||||
*/
|
||||
private static final String KEY_COMMENTS_VOTES = "sc";
|
||||
/**
|
||||
* The key of sort order for gallery tags
|
||||
*
|
||||
* @see #TAGS_SORT_ALPHABETICAL
|
||||
* @see #TAGS_SORT_POWER
|
||||
*/
|
||||
private static final String KEY_TAGS_SORT = "to";
|
||||
/**
|
||||
* The key of show gallery page numbers
|
||||
*
|
||||
* @see #SHOW_GALLERY_INDEX_NO
|
||||
* @see #SHOW_GALLERY_INDEX_YES
|
||||
*/
|
||||
private static final String KEY_SHOW_GALLERY_INDEX = "pn";
|
||||
/**
|
||||
* The key of the IP:Port of a proxy-enabled Hentai@Home Client
|
||||
* to load all images
|
||||
*/
|
||||
private static final String KEY_HAH_CLIENT_IP_PORT = "hp";
|
||||
/**
|
||||
* The key of the passkey of a proxy-enabled Hentai@Home Client
|
||||
* to load all images
|
||||
*/
|
||||
private static final String KEY_HAH_CLIENT_PASSKEY = "hk";
|
||||
/**
|
||||
* The key of enable Tag Flagging
|
||||
*
|
||||
* @see #ENABLE_TAG_FLAGGING_NO
|
||||
* @see #ENABLE_TAG_FLAGGING_YES
|
||||
*/
|
||||
private static final String KEY_ENABLE_TAG_FLAGGING = "tf";
|
||||
/**
|
||||
* The key of always display the original images instead of the resampled versions
|
||||
*
|
||||
* @see #ALWAYS_ORIGINAL_NO
|
||||
* @see #ALWAYS_ORIGINAL_YES
|
||||
*/
|
||||
private static final String KEY_ALWAYS_ORIGINAL = "oi";
|
||||
/**
|
||||
* The key of enable the multi-Page Viewer
|
||||
*
|
||||
* @see #MULTI_PAGE_NO
|
||||
* @see #MULTI_PAGE_YES
|
||||
*/
|
||||
private static final String KEY_MULTI_PAGE = "qb";
|
||||
/**
|
||||
* The key of multi-Page Viewer Display Style
|
||||
*
|
||||
* @see #MULTI_PAGE_STYLE_C
|
||||
* @see #MULTI_PAGE_STYLE_N
|
||||
* @see #MULTI_PAGE_STYLE_Y
|
||||
*/
|
||||
private static final String KEY_MULTI_PAGE_STYLE = "ms";
|
||||
/**
|
||||
* The key of multi-Page Viewer Thumbnail Pane
|
||||
*
|
||||
* @see #MULTI_PAGE_THUMB_HIDE
|
||||
* @see #MULTI_PAGE_THUMB_SHOW
|
||||
*/
|
||||
private static final String KEY_MULTI_PAGE_THUMB = "mt";
|
||||
/**
|
||||
* Default gallery title
|
||||
*/
|
||||
private static final String GALLERY_TITLE_DEFAULT = "r";
|
||||
/**
|
||||
* Japanese gallery title
|
||||
*/
|
||||
private static final String GALLERY_TITLE_JAPANESE = "j";
|
||||
/**
|
||||
* Show popular
|
||||
*/
|
||||
private static final String POPULAR_YES = "y";
|
||||
/**
|
||||
* Don't show popular
|
||||
*/
|
||||
private static final String POPULAR_NO = "n";
|
||||
/**
|
||||
* Sort favorites by last gallery update time
|
||||
*/
|
||||
private static final String FAVORITES_SORT_GALLERY_UPDATE_TIME = "p";
|
||||
/**
|
||||
* Sort favorites by favorited time
|
||||
*/
|
||||
private static final String FAVORITES_SORT_FAVORITED_TIME = "f";
|
||||
/**
|
||||
* Load images through the Hentai@Home Network<br/>
|
||||
* key: {@link #KEY_LOAD_FROM_HAH}<br/>
|
||||
* value: {@link #LOAD_FROM_HAH_YES}, {@link #LOAD_FROM_HAH_NO}
|
||||
*/
|
||||
public String loadFromHAH = LOAD_FROM_HAH_YES;
|
||||
|
||||
/**
|
||||
* Image Size<br/>
|
||||
* key: {@link #KEY_IMAGE_SIZE}<br/>
|
||||
* value: {@link #IMAGE_SIZE_AUTO}, {@link #IMAGE_SIZE_780X}, {@link #IMAGE_SIZE_980X},
|
||||
* {@link #IMAGE_SIZE_1280X}, {@link #IMAGE_SIZE_1600X}, {@link #IMAGE_SIZE_2400X}
|
||||
*/
|
||||
public String imageSize = IMAGE_SIZE_AUTO;
|
||||
|
||||
/**
|
||||
* Scale width<br/>
|
||||
* key: {@link #KEY_SCALE_WIDTH}<br/>
|
||||
* value: 0 for no limit
|
||||
*/
|
||||
public int scaleWidth = 0;
|
||||
|
||||
/**
|
||||
* Scale height<br/>
|
||||
* key: {@link #KEY_SCALE_HEIGHT}<br/>
|
||||
* value: 0 for no limit
|
||||
*/
|
||||
public int scaleHeight = 0;
|
||||
|
||||
/**
|
||||
* Gallery title<br/>
|
||||
* key: {@link #KEY_GALLERY_TITLE}<br/>
|
||||
* value: {@link #GALLERY_TITLE_DEFAULT}, {@link #GALLERY_TITLE_JAPANESE}
|
||||
*/
|
||||
public String galleryTitle = GALLERY_TITLE_DEFAULT;
|
||||
|
||||
/**
|
||||
* The default behavior for downloading an archiver<br/>
|
||||
* key: {@link #KEY_ARCHIVER_DOWNLOAD}<br/>
|
||||
* value: {@link #ARCHIVER_DOWNLOAD_MAMS}, {@link #ARCHIVER_DOWNLOAD_AAMS},
|
||||
* {@link #ARCHIVER_DOWNLOAD_MAAS}, {@link #ARCHIVER_DOWNLOAD_AAAS}
|
||||
*/
|
||||
public String archiverDownload = ARCHIVER_DOWNLOAD_MAMS;
|
||||
|
||||
/**
|
||||
* Display mode used on the front and search pages<br/>
|
||||
* false for list, true for thumb<br/>
|
||||
* key: {@link #KEY_LAYOUT_MODE}<br/>
|
||||
* value: {@link #LAYOUT_MODE_LIST}, {@link #LAYOUT_MODE_THUMB}
|
||||
*/
|
||||
public String layoutMode = LAYOUT_MODE_LIST;
|
||||
|
||||
/**
|
||||
* Show popular or not<br/>
|
||||
* key: {@link #KEY_POPULAR}<br/>
|
||||
* value: {@link #POPULAR_YES}, {@link #POPULAR_NO}
|
||||
*/
|
||||
public String popular = POPULAR_YES;
|
||||
|
||||
/**
|
||||
* Default categories on the front page<br/>
|
||||
* key: {@link #KEY_DEFAULT_CATEGORIES}<br/>
|
||||
* value: the value of categories, for multiple use & operation,
|
||||
* 0 for none
|
||||
*/
|
||||
public int defaultCategories = 0;
|
||||
|
||||
/**
|
||||
* <br/>
|
||||
* key: {@link #KEY_FAVORITES_SORT}<br/>
|
||||
* value: {@link #FAVORITES_SORT_GALLERY_UPDATE_TIME}, {@link #FAVORITES_SORT_FAVORITED_TIME}
|
||||
*/
|
||||
public String favoritesSort = FAVORITES_SORT_FAVORITED_TIME;
|
||||
|
||||
/**
|
||||
* Certain namespaces excluded from a default tag search<br/>
|
||||
* key: {@link #KEY_EXCLUDED_NAMESPACES}<br/>
|
||||
* value: the value of namespaces, for multiple use & operation,
|
||||
* 0 for none
|
||||
*/
|
||||
public int excludedNamespaces = 0;
|
||||
|
||||
/**
|
||||
* Certain languages excluded from list and searches<br/>
|
||||
* key: {@link #KEY_EXCLUDED_LANGUAGES}<br/>
|
||||
* value: {@link #JAPANESE_TRANSLATED}, {@link #JAPANESE_REWRITE} ...
|
||||
* For multiple languages, use <code>x<code/> to combine them, like 1x1024x2048
|
||||
*/
|
||||
public String excludedLanguages = "";
|
||||
|
||||
/**
|
||||
* How many results would you like per page for the index/search page
|
||||
* and torrent search pages<br/>
|
||||
* key: {@link #KEY_RESULT_COUNT}<br/>
|
||||
* value: {@link #RESULT_COUNT_25}, {@link #RESULT_COUNT_50},
|
||||
* {@link #RESULT_COUNT_100}, {@link #RESULT_COUNT_200}<br/>
|
||||
* Require <code>Hath Perk:Paging Enlargement</code>
|
||||
*/
|
||||
public String resultCount = RESULT_COUNT_25;
|
||||
|
||||
/**
|
||||
* mouse-over thumb<br/>
|
||||
* key: {@link #KEY_MOUSE_OVER}<br/>
|
||||
* value: {@link #MOUSE_OVER_YES}, {@link #MOUSE_OVER_NO}
|
||||
*/
|
||||
public String mouseOver = MOUSE_OVER_YES;
|
||||
|
||||
/**
|
||||
* Default preview mode<br/>
|
||||
* key: {@link #KEY_PREVIEW_SIZE}<br/>
|
||||
* value: {@link #PREVIEW_SIZE_NORMAL}, {@link #PREVIEW_SIZE_LARGE}
|
||||
*/
|
||||
public String previewSize = PREVIEW_SIZE_LARGE;
|
||||
|
||||
/**
|
||||
* Preview row<br/>
|
||||
* key: {@link #KEY_PREVIEW_ROW}<br/>
|
||||
* value: {@link #PREVIEW_ROW_4}, {@link #PREVIEW_ROW_10},
|
||||
* {@link #PREVIEW_ROW_20}, {@link #PREVIEW_ROW_40}
|
||||
*/
|
||||
public String previewRow = PREVIEW_ROW_4;
|
||||
|
||||
/**
|
||||
* Sort order for gallery comments<br/>
|
||||
* key: {@link #KEY_COMMENTS_SORT}<br/>
|
||||
* value: {@link #COMMENTS_SORT_OLDEST_FIRST}, {@link #COMMENTS_SORT_RECENT_FIRST},
|
||||
* {@link #COMMENTS_SORT_HIGHEST_SCORE_FIRST}
|
||||
*/
|
||||
public String commentSort = COMMENTS_SORT_OLDEST_FIRST;
|
||||
|
||||
/**
|
||||
* Show gallery comment votes mode<br/>
|
||||
* key: {@link #KEY_COMMENTS_VOTES}<br/>
|
||||
* value: {@link #COMMENTS_VOTES_POP}, {@link #COMMENTS_VOTES_ALWAYS}
|
||||
*/
|
||||
public String commentVotes = COMMENTS_VOTES_POP;
|
||||
|
||||
|
||||
/**
|
||||
* Sort order for gallery tags<br/>
|
||||
* key: {@link #KEY_TAGS_SORT}<br/>
|
||||
* value: {@link #TAGS_SORT_ALPHABETICAL}, {@link #TAGS_SORT_POWER}
|
||||
*/
|
||||
public String tagSort = TAGS_SORT_ALPHABETICAL;
|
||||
|
||||
/**
|
||||
* Show gallery page numbers<br/>
|
||||
* key: {@link #KEY_SHOW_GALLERY_INDEX}<br/>
|
||||
* value: {@link #SHOW_GALLERY_INDEX_YES}, {@link #SHOW_GALLERY_INDEX_NO}
|
||||
*/
|
||||
public String showGalleryIndex = SHOW_GALLERY_INDEX_YES;
|
||||
|
||||
/**
|
||||
* The IP of a proxy-enabled Hentai@Home Client
|
||||
* to load all images<br/>
|
||||
* key: {@link #KEY_HAH_CLIENT_IP_PORT}<br/>
|
||||
*/
|
||||
public String hahClientIp = "";
|
||||
|
||||
/**
|
||||
* The PORT of a proxy-enabled Hentai@Home Client
|
||||
* to load all images<br/>
|
||||
* key: {@link #KEY_HAH_CLIENT_IP_PORT}<br/>
|
||||
*/
|
||||
public int hahClientPort = -1;
|
||||
|
||||
/**
|
||||
* The passkey of a proxy-enabled Hentai@Home Client
|
||||
* to load all images<br/>
|
||||
* key: {@link #KEY_HAH_CLIENT_PASSKEY}<br/>
|
||||
*/
|
||||
public String hahClientPasskey = "";
|
||||
|
||||
/**
|
||||
* Enable tag flagging
|
||||
* key: {@link #KEY_ENABLE_TAG_FLAGGING}<br/>
|
||||
* value: {@link #ENABLE_TAG_FLAGGING_YES}, {@link #ENABLE_TAG_FLAGGING_NO}<br/>
|
||||
* <code>Bronze Star</code> or <code>Hath Perk: Tag Flagging</code> Required
|
||||
*/
|
||||
public String enableTagFlagging = ENABLE_TAG_FLAGGING_NO;
|
||||
|
||||
/**
|
||||
* Always display the original images instead of the resampled versions<br/>
|
||||
* key: {@link #KEY_ALWAYS_ORIGINAL}<br/>
|
||||
* value: {@link #ALWAYS_ORIGINAL_YES}, {@link #ALWAYS_ORIGINAL_NO}<br/>
|
||||
* <code>Silver Star</code> or <code>Hath Perk: Source Nexus</code> Required
|
||||
*/
|
||||
public String alwaysOriginal = ALWAYS_ORIGINAL_NO;
|
||||
|
||||
/**
|
||||
* Enable the multi-Page Viewer<br/>
|
||||
* key: {@link #KEY_MULTI_PAGE}<br/>
|
||||
* value: {@link #MULTI_PAGE_YES}, {@link #MULTI_PAGE_NO}<br/>
|
||||
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
|
||||
*/
|
||||
public String multiPage = MULTI_PAGE_NO;
|
||||
|
||||
/**
|
||||
* Multi-Page Viewer Display Style<br/>
|
||||
* key: {@link #KEY_MULTI_PAGE_STYLE}<br/>
|
||||
* value: {@link #MULTI_PAGE_STYLE_N}, {@link #MULTI_PAGE_STYLE_C},
|
||||
* {@link #MULTI_PAGE_STYLE_Y}<br/>
|
||||
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
|
||||
*/
|
||||
public String multiPageStyle = MULTI_PAGE_STYLE_N;
|
||||
|
||||
/**
|
||||
* Multi-Page Viewer Thumbnail Pane<br/>
|
||||
* key: {@link #KEY_MULTI_PAGE_THUMB}<br/>
|
||||
* value: {@link #MULTI_PAGE_THUMB_SHOW}, {@link #MULTI_PAGE_THUMB_HIDE}<br/>
|
||||
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
|
||||
*/
|
||||
public String multiPageThumb = MULTI_PAGE_THUMB_SHOW;
|
||||
|
||||
/**
|
||||
* Lofi resolution
|
||||
* key: {@link #KEY_LOFI_RESOLUTION}<br/>
|
||||
* value: {@link #LOFI_RESOLUTION_460X}, {@link #LOFI_RESOLUTION_780X},
|
||||
* {@link #LOFI_RESOLUTION_980X}
|
||||
*/
|
||||
public String lofiResolution = LOFI_RESOLUTION_980X;
|
||||
|
||||
/**
|
||||
* Show content warning
|
||||
* key: {@link #KEY_CONTENT_WARNING}<br/>
|
||||
* value: {@link #CONTENT_WARNING_SHOW}, {@link #CONTENT_WARNING_NOT_SHOW}
|
||||
*/
|
||||
public String contentWarning = CONTENT_WARNING_NOT_SHOW;
|
||||
|
||||
private String mUconfig;
|
||||
|
||||
private boolean mDirty = true;
|
||||
|
||||
@Override
|
||||
public EhConfig clone() {
|
||||
try {
|
||||
return (EhConfig) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setDirty() {
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
private void updateUconfig() {
|
||||
String hahClientIpPort = (InetValidator.isValidInet4Address(hahClientIp) &&
|
||||
InetValidator.isValidInetPort(hahClientPort)) ?
|
||||
hahClientIp + "%3A" + hahClientPort : "";
|
||||
String hahClientPasskey = null == this.hahClientPasskey ? "" : this.hahClientPasskey;
|
||||
mUconfig = KEY_LOAD_FROM_HAH + "_" + loadFromHAH + "-" +
|
||||
KEY_IMAGE_SIZE + "_" + imageSize + "-" +
|
||||
KEY_SCALE_WIDTH + "_" + scaleWidth + "-" +
|
||||
KEY_SCALE_HEIGHT + "_" + scaleHeight + "-" +
|
||||
KEY_GALLERY_TITLE + "_" + galleryTitle + "-" +
|
||||
KEY_ARCHIVER_DOWNLOAD + "_" + archiverDownload + "-" +
|
||||
KEY_LAYOUT_MODE + "_" + layoutMode + "-" +
|
||||
KEY_POPULAR + "_" + popular + "-" +
|
||||
KEY_DEFAULT_CATEGORIES + "_" + defaultCategories + "-" +
|
||||
KEY_FAVORITES_SORT + "_" + favoritesSort + "-" +
|
||||
KEY_EXCLUDED_NAMESPACES + "_" + excludedNamespaces + "-" +
|
||||
KEY_EXCLUDED_LANGUAGES + "_" + excludedLanguages + "-" +
|
||||
KEY_RESULT_COUNT + "_" + resultCount + "-" +
|
||||
KEY_MOUSE_OVER + "_" + mouseOver + "-" +
|
||||
KEY_PREVIEW_SIZE + "_" + previewSize + "-" +
|
||||
KEY_PREVIEW_ROW + "_" + previewRow + "-" +
|
||||
KEY_COMMENTS_SORT + "_" + commentSort + "-" +
|
||||
KEY_COMMENTS_VOTES + "_" + commentVotes + "-" +
|
||||
KEY_TAGS_SORT + "_" + tagSort + "-" +
|
||||
KEY_SHOW_GALLERY_INDEX + "_" + showGalleryIndex + "-" +
|
||||
KEY_HAH_CLIENT_IP_PORT + "_" + hahClientIpPort + "-" +
|
||||
KEY_HAH_CLIENT_PASSKEY + "_" + hahClientPasskey + "-" +
|
||||
KEY_ENABLE_TAG_FLAGGING + "_" + enableTagFlagging + "-" +
|
||||
KEY_ALWAYS_ORIGINAL + "_" + alwaysOriginal + "-" +
|
||||
KEY_MULTI_PAGE + "_" + multiPage + "-" +
|
||||
KEY_MULTI_PAGE_STYLE + "_" + multiPageStyle + "-" +
|
||||
KEY_MULTI_PAGE_THUMB + "_" + multiPageThumb;
|
||||
}
|
||||
|
||||
public synchronized String uconfig() {
|
||||
if (mDirty) {
|
||||
mDirty = false;
|
||||
updateUconfig();
|
||||
}
|
||||
|
||||
return mUconfig;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1058 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user