Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f21c8d39 | ||
|
|
07f27a8438 | ||
|
|
4f4dc55b4e | ||
|
|
8e9cda8e93 | ||
|
|
d5cfabd73e | ||
|
|
4f1b371e75 | ||
|
|
4d8b50912d | ||
|
|
660c717cfb | ||
|
|
70d3d9a5b2 | ||
|
|
464da25ca1 | ||
|
|
c2795120e1 | ||
|
|
b10fb46f1f | ||
|
|
e39897176a | ||
|
|
6e7e6fab74 | ||
|
|
94009b6829 | ||
|
|
289217fefc | ||
|
|
97feab1203 | ||
|
|
a9a3b20eb3 | ||
|
|
7af1e168a4 | ||
|
|
c8bf34e0d7 | ||
|
|
86d2282cd6 | ||
|
|
cfdb23d6dc | ||
|
|
1874ddd15b | ||
|
|
a004c308cc | ||
|
|
863337c789 | ||
|
|
64d91eed33 | ||
|
|
d0c3a3e14d | ||
|
|
464636d87d | ||
|
|
0b0a1f456f | ||
|
|
302030ca65 | ||
|
|
3e63738542 | ||
|
|
d6823c9c8e | ||
|
|
6bc3866660 | ||
|
|
f41f4dd8ba | ||
|
|
cde39812ce | ||
|
|
03c2abc901 | ||
|
|
7646334525 | ||
|
|
f8d1cd3fab | ||
|
|
4ea94be64a | ||
|
|
dc0470e75a | ||
|
|
5d49ecdd7d | ||
|
|
ef2441116d | ||
|
|
207909c7be | ||
|
|
ddb3dd2d95 | ||
|
|
8bb980fe97 | ||
|
|
3c3a201ed8 | ||
|
|
81bb37e929 | ||
|
|
ae34690eb2 | ||
|
|
285b0f5a69 | ||
|
|
a5327fce07 | ||
|
|
a4b750f9cd | ||
|
|
943344d4bb | ||
|
|
79f1b92960 | ||
|
|
575ae38a28 | ||
|
|
f90e2a5c80 | ||
|
|
4d9b2877fe | ||
|
|
4a955947a2 | ||
|
|
7307c136ee | ||
|
|
657e1b1d0f | ||
|
|
43edf11275 | ||
|
|
32ec4c6715 | ||
|
|
84ac57d69e | ||
|
|
328e7745b7 | ||
|
|
ccf22eaef4 | ||
|
|
c89d42f368 | ||
|
|
41192ea7f5 | ||
|
|
7840987a7c | ||
|
|
fcbe0a7f55 | ||
|
|
f12894c95d | ||
|
|
40f139fda2 | ||
|
|
b8507a371e | ||
|
|
e5a71ce906 | ||
|
|
f9e708fe1e | ||
|
|
50ba6f2bd2 | ||
|
|
2484371f8c | ||
|
|
ba675c4212 | ||
|
|
6f9bc3135a | ||
|
|
4475d4d018 | ||
|
|
08e45db49d | ||
|
|
85c76065a5 | ||
|
|
13582c3f1b | ||
|
|
ae941a4955 | ||
|
|
72da7ebfe4 | ||
|
|
8e1bacbfac | ||
|
|
747c19c028 | ||
|
|
aca83fc1ee | ||
|
|
35babd089d | ||
|
|
457ea56aab | ||
|
|
ed22de2360 | ||
|
|
8d386b44bc | ||
|
|
c4c88c40c5 | ||
|
|
e637ad6acb | ||
|
|
3b9e08e1c7 | ||
|
|
4a2aa8a975 | ||
|
|
9c08eda38a | ||
|
|
713d972d16 |
No files matched your search
@@ -7,22 +7,48 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
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;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: github-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
name: arm64-${{ 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
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Releases
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
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;24.0.8215888" --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: Releases
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
with:
|
||||
body: Bump Version
|
||||
files: |
|
||||
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
|
||||
@@ -1,12 +1,21 @@
|
||||
[submodule "native/src/main/jni/a7zip/p7zip/p7zip"]
|
||||
path = native/src/main/jni/a7zip/p7zip/p7zip
|
||||
url = https://github.com/seven332/p7zip.git
|
||||
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
|
||||
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
|
||||
url = https://gitlab.com/NekoInverter/libjpeg-turbo.git
|
||||
url = https://github.com/asuka-mio/libjpeg-turbo
|
||||
[submodule "native/src/main/jni/image/giflib/giflib"]
|
||||
path = native/src/main/jni/image/giflib/giflib
|
||||
url = https://gitlab.com/NekoInverter/giflib.git
|
||||
[submodule "native/src/main/jni/image/libpng/libpng"]
|
||||
path = native/src/main/jni/image/libpng/libpng
|
||||
url = https://gitlab.com/NekoInverter/libpng.git
|
||||
[submodule "native/src/main/jni/libarchive"]
|
||||
path = native/src/main/jni/libarchive
|
||||
url = https://github.com/asuka-mio/libarchive
|
||||
[submodule "native/src/main/jni/liblzma"]
|
||||
path = native/src/main/jni/liblzma
|
||||
url = https://git.tukaani.org/xz.git
|
||||
[submodule "native/src/main/jni/libzstd"]
|
||||
path = native/src/main/jni/libzstd
|
||||
url = https://github.com/facebook/zstd
|
||||
[submodule "native/src/main/jni/libfuse/libfuse"]
|
||||
path = native/src/main/jni/libfuse/libfuse
|
||||
url = https://github.com/libfuse/libfuse/
|
||||
@@ -1,11 +1,40 @@
|
||||
# EhViewer
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.png" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||

|
||||
<p align="center">
|
||||
<a href="https://github.com/asuka-mio/Ehviewer/actions">
|
||||
<img src="https://img.shields.io/github/workflow/status/asuka-mio/Ehviewer/EhViewer" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/asuka-mio/Ehviewer/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/asuka-mio/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/asuka-mio/ehviewer">
|
||||
<img src="https://www.codefactor.io/repository/github/asuka-mio/ehviewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/asuka-mio/Ehviewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/asuka-mio/ehviewer?include_prereleases" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/asuka-mio/Ehviewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/asuka-mio/ehviewer" alt="Issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Description
|
||||
这是一个 E-Hentai Android 平台的浏览器
|
||||
|
||||
已针对性适配 Material Design 3 及其动态取色系统
|
||||
|
||||
仅供 Android 12 及以上平台使用!
|
||||
|
||||
这是一个 E-Hentai Android 平台的浏览器。
|
||||
|
||||
An E-Hentai Application for Android.
|
||||
|
||||
with Material Design 3 and Dynamic Color Support.
|
||||
|
||||
Only Support Android S and above!
|
||||
|
||||
# Download
|
||||
|
||||
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
|
||||
@@ -15,6 +44,16 @@ An E-Hentai Application for Android.
|
||||

|
||||
|
||||
|
||||
# Build
|
||||
|
||||
本项目使用了 CMake 3.23.1 作为构建工具链之一,而当前版本的 Android Studio SDK Tool 无法满足本项目需求。
|
||||
|
||||
因此,在构建本项目前需要提前配置好 CMake >=3.23.1。
|
||||
|
||||
This Project use CMake 3.23.1, with is highter than Android Studio bundle CMake.
|
||||
|
||||
So you need to pre-install CMake>=3.23.1 before build.
|
||||
|
||||
# Thanks
|
||||
|
||||
本项目受到了诸多开源项目的帮助
|
||||
@@ -35,6 +74,8 @@ Here is the libraries
|
||||
- [ShowcaseView](https://github.com/amlcurran/ShowcaseView)
|
||||
- [Slabo](https://github.com/TiroTypeworks/Slabo)
|
||||
- [TagSoup](http://home.ccil.org/~cowan/tagsoup/)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
@@ -17,27 +17,29 @@ android {
|
||||
abi {
|
||||
enable = true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64'
|
||||
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (localProperties.getProperty("storeFile") != null) {
|
||||
sign {
|
||||
storeFile file(localProperties.getProperty("storeFile"))
|
||||
storePassword localProperties.getProperty("storePassword")
|
||||
keyAlias localProperties.getProperty("keyAlias")
|
||||
keyPassword localProperties.getProperty("keyPassword")
|
||||
}
|
||||
sign {
|
||||
storeFile file(signStoreFile)
|
||||
storePassword signStorePassword
|
||||
keyAlias signkeyAlias
|
||||
keyPassword signkeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.nekoinverter.ehviewer"
|
||||
minSdkVersion 31
|
||||
targetSdkVersion 32
|
||||
versionCode 114517
|
||||
versionName "1.7.26.8"
|
||||
versionCode 114524
|
||||
versionName "1.7.27.4"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -72,17 +74,11 @@ android {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
if (localProperties.getProperty("storeFile") != null) {
|
||||
signingConfig signingConfigs.sign
|
||||
}
|
||||
signingConfig signingConfigs.sign
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
if (localProperties.getProperty("storeFile") != null) {
|
||||
signingConfig signingConfigs.sign
|
||||
} else {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +87,10 @@ android {
|
||||
java.srcDirs += 'src/main/java-gen'
|
||||
}
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion compose
|
||||
}
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ tasks.withType(JavaCompile) {
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.activity:activity:1.4.0'
|
||||
implementation "androidx.activity:activity-compose:1.4.0"
|
||||
implementation 'androidx.biometric:biometric:1.1.0'
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'androidx.core:core:1.7.0'
|
||||
@@ -132,12 +133,17 @@ dependencies {
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.material:material:2.4.0'
|
||||
implementation 'dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1'
|
||||
implementation 'dev.rikka.rikkax.widget:borderview:1.1.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
|
||||
implementation 'org.greenrobot:greendao:3.3.0'
|
||||
implementation 'org.jsoup:jsoup:1.15.1'
|
||||
implementation projects.native
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-alpha12"
|
||||
|
||||
implementation "androidx.compose.ui:ui:$compose"
|
||||
implementation "androidx.compose.ui:ui-util:$compose"
|
||||
implementation "androidx.compose.animation:animation-graphics:$compose"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
||||
@@ -297,11 +297,6 @@ public class EhApplication extends SceneApplication {
|
||||
return application.mFavouriteStatusRouter;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getDeveloperEmail() {
|
||||
return "nekoworkshop$protonmail.com".replace('$', '@');
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
||||
@@ -457,14 +457,6 @@ public class Settings {
|
||||
putBoolean(KEY_SELECT_SITE, value);
|
||||
}
|
||||
|
||||
public static boolean getQuickSearchTip() {
|
||||
return getBoolean(KEY_QUICK_SEARCH_TIP, DEFAULT_QUICK_SEARCH_TIP);
|
||||
}
|
||||
|
||||
public static void putQuickSearchTip(boolean value) {
|
||||
putBoolean(KEY_QUICK_SEARCH_TIP, value);
|
||||
}
|
||||
|
||||
public static int getTheme() {
|
||||
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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.gallery;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.a7zip.ArchiveException;
|
||||
import com.hippo.a7zip.InArchive;
|
||||
import com.hippo.a7zip.PropID;
|
||||
import com.hippo.a7zip.PropType;
|
||||
import com.hippo.a7zip.SeekableInputStream;
|
||||
import com.hippo.unifile.UniRandomAccessFile;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class A7ZipArchive implements Closeable {
|
||||
|
||||
private final InArchive archive;
|
||||
|
||||
private A7ZipArchive(InArchive archive) {
|
||||
this.archive = archive;
|
||||
}
|
||||
|
||||
private static boolean isSupportedFilename(String name) {
|
||||
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||
if (name.endsWith(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static A7ZipArchive create(UniRandomAccessFile file) throws ArchiveException {
|
||||
SeekableInputStream store = new UniRandomAccessFileInStream(file);
|
||||
InArchive archive = InArchive.open(store);
|
||||
if ((archive.getArchivePropertyType(PropID.ENCRYPTED) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.ENCRYPTED))
|
||||
|| (archive.getArchivePropertyType(PropID.SOLID) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.SOLID))
|
||||
|| (archive.getArchivePropertyType(PropID.IS_VOLUME) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.IS_VOLUME))) {
|
||||
throw new ArchiveException("Unsupported archive");
|
||||
}
|
||||
return new A7ZipArchive(archive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
archive.close();
|
||||
}
|
||||
|
||||
List<A7ZipArchiveEntry> getArchiveEntries() {
|
||||
List<A7ZipArchiveEntry> entries = new ArrayList<>();
|
||||
|
||||
for (int i = 0, n = archive.getNumberOfEntries(); i < n; i++) {
|
||||
if (!archive.getEntryBooleanProperty(i, PropID.ENCRYPTED)
|
||||
&& !archive.getEntryBooleanProperty(i, PropID.IS_DIR)
|
||||
&& !archive.getEntryBooleanProperty(i, PropID.IS_VOLUME)
|
||||
&& !archive.getEntryBooleanProperty(i, PropID.SOLID)) {
|
||||
String path = archive.getEntryPath(i);
|
||||
if (isSupportedFilename(path.toLowerCase())) {
|
||||
entries.add(new A7ZipArchiveEntry(archive, i, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
static class A7ZipArchiveEntry {
|
||||
|
||||
private final InArchive archive;
|
||||
private final int index;
|
||||
private final String path;
|
||||
|
||||
private A7ZipArchiveEntry(InArchive archive, int index, String path) {
|
||||
this.archive = archive;
|
||||
this.index = index;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
void extract(OutputStream os) throws ArchiveException {
|
||||
archive.extractEntry(index, new OutputStreamSequentialOutStream(os));
|
||||
}
|
||||
}
|
||||
|
||||
private static class UniRandomAccessFileInStream extends SeekableInputStream {
|
||||
|
||||
private final byte[] scratch = new byte[8];
|
||||
private final UniRandomAccessFile file;
|
||||
|
||||
public UniRandomAccessFileInStream(UniRandomAccessFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
file.seek(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
return file.getFilePointer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return (file.read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return file.read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return file.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class OutputStreamSequentialOutStream extends OutputStream {
|
||||
|
||||
private final OutputStream stream;
|
||||
|
||||
public OutputStreamSequentialOutStream(OutputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
stream.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
stream.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,23 +23,18 @@ import android.os.Process;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.a7zip.ArchiveException;
|
||||
import com.hippo.UriArchiveAccessor;
|
||||
import com.hippo.ehviewer.GetText;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.glgallery.GalleryPageView;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.unifile.UniRandomAccessFile;
|
||||
import com.hippo.util.NaturalComparator;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -47,15 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||
private static final Comparator<A7ZipArchive.A7ZipArchiveEntry> naturalComparator = new Comparator<A7ZipArchive.A7ZipArchiveEntry>() {
|
||||
private final NaturalComparator comparator = new NaturalComparator();
|
||||
|
||||
@Override
|
||||
public int compare(A7ZipArchive.A7ZipArchiveEntry o1, A7ZipArchive.A7ZipArchiveEntry o2) {
|
||||
return comparator.compare(o1.getPath(), o2.getPath());
|
||||
}
|
||||
};
|
||||
private final UniFile file;
|
||||
private final UriArchiveAccessor archiveAccessor;
|
||||
private final Stack<Integer> requests = new Stack<>();
|
||||
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
|
||||
@@ -66,7 +53,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
private String error;
|
||||
|
||||
public ArchiveGalleryProvider(Context context, Uri uri) {
|
||||
file = UniFile.fromMediaUri(context, uri);
|
||||
UriArchiveAccessor archiveAccessor1;
|
||||
try {
|
||||
archiveAccessor1 = new UriArchiveAccessor(context, uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
archiveAccessor1 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
archiveAccessor = archiveAccessor1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,37 +161,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
private class ArchiveTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
UniRandomAccessFile uraf = null;
|
||||
try {
|
||||
uraf = file.createRandomAccessFile("r");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (uraf == null) {
|
||||
size = archiveAccessor.open();
|
||||
if (size <= 0) {
|
||||
size = STATE_ERROR;
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
notifyDataChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
A7ZipArchive archive = null;
|
||||
try {
|
||||
archive = A7ZipArchive.create(uraf);
|
||||
} catch (ArchiveException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (archive == null) {
|
||||
size = STATE_ERROR;
|
||||
error = GetText.getString(R.string.error_invalid_archive);
|
||||
notifyDataChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
List<A7ZipArchive.A7ZipArchiveEntry> entries = archive.getArchiveEntries();
|
||||
Collections.sort(entries, naturalComparator);
|
||||
|
||||
// Update size and notify changed
|
||||
size = entries.size();
|
||||
notifyDataChanged();
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
@@ -217,13 +188,13 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= entries.size()) {
|
||||
if (index < 0 || index >= size) {
|
||||
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
continue;
|
||||
}
|
||||
|
||||
Pipe pipe = new Pipe(4 * 1024);
|
||||
Pipe pipe = new Pipe(4096);
|
||||
|
||||
synchronized (streams) {
|
||||
if (streams.get(index) != null) {
|
||||
@@ -233,13 +204,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
streams.notify();
|
||||
}
|
||||
|
||||
try {
|
||||
entries.get(index).extract(pipe.getOutputStream());
|
||||
} catch (ArchiveException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
}
|
||||
archiveAccessor.extractTargetIndexToOutputStream(index, pipe.getOutputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,4 +247,4 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,13 +107,6 @@ class Pipe {
|
||||
public void write(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
synchronized (Pipe.this) {
|
||||
while (len != 0) {
|
||||
if (outClosed) {
|
||||
throw new IOException("The OutputStream is closed");
|
||||
}
|
||||
if (inClosed) {
|
||||
throw new IOException("The InputStream is closed");
|
||||
}
|
||||
|
||||
if (head == tail && full) {
|
||||
// The buffer is full, wait for InputStream read bytes
|
||||
try {
|
||||
|
||||
@@ -1,58 +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.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class VersionPreference extends Preference {
|
||||
|
||||
public VersionPreference(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public VersionPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public VersionPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
setTitle(R.string.settings_about_version);
|
||||
|
||||
String version;
|
||||
try {
|
||||
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
version = pInfo.versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
version = context.getString(R.string.error_unknown);
|
||||
}
|
||||
setSummary(version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.Native
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.preference.MessagePreference
|
||||
|
||||
class VersionPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : MessagePreference(context, attrs) {
|
||||
init {
|
||||
setTitle(R.string.settings_about_version)
|
||||
val version: String = try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
pInfo.versionName
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
context.getString(R.string.error_unknown)
|
||||
}
|
||||
summary = version
|
||||
}
|
||||
|
||||
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
builder.setTitle(R.string.show_library_version)
|
||||
builder.setMessage(Native.getlibarchiveVersion() + "\nlzma(xz-utils) " + Native.getliblzmaVersion() + "\nlibzstd " + Native.getlibzstdVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
@@ -66,6 +66,7 @@ import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.BuildConfig;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -221,16 +222,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
private int mLayoutMode;
|
||||
private int mSize;
|
||||
private int mCurrentIndex;
|
||||
private int mSavingPage = -1; ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
private int mSavingPage = -1;
|
||||
|
||||
private void buildProvider() {
|
||||
if (mGalleryProvider != null) {
|
||||
@@ -247,13 +239,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
} else if (Intent.ACTION_VIEW.equals(mAction)) {
|
||||
if (mUri != null) {
|
||||
// Only support zip now
|
||||
try {
|
||||
grantUriPermission(BuildConfig.APPLICATION_ID, mUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} catch (Exception e) {
|
||||
// Some stupid file manager send us a uri and don't allow us to read it
|
||||
// java.lang.SecurityException: UID 10671 does not have permission to
|
||||
// content://com.UCMobile.fileProvider/external_files/BaiduNetdisk/getvoice [user 0]
|
||||
Toast.makeText(this, R.string.error_reading_failed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mGalleryProvider = new ArchiveGalleryProvider(this, mUri);
|
||||
@@ -273,7 +261,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
mGalleryInfo = intent.getParcelableExtra(KEY_GALLERY_INFO);
|
||||
mPage = intent.getIntExtra(KEY_PAGE, -1);
|
||||
buildProvider();
|
||||
}
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
|
||||
private void onRestore(@NonNull Bundle savedInstanceState) {
|
||||
mAction = savedInstanceState.getString(KEY_ACTION);
|
||||
@@ -1051,15 +1048,15 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
private final Spinner mScaleMode;
|
||||
private final Spinner mStartPosition;
|
||||
private final Spinner mReadTheme;
|
||||
private final Switch mKeepScreenOn;
|
||||
private final Switch mShowClock;
|
||||
private final Switch mShowProgress;
|
||||
private final Switch mShowBattery;
|
||||
private final Switch mShowPageInterval;
|
||||
private final Switch mVolumePage;
|
||||
private final Switch mReverseVolumePage;
|
||||
private final Switch mReadingFullscreen;
|
||||
private final Switch mCustomScreenLightness;
|
||||
private final SwitchMaterial mKeepScreenOn;
|
||||
private final SwitchMaterial mShowClock;
|
||||
private final SwitchMaterial mShowProgress;
|
||||
private final SwitchMaterial mShowBattery;
|
||||
private final SwitchMaterial mShowPageInterval;
|
||||
private final SwitchMaterial mVolumePage;
|
||||
private final SwitchMaterial mReverseVolumePage;
|
||||
private final SwitchMaterial mReadingFullscreen;
|
||||
private final SwitchMaterial mCustomScreenLightness;
|
||||
private final SeekBar mScreenLightness;
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@@ -1304,4 +1301,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.verify.domain.DomainVerificationManager;
|
||||
import android.content.pm.verify.domain.DomainVerificationUserState;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
@@ -33,7 +32,6 @@ import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -43,7 +41,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -85,7 +82,6 @@ import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.BitmapUtils;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
import com.hippo.widget.DrawerView;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
@@ -96,8 +92,6 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import rikka.material.app.DayNightDelegate;
|
||||
|
||||
public final class MainActivity extends StageActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
@@ -139,10 +133,6 @@ public final class MainActivity extends StageActivity
|
||||
private NavigationView mNavView;
|
||||
@Nullable
|
||||
private DrawerView mRightDrawer;
|
||||
@Nullable
|
||||
private LoadImageView mAvatar;
|
||||
@Nullable
|
||||
private TextView mDisplayName;
|
||||
private int mNavCheckedItem = 0;
|
||||
|
||||
@Override
|
||||
@@ -320,19 +310,6 @@ public final class MainActivity extends StageActivity
|
||||
mDrawerLayout.setStatusBarBackgroundColor(0);
|
||||
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
|
||||
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
|
||||
View headerLayout = mNavView.getHeaderView(0);
|
||||
mAvatar = (LoadImageView) ViewUtils.$$(headerLayout, R.id.avatar);
|
||||
mDisplayName = (TextView) ViewUtils.$$(headerLayout, R.id.display_name);
|
||||
ViewUtils.$$(headerLayout, R.id.night_mode).setOnClickListener(v -> {
|
||||
int theme = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0 ? DayNightDelegate.MODE_NIGHT_NO : DayNightDelegate.MODE_NIGHT_YES;
|
||||
DayNightDelegate.setDefaultNightMode(theme);
|
||||
recreate();
|
||||
if (Settings.getTheme() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
|
||||
Settings.putTheme(theme);
|
||||
}
|
||||
});
|
||||
|
||||
updateProfile();
|
||||
|
||||
if (mNavView != null) {
|
||||
mNavView.setNavigationItemSelectedListener(this);
|
||||
@@ -440,23 +417,18 @@ public final class MainActivity extends StageActivity
|
||||
mDrawerLayout = null;
|
||||
mNavView = null;
|
||||
mRightDrawer = null;
|
||||
mAvatar = null;
|
||||
mDisplayName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
setNavCheckedItem(mNavCheckedItem);
|
||||
|
||||
checkClipboardUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransactScene() {
|
||||
super.onTransactScene();
|
||||
|
||||
checkClipboardUrl();
|
||||
}
|
||||
|
||||
@@ -546,25 +518,6 @@ public final class MainActivity extends StageActivity
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProfile() {
|
||||
if (null == mAvatar || null == mDisplayName) {
|
||||
return;
|
||||
}
|
||||
|
||||
String avatarUrl = Settings.getAvatar();
|
||||
if (TextUtils.isEmpty(avatarUrl)) {
|
||||
mAvatar.load(R.drawable.default_avatar);
|
||||
} else {
|
||||
mAvatar.load(avatarUrl, avatarUrl);
|
||||
}
|
||||
|
||||
String displayName = Settings.getDisplayName();
|
||||
if (TextUtils.isEmpty(displayName)) {
|
||||
displayName = getString(R.string.default_display_name);
|
||||
}
|
||||
mDisplayName.setText(displayName);
|
||||
}
|
||||
|
||||
public void addAboveSnackView(View view) {
|
||||
if (mStageLayout != null) {
|
||||
mStageLayout.addAboveSnackView(view);
|
||||
|
||||
@@ -21,40 +21,19 @@ import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.text.Html;
|
||||
import com.hippo.util.AppHelper;
|
||||
|
||||
public class AboutFragment extends BasePreferenceFragment {
|
||||
|
||||
private static final String KEY_AUTHOR = "author";
|
||||
//private static final String KEY_DONATE = "donate";
|
||||
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
|
||||
|
||||
@Override
|
||||
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.about_settings);
|
||||
|
||||
Preference author = findPreference(KEY_AUTHOR);
|
||||
//Preference donate = findPreference(KEY_DONATE);
|
||||
Preference checkForUpdate = findPreference(KEY_CHECK_FOR_UPDATES);
|
||||
|
||||
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
|
||||
|
||||
author.setOnPreferenceClickListener(this);
|
||||
//donate.setOnPreferenceClickListener(this);
|
||||
checkForUpdate.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
if (KEY_AUTHOR.equals(key)) {
|
||||
AppHelper.sendEmail(requireActivity(), EhApplication.getDeveloperEmail(),
|
||||
"About EhViewer", null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -58,13 +58,6 @@ public abstract class BaseScene extends SceneFragment {
|
||||
private SparseArray<Parcelable> drawerViewState;
|
||||
private boolean needWhiteStatusBar = needWhiteStatusBar();
|
||||
|
||||
public void updateAvatar() {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof MainActivity) {
|
||||
((MainActivity) activity).updateProfile();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAboveSnackView(View view) {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof MainActivity) {
|
||||
|
||||
@@ -69,7 +69,6 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
@@ -709,15 +708,10 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
final CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(
|
||||
context, getString(R.string.add_quick_search_tip), getString(R.string.get_it), false);
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setMessage(R.string.add_quick_search_tip);
|
||||
builder.setTitle(R.string.readme);
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
if (builder.isChecked()) {
|
||||
Settings.putQuickSearchTip(false);
|
||||
}
|
||||
showAddQuickSearchDialog(adapter, recyclerView, tip);
|
||||
}).show();
|
||||
builder.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -823,11 +817,9 @@ public final class GalleryListScene extends BaseScene
|
||||
toolbar.setOnMenuItemClickListener(item -> {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_add) {
|
||||
if (Settings.getQuickSearchTip()) {
|
||||
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
} else {
|
||||
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
}
|
||||
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
} else if (id == R.id.action_help) {
|
||||
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -357,7 +357,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
|
||||
public void onGetProfileEnd() {
|
||||
mSigningIn = false;
|
||||
updateAvatar();
|
||||
redirectTo();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* 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.ehviewer.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TableLayout;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.widget.CheckTextView;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
|
||||
public class CategoryTable extends TableLayout implements View.OnLongClickListener {
|
||||
|
||||
private static final String STATE_KEY_SUPER = "super";
|
||||
private static final String STATE_KEY_CATEGORY = "category";
|
||||
|
||||
private CheckTextView mDoujinshi;
|
||||
private CheckTextView mManga;
|
||||
private CheckTextView mArtistCG;
|
||||
private CheckTextView mGameCG;
|
||||
private CheckTextView mWestern;
|
||||
private CheckTextView mNonH;
|
||||
private CheckTextView mImageSets;
|
||||
private CheckTextView mCosplay;
|
||||
private CheckTextView mAsianPorn;
|
||||
private CheckTextView mMisc;
|
||||
|
||||
private CheckTextView[] mOptions;
|
||||
|
||||
public CategoryTable(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CategoryTable(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(R.layout.widget_category_table, this);
|
||||
|
||||
ViewGroup row0 = (ViewGroup) getChildAt(0);
|
||||
mDoujinshi = (CheckTextView) row0.getChildAt(0);
|
||||
mManga = (CheckTextView) row0.getChildAt(1);
|
||||
|
||||
ViewGroup row1 = (ViewGroup) getChildAt(1);
|
||||
mArtistCG = (CheckTextView) row1.getChildAt(0);
|
||||
mGameCG = (CheckTextView) row1.getChildAt(1);
|
||||
|
||||
ViewGroup row2 = (ViewGroup) getChildAt(2);
|
||||
mWestern = (CheckTextView) row2.getChildAt(0);
|
||||
mNonH = (CheckTextView) row2.getChildAt(1);
|
||||
|
||||
ViewGroup row3 = (ViewGroup) getChildAt(3);
|
||||
mImageSets = (CheckTextView) row3.getChildAt(0);
|
||||
mCosplay = (CheckTextView) row3.getChildAt(1);
|
||||
|
||||
ViewGroup row4 = (ViewGroup) getChildAt(4);
|
||||
mAsianPorn = (CheckTextView) row4.getChildAt(0);
|
||||
mMisc = (CheckTextView) row4.getChildAt(1);
|
||||
|
||||
mOptions = new CheckTextView[]{
|
||||
mDoujinshi, mManga, mArtistCG, mGameCG, mWestern,
|
||||
mNonH, mImageSets, mCosplay, mAsianPorn, mMisc
|
||||
};
|
||||
|
||||
for (CheckTextView option : mOptions) {
|
||||
option.setOnLongClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (v instanceof CheckTextView) {
|
||||
boolean checked = ((CheckTextView) v).isChecked();
|
||||
for (CheckTextView option : mOptions) {
|
||||
if (option != v) {
|
||||
option.setChecked(!checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category according to button.
|
||||
*
|
||||
* @return the category of this view
|
||||
*/
|
||||
public int getCategory() {
|
||||
int category = 0;
|
||||
if (!mDoujinshi.isChecked()) category |= EhConfig.DOUJINSHI;
|
||||
if (!mManga.isChecked()) category |= EhConfig.MANGA;
|
||||
if (!mArtistCG.isChecked()) category |= EhConfig.ARTIST_CG;
|
||||
if (!mGameCG.isChecked()) category |= EhConfig.GAME_CG;
|
||||
if (!mWestern.isChecked()) category |= EhConfig.WESTERN;
|
||||
if (!mNonH.isChecked()) category |= EhConfig.NON_H;
|
||||
if (!mImageSets.isChecked()) category |= EhConfig.IMAGE_SET;
|
||||
if (!mCosplay.isChecked()) category |= EhConfig.COSPLAY;
|
||||
if (!mAsianPorn.isChecked()) category |= EhConfig.ASIAN_PORN;
|
||||
if (!mMisc.isChecked()) category |= EhConfig.MISC;
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set each button checked or not according to category.
|
||||
*
|
||||
* @param category target category
|
||||
*/
|
||||
public void setCategory(int category) {
|
||||
mDoujinshi.setChecked(!NumberUtils.int2boolean(category & EhConfig.DOUJINSHI));
|
||||
mManga.setChecked(!NumberUtils.int2boolean(category & EhConfig.MANGA));
|
||||
mArtistCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.ARTIST_CG));
|
||||
mGameCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.GAME_CG));
|
||||
mWestern.setChecked(!NumberUtils.int2boolean(category & EhConfig.WESTERN));
|
||||
mNonH.setChecked(!NumberUtils.int2boolean(category & EhConfig.NON_H));
|
||||
mImageSets.setChecked(!NumberUtils.int2boolean(category & EhConfig.IMAGE_SET));
|
||||
mCosplay.setChecked(!NumberUtils.int2boolean(category & EhConfig.COSPLAY));
|
||||
mAsianPorn.setChecked(!NumberUtils.int2boolean(category & EhConfig.ASIAN_PORN));
|
||||
mMisc.setChecked(!NumberUtils.int2boolean(category & EhConfig.MISC));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final Bundle state = new Bundle();
|
||||
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
|
||||
state.putInt(STATE_KEY_CATEGORY, getCategory());
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
final Bundle savedState = (Bundle) state;
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
|
||||
setCategory(savedState.getInt(STATE_KEY_CATEGORY));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
|
||||
mImagePath = temp.getPath();
|
||||
mPreview.setImageBitmap(bitmap);
|
||||
mPreview.setVisibility(VISIBLE);
|
||||
((View) mPreview.getParent()).setVisibility(VISIBLE);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
@@ -166,7 +166,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
}
|
||||
mImagePath = imagePath;
|
||||
mPreview.setImageBitmap(bitmap);
|
||||
mPreview.setVisibility(VISIBLE);
|
||||
((View) mPreview.getParent()).setVisibility(VISIBLE);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Ignore
|
||||
} finally {
|
||||
|
||||
@@ -1,403 +0,0 @@
|
||||
/*
|
||||
* 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.ehviewer.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.easyrecyclerview.SimpleHolder;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.client.exception.EhException;
|
||||
import com.hippo.widget.RadioGridGroup;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnCheckedChangeListener,
|
||||
View.OnClickListener, ImageSearchLayout.Helper, TabLayout.OnTabSelectedListener {
|
||||
|
||||
public static final int SEARCH_MODE_NORMAL = 0;
|
||||
public static final int SEARCH_MODE_IMAGE = 1;
|
||||
private static final String STATE_KEY_SUPER = "super";
|
||||
private static final String STATE_KEY_SEARCH_MODE = "search_mode";
|
||||
private static final String STATE_KEY_ENABLE_ADVANCE = "enable_advance";
|
||||
private static final int ITEM_TYPE_NORMAL = 0;
|
||||
private static final int ITEM_TYPE_NORMAL_ADVANCE = 1;
|
||||
private static final int ITEM_TYPE_IMAGE = 2;
|
||||
private static final int ITEM_TYPE_ACTION = 3;
|
||||
private static final int[] SEARCH_ITEM_COUNT_ARRAY = {
|
||||
3, 2
|
||||
};
|
||||
private static final int[][] SEARCH_ITEM_TYPE = {
|
||||
{ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION}, // SEARCH_MODE_NORMAL
|
||||
{ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION}, // SEARCH_MODE_IMAGE
|
||||
};
|
||||
private LayoutInflater mInflater;
|
||||
@SearchMode
|
||||
private int mSearchMode = SEARCH_MODE_NORMAL;
|
||||
private boolean mEnableAdvance = false;
|
||||
private View mNormalView;
|
||||
private CategoryTable mCategoryTable;
|
||||
private RadioGridGroup mNormalSearchMode;
|
||||
private ImageView mNormalSearchModeHelp;
|
||||
private Switch mEnableAdvanceSwitch;
|
||||
private View mAdvanceView;
|
||||
private AdvanceSearchTable mTableAdvanceSearch;
|
||||
private ImageSearchLayout mImageView;
|
||||
private View mActionView;
|
||||
private TabLayout mAction;
|
||||
private LinearLayoutManager mLayoutManager;
|
||||
private SearchAdapter mAdapter;
|
||||
private Helper mHelper;
|
||||
|
||||
public SearchLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public SearchLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private void init(Context context) {
|
||||
Resources resources = context.getResources();
|
||||
mInflater = LayoutInflater.from(context);
|
||||
|
||||
mLayoutManager = new SearchLayoutManager(context);
|
||||
mAdapter = new SearchAdapter();
|
||||
mAdapter.setHasStableIds(true);
|
||||
setLayoutManager(mLayoutManager);
|
||||
setAdapter(mAdapter);
|
||||
setHasFixedSize(true);
|
||||
setClipToPadding(false);
|
||||
((DefaultItemAnimator) getItemAnimator()).setSupportsChangeAnimations(false);
|
||||
int interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval);
|
||||
int paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h);
|
||||
int paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v);
|
||||
MarginItemDecoration decoration = new MarginItemDecoration(
|
||||
interval, paddingH, paddingV, paddingH, paddingV);
|
||||
addItemDecoration(decoration);
|
||||
decoration.applyPaddings(this);
|
||||
|
||||
// Create normal view
|
||||
View normalView = mInflater.inflate(R.layout.search_normal, null);
|
||||
mNormalView = normalView;
|
||||
mCategoryTable = normalView.findViewById(R.id.search_category_table);
|
||||
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode);
|
||||
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help);
|
||||
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance);
|
||||
mNormalSearchModeHelp.setOnClickListener(this);
|
||||
mEnableAdvanceSwitch.setOnCheckedChangeListener(this);
|
||||
mEnableAdvanceSwitch.setSwitchPadding(resources.getDimensionPixelSize(R.dimen.switch_padding));
|
||||
|
||||
// Create advance view
|
||||
mAdvanceView = mInflater.inflate(R.layout.search_advance, null);
|
||||
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table);
|
||||
|
||||
// Create image view
|
||||
mImageView = (ImageSearchLayout) mInflater.inflate(R.layout.search_image, null);
|
||||
mImageView.setHelper(this);
|
||||
|
||||
// Create action view
|
||||
mActionView = mInflater.inflate(R.layout.search_action, null);
|
||||
mActionView.setLayoutParams(new RecyclerView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
mAction = mActionView.findViewById(R.id.action);
|
||||
mAction.addOnTabSelectedListener(this);
|
||||
}
|
||||
|
||||
public void setHelper(Helper helper) {
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
public void scrollSearchContainerToTop() {
|
||||
mLayoutManager.scrollToPositionWithOffset(0, 0);
|
||||
}
|
||||
|
||||
public void setImageUri(Uri imageUri) {
|
||||
mImageView.setImageUri(imageUri);
|
||||
}
|
||||
|
||||
public void setNormalSearchMode(int id) {
|
||||
mNormalSearchMode.check(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectImage() {
|
||||
if (mHelper != null) {
|
||||
mHelper.onSelectImage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
|
||||
super.dispatchSaveInstanceState(container);
|
||||
|
||||
mNormalView.saveHierarchyState(container);
|
||||
mAdvanceView.saveHierarchyState(container);
|
||||
mImageView.saveHierarchyState(container);
|
||||
mActionView.saveHierarchyState(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
|
||||
super.dispatchRestoreInstanceState(container);
|
||||
|
||||
mNormalView.restoreHierarchyState(container);
|
||||
mAdvanceView.restoreHierarchyState(container);
|
||||
mImageView.restoreHierarchyState(container);
|
||||
mActionView.restoreHierarchyState(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final Bundle state = new Bundle();
|
||||
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
|
||||
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode);
|
||||
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance);
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
final Bundle savedState = (Bundle) state;
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
|
||||
mSearchMode = savedState.getInt(STATE_KEY_SEARCH_MODE);
|
||||
mEnableAdvance = savedState.getBoolean(STATE_KEY_ENABLE_ADVANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (buttonView == mEnableAdvanceSwitch) {
|
||||
post(() -> {
|
||||
mEnableAdvance = isChecked;
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL) {
|
||||
if (mEnableAdvance) {
|
||||
mAdapter.notifyItemInserted(1);
|
||||
} else {
|
||||
mAdapter.notifyItemRemoved(1);
|
||||
}
|
||||
|
||||
if (mHelper != null) {
|
||||
mHelper.onChangeSearchMode();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NonConstantResourceId")
|
||||
public void formatListUrlBuilder(ListUrlBuilder urlBuilder, String query) throws EhException {
|
||||
urlBuilder.reset();
|
||||
|
||||
switch (mSearchMode) {
|
||||
case SEARCH_MODE_NORMAL:
|
||||
int nsMode = mNormalSearchMode.getCheckedRadioButtonId();
|
||||
if (nsMode == R.id.search_subscription_search) {
|
||||
urlBuilder.setMode(ListUrlBuilder.MODE_SUBSCRIPTION);
|
||||
} else if (nsMode == R.id.search_specify_uploader) {
|
||||
urlBuilder.setMode(ListUrlBuilder.MODE_UPLOADER);
|
||||
} else if (nsMode == R.id.search_specify_tag) {
|
||||
urlBuilder.setMode(ListUrlBuilder.MODE_TAG);
|
||||
} else {
|
||||
urlBuilder.setMode(ListUrlBuilder.MODE_NORMAL);
|
||||
}
|
||||
urlBuilder.setKeyword(query);
|
||||
urlBuilder.setCategory(mCategoryTable.getCategory());
|
||||
if (mEnableAdvance) {
|
||||
urlBuilder.setAdvanceSearch(mTableAdvanceSearch.getAdvanceSearch());
|
||||
urlBuilder.setMinRating(mTableAdvanceSearch.getMinRating());
|
||||
urlBuilder.setPageFrom(mTableAdvanceSearch.getPageFrom());
|
||||
urlBuilder.setPageTo(mTableAdvanceSearch.getPageTo());
|
||||
}
|
||||
break;
|
||||
case SEARCH_MODE_IMAGE:
|
||||
urlBuilder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
mImageView.formatListUrlBuilder(urlBuilder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mNormalSearchModeHelp == v) {
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(R.string.search_tip)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
post(() -> setSearchMode(tab.getPosition()));
|
||||
}
|
||||
|
||||
public void setSearchMode(@SearchMode int mode) {
|
||||
int oldItemCount = mAdapter.getItemCount();
|
||||
|
||||
mSearchMode = mode;
|
||||
|
||||
int newItemCount = mAdapter.getItemCount();
|
||||
|
||||
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1);
|
||||
mAdapter.notifyItemRangeInserted(0, newItemCount - 1);
|
||||
|
||||
if (mHelper != null) {
|
||||
mHelper.onChangeSearchMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@IntDef({SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface SearchMode {
|
||||
}
|
||||
|
||||
public interface Helper {
|
||||
void onChangeSearchMode();
|
||||
|
||||
void onSelectImage();
|
||||
}
|
||||
|
||||
static class SearchLayoutManager extends LinearLayoutManager {
|
||||
|
||||
public SearchLayoutManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
try {
|
||||
super.onLayoutChildren(recycler, state);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchAdapter extends EasyRecyclerView.Adapter<SimpleHolder> {
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode];
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
|
||||
count--;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
|
||||
type = ITEM_TYPE_ACTION;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view;
|
||||
|
||||
if (viewType == ITEM_TYPE_ACTION) {
|
||||
ViewUtils.removeFromParent(mActionView);
|
||||
view = mActionView;
|
||||
} else {
|
||||
view = mInflater.inflate(R.layout.search_category, parent, false);
|
||||
TextView title = view.findViewById(R.id.category_title);
|
||||
FrameLayout content = view.findViewById(R.id.category_content);
|
||||
switch (viewType) {
|
||||
case ITEM_TYPE_NORMAL:
|
||||
title.setText(R.string.search_normal);
|
||||
ViewUtils.removeFromParent(mNormalView);
|
||||
content.addView(mNormalView);
|
||||
break;
|
||||
case ITEM_TYPE_NORMAL_ADVANCE:
|
||||
title.setText(R.string.search_advance);
|
||||
ViewUtils.removeFromParent(mAdvanceView);
|
||||
content.addView(mAdvanceView);
|
||||
break;
|
||||
case ITEM_TYPE_IMAGE:
|
||||
title.setText(R.string.search_image);
|
||||
ViewUtils.removeFromParent(mImageView);
|
||||
content.addView(mImageView);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new SimpleHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(SimpleHolder holder, int position) {
|
||||
if (holder.getItemViewType() == ITEM_TYPE_ACTION) {
|
||||
mAction.selectTab(mAction.getTabAt(mSearchMode));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
|
||||
type = ITEM_TYPE_ACTION;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (C) 2022 tarsin norbin
|
||||
*
|
||||
* 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.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.easyrecyclerview.SimpleHolder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhConfig
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.widget.RadioGridGroup
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
class SearchLayout @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : EasyRecyclerView(context, attrs), CompoundButton.OnCheckedChangeListener, View.OnClickListener,
|
||||
ImageSearchLayout.Helper, OnTabSelectedListener {
|
||||
|
||||
@SearchMode
|
||||
private var mSearchMode = SEARCH_MODE_NORMAL
|
||||
private var mEnableAdvance = false
|
||||
private var mCategoryGroup: ChipGroup
|
||||
private var mNormalView: View
|
||||
private var mNormalSearchMode: RadioGridGroup
|
||||
private var mNormalSearchModeHelp: ImageView
|
||||
private var mEnableAdvanceSwitch: SwitchMaterial
|
||||
private var mAdvanceView: View
|
||||
private var mTableAdvanceSearch: AdvanceSearchTable
|
||||
private var mImageView: ImageSearchLayout
|
||||
private var mActionView: View
|
||||
private var mAction: TabLayout
|
||||
private var mAdapter: SearchAdapter
|
||||
private var mHelper: Helper? = null
|
||||
private val mSharePref: SharedPreferences
|
||||
private val mInflater: LayoutInflater
|
||||
|
||||
init {
|
||||
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
mInflater = LayoutInflater.from(context)
|
||||
val resources = context.resources
|
||||
layoutManager = SearchLayoutManager(context)
|
||||
mAdapter = SearchAdapter()
|
||||
mAdapter.setHasStableIds(true)
|
||||
adapter = mAdapter
|
||||
setHasFixedSize(true)
|
||||
clipToPadding = false
|
||||
(itemAnimator as DefaultItemAnimator?)?.supportsChangeAnimations = false
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v)
|
||||
val decoration = MarginItemDecoration(
|
||||
interval, paddingH, paddingV, paddingH, paddingV
|
||||
)
|
||||
addItemDecoration(decoration)
|
||||
decoration.applyPaddings(this)
|
||||
// Create normal view
|
||||
mNormalView = mInflater.inflate(R.layout.search_normal, null)
|
||||
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
|
||||
mCategoryGroup = mNormalView.findViewById(R.id.search_category_chipgroup)
|
||||
for (mPair in mCategoryTable) {
|
||||
val mChip = IdentifiedChip(context)
|
||||
mChip.isCheckable = true
|
||||
mChip.setText(mPair.second)
|
||||
mChip.idt = mPair.first
|
||||
mChip.isChecked = NumberUtils.int2boolean(mPair.first and mCategoryStored)
|
||||
mChip.setOnLongClickListener {
|
||||
mCategoryGroup.clearCheck()
|
||||
mChip.isChecked = true
|
||||
true
|
||||
}
|
||||
mCategoryGroup.addView(mChip)
|
||||
}
|
||||
mCategoryGroup.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
var mCategory = 0
|
||||
for (index in checkedIds) {
|
||||
mCategory = mCategory or findViewById<IdentifiedChip>(index).idt
|
||||
}
|
||||
mSharePref.edit { putInt(SEARCH_CATEGORY_PREF, mCategory) }
|
||||
}
|
||||
mNormalSearchMode = mNormalView.findViewById(R.id.normal_search_mode)
|
||||
mNormalSearchModeHelp = mNormalView.findViewById(R.id.normal_search_mode_help)
|
||||
mEnableAdvanceSwitch = mNormalView.findViewById(R.id.search_enable_advance)
|
||||
mNormalSearchModeHelp.setOnClickListener(this)
|
||||
mEnableAdvanceSwitch.setOnCheckedChangeListener(this)
|
||||
mEnableAdvanceSwitch.switchPadding =
|
||||
resources.getDimensionPixelSize(R.dimen.switch_padding)
|
||||
// Create advance view
|
||||
mAdvanceView = mInflater.inflate(R.layout.search_advance, null)
|
||||
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table)
|
||||
// Create image view
|
||||
mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout
|
||||
mImageView.setHelper(this)
|
||||
// Create action view
|
||||
mActionView = mInflater.inflate(R.layout.search_action, null)
|
||||
mActionView.layoutParams = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
mAction = mActionView.findViewById(R.id.action)
|
||||
mAction.addOnTabSelectedListener(this)
|
||||
}
|
||||
|
||||
|
||||
fun setHelper(helper: Helper?) {
|
||||
mHelper = helper
|
||||
}
|
||||
|
||||
fun scrollSearchContainerToTop() {
|
||||
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0)
|
||||
}
|
||||
|
||||
fun setImageUri(imageUri: Uri?) {
|
||||
mImageView.setImageUri(imageUri)
|
||||
}
|
||||
|
||||
fun setNormalSearchMode(id: Int) {
|
||||
mNormalSearchMode.check(id)
|
||||
}
|
||||
|
||||
override fun onSelectImage() {
|
||||
if (mHelper != null) {
|
||||
mHelper!!.onSelectImage()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
if (buttonView === mEnableAdvanceSwitch) {
|
||||
post {
|
||||
mEnableAdvance = isChecked
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL) {
|
||||
if (mEnableAdvance) {
|
||||
mAdapter.notifyItemInserted(1)
|
||||
} else {
|
||||
mAdapter.notifyItemRemoved(1)
|
||||
}
|
||||
if (mHelper != null) {
|
||||
mHelper!!.onChangeSearchMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NonConstantResourceId")
|
||||
@Throws(EhException::class)
|
||||
fun formatListUrlBuilder(urlBuilder: ListUrlBuilder, query: String?) {
|
||||
urlBuilder.reset()
|
||||
when (mSearchMode) {
|
||||
SEARCH_MODE_NORMAL -> {
|
||||
when (mNormalSearchMode.checkedRadioButtonId) {
|
||||
R.id.search_subscription_search -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION
|
||||
}
|
||||
R.id.search_specify_uploader -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER
|
||||
}
|
||||
R.id.search_specify_tag -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_TAG
|
||||
}
|
||||
else -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_NORMAL
|
||||
}
|
||||
}
|
||||
urlBuilder.keyword = query
|
||||
urlBuilder.category = PreferenceManager.getDefaultSharedPreferences(context).getInt(
|
||||
SEARCH_CATEGORY_PREF, 0
|
||||
)
|
||||
if (mEnableAdvance) {
|
||||
urlBuilder.advanceSearch = mTableAdvanceSearch.advanceSearch
|
||||
urlBuilder.minRating = mTableAdvanceSearch.minRating
|
||||
urlBuilder.pageFrom = mTableAdvanceSearch.pageFrom
|
||||
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
|
||||
}
|
||||
}
|
||||
SEARCH_MODE_IMAGE -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
||||
mImageView.formatListUrlBuilder(urlBuilder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (mNormalSearchModeHelp === v) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.search_tip)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
post { setSearchMode(tab.position) }
|
||||
}
|
||||
|
||||
fun setSearchMode(@SearchMode mode: Int) {
|
||||
val oldItemCount = mAdapter.itemCount
|
||||
mSearchMode = mode
|
||||
val newItemCount = mAdapter.itemCount
|
||||
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1)
|
||||
mAdapter.notifyItemRangeInserted(0, newItemCount - 1)
|
||||
if (mHelper != null) {
|
||||
mHelper!!.onChangeSearchMode()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
|
||||
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class SearchMode
|
||||
interface Helper {
|
||||
fun onChangeSearchMode()
|
||||
fun onSelectImage()
|
||||
}
|
||||
|
||||
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context) {
|
||||
override fun onLayoutChildren(recycler: Recycler, state: State) {
|
||||
try {
|
||||
super.onLayoutChildren(recycler, state)
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class SearchAdapter : Adapter<SimpleHolder>() {
|
||||
override fun getItemCount(): Int {
|
||||
var count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode]
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
|
||||
count--
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
|
||||
type = ITEM_TYPE_ACTION
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder {
|
||||
val view: View?
|
||||
if (viewType == ITEM_TYPE_ACTION) {
|
||||
ViewUtils.removeFromParent(mActionView)
|
||||
view = mActionView
|
||||
} else {
|
||||
view = mInflater.inflate(R.layout.search_category, parent, false)
|
||||
val title = view.findViewById<TextView>(R.id.category_title)
|
||||
val content = view.findViewById<FrameLayout>(R.id.category_content)
|
||||
when (viewType) {
|
||||
ITEM_TYPE_NORMAL -> {
|
||||
title.setText(R.string.search_normal)
|
||||
ViewUtils.removeFromParent(mNormalView)
|
||||
content.addView(mNormalView)
|
||||
}
|
||||
ITEM_TYPE_NORMAL_ADVANCE -> {
|
||||
title.setText(R.string.search_advance)
|
||||
ViewUtils.removeFromParent(mAdvanceView)
|
||||
content.addView(mAdvanceView)
|
||||
}
|
||||
ITEM_TYPE_IMAGE -> {
|
||||
title.setText(R.string.search_image)
|
||||
ViewUtils.removeFromParent(mImageView)
|
||||
content.addView(mImageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
return SimpleHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SimpleHolder, position: Int) {
|
||||
if (holder.itemViewType == ITEM_TYPE_ACTION) {
|
||||
mAction.selectTab(mAction.getTabAt(mSearchMode))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
|
||||
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
|
||||
type = ITEM_TYPE_ACTION
|
||||
}
|
||||
return type.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
inner class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(context, attrs) {
|
||||
var idt = 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SEARCH_MODE_NORMAL = 0
|
||||
const val SEARCH_MODE_IMAGE = 1
|
||||
const val SEARCH_CATEGORY_PREF = "search_pref"
|
||||
private const val ITEM_TYPE_NORMAL = 0
|
||||
private const val ITEM_TYPE_NORMAL_ADVANCE = 1
|
||||
private const val ITEM_TYPE_IMAGE = 2
|
||||
private const val ITEM_TYPE_ACTION = 3
|
||||
private val SEARCH_ITEM_COUNT_ARRAY = intArrayOf(
|
||||
3, 2
|
||||
)
|
||||
private val mCategoryTable: ArrayList<Pair<Int, Int>> = arrayListOf(
|
||||
Pair(EhConfig.DOUJINSHI, R.string.doujinshi),
|
||||
Pair(EhConfig.MANGA, R.string.manga),
|
||||
Pair(EhConfig.ARTIST_CG, R.string.artist_cg),
|
||||
Pair(EhConfig.GAME_CG, R.string.game_cg),
|
||||
Pair(EhConfig.WESTERN, R.string.western),
|
||||
Pair(EhConfig.NON_H, R.string.non_h),
|
||||
Pair(EhConfig.IMAGE_SET, R.string.image_set),
|
||||
Pair(EhConfig.COSPLAY, R.string.cosplay),
|
||||
Pair(EhConfig.ASIAN_PORN, R.string.asian_porn),
|
||||
Pair(EhConfig.MISC, R.string.misc)
|
||||
)
|
||||
private val SEARCH_ITEM_TYPE = arrayOf(
|
||||
intArrayOf(ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION), intArrayOf(
|
||||
ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,12 @@ public class BatteryView extends AppCompatTextView {
|
||||
private boolean mCharging = false;
|
||||
|
||||
private BatteryDrawable mDrawable;
|
||||
private boolean mAttached = false; private final Runnable mCharger = new Runnable() {
|
||||
private boolean mAttached = false;
|
||||
private boolean mIsChargerWorking = false;
|
||||
public BatteryView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
|
||||
@@ -55,43 +60,10 @@ public class BatteryView extends AppCompatTextView {
|
||||
getHandler().postDelayed(mCharger, 200);
|
||||
}
|
||||
};
|
||||
private boolean mIsChargerWorking = false;
|
||||
public BatteryView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BatteryView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
|
||||
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
|
||||
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
== BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
|
||||
if (mLevel != level || mCharging != charging) {
|
||||
mLevel = level;
|
||||
mCharging = charging;
|
||||
if (mCharging && mLevel != 100) {
|
||||
startCharger();
|
||||
} else {
|
||||
stopCharger();
|
||||
mDrawable.setElect(mLevel);
|
||||
}
|
||||
|
||||
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
|
||||
setTextColor(mWarningColor);
|
||||
} else {
|
||||
setTextColor(mColor);
|
||||
}
|
||||
setText(mLevel + "%");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
@@ -129,7 +101,36 @@ public class BatteryView extends AppCompatTextView {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
}
|
||||
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
|
||||
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
|
||||
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
== BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
|
||||
if (mLevel != level || mCharging != charging) {
|
||||
mLevel = level;
|
||||
mCharging = charging;
|
||||
if (mCharging && mLevel != 100) {
|
||||
startCharger();
|
||||
} else {
|
||||
stopCharger();
|
||||
mDrawable.setElect(mLevel);
|
||||
}
|
||||
|
||||
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
|
||||
setTextColor(mWarningColor);
|
||||
} else {
|
||||
setTextColor(mColor);
|
||||
}
|
||||
setText(mLevel + "%");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
@@ -173,4 +174,6 @@ public class BatteryView extends AppCompatTextView {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.AppCompatCheckedTextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class CheckTextView extends AppCompatCheckedTextView implements View.OnClickListener {
|
||||
|
||||
private final Rect mSelfBounds = new Rect();
|
||||
private final Rect mOverlayBounds = new Rect();
|
||||
protected boolean mForegroundInPadding = true;
|
||||
boolean mForegroundBoundsChanged = false;
|
||||
private Drawable mForeground;
|
||||
private int mForegroundGravity = Gravity.FILL;
|
||||
|
||||
public CheckTextView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckTextView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckTextView,
|
||||
defStyleAttr, 0);
|
||||
|
||||
mForegroundGravity = a.getInt(
|
||||
R.styleable.CheckTextView_android_foregroundGravity, mForegroundGravity);
|
||||
|
||||
Drawable d = a.getDrawable(R.styleable.CheckTextView_android_foreground);
|
||||
if (d != null) {
|
||||
setForeground(d);
|
||||
}
|
||||
|
||||
mForegroundInPadding = a.getBoolean(
|
||||
R.styleable.CheckTextView_foregroundInsidePadding, true);
|
||||
|
||||
a.recycle();
|
||||
setOnClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes how the foreground is positioned.
|
||||
*
|
||||
* @return foreground gravity.
|
||||
* @see #setForegroundGravity(int)
|
||||
*/
|
||||
public int getForegroundGravity() {
|
||||
return mForegroundGravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes how the foreground is positioned. Defaults to START and TOP.
|
||||
*
|
||||
* @param foregroundGravity See {@link android.view.Gravity}
|
||||
* @see #getForegroundGravity()
|
||||
*/
|
||||
public void setForegroundGravity(int foregroundGravity) {
|
||||
if (mForegroundGravity != foregroundGravity) {
|
||||
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
|
||||
foregroundGravity |= Gravity.START;
|
||||
}
|
||||
|
||||
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
|
||||
foregroundGravity |= Gravity.TOP;
|
||||
}
|
||||
|
||||
mForegroundGravity = foregroundGravity;
|
||||
|
||||
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
|
||||
Rect padding = new Rect();
|
||||
mForeground.getPadding(padding);
|
||||
}
|
||||
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(@NonNull Drawable who) {
|
||||
return super.verifyDrawable(who) || (who == mForeground);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jumpDrawablesToCurrentState() {
|
||||
super.jumpDrawablesToCurrentState();
|
||||
if (mForeground != null) {
|
||||
mForeground.jumpToCurrentState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
if (mForeground != null && mForeground.isStateful()) {
|
||||
mForeground.setState(getDrawableState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drawable used as the foreground of this FrameLayout. The
|
||||
* foreground drawable, if non-null, is always drawn on top of the children.
|
||||
*
|
||||
* @return A Drawable or null if no foreground was set.
|
||||
*/
|
||||
public Drawable getForeground() {
|
||||
return mForeground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply a Drawable that is to be rendered on top of all of the child
|
||||
* views in the frame layout. Any padding in the Drawable will be taken
|
||||
* into account by ensuring that the children are inset to be placed
|
||||
* inside of the padding area.
|
||||
*
|
||||
* @param drawable The Drawable to be drawn on top of the children.
|
||||
*/
|
||||
public void setForeground(Drawable drawable) {
|
||||
if (mForeground != drawable) {
|
||||
if (mForeground != null) {
|
||||
mForeground.setCallback(null);
|
||||
unscheduleDrawable(mForeground);
|
||||
}
|
||||
|
||||
mForeground = drawable;
|
||||
|
||||
if (drawable != null) {
|
||||
setWillNotDraw(false);
|
||||
drawable.setCallback(this);
|
||||
if (drawable.isStateful()) {
|
||||
drawable.setState(getDrawableState());
|
||||
}
|
||||
if (mForegroundGravity == Gravity.FILL) {
|
||||
Rect padding = new Rect();
|
||||
drawable.getPadding(padding);
|
||||
}
|
||||
} else {
|
||||
setWillNotDraw(true);
|
||||
}
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
mForegroundBoundsChanged |= changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mForegroundBoundsChanged = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
|
||||
if (mForeground != null) {
|
||||
final Drawable foreground = mForeground;
|
||||
|
||||
if (mForegroundBoundsChanged) {
|
||||
mForegroundBoundsChanged = false;
|
||||
final Rect selfBounds = mSelfBounds;
|
||||
final Rect overlayBounds = mOverlayBounds;
|
||||
|
||||
final int w = getRight() - getLeft();
|
||||
final int h = getBottom() - getTop();
|
||||
|
||||
if (mForegroundInPadding) {
|
||||
selfBounds.set(0, 0, w, h);
|
||||
} else {
|
||||
selfBounds.set(getPaddingLeft(), getPaddingTop(),
|
||||
w - getPaddingRight(), h - getPaddingBottom());
|
||||
}
|
||||
|
||||
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
|
||||
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
|
||||
foreground.setBounds(overlayBounds);
|
||||
}
|
||||
|
||||
foreground.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawableHotspotChanged(float x, float y) {
|
||||
super.drawableHotspotChanged(x, y);
|
||||
if (mForeground != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mForeground.setHotspot(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setChecked(!isChecked());
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,20 @@ public class TextClock extends AppCompatTextView {
|
||||
private boolean mAttached;
|
||||
|
||||
private Calendar mTime;
|
||||
private String mTimeZone; private final Runnable mTicker = new Runnable() {
|
||||
private String mTimeZone;
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
|
||||
final String timeZone = intent.getStringExtra("time-zone");
|
||||
createTime(timeZone);
|
||||
}
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
public TextClock(Context context) {
|
||||
this(context, null);
|
||||
} private final Runnable mTicker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onTimeChanged();
|
||||
@@ -66,31 +79,7 @@ public class TextClock extends AppCompatTextView {
|
||||
getHandler().postAtTime(mTicker, next);
|
||||
}
|
||||
};
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
|
||||
final String timeZone = intent.getStringExtra("time-zone");
|
||||
createTime(timeZone);
|
||||
}
|
||||
onTimeChanged();
|
||||
}
|
||||
}; private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
public TextClock(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
public TextClock(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
@@ -109,7 +98,19 @@ public class TextClock extends AppCompatTextView {
|
||||
createTime(mTimeZone);
|
||||
// Wait until onAttachedToWindow() to handle the ticker
|
||||
chooseFormat(false);
|
||||
}
|
||||
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private void createTime(String timeZone) {
|
||||
if (timeZone != null) {
|
||||
@@ -261,4 +262,6 @@ public class TextClock extends AppCompatTextView {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -14,7 +14,7 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<rikka.widget.borderview.BorderNestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/settings_read_screen_rotation"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_screen_rotation" />
|
||||
|
||||
<com.hippo.widget.CuteSpinner
|
||||
android:id="@+id/screen_rotation"
|
||||
@@ -60,8 +59,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/settings_read_reading_direction"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_reading_direction" />
|
||||
|
||||
<com.hippo.widget.CuteSpinner
|
||||
android:id="@+id/reading_direction"
|
||||
@@ -83,8 +81,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/settings_read_page_scaling"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_page_scaling" />
|
||||
|
||||
<com.hippo.widget.CuteSpinner
|
||||
android:id="@+id/page_scaling"
|
||||
@@ -106,8 +103,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/settings_read_start_position"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_start_position" />
|
||||
|
||||
<com.hippo.widget.CuteSpinner
|
||||
android:id="@+id/start_position"
|
||||
@@ -129,8 +125,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/read_theme"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/read_theme" />
|
||||
|
||||
<com.hippo.widget.CuteSpinner
|
||||
android:id="@+id/read_theme"
|
||||
@@ -142,68 +137,60 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/keep_screen_on"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_keep_screen_on"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_keep_screen_on" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_clock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_show_clock"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_show_clock" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_show_progress"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_show_progress" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_battery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_show_battery"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_show_battery" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_page_interval"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_show_page_interval"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_show_page_interval" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/volume_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_volume_page"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/reverse_volume_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_reverse_volume"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_reverse_volume" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/reading_fullscreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_reading_fullscreen"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_reading_fullscreen" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/custom_screen_lightness"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_read_custom_screen_lightness"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/settings_read_custom_screen_lightness" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/screen_lightness"
|
||||
@@ -212,4 +199,4 @@
|
||||
android:max="200" />
|
||||
|
||||
</LinearLayout>
|
||||
</rikka.widget.borderview.BorderNestedScrollView>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@@ -14,12 +14,10 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:cardBackgroundColor="?android:attr/windowBackground">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -32,7 +30,6 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="4dp"
|
||||
android:saveEnabled="false" />
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -65,41 +65,32 @@
|
||||
android:layout_alignTop="@id/thumb"
|
||||
android:layout_marginLeft="@dimen/keyline_margin"
|
||||
android:layout_marginRight="@dimen/keyline_margin"
|
||||
android:layout_toRightOf="@id/thumb"
|
||||
android:layout_toEndOf="@id/thumb"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:textSize="@dimen/text_little_large" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/uploader"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="@dimen/keyline_margin"
|
||||
android:background="?selectableItemBackground"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textSize="@dimen/text_little_small" />
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/category"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/uploader"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginRight="@dimen/keyline_margin"
|
||||
android:background="@drawable/category_background"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toRightOf="@id/uploader"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:singleLine="true"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/other_actions"
|
||||
@@ -119,47 +110,37 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<LinearLayout
|
||||
android:id="@+id/action_card"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/keyline_margin"
|
||||
android:layout_marginRight="@dimen/keyline_margin"
|
||||
app:cardUseCompatPadding="true">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<Button
|
||||
android:id="@+id/download"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download"
|
||||
style="@style/ButtonInCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
<android.widget.Space
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?attr/dividerColor" />
|
||||
<Button
|
||||
android:id="@+id/read"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/read" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/read"
|
||||
style="@style/ButtonInCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/read" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -19,8 +19,7 @@
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="2dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:layout_margin="4dp"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
@@ -29,13 +28,21 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
android:id="@+id/thumb"
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/thumb_card"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="@dimen/gallery_list_thumb_width"
|
||||
android:layout_height="@dimen/gallery_list_thumb_height"
|
||||
app:maxAspect="1.333"
|
||||
app:minAspect="0.333"
|
||||
app:retryType="click" />
|
||||
android:layout_height="@dimen/gallery_list_thumb_height">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:maxAspect="1.333"
|
||||
app:minAspect="0.333"
|
||||
app:retryType="click" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
@@ -46,15 +53,15 @@
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_toRightOf="@id/thumb" />
|
||||
android:layout_toEndOf="@id/thumb_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uploader"
|
||||
style="@style/CardMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:layout_above="@id/rating"
|
||||
android:layout_marginTop="2dp" />
|
||||
|
||||
<TextView
|
||||
@@ -62,8 +69,8 @@
|
||||
style="@style/CardMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_alignBottom="@id/thumb"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:layout_alignBottom="@id/thumb_card"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="2dp"
|
||||
@@ -77,25 +84,25 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/category"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_marginBottom="4dp" />
|
||||
android:layout_alignStart="@id/title"
|
||||
android:layout_marginBottom="2dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posted"
|
||||
style="@style/CardMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/thumb"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_alignBottom="@id/thumb_card"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/posted"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:divider="@drawable/spacer_x6"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
|
||||
@@ -19,15 +19,21 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/image"
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?selectableItemBackground"
|
||||
auto:aspect="0.667"
|
||||
auto:maxAspect="0.8"
|
||||
auto:minAspect="0.5"
|
||||
auto:retryType="longClick" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?selectableItemBackground"
|
||||
auto:aspect="0.667"
|
||||
auto:maxAspect="0.8"
|
||||
auto:minAspect="0.5"
|
||||
auto:retryType="longClick" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
|
||||
@@ -27,41 +27,4 @@
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@drawable/sadpanda_low_poly" />
|
||||
|
||||
<com.hippo.widget.LoadImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="48dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/display_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/text_small" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipToPadding="false"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/night_mode"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="6dp"
|
||||
app:srcCompat="@drawable/ic_baseline_dark_mode_24"
|
||||
app:tint="#fff" />
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -20,8 +20,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.hippo.ehviewer.widget.CategoryTable
|
||||
android:id="@+id/search_category_table"
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/search_category_chipgroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/search_item_interval" />
|
||||
@@ -89,7 +89,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Switch
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/search_enable_advance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/doujinshi"
|
||||
android:text="@string/doujinshi"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/manga"
|
||||
android:text="@string/manga"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginRight="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/artist_cg"
|
||||
android:text="@string/artist_cg"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/category_table_item_margin"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/game_cg"
|
||||
android:text="@string/game_cg"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginRight="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/western"
|
||||
android:text="@string/western"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/category_table_item_margin"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/non_h"
|
||||
android:text="@string/non_h"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginRight="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/image_set"
|
||||
android:text="@string/image_set"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/category_table_item_margin"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/cosplay"
|
||||
android:text="@string/cosplay"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_marginRight="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/asian_porn"
|
||||
android:text="@string/asian_porn"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<com.hippo.widget.CheckTextView
|
||||
style="@style/CategoryText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/category_table_item_margin"
|
||||
android:layout_marginTop="@dimen/category_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/misc"
|
||||
android:text="@string/misc"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
</merge>
|
||||
@@ -16,19 +16,26 @@
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<com.hippo.widget.FixedAspectImageView
|
||||
android:id="@+id/preview"
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxWidth="@dimen/image_search_max_size"
|
||||
android:maxHeight="@dimen/image_search_max_size"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone">
|
||||
|
||||
|
||||
<com.hippo.widget.FixedAspectImageView
|
||||
android:id="@+id/preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxWidth="@dimen/image_search_max_size"
|
||||
android:maxHeight="@dimen/image_search_max_size" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/select_image"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
@@ -44,7 +51,7 @@
|
||||
android:id="@+id/search_uss"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="true"
|
||||
android:minWidth="0dp"
|
||||
@@ -55,7 +62,7 @@
|
||||
android:id="@+id/search_osc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginStart="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="false"
|
||||
android:minWidth="0dp"
|
||||
@@ -69,7 +76,7 @@
|
||||
android:id="@+id/search_se"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="false"
|
||||
android:minWidth="0dp"
|
||||
@@ -79,7 +86,7 @@
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginStart="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="false" />
|
||||
|
||||
|
||||
@@ -14,20 +14,19 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
|
||||
android:id="@+id/action_add"
|
||||
android:icon="@drawable/v_plus_dark_x24"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="always" />
|
||||
android:showAsAction="always"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_default_download_label"
|
||||
android:icon="@drawable/v_download_box_dark_x24"
|
||||
android:title="@string/default_download_label"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/default_download_label" />
|
||||
|
||||
</menu>
|
||||
@@ -14,13 +14,18 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_help"
|
||||
android:icon="@drawable/v_help_circle_x24"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/add" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add"
|
||||
android:icon="@drawable/v_plus_dark_x24"
|
||||
android:title="@string/add"
|
||||
app:showAsAction="always" />
|
||||
android:showAsAction="always"
|
||||
android:title="@string/add" />
|
||||
|
||||
</menu>
|
||||
@@ -759,4 +759,6 @@
|
||||
<string name="app_link_not_verified_message">对于 Android 12 及更新的版本,您需要手动添加链接到已验证链接才能在 EhViewer 中打开 E-Hentai 链接。</string>
|
||||
<string name="app_link_not_verified_title">应用链接未验证</string>
|
||||
<string name="open_by_default">默认打开</string>
|
||||
|
||||
<string name="show_library_version">依赖库之版本</string>
|
||||
</resources>
|
||||
@@ -823,4 +823,6 @@
|
||||
<string name="app_link_not_verified_message">For Android 12 and newer, you need to manually add link to verified links in order to open E-Hentai links in EhViewer.</string>
|
||||
<string name="app_link_not_verified_title">App links not verified</string>
|
||||
<string name="open_by_default">Open by default</string>
|
||||
|
||||
<string name="show_library_version">Library Version</string>
|
||||
</resources>
|
||||
@@ -28,37 +28,19 @@
|
||||
<item name="android:minHeight">48dp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.CardTitle">
|
||||
<item name="android:textSize">@dimen/text_little_small</item>
|
||||
</style>
|
||||
|
||||
<style name="CardTitle">
|
||||
<item name="android:textAppearance">@style/TextAppearance.CardTitle</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Material3.TitleSmall</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:maxLines">2</item>
|
||||
<item name="android:lineSpacingExtra">0sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.CardMessage">
|
||||
<item name="android:textSize">@dimen/text_small</item>
|
||||
</style>
|
||||
|
||||
<style name="CardMessage">
|
||||
<item name="android:textAppearance">@style/TextAppearance.CardMessage</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
</style>
|
||||
|
||||
<!-- CategoryText -->
|
||||
<style name="CategoryText">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Material3.ActionBar.Subtitle</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:minHeight">@dimen/category_table_item_height</item>
|
||||
<item name="android:foreground">@drawable/check_text_view_foreground</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.CategoryTitle" parent="TextAppearance.Material3.TitleMedium">
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="save_parse_error_body"
|
||||
android:summary="@string/settings_advanced_save_parse_error_body_summary"
|
||||
android:title="@string/settings_advanced_save_parse_error_body"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="save_crash_log"
|
||||
android:summary="@string/settings_advanced_save_crash_log_summary"
|
||||
@@ -72,7 +72,7 @@
|
||||
android:title="@string/settings_advanced_proxy"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="built_in_hosts_2"
|
||||
android:summary="@string/settings_advanced_built_in_hosts_summary"
|
||||
@@ -86,7 +86,7 @@
|
||||
android:title="@string/settings_advanced_custom_hosts_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="domain_fronting"
|
||||
android:summary="@string/settings_advanced_domain_fronting_summary"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:title="@string/settings_download_download_location"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="media_scan"
|
||||
android:title="@string/settings_download_media_scan"
|
||||
@@ -66,7 +66,7 @@
|
||||
app:entryValues="@array/image_resolution_entry_values"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="download_origin_image"
|
||||
android:summary="@string/settings_download_download_origin_image_summary"
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
android:title="@string/dark_theme"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:key="black_dark_theme"
|
||||
android:title="@string/black_dark_theme"
|
||||
app:iconSpaceReserved="false" />
|
||||
@@ -111,27 +111,27 @@
|
||||
app:entryValues="@array/thumb_resolution_entry_values"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="fix_thumb_url"
|
||||
android:title="@string/settings_eh_fix_thumb_url"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="show_jpn_title"
|
||||
android:summary="@string/settings_eh_show_jpn_title_summary"
|
||||
android:title="@string/settings_eh_show_jpn_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="show_gallery_pages"
|
||||
android:summary="@string/settings_eh_show_gallery_pages_summary"
|
||||
android:title="@string/settings_eh_show_gallery_pages"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="show_tag_translations"
|
||||
android:summary="@string/settings_eh_show_tag_translations_summary"
|
||||
@@ -151,13 +151,13 @@
|
||||
android:title="@string/settings_eh_filter"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="cellular_network_warning"
|
||||
android:title="@string/settings_eh_metered_network_warning"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="request_news"
|
||||
android:title="@string/settings_request_news_title"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:title="@string/settings_privacy_pattern_protection_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="enable_secure"
|
||||
android:title="@string/settings_privacy_secure"
|
||||
|
||||
@@ -62,56 +62,56 @@
|
||||
app:entryValues="@array/read_theme_entry_values"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="keep_screen_on"
|
||||
android:title="@string/settings_read_keep_screen_on"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="gallery_show_clock"
|
||||
android:title="@string/settings_read_show_clock"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="gallery_show_progress"
|
||||
android:title="@string/settings_read_show_progress"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="gallery_show_battery"
|
||||
android:title="@string/settings_read_show_battery"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="gallery_show_page_interval"
|
||||
android:title="@string/settings_read_show_page_interval"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="volume_page"
|
||||
android:title="@string/settings_read_volume_page"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:dependency="volume_page"
|
||||
android:key="reserve_volume_page"
|
||||
android:title="@string/settings_read_reverse_volume"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="reading_fullscreen"
|
||||
android:title="@string/settings_read_reading_fullscreen"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="custom_screen_lightness"
|
||||
android:title="@string/settings_read_custom_screen_lightness"
|
||||
|
||||
|
Before Width: | Height: | Size: 735 KiB After Width: | Height: | Size: 1003 KiB |
@@ -1,3 +1,9 @@
|
||||
buildscript {
|
||||
ext {
|
||||
compose = '1.2.0-beta02'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
|
||||
|
||||
@@ -24,6 +24,6 @@ task executeDaoGenerator(type:JavaExec) {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'org.greenrobot:greendao-generator:3.3.0'
|
||||
implementation 'org.jboss.forge.roaster:roaster-api:2.24.0.Final'
|
||||
implementation 'org.jboss.forge.roaster:roaster-jdt:2.24.0.Final'
|
||||
implementation 'org.jboss.forge.roaster:roaster-api:2.25.0.Final'
|
||||
implementation 'org.jboss.forge.roaster:roaster-jdt:2.25.0.Final'
|
||||
}
|
||||
@@ -21,3 +21,10 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableAppCompileTimeRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# signing environment variable
|
||||
signStoreFile=keystore/androidkey.jks
|
||||
signStorePassword=000000
|
||||
signkeyAlias=key0
|
||||
signkeyPassword=000000
|
||||
|
||||
@@ -14,17 +14,18 @@ android {
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
arguments '-DANDROID_STL=none'
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a', 'x86_64'
|
||||
abiFilters 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version "3.23.1"
|
||||
path 'src/main/jni/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
-keep class com.hippo.a7zip.* { *; }
|
||||
-keep class com.hippo.* { *; }
|
||||
-keep class com.hippo.image.* { *; }
|
||||
@@ -5,4 +5,14 @@ public class Native {
|
||||
public static void initialize() {
|
||||
System.loadLibrary("ehviewer");
|
||||
}
|
||||
|
||||
public static native String getlibarchiveVersion();
|
||||
|
||||
public static native String getliblzmaVersion();
|
||||
|
||||
public static native String getlibzstdVersion();
|
||||
|
||||
public static native String getlibjpeg_turboVersion();
|
||||
|
||||
public static native String getzlibVersion();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.hippo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class UriArchiveAccessor implements Closeable {
|
||||
public static int OPEN_OK = 1;
|
||||
public static int OPEN_ERR = 2;
|
||||
private final OsReadableFile osf;
|
||||
|
||||
public UriArchiveAccessor(Context ctx, Uri uri) throws FileNotFoundException {
|
||||
osf = new OsReadableFile(ctx, uri);
|
||||
}
|
||||
|
||||
public int open() {
|
||||
return openArchive(osf);
|
||||
}
|
||||
|
||||
public void extractTargetIndexToOutputStream(int index, OutputStream os) {
|
||||
extracttoOutputStream(osf, index, os);
|
||||
}
|
||||
|
||||
private native int openArchive(OsReadableFile osf);
|
||||
|
||||
private native int extracttoOutputStream(OsReadableFile osf, int index, OutputStream os);
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
osf.close();
|
||||
}
|
||||
|
||||
static class OsReadableFile {
|
||||
ParcelFileDescriptor pfd;
|
||||
FileDescriptor fd;
|
||||
|
||||
public OsReadableFile(Context ctx, Uri uri) throws FileNotFoundException {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
fd = pfd.getFileDescriptor();
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException, ErrnoException {
|
||||
return Os.read(fd, b, off, len);
|
||||
}
|
||||
|
||||
public int read(byte[] b) throws IOException, ErrnoException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
public long length() throws IOException, ErrnoException {
|
||||
return Os.fstat(fd).st_size;
|
||||
}
|
||||
|
||||
public long seek(long pos, int whence) throws IOException, ErrnoException {
|
||||
try {
|
||||
return Os.lseek(fd, pos, whence);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -30;
|
||||
}
|
||||
}
|
||||
|
||||
public void rewind() throws IOException, ErrnoException {
|
||||
Os.lseek(fd, 0, OsConstants.SEEK_SET);
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException, ErrnoException {
|
||||
long pos;
|
||||
long len;
|
||||
long newpos;
|
||||
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
}
|
||||
pos = Os.lseek(fd, 0L, OsConstants.SEEK_CUR);
|
||||
len = length();
|
||||
newpos = pos + n;
|
||||
if (newpos > len) {
|
||||
newpos = len;
|
||||
}
|
||||
seek(newpos, OsConstants.SEEK_SET);
|
||||
|
||||
/* return the actual number of bytes skipped */
|
||||
return newpos - pos;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
pfd.close();
|
||||
try {
|
||||
Os.close(fd);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IOException("An ErrnoException occurs", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
public class ArchiveException extends Exception {
|
||||
|
||||
public ArchiveException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ArchiveException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.a7zip;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class FileSeekableInputStream extends SeekableInputStream {
|
||||
|
||||
private final RandomAccessFile file;
|
||||
|
||||
public FileSeekableInputStream(String path) throws FileNotFoundException {
|
||||
this(new File(path));
|
||||
}
|
||||
|
||||
public FileSeekableInputStream(File file) throws FileNotFoundException {
|
||||
this(new RandomAccessFile(file, "r"));
|
||||
}
|
||||
|
||||
public FileSeekableInputStream(RandomAccessFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
file.seek(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
return file.getFilePointer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return file.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return file.read(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
return file.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.a7zip;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class FileSeekableOutputStream extends SeekableOutputStream {
|
||||
|
||||
private final RandomAccessFile file;
|
||||
|
||||
public FileSeekableOutputStream(String path) throws FileNotFoundException {
|
||||
this(new File(path));
|
||||
}
|
||||
|
||||
public FileSeekableOutputStream(File file) throws FileNotFoundException {
|
||||
this(new RandomAccessFile(file, "rw"));
|
||||
}
|
||||
|
||||
public FileSeekableOutputStream(RandomAccessFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
file.seek(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
return file.getFilePointer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncate(long size) throws IOException {
|
||||
file.setLength(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
file.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull byte[] b) throws IOException {
|
||||
file.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
file.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class InArchive implements Closeable {
|
||||
|
||||
private long nativePtr;
|
||||
@Nullable
|
||||
private final Charset charset;
|
||||
@Nullable
|
||||
private final String password;
|
||||
|
||||
private InArchive(long nativePtr, @Nullable Charset charset, @Nullable String password) {
|
||||
this.nativePtr = nativePtr;
|
||||
this.charset = charset;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
private void checkClosed() {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("This InArchive is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes p7zip returns string in the original charset, sometimes in utf-16.
|
||||
// If it's in the original charset, each byte stores in each char,
|
||||
// so every char in the string is smaller than 0xFF.
|
||||
// But if every char in the string is smaller than 0xFF,
|
||||
// it's hard to tell the string is in utf-16 or the original charset.
|
||||
// TODO Let p7zip tell whether it have encoded the string.
|
||||
private static String applyCharsetToString(String str, Charset charset) {
|
||||
if (str == null || charset == null) {
|
||||
return str;
|
||||
}
|
||||
|
||||
int length = str.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c > 0xFF) {
|
||||
// It's not a pure byte list, can't apply charset
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
bytes[i] = (byte) str.charAt(i);
|
||||
}
|
||||
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
|
||||
private static String applyCharsetToPassword(String str, Charset charset) {
|
||||
if (str == null || charset == null) {
|
||||
return str;
|
||||
}
|
||||
|
||||
byte[] bytes = str.getBytes(charset);
|
||||
char[] chars = new char[bytes.length];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = (char) (bytes[i] & 0xFF);
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format name of this archive.
|
||||
* Empty string if can't get the name.
|
||||
*/
|
||||
@NonNull
|
||||
public String getFormatName() {
|
||||
checkClosed();
|
||||
return nativeGetFormatName(nativePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in this archive.
|
||||
* {@code -1} if get error.
|
||||
*/
|
||||
public int getNumberOfEntries() {
|
||||
checkClosed();
|
||||
return nativeGetNumberOfEntries(nativePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return one of {@link PropType}
|
||||
*/
|
||||
public PropType getArchivePropertyType(PropID propID) {
|
||||
int type = nativeGetArchivePropertyType(nativePtr, propID.ordinal());
|
||||
if (type >= 0 && type < PropType.values().length) {
|
||||
return PropType.values()[type];
|
||||
} else {
|
||||
return PropType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return the boolean property, {@code false} if get error
|
||||
*/
|
||||
public boolean getArchiveBooleanProperty(PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetArchiveBooleanProperty(nativePtr, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns int property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return the int property, {@code 0} if get error
|
||||
*/
|
||||
public int getArchiveIntProperty(PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetArchiveIntProperty(nativePtr, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns long property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return the long property, {@code 0} if get error
|
||||
*/
|
||||
public long getArchiveLongProperty(PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetArchiveLongProperty(nativePtr, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return the string property, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getArchiveStringProperty(PropID propID) {
|
||||
return getArchiveStringProperty(propID, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @param charset the charset of the string, {@code null} to let p7zip handle it
|
||||
* @return the string property, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getArchiveStringProperty(PropID propID, @Nullable Charset charset) {
|
||||
checkClosed();
|
||||
String str = nativeGetArchiveStringProperty(nativePtr, propID.ordinal());
|
||||
str = applyCharsetToString(str, charset);
|
||||
return str != null ? str : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the property for the archive.
|
||||
*
|
||||
* @param propID the id of the property
|
||||
* @return one of {@link PropType}
|
||||
*/
|
||||
public PropType getEntryPropertyType(int index, PropID propID) {
|
||||
int type = nativeGetEntryPropertyType(nativePtr, index, propID.ordinal());
|
||||
if (type >= 0 && type < PropType.values().length) {
|
||||
return PropType.values()[type];
|
||||
} else {
|
||||
return PropType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean property for the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param propID the id of the property
|
||||
* @return the boolean property, {@code false} if get error
|
||||
*/
|
||||
public boolean getEntryBooleanProperty(int index, PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetEntryBooleanProperty(nativePtr, index, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns int property for the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param propID the id of the property
|
||||
* @return the int property, {@code 0} if get error
|
||||
*/
|
||||
public int getEntryIntProperty(int index, PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetEntryIntProperty(nativePtr, index, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns long property for the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param propID the id of the property
|
||||
* @return the long property, {@code 0} if get error
|
||||
*/
|
||||
public long getEntryLongProperty(int index, PropID propID) {
|
||||
checkClosed();
|
||||
return nativeGetEntryLongProperty(nativePtr, index, propID.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string property for the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param propID the id of the property
|
||||
* @return the string property, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getEntryStringProperty(int index, PropID propID) {
|
||||
return getEntryStringProperty(index, propID, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string property for the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param propID the id of the property
|
||||
* @param charset the charset of the string, {@code null} to let p7zip handle it
|
||||
* @return the string property, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getEntryStringProperty(int index, PropID propID, @Nullable Charset charset) {
|
||||
checkClosed();
|
||||
String str = nativeGetEntryStringProperty(nativePtr, index, propID.ordinal());
|
||||
str = applyCharsetToString(str, charset);
|
||||
return str != null ? str : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @return the path, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getEntryPath(int index) {
|
||||
return getEntryPath(index, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the entry.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param charset the charset of the string, {@code null} to let p7zip handle it
|
||||
* @return the path, empty string if get error
|
||||
*/
|
||||
@NonNull
|
||||
public String getEntryPath(int index, @Nullable Charset charset) {
|
||||
return getEntryStringProperty(index, PropID.PATH, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream of the entry. Only a few archive formats support it.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @return the stream of the entry
|
||||
* @throws ArchiveException if the archive format doesn't support the operation or get error
|
||||
* @see #extractEntry(int, OutputStream)
|
||||
* @see #extractEntry(int, String, OutputStream)
|
||||
*/
|
||||
@NonNull
|
||||
public InputStream getEntryStream(int index) throws ArchiveException {
|
||||
return nativeGetEntryStream(nativePtr, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the context of the entry into the output stream.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param os the output steam to receive the content,
|
||||
* it will be closed at the end of this method
|
||||
* @throws ArchiveException if get error
|
||||
* @see #getEntryStream(int)
|
||||
*/
|
||||
public void extractEntry(int index, @NonNull OutputStream os) throws ArchiveException {
|
||||
extractEntry(index, password, os);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the context of the entry into the output stream.
|
||||
*
|
||||
* @param index the index of the entry
|
||||
* @param password the password of the entry
|
||||
* @param os the output steam to receive the content,
|
||||
* it will be closed at the end of this method
|
||||
* @throws ArchiveException if get error
|
||||
*/
|
||||
@SuppressWarnings("ThrowFromFinallyBlock")
|
||||
public void extractEntry(int index, String password, @NonNull OutputStream os) throws ArchiveException {
|
||||
try {
|
||||
checkClosed();
|
||||
nativeExtractEntry(nativePtr, index, password, os);
|
||||
} finally {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
throw new ArchiveException("Catch IOException while closing the OutputStream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (nativePtr != 0) {
|
||||
nativeClose(nativePtr);
|
||||
nativePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the archive is closed.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return nativePtr == 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static InArchive open(File file) throws ArchiveException {
|
||||
try {
|
||||
return open(new FileSeekableInputStream(file), null, null, file.getName(), new OpenVolumeInDirCallback(file.getParentFile()));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new ArchiveException("Can't open the archive: " + file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static InArchive open(SeekableInputStream stream) throws ArchiveException {
|
||||
return open(stream, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an archive to read from the specified stream.
|
||||
* <p>
|
||||
* {@code charset} is for password and string property.
|
||||
* The charset of string property can reset in
|
||||
* {@link InArchive#getArchiveStringProperty(PropID, Charset)}.
|
||||
*/
|
||||
@NonNull
|
||||
public static InArchive open(
|
||||
SeekableInputStream stream,
|
||||
@Nullable Charset charset,
|
||||
@Nullable String password,
|
||||
@Nullable String filename,
|
||||
@Nullable OpenVolumeCallback openVolumeCallback
|
||||
) throws ArchiveException {
|
||||
password = applyCharsetToPassword(password, charset);
|
||||
long nativePtr = nativeOpen(stream, password, filename, openVolumeCallback);
|
||||
|
||||
if (nativePtr == 0) {
|
||||
// It should not be 0
|
||||
throw new ArchiveException("a7zip is buggy");
|
||||
}
|
||||
|
||||
return new InArchive(nativePtr, charset, password);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public interface OpenVolumeCallback {
|
||||
@NonNull
|
||||
SeekableInputStream openVolume(String filename) throws ArchiveException;
|
||||
}
|
||||
|
||||
public static class OpenVolumeInDirCallback implements OpenVolumeCallback {
|
||||
|
||||
private final File dir;
|
||||
|
||||
public OpenVolumeInDirCallback(File dir) {
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SeekableInputStream openVolume(String filename) throws ArchiveException {
|
||||
try {
|
||||
return new FileSeekableInputStream(new File(dir, filename));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new ArchiveException("Can't find the file<" + filename + "> in dir<" + dir.getPath() + ">", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static native long nativeOpen(
|
||||
SeekableInputStream stream,
|
||||
String password,
|
||||
String filename,
|
||||
OpenVolumeCallback openVolumeCallback
|
||||
) throws ArchiveException;
|
||||
|
||||
private static native String nativeGetFormatName(long nativePtr);
|
||||
|
||||
private static native int nativeGetNumberOfEntries(long nativePtr);
|
||||
|
||||
private static native int nativeGetArchivePropertyType(long nativePtr, int propID);
|
||||
|
||||
private static native boolean nativeGetArchiveBooleanProperty(long nativePtr, int propID);
|
||||
|
||||
private static native int nativeGetArchiveIntProperty(long nativePtr, int propID);
|
||||
|
||||
private static native long nativeGetArchiveLongProperty(long nativePtr, int propID);
|
||||
|
||||
@Nullable
|
||||
private static native String nativeGetArchiveStringProperty(long nativePtr, int propID);
|
||||
|
||||
private static native int nativeGetEntryPropertyType(long nativePtr, int index, int propID);
|
||||
|
||||
private static native boolean nativeGetEntryBooleanProperty(long nativePtr, int index, int propID);
|
||||
|
||||
private static native int nativeGetEntryIntProperty(long nativePtr, int index, int propID);
|
||||
|
||||
private static native long nativeGetEntryLongProperty(long nativePtr, int index, int propID);
|
||||
|
||||
@Nullable
|
||||
private static native String nativeGetEntryStringProperty(long nativePtr, int index, int propID);
|
||||
|
||||
@NonNull
|
||||
private static native InputStream nativeGetEntryStream(long nativePtr, int index) throws ArchiveException;
|
||||
|
||||
private static native void nativeExtractEntry(long nativePtr, int index, String password, OutputStream os) throws ArchiveException;
|
||||
|
||||
private static native void nativeClose(long nativePtr);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class NativeInputStream extends InputStream {
|
||||
|
||||
private long nativePtr;
|
||||
private final byte[] scratch = new byte[8];
|
||||
|
||||
private NativeInputStream(long nativePtr) {
|
||||
this.nativePtr = nativePtr;
|
||||
}
|
||||
|
||||
private void checkClosed() {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("This JavaInputStream is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
checkClosed();
|
||||
return nativeRead(nativePtr, b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (nativePtr != 0) {
|
||||
nativeClose(nativePtr);
|
||||
nativePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException;
|
||||
|
||||
private static native void nativeClose(long nativePtr) throws IOException;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class NativeSeekableInputStream extends SeekableInputStream {
|
||||
|
||||
private long nativePtr;
|
||||
private final byte[] scratch = new byte[8];
|
||||
|
||||
private NativeSeekableInputStream(long nativePtr) {
|
||||
this.nativePtr = nativePtr;
|
||||
}
|
||||
|
||||
private void checkClosed() {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("This JavaSeekableInputStream is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
checkClosed();
|
||||
nativeSeek(nativePtr, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
checkClosed();
|
||||
return nativeTell(nativePtr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
checkClosed();
|
||||
return nativeSize(nativePtr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
checkClosed();
|
||||
return nativeRead(nativePtr, b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (nativePtr != 0) {
|
||||
nativeClose(nativePtr);
|
||||
nativePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static native void nativeSeek(long nativePtr, long pos) throws IOException;
|
||||
|
||||
private static native long nativeTell(long nativePtr) throws IOException;
|
||||
|
||||
private static native long nativeSize(long nativePtr) throws IOException;
|
||||
|
||||
private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException;
|
||||
|
||||
private static native void nativeClose(long nativePtr) throws IOException;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class PasswordException extends ArchiveException {
|
||||
|
||||
public PasswordException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PasswordException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
public enum PropID {
|
||||
NO_PROPERTY,
|
||||
MAIN_SUBFILE,
|
||||
HANDLER_ITEM_INDEX,
|
||||
PATH,
|
||||
NAME,
|
||||
EXTENSION,
|
||||
IS_DIR,
|
||||
SIZE,
|
||||
PACK_SIZE,
|
||||
ATTRIB,
|
||||
C_TIME,
|
||||
A_TIME,
|
||||
M_TIME,
|
||||
SOLID,
|
||||
COMMENTED,
|
||||
ENCRYPTED,
|
||||
SPLIT_BEFORE,
|
||||
SPLIT_AFTER,
|
||||
DICTIONARY_SIZE,
|
||||
CRC,
|
||||
TYPE,
|
||||
IS_ANTI,
|
||||
METHOD,
|
||||
HOST_OS,
|
||||
FILE_SYSTEM,
|
||||
USER,
|
||||
GROUP,
|
||||
BLOCK,
|
||||
COMMENT,
|
||||
POSITION,
|
||||
PREFIX,
|
||||
NUM_SUB_DIRS,
|
||||
NUM_SUB_FILES,
|
||||
UNPACK_VER,
|
||||
VOLUME,
|
||||
IS_VOLUME,
|
||||
OFFSET,
|
||||
LINKS,
|
||||
NUM_BLOCKS,
|
||||
NUM_VOLUMES,
|
||||
TIME_TYPE,
|
||||
BIT64,
|
||||
BIG_ENDIAN,
|
||||
CPU,
|
||||
PHY_SIZE,
|
||||
HEADERS_SIZE,
|
||||
CHECKSUM,
|
||||
CHARACTS,
|
||||
VA,
|
||||
ID,
|
||||
SHORT_NAME,
|
||||
CREATOR_APP,
|
||||
SECTOR_SIZE,
|
||||
POSIX_ATTRIB,
|
||||
SYM_LINK,
|
||||
ERROR,
|
||||
TOTAL_SIZE,
|
||||
FREE_SPACE,
|
||||
CLUSTER_SIZE,
|
||||
VOLUME_NAME,
|
||||
LOCAL_NAME,
|
||||
PROVIDER,
|
||||
NT_SECURE,
|
||||
IS_ALT_STREAM,
|
||||
IS_AUX,
|
||||
IS_DELETED,
|
||||
IS_TREE,
|
||||
SHA1,
|
||||
SHA256,
|
||||
ERROR_TYPE,
|
||||
NUM_ERRORS,
|
||||
ERROR_FLAGS,
|
||||
WARNING_FLAGS,
|
||||
WARNING,
|
||||
NUM_STREAMS,
|
||||
NUM_ALT_STREAMS,
|
||||
ALT_STREAMS_SIZE,
|
||||
VIRTUAL_SIZE,
|
||||
UNPACK_SIZE,
|
||||
TOTAL_PHY_SIZE,
|
||||
VOLUME_INDEX,
|
||||
SUB_TYPE,
|
||||
SHORT_COMMENT,
|
||||
CODE_PAGE,
|
||||
IS_NOT_ARC_TYPE,
|
||||
PHY_SIZE_CANT_BE_DETECTED,
|
||||
ZEROS_TAIL_IS_ALLOWED,
|
||||
TAIL_SIZE,
|
||||
EMBEDDED_STUB_SIZE,
|
||||
NT_REPARSE,
|
||||
HARD_LINK,
|
||||
INODE,
|
||||
STREAM_ID,
|
||||
READ_ONLY,
|
||||
OUT_NAME,
|
||||
COPY_LINK,
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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.a7zip;
|
||||
|
||||
public enum PropType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
BOOL,
|
||||
INT,
|
||||
LONG,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
STRING,
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.a7zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class SeekableInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Sets the position, measured from the beginning,
|
||||
* at which the next read occurs. The offset may be
|
||||
* set beyond the end of the file.
|
||||
*/
|
||||
public abstract void seek(long pos) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns current position, measured from the beginning.
|
||||
*/
|
||||
public abstract long tell() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the size.
|
||||
*/
|
||||
public abstract long size() throws IOException;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.a7zip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class SeekableOutputStream extends OutputStream {
|
||||
|
||||
/**
|
||||
* Sets the position, measured from the beginning,
|
||||
* at which the next write occurs. The offset may be
|
||||
* set beyond the end of the file.
|
||||
*/
|
||||
public abstract void seek(long pos) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns current position, measured from the beginning.
|
||||
*/
|
||||
public abstract long tell() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the size.
|
||||
*/
|
||||
public abstract long size() throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the size.
|
||||
*/
|
||||
public abstract void truncate(long size) throws IOException;
|
||||
}
|
||||
@@ -55,12 +55,10 @@ public final class Image {
|
||||
public static final int FORMAT_GIF = 3;
|
||||
|
||||
private static final AtomicInteger sImageCount = new AtomicInteger();
|
||||
|
||||
private long mNativePtr;
|
||||
private final int mFormat;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
|
||||
private long mNativePtr;
|
||||
private Throwable mRecycleTracker;
|
||||
|
||||
private Image(long nativePtr, int format, int width, int height) {
|
||||
@@ -72,6 +70,83 @@ public final class Image {
|
||||
sImageCount.getAndIncrement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(InputStream is, boolean partially) {
|
||||
if (!(is instanceof BufferedInputStream)) {
|
||||
is = new BufferedInputStream(is);
|
||||
}
|
||||
return nativeDecode(is, partially);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return nativeCreate(bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return sImageCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
|
||||
*/
|
||||
public static int[] getSupportedImageFormats() {
|
||||
return nativeGetSupportedImageFormats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decoder description of the image format,
|
||||
* {@code null} for invalid image format.
|
||||
*/
|
||||
public static String getDecoderDescription(int format) {
|
||||
return nativeGetDecoderDescription(format);
|
||||
}
|
||||
|
||||
private static native Image nativeDecode(InputStream is, boolean partially);
|
||||
|
||||
private static native Image nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native int nativeGetByteCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeComplete(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsCompleted(long nativePtr, int format);
|
||||
|
||||
private static native void nativeRender(long nativePtr, int format,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, int format,
|
||||
boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr, int format);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr, int format);
|
||||
|
||||
private static native int nativeFrameCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsGray(long nativePtr, int format, int error);
|
||||
|
||||
private static native void nativeClahe(long nativePtr, int format, boolean toGray);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr, int format);
|
||||
|
||||
private static native int[] nativeGetSupportedImageFormats();
|
||||
|
||||
private static native String nativeGetDecoderDescription(int format);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
@@ -218,81 +293,4 @@ public final class Image {
|
||||
public boolean isRecycled() {
|
||||
return mNativePtr == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(InputStream is, boolean partially) {
|
||||
if (!(is instanceof BufferedInputStream)) {
|
||||
is = new BufferedInputStream(is);
|
||||
}
|
||||
return nativeDecode(is, partially);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return nativeCreate(bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return sImageCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
|
||||
*/
|
||||
public static int[] getSupportedImageFormats() {
|
||||
return nativeGetSupportedImageFormats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return decoder description of the image format,
|
||||
* {@code null} for invalid image format.
|
||||
*/
|
||||
public static String getDecoderDescription(int format) {
|
||||
return nativeGetDecoderDescription(format);
|
||||
}
|
||||
|
||||
private static native Image nativeDecode(InputStream is, boolean partially);
|
||||
|
||||
private static native Image nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native int nativeGetByteCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeComplete(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsCompleted(long nativePtr, int format);
|
||||
|
||||
private static native void nativeRender(long nativePtr, int format,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, int format,
|
||||
boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr, int format);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr, int format);
|
||||
|
||||
private static native int nativeFrameCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsGray(long nativePtr, int format, int error);
|
||||
|
||||
private static native void nativeClahe(long nativePtr, int format, boolean toGray);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr, int format);
|
||||
|
||||
private static native int[] nativeGetSupportedImageFormats();
|
||||
|
||||
private static native String nativeGetDecoderDescription(int format);
|
||||
}
|
||||
@@ -40,19 +40,18 @@ import java.util.Set;
|
||||
public final class ImageBitmap implements Animatable, Runnable {
|
||||
|
||||
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
@NonNull
|
||||
private final Bitmap mBitmap;
|
||||
private final int mFormat;
|
||||
private final boolean mIsOpaque;
|
||||
private final int mByteCount;
|
||||
private final int mFrameCount;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
private int mReferences;
|
||||
private int mAnimationReferences;
|
||||
private boolean mRunning;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
|
||||
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
|
||||
int width = image.getWidth();
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Oz -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Oz -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-rtti -fno-stack-protector")
|
||||
# Build liblzma as shared lib
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
add_subdirectory(liblzma)
|
||||
|
||||
add_subdirectory(a7zip)
|
||||
# Build libzstd
|
||||
add_subdirectory(libzstd/build/cmake)
|
||||
|
||||
# Configure libarchive for android platform
|
||||
include_directories(libarchive/contrib/android/include)
|
||||
|
||||
# Configure lzma support for libarchive
|
||||
include_directories(liblzma/src/liblzma/api)
|
||||
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)
|
||||
|
||||
# Configure zstd support for libarchive
|
||||
include_directories(libzstd/lib)
|
||||
SET(HAVE_LIBZSTD 1)
|
||||
SET(HAVE_LIBZSTD_COMPRESSOR 1)
|
||||
SET(HAVE_ZSTD_H 1)
|
||||
|
||||
# Build libarchive
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build hippo's image lib
|
||||
add_subdirectory(image)
|
||||
add_subdirectory(integrity)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED main.c)
|
||||
# Build libfuse
|
||||
add_subdirectory(libfuse)
|
||||
include_directories(libfuse/libfuse/include)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -Oz -fvisibility=hidden -fvisibility-inlines-hidden -fno-exceptions)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC a7zip/a7zip image/image integrity)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} a7zip image genuine)
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED main.c archive.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC image/image libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} image archive fuse)
|
||||
@@ -1,24 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
project(a7zip C CXX)
|
||||
|
||||
add_subdirectory(p7zip)
|
||||
|
||||
set(A_SEVEN_ZIP_SOURCES
|
||||
a7zip/BlackHole.cpp
|
||||
a7zip/InArchive.cpp
|
||||
a7zip/JavaEnv.cpp
|
||||
a7zip/JavaHelper.cpp
|
||||
a7zip/JavaInArchive.cpp
|
||||
a7zip/JavaInitA7Zip.cpp
|
||||
a7zip/JavaInputStream.cpp
|
||||
a7zip/JavaSeekableInputStream.cpp
|
||||
a7zip/OpenVolumeCallback.cpp
|
||||
a7zip/OutputStream.cpp
|
||||
a7zip/SeekableInputStream.cpp
|
||||
a7zip/SevenZip.cpp
|
||||
)
|
||||
|
||||
|
||||
add_library(a7zip STATIC ${A_SEVEN_ZIP_SOURCES})
|
||||
target_link_libraries(a7zip PUBLIC p7zip log)
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "BlackHole.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
BlackHole::BlackHole(): pos(0), size(0) { }
|
||||
|
||||
HRESULT BlackHole::Write(const void*, UInt32 size, UInt32* processedSize) {
|
||||
if (processedSize != nullptr) {
|
||||
*processedSize = size;
|
||||
}
|
||||
|
||||
this->pos += size;
|
||||
if (this->size < this->pos) {
|
||||
this->size = this->pos;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BlackHole::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) {
|
||||
Int64 newPos = -1;
|
||||
|
||||
switch (seekOrigin) {
|
||||
case STREAM_SEEK_SET:
|
||||
newPos = offset;
|
||||
break;
|
||||
case STREAM_SEEK_CUR:
|
||||
newPos = this->pos + offset;
|
||||
break;
|
||||
case STREAM_SEEK_END:
|
||||
newPos = this->size + offset;
|
||||
break;
|
||||
default:
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (newPos < 0) {
|
||||
return STG_E_INVALIDFUNCTION;
|
||||
}
|
||||
|
||||
*newPosition = this->pos;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BlackHole::SetSize(UInt64 newSize) {
|
||||
this->size = newSize;
|
||||
if (this->pos > this->size) {
|
||||
this->pos = this->size;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_BLACK_HOLE_H__
|
||||
#define __A7ZIP_BLACK_HOLE_H__
|
||||
|
||||
#include <Common/MyCom.h>
|
||||
#include <7zip/IStream.h>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class BlackHole :
|
||||
public IOutStream,
|
||||
public CMyUnknownImp
|
||||
{
|
||||
public:
|
||||
BlackHole();
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP2(ISequentialOutStream, IOutStream)
|
||||
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
|
||||
STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
|
||||
STDMETHOD(SetSize)(UInt64 newSize);
|
||||
|
||||
private:
|
||||
UInt64 pos;
|
||||
UInt64 size;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_BLACK_HOLE_H__
|
||||
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "InArchive.h"
|
||||
|
||||
#include <Windows/PropVariant.h>
|
||||
#include <7zip/ICoder.h>
|
||||
#include <7zip/IPassword.h>
|
||||
|
||||
#include "BlackHole.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
class ArchiveExtractCallback :
|
||||
public IArchiveExtractCallback,
|
||||
public ICryptoGetTextPassword,
|
||||
public CMyUnknownImp
|
||||
{
|
||||
public:
|
||||
ArchiveExtractCallback(UInt32 index, BSTR password, CMyComPtr<ISequentialOutStream>& out_stream);
|
||||
~ArchiveExtractCallback();
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP2(IArchiveExtractCallback, ICryptoGetTextPassword)
|
||||
|
||||
STDMETHOD(SetTotal)(UInt64 total);
|
||||
STDMETHOD(SetCompleted)(const UInt64 *completeValue);
|
||||
|
||||
STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream** outStream, Int32 askExtractMode);
|
||||
STDMETHOD(PrepareOperation)(Int32 askExtractMode);
|
||||
STDMETHOD(SetOperationResult)(Int32 opRes);
|
||||
|
||||
STDMETHOD(CryptoGetTextPassword)(BSTR *password);
|
||||
|
||||
HRESULT GetBetterResult(HRESULT result);
|
||||
|
||||
private:
|
||||
UInt32 index;
|
||||
BSTR password;
|
||||
CMyComPtr<ISequentialOutStream> out_stream;
|
||||
bool has_asked_password;
|
||||
};
|
||||
|
||||
ArchiveExtractCallback::ArchiveExtractCallback(
|
||||
UInt32 index,
|
||||
BSTR password,
|
||||
CMyComPtr<ISequentialOutStream>& out_stream
|
||||
) :
|
||||
index(index),
|
||||
password(::SysAllocString(password)),
|
||||
out_stream(out_stream),
|
||||
has_asked_password(false) {}
|
||||
|
||||
ArchiveExtractCallback::~ArchiveExtractCallback() {
|
||||
::SysFreeString(password);
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::SetTotal(UInt64 total) {
|
||||
// Ignored
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::SetCompleted(const UInt64 *completeValue) {
|
||||
// Ignored
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::GetStream(
|
||||
UInt32 index,
|
||||
ISequentialOutStream** outStream,
|
||||
Int32 askExtractMode
|
||||
) {
|
||||
// If it's not extract mode or the index is different, return a black hole to skip data
|
||||
if (askExtractMode != NArchive::NExtract::NAskMode::kExtract || this->index != index) {
|
||||
CMyComPtr<ISequentialOutStream> black_hole(new BlackHole());
|
||||
*outStream = black_hole.Detach();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (out_stream == nullptr) {
|
||||
return E_NO_OUT_STREAM;
|
||||
}
|
||||
|
||||
CMyComPtr<ISequentialOutStream> steam_copy(out_stream);
|
||||
*outStream = steam_copy.Detach();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) {
|
||||
// Always return S_OK
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::SetOperationResult(Int32 opRes) {
|
||||
// Stop extracting action if operation result is not OK
|
||||
switch (opRes) {
|
||||
case NArchive::NExtract::NOperationResult::kOK:
|
||||
return S_OK;
|
||||
case NArchive::NExtract::NOperationResult::kUnsupportedMethod:
|
||||
return E_UNSUPPORTED_METHOD;
|
||||
case NArchive::NExtract::NOperationResult::kDataError:
|
||||
return E_DATA_ERROR;
|
||||
case NArchive::NExtract::NOperationResult::kCRCError:
|
||||
return E_CRC_ERROR;
|
||||
case NArchive::NExtract::NOperationResult::kUnavailable:
|
||||
return E_UNAVAILABLE;
|
||||
case NArchive::NExtract::NOperationResult::kUnexpectedEnd:
|
||||
return E_UNEXPECTED_END;
|
||||
case NArchive::NExtract::NOperationResult::kDataAfterEnd:
|
||||
return E_DATA_AFTER_END;
|
||||
case NArchive::NExtract::NOperationResult::kIsNotArc:
|
||||
return E_IS_NOT_ARC;
|
||||
case NArchive::NExtract::NOperationResult::kHeadersError:
|
||||
return E_HEADERS_ERROR;
|
||||
case NArchive::NExtract::NOperationResult::kWrongPassword:
|
||||
return E_WRONG_PASSWORD;
|
||||
default:
|
||||
return E_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::CryptoGetTextPassword(BSTR* password) {
|
||||
has_asked_password = true;
|
||||
*password = ::SysAllocString(this->password);
|
||||
return this->password != nullptr ? S_OK : E_NO_PASSWORD;
|
||||
}
|
||||
|
||||
HRESULT ArchiveExtractCallback::GetBetterResult(HRESULT result) {
|
||||
if (result == S_OK) {
|
||||
return S_OK;
|
||||
} else if (has_asked_password) {
|
||||
if (password != nullptr) {
|
||||
return E_WRONG_PASSWORD;
|
||||
} else {
|
||||
return E_NO_PASSWORD;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
InArchive::InArchive(
|
||||
InArchive* parent,
|
||||
CMyComPtr<IInArchive>& in_archive,
|
||||
AString& format_name
|
||||
) :
|
||||
parent(parent),
|
||||
in_archive(in_archive),
|
||||
format_name(format_name) { }
|
||||
|
||||
InArchive::~InArchive() {
|
||||
this->in_archive->Close();
|
||||
if (parent != nullptr) {
|
||||
delete parent;
|
||||
parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const AString& InArchive::GetFormatName() {
|
||||
return this->format_name;
|
||||
}
|
||||
|
||||
HRESULT InArchive::GetNumberOfEntries(UInt32& number) {
|
||||
return this->in_archive->GetNumberOfItems(&number);
|
||||
}
|
||||
|
||||
static PropType VarTypeToPropType(VARTYPE var_enum) {
|
||||
// TODO VT_ERROR
|
||||
switch (var_enum) {
|
||||
case VT_EMPTY:
|
||||
return PT_EMPTY;
|
||||
case VT_BOOL:
|
||||
return PT_BOOL;
|
||||
case VT_I1:
|
||||
case VT_I2:
|
||||
case VT_I4:
|
||||
case VT_INT:
|
||||
case VT_UI1:
|
||||
case VT_UI2:
|
||||
case VT_UI4:
|
||||
case VT_UINT:
|
||||
return PT_INT;
|
||||
case VT_I8:
|
||||
case VT_UI8:
|
||||
case VT_FILETIME:
|
||||
return PT_LONG;
|
||||
case VT_BSTR:
|
||||
return PT_STRING;
|
||||
default:
|
||||
return PT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
#define GET_ARCHIVE_PROPERTY_START(METHOD_NAME, VALUE_TYPE) \
|
||||
HRESULT InArchive::METHOD_NAME(PROPID prop_id, VALUE_TYPE value) { \
|
||||
NWindows::NCOM::CPropVariant prop; \
|
||||
RETURN_SAME_IF_NOT_ZERO(this->in_archive->GetArchiveProperty(prop_id, &prop));
|
||||
|
||||
#define GET_ARCHIVE_PROPERTY_END \
|
||||
}
|
||||
|
||||
#define GET_ENTRY_PROPERTY_START(METHOD_NAME, VALUE_TYPE) \
|
||||
HRESULT InArchive::METHOD_NAME(UInt32 index, PROPID prop_id, VALUE_TYPE value) { \
|
||||
NWindows::NCOM::CPropVariant prop; \
|
||||
RETURN_SAME_IF_NOT_ZERO(this->in_archive->GetProperty(index, prop_id, &prop));
|
||||
|
||||
#define GET_ENTRY_PROPERTY_END \
|
||||
}
|
||||
|
||||
#define GET_PROPERTY_TYPE \
|
||||
*value = VarTypeToPropType(prop.vt); \
|
||||
return S_OK;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(GetArchivePropertyType, PropType*)
|
||||
GET_PROPERTY_TYPE
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(GetEntryPropertyType, PropType*)
|
||||
GET_PROPERTY_TYPE
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_BOOLEAN_PROPERTY \
|
||||
switch (prop.vt) { \
|
||||
case VT_BOOL: \
|
||||
*value = prop.boolVal != 0; \
|
||||
return S_OK; \
|
||||
case VT_EMPTY: \
|
||||
return E_EMPTY_PROP; \
|
||||
default: \
|
||||
return E_INCONSISTENT_PROP_TYPE; \
|
||||
}
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(GetArchiveBooleanProperty, bool*)
|
||||
GET_BOOLEAN_PROPERTY
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(GetEntryBooleanProperty, bool*)
|
||||
GET_BOOLEAN_PROPERTY
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_INT_PROPERTY \
|
||||
switch (prop.vt) { \
|
||||
case VT_I1: \
|
||||
*value = prop.cVal; \
|
||||
return S_OK; \
|
||||
case VT_I2: \
|
||||
*value = prop.iVal; \
|
||||
return S_OK; \
|
||||
case VT_I4: \
|
||||
*value = prop.lVal; \
|
||||
return S_OK; \
|
||||
case VT_INT: \
|
||||
*value = prop.intVal; \
|
||||
return S_OK; \
|
||||
case VT_UI1: \
|
||||
*value = prop.bVal; \
|
||||
return S_OK; \
|
||||
case VT_UI2: \
|
||||
*value = prop.uiVal; \
|
||||
return S_OK; \
|
||||
case VT_UI4: \
|
||||
*value = prop.ulVal; \
|
||||
return S_OK; \
|
||||
case VT_UINT: \
|
||||
*value = prop.uintVal; \
|
||||
return S_OK; \
|
||||
case VT_EMPTY: \
|
||||
return E_EMPTY_PROP; \
|
||||
default: \
|
||||
return E_INCONSISTENT_PROP_TYPE; \
|
||||
}
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(GetArchiveIntProperty, Int32*)
|
||||
GET_INT_PROPERTY
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(GetEntryIntProperty, Int32*)
|
||||
GET_INT_PROPERTY
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
static const UInt64 FILE_TIME_OFFSET = (369 * 365 + 89) * 86400ULL * 10000000ULL;
|
||||
static const UInt64 FILE_TIME_MULTIPLE = 10000ULL;
|
||||
|
||||
#define GET_LONG_PROPERTY \
|
||||
switch (prop.vt) { \
|
||||
case VT_I8: \
|
||||
*value = prop.hVal.QuadPart; \
|
||||
return S_OK; \
|
||||
case VT_UI8: \
|
||||
*value = prop.uhVal.QuadPart; \
|
||||
return S_OK; \
|
||||
case VT_FILETIME: \
|
||||
*value = (*reinterpret_cast<UInt64*>(&prop.filetime) - FILE_TIME_OFFSET) / \
|
||||
FILE_TIME_MULTIPLE; \
|
||||
return S_OK; \
|
||||
case VT_EMPTY: \
|
||||
return E_EMPTY_PROP; \
|
||||
default: \
|
||||
return E_INCONSISTENT_PROP_TYPE; \
|
||||
}
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(GetArchiveLongProperty, Int64*)
|
||||
GET_LONG_PROPERTY
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(GetEntryLongProperty, Int64*)
|
||||
GET_LONG_PROPERTY
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_STRING_PROPERTY \
|
||||
switch (prop.vt) { \
|
||||
case VT_BSTR: \
|
||||
*value = ::SysAllocString(prop.bstrVal); \
|
||||
return S_OK; \
|
||||
case VT_EMPTY: \
|
||||
return E_EMPTY_PROP; \
|
||||
default: \
|
||||
return E_INCONSISTENT_PROP_TYPE; \
|
||||
}
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(GetArchiveStringProperty, BSTR*)
|
||||
GET_STRING_PROPERTY
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(GetEntryStringProperty, BSTR*)
|
||||
GET_STRING_PROPERTY
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
HRESULT InArchive::GetEntryStream(UInt32 index, ISequentialInStream** stream) {
|
||||
*stream = nullptr;
|
||||
CMyComPtr<IInArchiveGetStream> in_archive_get_stream;
|
||||
in_archive->QueryInterface(IID_IInArchiveGetStream, reinterpret_cast<void **>(&in_archive_get_stream));
|
||||
if (in_archive_get_stream != nullptr) {
|
||||
return in_archive_get_stream->GetStream(index, stream);
|
||||
} else {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT InArchive::ExtractEntry(UInt32 index, BSTR password, CMyComPtr<ISequentialOutStream>& out_stream) {
|
||||
CMyComPtr<ArchiveExtractCallback> callback(new ArchiveExtractCallback(index, password, out_stream));
|
||||
HRESULT result = this->in_archive->Extract(&index, 1, false, callback);
|
||||
return callback->GetBetterResult(result);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_IN_ARCHIVE_H__
|
||||
#define __A7ZIP_IN_ARCHIVE_H__
|
||||
|
||||
#include <include_windows/windows.h>
|
||||
#include <Common/MyCom.h>
|
||||
#include <Common/MyString.h>
|
||||
#include <7zip/Archive/IArchive.h>
|
||||
|
||||
#include "PropType.h"
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class InArchive {
|
||||
public:
|
||||
InArchive(InArchive* parent, CMyComPtr<IInArchive>& in_archive, AString& format_name);
|
||||
~InArchive();
|
||||
|
||||
public:
|
||||
const AString& GetFormatName();
|
||||
HRESULT GetNumberOfEntries(UInt32& number);
|
||||
|
||||
HRESULT GetArchivePropertyType(PROPID prop_id, PropType* prop_type);
|
||||
HRESULT GetArchiveBooleanProperty(PROPID prop_id, bool *bool_prop);
|
||||
HRESULT GetArchiveIntProperty(PROPID prop_id, Int32* int_prop);
|
||||
HRESULT GetArchiveLongProperty(PROPID prop_id, Int64* long_prop);
|
||||
HRESULT GetArchiveStringProperty(PROPID prop_id, BSTR* str_prop);
|
||||
|
||||
HRESULT GetEntryPropertyType(UInt32 index, PROPID prop_id, PropType* prop_type);
|
||||
HRESULT GetEntryBooleanProperty(UInt32 index, PROPID prop_id, bool *bool_prop);
|
||||
HRESULT GetEntryIntProperty(UInt32 index, PROPID prop_id, Int32* int_prop);
|
||||
HRESULT GetEntryLongProperty(UInt32 index, PROPID prop_id, Int64* long_prop);
|
||||
HRESULT GetEntryStringProperty(UInt32 index, PROPID prop_id, BSTR* str_prop);
|
||||
|
||||
HRESULT GetEntryStream(UInt32 index, ISequentialInStream** stream);
|
||||
|
||||
HRESULT ExtractEntry(UInt32 index, BSTR password, CMyComPtr<ISequentialOutStream>& out_stream);
|
||||
|
||||
private:
|
||||
InArchive* parent;
|
||||
CMyComPtr<IInArchive> in_archive;
|
||||
AString format_name;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_IN_ARCHIVE_H__
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "JavaEnv.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
JavaVM* JavaEnv::jvm = nullptr;
|
||||
|
||||
void JavaEnv::Initialize(JavaVM* jvm) {
|
||||
JavaEnv::jvm = jvm;
|
||||
}
|
||||
|
||||
JavaEnv::JavaEnv() {
|
||||
env = nullptr;
|
||||
if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_EDETACHED) {
|
||||
should_detach = jvm->AttachCurrentThread(&env, nullptr) == JNI_OK;
|
||||
} else {
|
||||
should_detach = false;
|
||||
}
|
||||
}
|
||||
|
||||
JavaEnv::~JavaEnv() {
|
||||
if (jvm != nullptr && should_detach) {
|
||||
jvm->DetachCurrentThread();
|
||||
env = nullptr;
|
||||
should_detach = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool JavaEnv::IsValid() {
|
||||
return env != nullptr;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_JAVA_ENV_H__
|
||||
#define __A7ZIP_JAVA_ENV_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class JavaEnv {
|
||||
|
||||
public:
|
||||
JavaEnv();
|
||||
~JavaEnv();
|
||||
bool IsValid();
|
||||
JNIEnv* operator->() const { return env; }
|
||||
explicit operator JNIEnv*() const { return env; }
|
||||
|
||||
private:
|
||||
JNIEnv* env;
|
||||
bool should_detach;
|
||||
|
||||
public:
|
||||
static void Initialize(JavaVM* jvm);
|
||||
|
||||
private:
|
||||
static JavaVM* jvm;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_JAVA_ENV_H__
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "JavaHelper.h"
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
#define MAX_MSG_SIZE 1024
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
static jint ThrowExceptionInternal(
|
||||
JNIEnv* env,
|
||||
const char* exception_name,
|
||||
const char* message,
|
||||
va_list va_args
|
||||
) {
|
||||
char formatted_message[MAX_MSG_SIZE];
|
||||
vsnprintf(formatted_message, MAX_MSG_SIZE, message, va_args);
|
||||
jclass exception_class = env->FindClass(exception_name);
|
||||
return env->ThrowNew(exception_class, formatted_message);
|
||||
}
|
||||
|
||||
jint JavaHelper::ThrowException(
|
||||
JNIEnv* env,
|
||||
const char* exception_name,
|
||||
const char* message,
|
||||
...
|
||||
) {
|
||||
va_list va_args;
|
||||
va_start(va_args, message);
|
||||
jint result = ThrowExceptionInternal(env, exception_name, message, va_args);
|
||||
va_end(va_args);
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char* GetMessageForCode(HRESULT code) {
|
||||
switch (code) {
|
||||
case E_NOT_INITIALIZED:
|
||||
return "The module is not initialized";
|
||||
case S_OK:
|
||||
case E_INTERNAL:
|
||||
return "a7zip is buggy";
|
||||
case E_CLASS_NOT_FOUND:
|
||||
return "Can't find the class";
|
||||
case E_METHOD_NOT_FOUND:
|
||||
return "Can't find the method";
|
||||
case E_JAVA_EXCEPTION:
|
||||
return "Catch a java exception";
|
||||
case E_FAILED_CONSTRUCT:
|
||||
return "Failed to create new class";
|
||||
case E_FAILED_REGISTER:
|
||||
return "Failed to register methods";
|
||||
case E_INCONSISTENT_PROP_TYPE:
|
||||
return "Inconsistent property type";
|
||||
case E_EMPTY_PROP:
|
||||
return "Empty property";
|
||||
case E_UNKNOWN_FORMAT:
|
||||
return "Unknown archive format";
|
||||
case E_UNSUPPORTED_EXTRACT_MODE:
|
||||
return "Unsupported extract mode";
|
||||
case E_NO_OUT_STREAM:
|
||||
return "No out stream";
|
||||
case E_UNSUPPORTED_METHOD:
|
||||
return "Unsupported method";
|
||||
case E_DATA_ERROR:
|
||||
return "Data error";
|
||||
case E_DATA_ERROR_ENCRYPTED:
|
||||
return "Data Error in encrypted file. Wrong password?";
|
||||
case E_CRC_ERROR:
|
||||
return "CRC failed";
|
||||
case E_CRC_ERROR_ENCRYPTED:
|
||||
return "CRC Failed in encrypted file. Wrong password?";
|
||||
case E_UNAVAILABLE:
|
||||
return "Unavailable data";
|
||||
case E_UNEXPECTED_END:
|
||||
return "Unexpected end of data";
|
||||
case E_DATA_AFTER_END:
|
||||
return "There are some data after the end of the payload data";
|
||||
case E_IS_NOT_ARC:
|
||||
return "Is not archive";
|
||||
case E_HEADERS_ERROR:
|
||||
return "Headers Error";
|
||||
case E_WRONG_PASSWORD:
|
||||
return "Wrong password";
|
||||
case E_NO_PASSWORD:
|
||||
return "No password";
|
||||
case E_NOTIMPL:
|
||||
return "Not implemented";
|
||||
case E_OUTOFMEMORY:
|
||||
return "Out of memory";
|
||||
case E_UNKNOWN_ERROR:
|
||||
default:
|
||||
return "Unknown error.";
|
||||
}
|
||||
}
|
||||
|
||||
jint JavaHelper::ThrowException(
|
||||
JNIEnv* env,
|
||||
const char* exception_name,
|
||||
HRESULT code
|
||||
) {
|
||||
return ThrowException(env, exception_name, GetMessageForCode(code));
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_JAVA_HELPER_H__
|
||||
#define __A7ZIP_JAVA_HELPER_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyWindows.h>
|
||||
|
||||
#define CLASS_NAME_ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException"
|
||||
#define CLASS_NAME_IO_EXCEPTION "java/io/IOException"
|
||||
#define CLASS_NAME_ARCHIVE_EXCEPTION "com/hippo/a7zip/ArchiveException"
|
||||
#define CLASS_NAME_PASSWORD_EXCEPTION "com/hippo/a7zip/PasswordException"
|
||||
|
||||
#define THROW_EXCEPTION(ENV, EXCEPTION_NAME, ...) \
|
||||
do { \
|
||||
a7zip::JavaHelper::ThrowException((ENV), (EXCEPTION_NAME), __VA_ARGS__); \
|
||||
return; \
|
||||
} while (0)
|
||||
|
||||
#define THROW_EXCEPTION_RET(ENV, RET, EXCEPTION_NAME, ...) \
|
||||
do { \
|
||||
a7zip::JavaHelper::ThrowException((ENV), (EXCEPTION_NAME), __VA_ARGS__); \
|
||||
return (RET); \
|
||||
} while (0)
|
||||
|
||||
#define THROW_IO_EXCEPTION(ENV, CODE) \
|
||||
THROW_EXCEPTION(ENV, CLASS_NAME_IO_EXCEPTION, CODE)
|
||||
|
||||
#define THROW_IO_EXCEPTION_RET(ENV, RET, CODE) \
|
||||
THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_IO_EXCEPTION, CODE)
|
||||
|
||||
#define THROW_ARCHIVE_EXCEPTION(ENV, CODE) \
|
||||
do { \
|
||||
if ((CODE) == E_NO_PASSWORD || (CODE) == E_WRONG_PASSWORD) { \
|
||||
THROW_EXCEPTION(ENV, CLASS_NAME_PASSWORD_EXCEPTION, CODE); \
|
||||
} else { \
|
||||
THROW_EXCEPTION(ENV, CLASS_NAME_ARCHIVE_EXCEPTION, CODE); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define THROW_ARCHIVE_EXCEPTION_RET(ENV, RET, CODE) \
|
||||
do { \
|
||||
if ((CODE) == E_NO_PASSWORD || (CODE) == E_WRONG_PASSWORD) { \
|
||||
THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_PASSWORD_EXCEPTION, CODE); \
|
||||
} else { \
|
||||
THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_ARCHIVE_EXCEPTION, CODE); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_CLOSED(ENV, NATIVE_PTR) \
|
||||
do { \
|
||||
if ((NATIVE_PTR) == 0) { \
|
||||
THROW_EXCEPTION(ENV, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, "It's closed"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_CLOSED_RET(ENV, RET, NATIVE_PTR) \
|
||||
do { \
|
||||
if ((NATIVE_PTR) == 0) { \
|
||||
THROW_EXCEPTION_RET( \
|
||||
ENV, RET, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, "It's closed"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace a7zip {
|
||||
namespace JavaHelper {
|
||||
|
||||
jint ThrowException(JNIEnv* env, const char* exception_name, const char* message, ...);
|
||||
jint ThrowException(JNIEnv* env, const char* exception_name, HRESULT code);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_JAVA_HELPER_H__
|
||||
@@ -1,397 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "JavaInArchive.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
|
||||
#include <include_windows/windows.h>
|
||||
#include <7zip/Archive/IArchive.h>
|
||||
|
||||
#include "OpenVolumeCallback.h"
|
||||
#include "SeekableInputStream.h"
|
||||
#include "JavaHelper.h"
|
||||
#include "JavaSeekableInputStream.h"
|
||||
#include "JavaInputStream.h"
|
||||
#include "Log.h"
|
||||
#include "OutputStream.h"
|
||||
#include "SevenZip.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#ifdef LOG_TAG
|
||||
# undef LOG_TAG
|
||||
#endif //LOG_TAG
|
||||
#define LOG_TAG "JavaArchive"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
static void CopyJStringToBSTR(BSTR bstr, const jchar* jstr, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
*bstr++ = *jstr++;
|
||||
}
|
||||
*bstr = 0;
|
||||
}
|
||||
|
||||
static BSTR JStringToBSTR(JNIEnv* env, jstring jstr) {
|
||||
BSTR bstr = nullptr;
|
||||
if (jstr != nullptr) {
|
||||
jsize length = env->GetStringLength(jstr);
|
||||
const jchar* jchars = env->GetStringChars(jstr, nullptr);
|
||||
bstr = ::SysAllocStringLen(nullptr, static_cast<UINT>(length));
|
||||
CopyJStringToBSTR(bstr, jchars, length);
|
||||
env->ReleaseStringChars(jstr, jchars);
|
||||
}
|
||||
return bstr;
|
||||
}
|
||||
|
||||
static jlong NativeOpen(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jobject stream,
|
||||
jstring password,
|
||||
jstring filename,
|
||||
jobject open_volume_callback
|
||||
) {
|
||||
CMyComPtr<IInStream> in_stream = nullptr;
|
||||
HRESULT result = SeekableInputStream::Create(env, stream, in_stream);
|
||||
if (result != S_OK || in_stream == nullptr) {
|
||||
// Call java methods before throw exception
|
||||
if (in_stream != nullptr) {
|
||||
in_stream.Release();
|
||||
}
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, 0, result);
|
||||
}
|
||||
|
||||
BSTR bstr_password = nullptr;
|
||||
BSTR bstr_filename = nullptr;
|
||||
CMyComPtr<OpenVolumeCallback> open_volume_callback_wrapper = nullptr;
|
||||
|
||||
if (filename != nullptr && open_volume_callback != nullptr) {
|
||||
result = OpenVolumeCallback::Create(env, open_volume_callback, open_volume_callback_wrapper);
|
||||
if (result != S_OK) {
|
||||
// Call java methods before throw exception
|
||||
if (in_stream != nullptr) {
|
||||
in_stream.Release();
|
||||
}
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, 0, result);
|
||||
}
|
||||
bstr_filename = JStringToBSTR(env, filename);
|
||||
}
|
||||
bstr_password = JStringToBSTR(env, password);
|
||||
|
||||
InArchive* archive = nullptr;
|
||||
result = SevenZip::OpenArchive(in_stream, bstr_password, bstr_filename, open_volume_callback_wrapper, &archive);
|
||||
|
||||
::SysFreeString(bstr_password);
|
||||
::SysFreeString(bstr_filename);
|
||||
|
||||
if (result != S_OK || archive == nullptr) {
|
||||
// Call java methods before throw exception
|
||||
in_stream.Release();
|
||||
delete archive;
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, 0, result);
|
||||
}
|
||||
|
||||
return reinterpret_cast<jlong>(archive);
|
||||
}
|
||||
|
||||
static jstring NativeGetFormatName(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, nullptr, native_ptr);
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
return env->NewStringUTF(archive->GetFormatName());
|
||||
}
|
||||
|
||||
static jint NativeGetNumberOfEntries(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr);
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
|
||||
UInt32 number = 0;
|
||||
HRESULT result = archive->GetNumberOfEntries(number);
|
||||
return result == S_OK ? number : -1;
|
||||
}
|
||||
|
||||
#define GET_ARCHIVE_PROPERTY_START(METHOD_NAME, RETURN_TYPE) \
|
||||
static RETURN_TYPE METHOD_NAME(JNIEnv* env, jclass, jlong native_ptr, jint prop_id) { \
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr); \
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
|
||||
#define GET_ARCHIVE_PROPERTY_END \
|
||||
}
|
||||
|
||||
#define GET_ENTRY_PROPERTY_START(METHOD_NAME, RETURN_TYPE) \
|
||||
static RETURN_TYPE METHOD_NAME(JNIEnv* env, jclass, jlong native_ptr, jint index, jint prop_id) {\
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr); \
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
|
||||
#define GET_ENTRY_PROPERTY_END \
|
||||
}
|
||||
|
||||
#define GET_PROPERTY_TYPE(GETTER) \
|
||||
PropType prop_type; \
|
||||
HRESULT result = (GETTER); \
|
||||
return result == S_OK ? prop_type : PT_UNKNOWN;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(NativeGetArchivePropertyType, jint)
|
||||
GET_PROPERTY_TYPE(archive->GetArchivePropertyType(static_cast<PROPID>(prop_id), &prop_type))
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(NativeGetEntryPropertyType, jint)
|
||||
GET_PROPERTY_TYPE(archive->GetEntryPropertyType(static_cast<UInt32>(index), static_cast<PROPID>(prop_id), &prop_type))
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_BOOL_TYPE(GETTER) \
|
||||
bool bool_prop; \
|
||||
HRESULT result = (GETTER); \
|
||||
return result == S_OK ? bool_prop : false;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(NativeGetArchiveBooleanProperty, jboolean)
|
||||
GET_BOOL_TYPE(archive->GetArchiveBooleanProperty(static_cast<PROPID>(prop_id), &bool_prop))
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(NativeGetEntryBooleanProperty, jboolean)
|
||||
GET_BOOL_TYPE(archive->GetEntryBooleanProperty(static_cast<UInt32>(index), static_cast<PROPID>(prop_id), &bool_prop))
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_INT_TYPE(GETTER) \
|
||||
Int32 int_prop; \
|
||||
HRESULT result = (GETTER); \
|
||||
return result == S_OK ? int_prop : 0;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(NativeGetArchiveIntProperty, jint)
|
||||
GET_INT_TYPE(archive->GetArchiveIntProperty(static_cast<PROPID>(prop_id), &int_prop))
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(NativeGetEntryIntProperty, jint)
|
||||
GET_INT_TYPE(archive->GetEntryIntProperty(static_cast<UInt32>(index), static_cast<PROPID>(prop_id), &int_prop))
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
#define GET_LONG_TYPE(GETTER) \
|
||||
Int64 long_prop; \
|
||||
HRESULT result = (GETTER); \
|
||||
return result == S_OK ? long_prop : 0;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(NativeGetArchiveLongProperty, jlong)
|
||||
GET_LONG_TYPE(archive->GetArchiveLongProperty(static_cast<PROPID>(prop_id), &long_prop))
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(NativeGetEntryLongProperty, jlong)
|
||||
GET_LONG_TYPE(archive->GetEntryLongProperty(static_cast<UInt32>(index), static_cast<PROPID>(prop_id), &long_prop))
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
static void shrink(BSTR bstr) {
|
||||
jchar* jstr = reinterpret_cast<jchar*>(bstr);
|
||||
UINT n = ::SysStringLen(bstr);
|
||||
for (UINT i = 0; i < n; i++) {
|
||||
jstr[i] = static_cast<jchar>(bstr[i]);
|
||||
}
|
||||
jstr[n] = 0;
|
||||
}
|
||||
|
||||
#define GET_STRING_PROPERTY(GETTER) \
|
||||
BSTR str_prop = nullptr; \
|
||||
HRESULT result = (GETTER); \
|
||||
if (result != S_OK || str_prop == nullptr) { \
|
||||
if (str_prop != nullptr) ::SysFreeString(str_prop); \
|
||||
return nullptr; \
|
||||
} \
|
||||
shrink(str_prop); \
|
||||
jstring jstr = \
|
||||
env->NewString(reinterpret_cast<const jchar*>(str_prop), ::SysStringLen(str_prop)); \
|
||||
::SysFreeString(str_prop); \
|
||||
return jstr;
|
||||
|
||||
GET_ARCHIVE_PROPERTY_START(NativeGetArchiveStringProperty, jstring)
|
||||
GET_STRING_PROPERTY(archive->GetArchiveStringProperty(static_cast<PROPID>(prop_id), &str_prop))
|
||||
GET_ARCHIVE_PROPERTY_END
|
||||
|
||||
GET_ENTRY_PROPERTY_START(NativeGetEntryStringProperty, jstring)
|
||||
GET_STRING_PROPERTY(archive->GetEntryStringProperty(static_cast<UInt32>(index), static_cast<PROPID>(prop_id), &str_prop))
|
||||
GET_ENTRY_PROPERTY_END
|
||||
|
||||
static jobject NativeGetEntryStream(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr,
|
||||
jint index
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, nullptr, native_ptr);
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
|
||||
CMyComPtr<ISequentialInStream> sequential_in_stream = nullptr;
|
||||
HRESULT result = archive->GetEntryStream(static_cast<UInt32>(index), &sequential_in_stream);
|
||||
if (result != S_OK || sequential_in_stream == nullptr) {
|
||||
if (sequential_in_stream != nullptr) {
|
||||
// Release the stream manually before throw java exception
|
||||
sequential_in_stream.Release();
|
||||
}
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result);
|
||||
}
|
||||
|
||||
// Try to get IInStream
|
||||
CMyComPtr<IInStream> in_stream = nullptr;
|
||||
sequential_in_stream.QueryInterface(IID_IInStream, &in_stream);
|
||||
|
||||
if (in_stream != nullptr) {
|
||||
// It's an IInStream
|
||||
jobject object = nullptr;
|
||||
result = JavaSeekableInputStream::NewInstance(env, in_stream, &object);
|
||||
if (object == nullptr) {
|
||||
// Release the stream manually before throw java exception
|
||||
in_stream->Release();
|
||||
sequential_in_stream->Release();
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result);
|
||||
}
|
||||
return object;
|
||||
} else {
|
||||
// It's just an ISequentialInStream
|
||||
jobject object = nullptr;
|
||||
result = JavaInputStream::NewInstance(env, sequential_in_stream, &object);
|
||||
if (object == nullptr) {
|
||||
// Release the stream manually before throw java exception
|
||||
sequential_in_stream->Release();
|
||||
THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
static void NativeExtractEntry(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr,
|
||||
jint index,
|
||||
jstring password,
|
||||
jobject stream
|
||||
) {
|
||||
CHECK_CLOSED(env, native_ptr);
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
|
||||
CMyComPtr<ISequentialOutStream> out_stream = nullptr;
|
||||
HRESULT result = OutputStream::Create(env, stream, out_stream);
|
||||
if (result != S_OK || out_stream == nullptr) {
|
||||
if (out_stream != nullptr) {
|
||||
// Call java methods before throw exception
|
||||
out_stream.Release();
|
||||
} else {
|
||||
// Let java code closes the OutStream
|
||||
}
|
||||
THROW_ARCHIVE_EXCEPTION(env, result);
|
||||
}
|
||||
|
||||
const jchar* j_password = nullptr;
|
||||
BSTR bstr_password = nullptr;
|
||||
if (password != nullptr) {
|
||||
jsize length = env->GetStringLength(password);
|
||||
j_password = env->GetStringChars(password, nullptr);
|
||||
bstr_password = ::SysAllocStringLen(nullptr, static_cast<UINT>(length));
|
||||
CopyJStringToBSTR(bstr_password, j_password, length);
|
||||
}
|
||||
|
||||
result = archive->ExtractEntry(static_cast<UInt32>(index), bstr_password, out_stream);
|
||||
|
||||
if (password != nullptr) {
|
||||
::SysFreeString(bstr_password);
|
||||
env->ReleaseStringChars(password, j_password);
|
||||
}
|
||||
|
||||
if (result != S_OK) {
|
||||
// Call java methods before throw exception
|
||||
out_stream.Release();
|
||||
THROW_ARCHIVE_EXCEPTION(env, result);
|
||||
}
|
||||
}
|
||||
|
||||
static void NativeClose(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED(env, native_ptr);
|
||||
InArchive* archive = reinterpret_cast<InArchive*>(native_ptr);
|
||||
delete archive;
|
||||
}
|
||||
|
||||
static JNINativeMethod archive_methods[] = {
|
||||
{ "nativeOpen",
|
||||
"(Lcom/hippo/a7zip/SeekableInputStream;Ljava/lang/String;Ljava/lang/String;Lcom/hippo/a7zip/InArchive$OpenVolumeCallback;)J",
|
||||
reinterpret_cast<void *>(NativeOpen) },
|
||||
{ "nativeGetFormatName",
|
||||
"(J)Ljava/lang/String;",
|
||||
reinterpret_cast<void *>(NativeGetFormatName) },
|
||||
{ "nativeGetNumberOfEntries",
|
||||
"(J)I",
|
||||
reinterpret_cast<void *>(NativeGetNumberOfEntries) },
|
||||
{ "nativeGetArchivePropertyType",
|
||||
"(JI)I",
|
||||
reinterpret_cast<void *>(NativeGetArchivePropertyType) },
|
||||
{ "nativeGetArchiveBooleanProperty",
|
||||
"(JI)Z",
|
||||
reinterpret_cast<void *>(NativeGetArchiveBooleanProperty) },
|
||||
{ "nativeGetArchiveIntProperty",
|
||||
"(JI)I",
|
||||
reinterpret_cast<void *>(NativeGetArchiveIntProperty) },
|
||||
{ "nativeGetArchiveLongProperty",
|
||||
"(JI)J",
|
||||
reinterpret_cast<void *>(NativeGetArchiveLongProperty) },
|
||||
{ "nativeGetArchiveStringProperty",
|
||||
"(JI)Ljava/lang/String;",
|
||||
reinterpret_cast<void *>(NativeGetArchiveStringProperty) },
|
||||
{ "nativeGetEntryPropertyType",
|
||||
"(JII)I",
|
||||
reinterpret_cast<void *>(NativeGetEntryPropertyType) },
|
||||
{ "nativeGetEntryBooleanProperty",
|
||||
"(JII)Z",
|
||||
reinterpret_cast<void *>(NativeGetEntryBooleanProperty) },
|
||||
{ "nativeGetEntryIntProperty",
|
||||
"(JII)I",
|
||||
reinterpret_cast<void *>(NativeGetEntryIntProperty) },
|
||||
{ "nativeGetEntryLongProperty",
|
||||
"(JII)J",
|
||||
reinterpret_cast<void *>(NativeGetEntryLongProperty) },
|
||||
{ "nativeGetEntryStringProperty",
|
||||
"(JII)Ljava/lang/String;",
|
||||
reinterpret_cast<void *>(NativeGetEntryStringProperty) },
|
||||
{ "nativeGetEntryStream",
|
||||
"(JI)Ljava/io/InputStream;",
|
||||
reinterpret_cast<void *>(NativeGetEntryStream) },
|
||||
{ "nativeExtractEntry",
|
||||
"(JILjava/lang/String;Ljava/io/OutputStream;)V",
|
||||
reinterpret_cast<void *>(NativeExtractEntry) },
|
||||
{ "nativeClose",
|
||||
"(J)V",
|
||||
reinterpret_cast<void *>(NativeClose) }
|
||||
};
|
||||
|
||||
HRESULT JavaInArchive::RegisterMethods(JNIEnv* env) {
|
||||
jclass clazz = env->FindClass("com/hippo/a7zip/InArchive");
|
||||
if (clazz == nullptr) return E_CLASS_NOT_FOUND;
|
||||
|
||||
jint result = env->RegisterNatives(clazz, archive_methods, std::extent<decltype(archive_methods)>::value);
|
||||
if (result < 0) {
|
||||
return E_FAILED_REGISTER;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_JAVA_IN_ARCHIVE_H__
|
||||
#define __A7ZIP_JAVA_IN_ARCHIVE_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyWindows.h>
|
||||
|
||||
namespace a7zip {
|
||||
namespace JavaInArchive {
|
||||
|
||||
HRESULT RegisterMethods(JNIEnv* env);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_JAVA_IN_ARCHIVE_H__
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "SeekableInputStream.h"
|
||||
#include "JavaEnv.h"
|
||||
#include "JavaInArchive.h"
|
||||
#include "JavaInitA7Zip.h"
|
||||
#include "JavaSeekableInputStream.h"
|
||||
#include "JavaInputStream.h"
|
||||
#include "OpenVolumeCallback.h"
|
||||
#include "OutputStream.h"
|
||||
#include "SevenZip.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
bool a7zip_onLoad(JavaVM *vm) {
|
||||
JNIEnv *env;
|
||||
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JavaEnv::Initialize(vm);
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(SeekableInputStream::Initialize(env));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(JavaSeekableInputStream::Initialize(env));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(JavaInputStream::Initialize(env));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(OpenVolumeCallback::Initialize(env));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(OutputStream::Initialize(env));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(SevenZip::Initialize());
|
||||
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(JavaInArchive::RegisterMethods(static_cast<JNIEnv *>(env)));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(
|
||||
JavaSeekableInputStream::RegisterMethods(static_cast<JNIEnv *>(env)));
|
||||
RETURN_JNI_ERR_IF_NOT_ZERO(JavaInputStream::RegisterMethods(static_cast<JNIEnv *>(env)));
|
||||
|
||||
return true;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +0,0 @@
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
bool a7zip_onLoad(JavaVM *vm);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "JavaInputStream.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <Common/MyCom.h>
|
||||
|
||||
#include "JavaHelper.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
static jint NativeRead(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr,
|
||||
jbyteArray array,
|
||||
jint offset,
|
||||
jint length
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr);
|
||||
ISequentialInStream* stream = reinterpret_cast<ISequentialInStream*>(native_ptr);
|
||||
|
||||
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
|
||||
if (bytes == nullptr) THROW_IO_EXCEPTION_RET(env, 0, E_JAVA_EXCEPTION);
|
||||
|
||||
UInt32 processed_size;
|
||||
HRESULT result = stream->Read(bytes + offset, static_cast<UInt32>(length), &processed_size);
|
||||
env->ReleaseByteArrayElements(array, bytes, 0);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
|
||||
if (length != 0 && processed_size == 0) {
|
||||
// EOF
|
||||
return -1;
|
||||
}
|
||||
|
||||
return processed_size;
|
||||
}
|
||||
|
||||
static void NativeClose(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED(env, native_ptr);
|
||||
ISequentialInStream* stream = reinterpret_cast<ISequentialInStream*>(native_ptr);
|
||||
stream->Release();
|
||||
}
|
||||
|
||||
static bool initialized = false;
|
||||
static jclass class_native_sequential_in_stream = nullptr;
|
||||
static jmethodID constructor_native_sequential_in_stream = nullptr;
|
||||
|
||||
HRESULT JavaInputStream::Initialize(JNIEnv* env) {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class_native_sequential_in_stream = env->FindClass("com/hippo/a7zip/NativeInputStream");
|
||||
if (class_native_sequential_in_stream == nullptr) return E_CLASS_NOT_FOUND;
|
||||
class_native_sequential_in_stream = static_cast<jclass>(env->NewGlobalRef(class_native_sequential_in_stream));
|
||||
if (class_native_sequential_in_stream == nullptr) return E_OUTOFMEMORY;
|
||||
|
||||
constructor_native_sequential_in_stream = env->GetMethodID(class_native_sequential_in_stream, "<init>", "(J)V");
|
||||
if (constructor_native_sequential_in_stream == nullptr) return E_METHOD_NOT_FOUND;
|
||||
|
||||
initialized = true;
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
static JNINativeMethod stream_methods[] = {
|
||||
{ "nativeRead",
|
||||
"(J[BII)I",
|
||||
reinterpret_cast<void *>(NativeRead) },
|
||||
{ "nativeClose",
|
||||
"(J)V",
|
||||
reinterpret_cast<void *>(NativeClose) }
|
||||
};
|
||||
|
||||
HRESULT JavaInputStream::RegisterMethods(JNIEnv* env) {
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
jint result = env->RegisterNatives(class_native_sequential_in_stream, stream_methods, std::extent<decltype(stream_methods)>::value);
|
||||
if (result < 0) {
|
||||
return E_FAILED_REGISTER;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT JavaInputStream::NewInstance(JNIEnv* env, ISequentialInStream* stream, jobject* object) {
|
||||
*object = nullptr;
|
||||
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
*object = env->NewObject(class_native_sequential_in_stream, constructor_native_sequential_in_stream, stream);
|
||||
|
||||
if (*object == nullptr) {
|
||||
// Clear java exception and return
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
// No java exception, but it's still an error
|
||||
return E_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Ignore if any exception is pending
|
||||
CLEAR_IF_EXCEPTION_PENDING(env);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__
|
||||
#define __A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyWindows.h>
|
||||
#include <7zip/IStream.h>
|
||||
|
||||
namespace a7zip {
|
||||
namespace JavaInputStream {
|
||||
|
||||
HRESULT Initialize(JNIEnv* env);
|
||||
|
||||
HRESULT RegisterMethods(JNIEnv* env);
|
||||
|
||||
HRESULT NewInstance(JNIEnv* env, ISequentialInStream* stream, jobject* object);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "JavaSeekableInputStream.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <Common/MyCom.h>
|
||||
|
||||
#include "JavaHelper.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
static void NativeSeek(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr,
|
||||
jlong pos
|
||||
) {
|
||||
CHECK_CLOSED(env, native_ptr);
|
||||
IInStream* stream = reinterpret_cast<IInStream*>(native_ptr);
|
||||
|
||||
UInt64 newPosition; // Ignored
|
||||
HRESULT result = stream->Seek(pos, STREAM_SEEK_SET, &newPosition);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION(env, result);
|
||||
}
|
||||
|
||||
static jlong NativeTell(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr);
|
||||
IInStream* stream = reinterpret_cast<IInStream*>(native_ptr);
|
||||
|
||||
UInt64 newPosition;
|
||||
HRESULT result = stream->Seek(0, STREAM_SEEK_CUR, &newPosition);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
return static_cast<jlong>(newPosition);
|
||||
}
|
||||
|
||||
static jlong NativeSize(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr);
|
||||
IInStream* stream = reinterpret_cast<IInStream*>(native_ptr);
|
||||
|
||||
// Try to get IStreamGetSize
|
||||
CMyComPtr<IStreamGetSize> stream_get_size;
|
||||
stream->QueryInterface(IID_IStreamGetSize, reinterpret_cast<void **>(&stream_get_size));
|
||||
|
||||
if (stream_get_size != nullptr) {
|
||||
// It's an IStreamGetSize
|
||||
UInt64 size;
|
||||
HRESULT result = stream_get_size->GetSize(&size);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
return static_cast<jlong>(size);
|
||||
} else {
|
||||
// It's not an IStreamGetSize
|
||||
// Backup current position
|
||||
UInt64 cur;
|
||||
HRESULT result = stream->Seek(0, STREAM_SEEK_CUR, &cur);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
|
||||
// Seek to the end of steam to get size
|
||||
UInt64 size;
|
||||
result = stream->Seek(0, STREAM_SEEK_END, &size);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
|
||||
// Back to previous position
|
||||
UInt64 ignored;
|
||||
result = stream->Seek(cur, STREAM_SEEK_SET, &ignored);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
|
||||
return static_cast<jlong>(size);
|
||||
}
|
||||
}
|
||||
|
||||
static jint NativeRead(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr,
|
||||
jbyteArray array,
|
||||
jint offset,
|
||||
jint length
|
||||
) {
|
||||
CHECK_CLOSED_RET(env, 0, native_ptr);
|
||||
IInStream* stream = reinterpret_cast<IInStream*>(native_ptr);
|
||||
|
||||
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
|
||||
if (bytes == nullptr) THROW_IO_EXCEPTION_RET(env, 0, E_JAVA_EXCEPTION);
|
||||
|
||||
UInt32 processed_size;
|
||||
HRESULT result = stream->Read(bytes + offset, static_cast<UInt32>(length), &processed_size);
|
||||
env->ReleaseByteArrayElements(array, bytes, 0);
|
||||
if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result);
|
||||
|
||||
if (length != 0 && processed_size == 0) {
|
||||
// EOF
|
||||
return -1;
|
||||
}
|
||||
|
||||
return processed_size;
|
||||
}
|
||||
|
||||
static void NativeClose(
|
||||
JNIEnv* env,
|
||||
jclass,
|
||||
jlong native_ptr
|
||||
) {
|
||||
CHECK_CLOSED(env, native_ptr);
|
||||
IInStream* stream = reinterpret_cast<IInStream*>(native_ptr);
|
||||
stream->Release();
|
||||
}
|
||||
|
||||
static bool initialized = false;
|
||||
static jclass class_native_in_stream = nullptr;
|
||||
static jmethodID constructor_native_in_stream = nullptr;
|
||||
|
||||
HRESULT JavaSeekableInputStream::Initialize(JNIEnv* env) {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class_native_in_stream = env->FindClass("com/hippo/a7zip/NativeSeekableInputStream");
|
||||
if (class_native_in_stream == nullptr) return E_CLASS_NOT_FOUND;
|
||||
class_native_in_stream = static_cast<jclass>(env->NewGlobalRef(class_native_in_stream));
|
||||
if (class_native_in_stream == nullptr) return E_OUTOFMEMORY;
|
||||
|
||||
constructor_native_in_stream = env->GetMethodID(class_native_in_stream, "<init>", "(J)V");
|
||||
if (constructor_native_in_stream == nullptr) return E_METHOD_NOT_FOUND;
|
||||
|
||||
initialized = true;
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
static JNINativeMethod stream_methods[] = {
|
||||
{ "nativeSeek",
|
||||
"(JJ)V",
|
||||
reinterpret_cast<void *>(NativeSeek) },
|
||||
{ "nativeTell",
|
||||
"(J)J",
|
||||
reinterpret_cast<void *>(NativeTell) },
|
||||
{ "nativeSize",
|
||||
"(J)J",
|
||||
reinterpret_cast<void *>(NativeSize) },
|
||||
{ "nativeRead",
|
||||
"(J[BII)I",
|
||||
reinterpret_cast<void *>(NativeRead) },
|
||||
{ "nativeClose",
|
||||
"(J)V",
|
||||
reinterpret_cast<void *>(NativeClose) }
|
||||
};
|
||||
|
||||
HRESULT JavaSeekableInputStream::RegisterMethods(JNIEnv* env) {
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
jint result = env->RegisterNatives(class_native_in_stream, stream_methods, std::extent<decltype(stream_methods)>::value);
|
||||
if (result < 0) {
|
||||
return E_FAILED_REGISTER;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT JavaSeekableInputStream::NewInstance(JNIEnv* env, IInStream* stream, jobject* object) {
|
||||
*object = nullptr;
|
||||
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
*object = env->NewObject(class_native_in_stream, constructor_native_in_stream, stream);
|
||||
|
||||
if (*object == nullptr) {
|
||||
// Clear java exception and return
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
// No java exception, but it's still an error
|
||||
return E_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Ignore if any exception is pending
|
||||
CLEAR_IF_EXCEPTION_PENDING(env);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_JAVA_IN_STREAM_H__
|
||||
#define __A7ZIP_JAVA_IN_STREAM_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyWindows.h>
|
||||
#include <7zip/IStream.h>
|
||||
|
||||
namespace a7zip {
|
||||
namespace JavaSeekableInputStream {
|
||||
|
||||
HRESULT Initialize(JNIEnv* env);
|
||||
|
||||
HRESULT RegisterMethods(JNIEnv* env);
|
||||
|
||||
HRESULT NewInstance(JNIEnv* env, IInStream* stream, jobject* object);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_JAVA_IN_STREAM_H__
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef __A7ZIP_LOG_H__
|
||||
#define __A7ZIP_LOG_H__
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOG_TAG "a7zip"
|
||||
#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__)
|
||||
|
||||
#endif //__A7ZIP_LOG_H__
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
#include "OpenVolumeCallback.h"
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
#include <MyString.h>
|
||||
|
||||
#include "SeekableInputStream.h"
|
||||
#include "JavaEnv.h"
|
||||
#include "Utils.h"
|
||||
#include "Log.h"
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
bool OpenVolumeCallback::initialized = false;
|
||||
jmethodID OpenVolumeCallback::method_open_volume = nullptr;
|
||||
|
||||
OpenVolumeCallback::OpenVolumeCallback(jobject callback) : callback(callback) { }
|
||||
|
||||
OpenVolumeCallback::~OpenVolumeCallback() {
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return;
|
||||
|
||||
env->DeleteGlobalRef(callback);
|
||||
callback = nullptr;
|
||||
}
|
||||
|
||||
HRESULT OpenVolumeCallback::OpenVolume(const wchar_t *name, CMyComPtr<IInStream>& in_stream) {
|
||||
JavaEnv env;
|
||||
|
||||
// const wchar_t* to jstring
|
||||
unsigned len = MyStringLen(name);
|
||||
jchar *buffer = reinterpret_cast<jchar *>(malloc(len * sizeof(jchar)));
|
||||
if (buffer == nullptr) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
buffer[i] = static_cast<jchar>(name[i]);
|
||||
}
|
||||
jstring j_name = env->NewString(buffer, len);
|
||||
free(buffer);
|
||||
if (j_name == nullptr) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// Wrap java stream
|
||||
jobject stream = env->CallObjectMethod(callback, method_open_volume, j_name);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
RETURN_SAME_IF_NOT_ZERO(SeekableInputStream::Create(static_cast<JNIEnv*>(env), stream, in_stream));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT OpenVolumeCallback::Initialize(JNIEnv* env) {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
jclass clazz = env->FindClass("com/hippo/a7zip/InArchive$OpenVolumeCallback");
|
||||
if (clazz == nullptr) return E_CLASS_NOT_FOUND;
|
||||
|
||||
method_open_volume = env->GetMethodID(clazz, "openVolume", "(Ljava/lang/String;)Lcom/hippo/a7zip/SeekableInputStream;");
|
||||
if (method_open_volume == nullptr) return E_METHOD_NOT_FOUND;
|
||||
|
||||
initialized = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT OpenVolumeCallback::Create(
|
||||
JNIEnv* env,
|
||||
jobject callback,
|
||||
OpenVolumeCallback** result
|
||||
) {
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
jobject g_callback = env->NewGlobalRef(callback);
|
||||
if (g_callback == nullptr) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
*result = new OpenVolumeCallback(g_callback);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT OpenVolumeCallback::Create(
|
||||
JNIEnv* env,
|
||||
jobject callback,
|
||||
CMyComPtr<OpenVolumeCallback>& result
|
||||
) {
|
||||
OpenVolumeCallback *callback_ptr = nullptr;
|
||||
RETURN_SAME_IF_NOT_ZERO(Create(env, callback, &callback_ptr));
|
||||
result = callback_ptr;
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_OPEN_VOLUME_CALLBACK_H__
|
||||
#define __A7ZIP_OPEN_VOLUME_CALLBACK_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyCom.h>
|
||||
#include <7zip/IStream.h>
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class OpenVolumeCallback : public CMyUnknownImp
|
||||
{
|
||||
private:
|
||||
OpenVolumeCallback(jobject callback);
|
||||
public:
|
||||
virtual ~OpenVolumeCallback();
|
||||
|
||||
public:
|
||||
MY_ADDREF_RELEASE
|
||||
|
||||
HRESULT OpenVolume(const wchar_t *name, CMyComPtr<IInStream>& in_stream);
|
||||
|
||||
private:
|
||||
jobject callback;
|
||||
|
||||
public:
|
||||
static HRESULT Initialize(JNIEnv* env);
|
||||
static HRESULT Create(JNIEnv* env, jobject callback, OpenVolumeCallback** result);
|
||||
static HRESULT Create(JNIEnv* env, jobject callback, CMyComPtr<OpenVolumeCallback>& result);
|
||||
|
||||
private:
|
||||
static bool initialized;
|
||||
static jmethodID method_open_volume;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_OPEN_VOLUME_CALLBACK_H__
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "OutputStream.h"
|
||||
|
||||
#include "JavaEnv.h"
|
||||
#include "Utils.h"
|
||||
#include "Log.h"
|
||||
|
||||
#define ARRAY_SIZE DEFAULT_BUFFER_SIZE
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
bool OutputStream::initialized = false;
|
||||
jmethodID OutputStream::method_write = nullptr;
|
||||
jmethodID OutputStream::method_close = nullptr;
|
||||
|
||||
OutputStream::OutputStream(
|
||||
jobject stream,
|
||||
jbyteArray array
|
||||
) :
|
||||
stream(stream),
|
||||
array(array) { }
|
||||
|
||||
OutputStream::~OutputStream() {
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return;
|
||||
|
||||
env->CallVoidMethod(stream, method_close);
|
||||
CLEAR_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
env->DeleteGlobalRef(stream);
|
||||
env->DeleteGlobalRef(array);
|
||||
stream = nullptr;
|
||||
array = nullptr;
|
||||
}
|
||||
|
||||
HRESULT OutputStream::Write(const void* data, UInt32 size, UInt32* processedSize) {
|
||||
if (processedSize != nullptr) {
|
||||
*processedSize = 0;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return E_JAVA_EXCEPTION;
|
||||
|
||||
// Make size not bigger than ARRAY_SIZE
|
||||
size = MIN(ARRAY_SIZE, size);
|
||||
|
||||
// Copy data from native buffer to java buffer
|
||||
env->SetByteArrayRegion(array, 0, size, reinterpret_cast<const jbyte*>(data));
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
// Write data to sink
|
||||
env->CallVoidMethod(stream, method_write, array, 0, size);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
if (processedSize != nullptr) {
|
||||
*processedSize = size;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT OutputStream::Initialize(JNIEnv* env) {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
jclass clazz = env->FindClass("java/io/OutputStream");
|
||||
if (clazz == nullptr) return E_CLASS_NOT_FOUND;
|
||||
|
||||
method_write = env->GetMethodID(clazz, "write", "([BII)V");
|
||||
if (method_write == nullptr) return E_METHOD_NOT_FOUND;
|
||||
method_close = env->GetMethodID(clazz, "close", "()V");
|
||||
if (method_close == nullptr) return E_METHOD_NOT_FOUND;
|
||||
|
||||
initialized = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT OutputStream::Create(
|
||||
JNIEnv* env,
|
||||
jobject stream,
|
||||
CMyComPtr<ISequentialOutStream>& out_stream
|
||||
) {
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
jobject g_stream = env->NewGlobalRef(stream);
|
||||
if (g_stream == nullptr) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
jbyteArray array = env->NewByteArray(ARRAY_SIZE);
|
||||
if (array == nullptr) {
|
||||
env->DeleteGlobalRef(g_stream);
|
||||
return E_FAILED_CONSTRUCT;
|
||||
}
|
||||
|
||||
jbyteArray g_array = static_cast<jbyteArray>(env->NewGlobalRef(array));
|
||||
if (g_array == nullptr) {
|
||||
env->DeleteGlobalRef(g_stream);
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
out_stream = new OutputStream(g_stream, g_array);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__
|
||||
#define __A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <Common/MyCom.h>
|
||||
#include <7zip/IStream.h>
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class OutputStream :
|
||||
public ISequentialOutStream,
|
||||
public CMyUnknownImp
|
||||
{
|
||||
private:
|
||||
OutputStream(jobject stream, jbyteArray array);
|
||||
|
||||
public:
|
||||
virtual ~OutputStream();
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP
|
||||
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
|
||||
|
||||
private:
|
||||
jobject stream;
|
||||
jbyteArray array;
|
||||
|
||||
public:
|
||||
static HRESULT Initialize(JNIEnv* env);
|
||||
static HRESULT Create(JNIEnv* env, jobject stream, CMyComPtr<ISequentialOutStream>& out_stream);
|
||||
|
||||
private:
|
||||
static bool initialized;
|
||||
static jmethodID method_write;
|
||||
static jmethodID method_close;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_PROP_TYPE_H__
|
||||
#define __A7ZIP_PROP_TYPE_H__
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
enum PropTypeEnum {
|
||||
PT_UNKNOWN,
|
||||
PT_EMPTY,
|
||||
PT_BOOL,
|
||||
PT_INT,
|
||||
PT_LONG,
|
||||
PT_FLOAT,
|
||||
PT_DOUBLE,
|
||||
PT_STRING,
|
||||
};
|
||||
|
||||
typedef unsigned short PropType;
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_PROP_TYPE_H__
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "SeekableInputStream.h"
|
||||
|
||||
#include "JavaEnv.h"
|
||||
#include "Utils.h"
|
||||
#include "Log.h"
|
||||
|
||||
#define ARRAY_SIZE DEFAULT_BUFFER_SIZE
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
bool SeekableInputStream::initialized = false;
|
||||
jmethodID SeekableInputStream::method_read = nullptr;
|
||||
jmethodID SeekableInputStream::method_seek = nullptr;
|
||||
jmethodID SeekableInputStream::method_tell = nullptr;
|
||||
jmethodID SeekableInputStream::method_size = nullptr;
|
||||
jmethodID SeekableInputStream::method_close = nullptr;
|
||||
|
||||
SeekableInputStream::SeekableInputStream(
|
||||
jobject stream,
|
||||
jbyteArray array
|
||||
) :
|
||||
stream(stream),
|
||||
array(array) { }
|
||||
|
||||
SeekableInputStream::~SeekableInputStream() {
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return;
|
||||
|
||||
env->CallVoidMethod(stream, method_close);
|
||||
CLEAR_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
env->DeleteGlobalRef(stream);
|
||||
env->DeleteGlobalRef(array);
|
||||
stream = nullptr;
|
||||
array = nullptr;
|
||||
}
|
||||
|
||||
HRESULT SeekableInputStream::Read(void* data, UInt32 size, UInt32* processedSize) {
|
||||
if (processedSize != nullptr) {
|
||||
*processedSize = 0;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return E_JAVA_EXCEPTION;
|
||||
|
||||
// Make size not bigger than ARRAY_SIZE
|
||||
size = MIN(ARRAY_SIZE, size);
|
||||
|
||||
jint read = env->CallIntMethod(stream, method_read, array, 0, size);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
// Check EOF
|
||||
if (read <= 0) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
env->GetByteArrayRegion(array, 0, read, static_cast<jbyte*>(data));
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
if (processedSize != nullptr) {
|
||||
*processedSize = static_cast<UInt32>(read);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SeekableInputStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) {
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return E_JAVA_EXCEPTION;
|
||||
|
||||
jlong actual_offset;
|
||||
|
||||
switch (seekOrigin) {
|
||||
case STREAM_SEEK_SET: {
|
||||
actual_offset = static_cast<jlong>(offset);
|
||||
break;
|
||||
}
|
||||
case STREAM_SEEK_CUR: {
|
||||
jlong position = env->CallLongMethod(stream, method_tell);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
actual_offset = position + offset;
|
||||
break;
|
||||
}
|
||||
case STREAM_SEEK_END: {
|
||||
jlong size = env->CallLongMethod(stream, method_size);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
actual_offset = size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
}
|
||||
|
||||
if (actual_offset < 0) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(stream, method_seek, actual_offset);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
if (newPosition != nullptr) {
|
||||
*newPosition = static_cast<UInt64>(actual_offset);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SeekableInputStream::GetSize(UInt64* size) {
|
||||
JavaEnv env;
|
||||
if (!env.IsValid()) return E_JAVA_EXCEPTION;
|
||||
|
||||
jlong store_size = env->CallLongMethod(stream, method_size);
|
||||
RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env);
|
||||
|
||||
if (size != nullptr) {
|
||||
*size = static_cast<UInt64>(store_size);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SeekableInputStream::Initialize(JNIEnv* env) {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
jclass clazz = env->FindClass("com/hippo/a7zip/SeekableInputStream");
|
||||
if (clazz == nullptr) return E_CLASS_NOT_FOUND;
|
||||
|
||||
method_read = env->GetMethodID(clazz, "read", "([BII)I");
|
||||
if (method_read == nullptr) return E_METHOD_NOT_FOUND;
|
||||
method_seek = env->GetMethodID(clazz, "seek", "(J)V");
|
||||
if (method_seek == nullptr) return E_METHOD_NOT_FOUND;
|
||||
method_tell = env->GetMethodID(clazz, "tell", "()J");
|
||||
if (method_tell == nullptr) return E_METHOD_NOT_FOUND;
|
||||
method_size = env->GetMethodID(clazz, "size", "()J");
|
||||
if (method_size == nullptr) return E_METHOD_NOT_FOUND;
|
||||
method_close = env->GetMethodID(clazz, "close", "()V");
|
||||
if (method_close == nullptr) return E_METHOD_NOT_FOUND;
|
||||
|
||||
initialized = true;
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
HRESULT SeekableInputStream::Create(
|
||||
JNIEnv* env,
|
||||
jobject stream,
|
||||
CMyComPtr<IInStream>& in_stream
|
||||
) {
|
||||
if (!initialized) {
|
||||
return E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
jobject g_stream = env->NewGlobalRef(stream);
|
||||
if (g_stream == nullptr) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
jbyteArray array = env->NewByteArray(ARRAY_SIZE);
|
||||
if (array == nullptr) {
|
||||
env->DeleteGlobalRef(g_stream);
|
||||
return E_FAILED_CONSTRUCT;
|
||||
}
|
||||
|
||||
jbyteArray g_array = static_cast<jbyteArray>(env->NewGlobalRef(array));
|
||||
if (g_array == nullptr) {
|
||||
env->DeleteGlobalRef(g_stream);
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
in_stream = new SeekableInputStream(g_stream, g_array);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_IN_STREAM_H__
|
||||
#define __A7ZIP_IN_STREAM_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <7zip/IStream.h>
|
||||
#include <Common/MyCom.h>
|
||||
|
||||
namespace a7zip {
|
||||
|
||||
class SeekableInputStream :
|
||||
public IInStream,
|
||||
public IStreamGetSize,
|
||||
public CMyUnknownImp
|
||||
{
|
||||
private:
|
||||
SeekableInputStream(jobject stream, jbyteArray array);
|
||||
|
||||
public:
|
||||
virtual ~SeekableInputStream();
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP2(IInStream, IStreamGetSize)
|
||||
|
||||
STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize);
|
||||
STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition);
|
||||
|
||||
STDMETHOD(GetSize)(UInt64* size);
|
||||
|
||||
private:
|
||||
jobject stream;
|
||||
jbyteArray array;
|
||||
|
||||
public:
|
||||
static HRESULT Initialize(JNIEnv* env);
|
||||
static HRESULT Create(JNIEnv* env, jobject stream, CMyComPtr<IInStream>& in_stream);
|
||||
|
||||
private:
|
||||
static bool initialized;
|
||||
static jmethodID method_read;
|
||||
static jmethodID method_seek;
|
||||
static jmethodID method_tell;
|
||||
static jmethodID method_size;
|
||||
static jmethodID method_close;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_IN_STREAM_H__
|
||||
@@ -1,567 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "SevenZip.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <Windows/PropVariant.h>
|
||||
#include <7zip/Archive/IArchive.h>
|
||||
#include <7zip/IPassword.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "OpenVolumeCallback.h"
|
||||
|
||||
#ifdef LOG_TAG
|
||||
# undef LOG_TAG
|
||||
# define LOG_TAG "P7Zip"
|
||||
#endif
|
||||
|
||||
using namespace a7zip;
|
||||
|
||||
extern "C" HRESULT GetNumberOfMethods(UINT32 *numCodecs);
|
||||
extern "C" HRESULT GetNumberOfFormats(UINT32 *numFormats);
|
||||
extern "C" HRESULT GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT* value);
|
||||
extern "C" HRESULT GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT* value);
|
||||
extern "C" HRESULT CreateObject(const GUID* clsid, const GUID* iid, void** outObject);
|
||||
|
||||
typedef HRESULT (*GetPropertyFunc)(UInt32 index, PROPID propID, PROPVARIANT* value);
|
||||
|
||||
class Method {
|
||||
public:
|
||||
bool has_name;
|
||||
AString name;
|
||||
bool has_encoder;
|
||||
GUID encoder;
|
||||
bool has_decoder;
|
||||
GUID decoder;
|
||||
};
|
||||
|
||||
class Format {
|
||||
public:
|
||||
GUID class_id;
|
||||
bool has_name;
|
||||
AString name;
|
||||
UInt32 signature_offset;
|
||||
CObjectVector<CByteBuffer> signatures;
|
||||
};
|
||||
|
||||
class CompressCodecsInfo :
|
||||
public ICompressCodecsInfo,
|
||||
public CMyUnknownImp {
|
||||
public:
|
||||
MY_UNKNOWN_IMP1(ICompressCodecsInfo)
|
||||
STDMETHOD(GetNumMethods)(UInt32* numMethods);
|
||||
STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT* value);
|
||||
STDMETHOD(CreateDecoder)(UInt32 index, const GUID* interfaceID, void** coder);
|
||||
STDMETHOD(CreateEncoder)(UInt32 index, const GUID* interfaceID, void** coder);
|
||||
};
|
||||
|
||||
static bool initialized = false;
|
||||
static void* handle = nullptr;
|
||||
static CObjectVector<Method> methods;
|
||||
static CObjectVector<Format> formats;
|
||||
static CompressCodecsInfo compress_codecs_info;
|
||||
|
||||
HRESULT CompressCodecsInfo::GetNumMethods(UInt32 *numMethods) {
|
||||
if (numMethods != nullptr) {
|
||||
*numMethods = methods.Size();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT CompressCodecsInfo::GetProperty(UInt32 index, PROPID propID, PROPVARIANT* value) {
|
||||
Method& method = methods[index];
|
||||
|
||||
switch (propID) {
|
||||
case NMethodPropID::kDecoderIsAssigned: {
|
||||
NWindows::NCOM::CPropVariant propVariant;
|
||||
propVariant = method.has_decoder;
|
||||
propVariant.Detach(value);
|
||||
return S_OK;
|
||||
}
|
||||
case NMethodPropID::kEncoderIsAssigned: {
|
||||
NWindows::NCOM::CPropVariant propVariant;
|
||||
propVariant = method.has_encoder;
|
||||
propVariant.Detach(value);
|
||||
return S_OK;
|
||||
}
|
||||
default: {
|
||||
return GetMethodProperty(index, propID, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT CompressCodecsInfo::CreateDecoder(
|
||||
UInt32 index,
|
||||
const GUID* interfaceID,
|
||||
void** coder
|
||||
) {
|
||||
Method& method = methods[index];
|
||||
|
||||
if (method.has_decoder) {
|
||||
return CreateObject(&(method.decoder), interfaceID, coder);
|
||||
} else {
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT CompressCodecsInfo::CreateEncoder(
|
||||
UInt32 index,
|
||||
const GUID* interfaceID,
|
||||
void** coder
|
||||
) {
|
||||
Method& method = methods[index];
|
||||
|
||||
if (method.has_encoder) {
|
||||
return CreateObject(&(method.encoder), interfaceID, coder);
|
||||
} else {
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
class ArchiveOpenCallback :
|
||||
public IArchiveOpenCallback,
|
||||
public ICryptoGetTextPassword,
|
||||
public CMyUnknownImp {
|
||||
public:
|
||||
ArchiveOpenCallback(BSTR password) {
|
||||
this->password = ::SysAllocString(password);
|
||||
this->has_asked_password = false;
|
||||
}
|
||||
|
||||
~ArchiveOpenCallback() {
|
||||
::SysFreeString(password);
|
||||
}
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP2(IArchiveOpenCallback, ICryptoGetTextPassword)
|
||||
INTERFACE_IArchiveOpenCallback({ return S_OK; });
|
||||
|
||||
STDMETHOD(CryptoGetTextPassword)(BSTR *password) {
|
||||
has_asked_password = true;
|
||||
*password = ::SysAllocString(this->password);
|
||||
return this->password != nullptr ? S_OK : E_NO_PASSWORD;
|
||||
}
|
||||
|
||||
HRESULT GetBetterResult(HRESULT result) {
|
||||
if (result == S_OK) {
|
||||
return S_OK;
|
||||
} else if (has_asked_password) {
|
||||
if (password != nullptr) {
|
||||
return E_WRONG_PASSWORD;
|
||||
} else {
|
||||
return E_NO_PASSWORD;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BSTR password;
|
||||
bool has_asked_password;
|
||||
};
|
||||
|
||||
class ArchiveOpenCallback2 :
|
||||
public ArchiveOpenCallback,
|
||||
public IArchiveOpenVolumeCallback {
|
||||
|
||||
public:
|
||||
ArchiveOpenCallback2(
|
||||
BSTR password,
|
||||
BSTR filename,
|
||||
CMyComPtr<OpenVolumeCallback>& callback
|
||||
):
|
||||
ArchiveOpenCallback(password),
|
||||
filename(filename),
|
||||
callback(callback) { }
|
||||
|
||||
public:
|
||||
MY_UNKNOWN_IMP3(
|
||||
IArchiveOpenCallback,
|
||||
IArchiveOpenVolumeCallback,
|
||||
ICryptoGetTextPassword)
|
||||
|
||||
STDMETHOD(GetProperty)(PROPID propID, PROPVARIANT *value) {
|
||||
NWindows::NCOM::CPropVariant prop;
|
||||
if (propID == kpidName) {
|
||||
prop = filename;
|
||||
}
|
||||
prop.Detach(value);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHOD(GetStream)(const wchar_t *name, IInStream **inStream) {
|
||||
CMyComPtr<IInStream> stream = nullptr;
|
||||
HRESULT result = callback->OpenVolume(name, stream);
|
||||
*inStream = stream.Detach();
|
||||
return result != S_OK ? S_FALSE : S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
UString filename;
|
||||
CMyComPtr<OpenVolumeCallback> callback;
|
||||
};
|
||||
|
||||
#define GET_PROP_METHOD(METHOD_NAME, PROP_TYPE, VALUE_TYPE, CONVERTER) \
|
||||
HRESULT METHOD_NAME( \
|
||||
GetPropertyFunc get_property, \
|
||||
UInt32 index, \
|
||||
PROPID prop_id, \
|
||||
PROP_TYPE& value, \
|
||||
bool& is_assigned \
|
||||
) { \
|
||||
NWindows::NCOM::CPropVariant prop; \
|
||||
is_assigned = false; \
|
||||
\
|
||||
RETURN_SAME_IF_NOT_ZERO(get_property(index, prop_id, &prop)); \
|
||||
\
|
||||
if (prop.vt == VALUE_TYPE) { \
|
||||
is_assigned = true; \
|
||||
CONVERTER; \
|
||||
} else if (prop.vt != VT_EMPTY) { \
|
||||
return E_INCONSISTENT_PROP_TYPE; \
|
||||
} \
|
||||
\
|
||||
return S_OK; \
|
||||
}
|
||||
|
||||
GET_PROP_METHOD(GetAString, AString, VT_BSTR, value.SetFromWStr_if_Ascii(prop.bstrVal));
|
||||
|
||||
GET_PROP_METHOD(GetGUID, GUID, VT_BSTR, value = *reinterpret_cast<const GUID*>(prop.bstrVal));
|
||||
|
||||
GET_PROP_METHOD(GetCByteBuffer, CByteBuffer, VT_BSTR, value.CopyFrom((const Byte *)prop.bstrVal, ::SysStringByteLen(prop.bstrVal)));
|
||||
|
||||
GET_PROP_METHOD(GetUInt32, UInt32, VT_UI4, value = prop.ulVal);
|
||||
|
||||
#undef GET_PROP_METHOD
|
||||
|
||||
static HRESULT LoadMethods() {
|
||||
UInt32 method_number = 0;
|
||||
|
||||
RETURN_SAME_IF_NOT_ZERO(GetNumberOfMethods(&method_number));
|
||||
|
||||
for(UInt32 i = 0; i < method_number; i++) {
|
||||
Method& method = methods.AddNew();
|
||||
|
||||
# define GET_PROP(METHOD, PROP, VALUE, ASSIGNED) \
|
||||
if (METHOD(GetMethodProperty, i, PROP, VALUE, ASSIGNED) != S_OK) { \
|
||||
methods.DeleteBack(); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
// It's ok to assume all characters in method name are in ascii charset
|
||||
GET_PROP(GetAString, NMethodPropID::kName, method.name, method.has_name);
|
||||
GET_PROP(GetGUID, NMethodPropID::kDecoder, method.decoder, method.has_decoder);
|
||||
GET_PROP(GetGUID, NMethodPropID::kEncoder, method.encoder, method.has_encoder);
|
||||
|
||||
# undef GET_PROP
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void AppendMultiSignature(
|
||||
CObjectVector<CByteBuffer>& signatures,
|
||||
const unsigned char* multi_signature,
|
||||
size_t size
|
||||
) {
|
||||
while (size > 0) {
|
||||
unsigned length = *multi_signature++;
|
||||
size--;
|
||||
|
||||
if (length > size) {
|
||||
return;
|
||||
}
|
||||
|
||||
signatures.AddNew().CopyFrom(multi_signature, length);
|
||||
|
||||
multi_signature += length;
|
||||
size -= length;
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT LoadFormats() {
|
||||
UInt32 format_number = 0;
|
||||
|
||||
RETURN_SAME_IF_NOT_ZERO(GetNumberOfFormats(&format_number));
|
||||
|
||||
for(UInt32 i = 0; i < format_number; i++) {
|
||||
Format& format = formats.AddNew();
|
||||
|
||||
# define GET_PROP(METHOD, PROP, VALUE, ASSIGNED) \
|
||||
if (METHOD(GetHandlerProperty2, i, PROP, VALUE, ASSIGNED) != S_OK) { \
|
||||
formats.DeleteBack(); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
// ClassID is required
|
||||
bool has_class_id;
|
||||
GET_PROP(GetGUID, NArchive::NHandlerPropID::kClassID, format.class_id, has_class_id);
|
||||
if (!has_class_id) {
|
||||
formats.DeleteBack();
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's ok to assume all characters in format name are in ascii charset
|
||||
GET_PROP(GetAString, NArchive::NHandlerPropID::kName, format.name, format.has_name);
|
||||
|
||||
bool has_signature;
|
||||
CByteBuffer signature;
|
||||
GET_PROP(GetUInt32, NArchive::NHandlerPropID::kSignatureOffset, format.signature_offset, has_signature);
|
||||
if (!has_signature) {
|
||||
format.signature_offset = 0;
|
||||
}
|
||||
GET_PROP(GetCByteBuffer, NArchive::NHandlerPropID::kSignature, signature, has_signature);
|
||||
if (has_signature) {
|
||||
format.signatures.AddNew().CopyFrom(signature, signature.Size());
|
||||
}
|
||||
GET_PROP(GetCByteBuffer, NArchive::NHandlerPropID::kMultiSignature, signature, has_signature);
|
||||
if (has_signature) {
|
||||
AppendMultiSignature(format.signatures, signature, signature.Size());
|
||||
}
|
||||
|
||||
# undef GET_PROP
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SevenZip::Initialize() {
|
||||
if (initialized) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
RETURN_SAME_IF_NOT_ZERO(LoadMethods());
|
||||
RETURN_SAME_IF_NOT_ZERO(LoadFormats());
|
||||
|
||||
initialized = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT ReadFully(CMyComPtr<IInStream>& stream, Byte* data, UInt32 size, UInt32* processedSize) {
|
||||
UInt32 read = 0;
|
||||
|
||||
while (read < size) {
|
||||
RETURN_SAME_IF_NOT_ZERO(stream->Read(data + read, size - read, processedSize));
|
||||
|
||||
if (*processedSize == 0) {
|
||||
// EOF
|
||||
break;
|
||||
}
|
||||
|
||||
read += *processedSize;
|
||||
}
|
||||
|
||||
*processedSize = read;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT OpenInArchive(
|
||||
GUID& class_id,
|
||||
CMyComPtr<IInStream>& in_stream,
|
||||
BSTR password,
|
||||
BSTR filename,
|
||||
CMyComPtr<OpenVolumeCallback>& open_volume_callback,
|
||||
CMyComPtr<IInArchive>& in_archive
|
||||
) {
|
||||
RETURN_SAME_IF_NOT_ZERO(CreateObject(&class_id, &IID_IInArchive, reinterpret_cast<void **>(&in_archive)));
|
||||
|
||||
UInt64 newPosition = 0;
|
||||
HRESULT result = in_stream->Seek(0, STREAM_SEEK_SET, &newPosition);
|
||||
if (result != S_OK) {
|
||||
in_archive->Close();
|
||||
in_archive = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
UInt64 maxCheckStartPosition = 1 << 22;
|
||||
|
||||
ArchiveOpenCallback* callback_ptr = nullptr;
|
||||
if (filename != nullptr && open_volume_callback != nullptr) {
|
||||
callback_ptr = new ArchiveOpenCallback2(password, filename, open_volume_callback);
|
||||
} else {
|
||||
callback_ptr = new ArchiveOpenCallback(password);
|
||||
}
|
||||
CMyComPtr<ArchiveOpenCallback> callback(callback_ptr);
|
||||
|
||||
result = in_archive->Open(in_stream, &maxCheckStartPosition, callback);
|
||||
result = callback->GetBetterResult(result);
|
||||
if (result != S_OK) {
|
||||
in_archive->Close();
|
||||
in_archive = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT OpenInArchive(
|
||||
CMyComPtr<IInStream>& in_stream,
|
||||
BSTR password,
|
||||
BSTR filename,
|
||||
CMyComPtr<OpenVolumeCallback>& open_volume_callback,
|
||||
CMyComPtr<IInArchive>& in_archive,
|
||||
AString& format_name
|
||||
) {
|
||||
bool formats_checked[formats.Size()];
|
||||
memset(formats_checked, 0, formats.Size() * sizeof(bool));
|
||||
|
||||
for (int i = 0; i < formats.Size(); i++) {
|
||||
Format& format = formats[i];
|
||||
|
||||
// Skip format without signatures
|
||||
if (format.signatures.Size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check each signature
|
||||
for (int j = 0; j < format.signatures.Size(); j++) {
|
||||
CByteBuffer& signature = format.signatures[j];
|
||||
|
||||
UInt32 processedSize;
|
||||
CByteBuffer bytes(signature.Size());
|
||||
|
||||
UInt64 newPosition;
|
||||
CONTINUE_IF_NOT_ZERO(in_stream->Seek(format.signature_offset, STREAM_SEEK_SET, &newPosition));
|
||||
if (newPosition != format.signature_offset) continue;
|
||||
CONTINUE_IF_NOT_ZERO(ReadFully(in_stream, bytes, static_cast<UInt32>(signature.Size()), &processedSize));
|
||||
if (processedSize != signature.Size() || bytes != signature) continue;
|
||||
|
||||
// The signature matched, try to open it
|
||||
HRESULT result = OpenInArchive(format.class_id, in_stream, password, filename, open_volume_callback, in_archive);
|
||||
|
||||
// Mark the format
|
||||
formats_checked[i] = true;
|
||||
|
||||
if (result == S_OK) {
|
||||
format_name = format.name;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (result == E_NO_PASSWORD || result == E_WRONG_PASSWORD) {
|
||||
// It's a password error, the archive format is confirmed
|
||||
return result;
|
||||
}
|
||||
|
||||
// Can't open archive in this format
|
||||
// Skip this format
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Try other unchecked formats
|
||||
for (int i = 0; i < formats.Size(); i++) {
|
||||
if (!formats_checked[i]) {
|
||||
Format& format = formats[i];
|
||||
HRESULT result = OpenInArchive(format.class_id, in_stream, password, filename, open_volume_callback, in_archive);
|
||||
|
||||
if (result == S_OK) {
|
||||
format_name = format.name;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (result == E_NO_PASSWORD || result == E_WRONG_PASSWORD) {
|
||||
// It's a password error, the archive format is confirmed
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return E_UNKNOWN_FORMAT;
|
||||
}
|
||||
|
||||
HRESULT SevenZip::OpenArchive(
|
||||
CMyComPtr<IInStream>& in_stream,
|
||||
BSTR password,
|
||||
BSTR filename,
|
||||
CMyComPtr<OpenVolumeCallback>& open_volume_callback,
|
||||
InArchive** archive
|
||||
) {
|
||||
HRESULT result = S_FALSE;
|
||||
InArchive* previous_archive = nullptr;
|
||||
|
||||
CMyComPtr<IInStream> arg_in_stream = in_stream;
|
||||
BSTR arg_password = password;
|
||||
BSTR arg_filename = filename;
|
||||
CMyComPtr<OpenVolumeCallback> arg_open_volume_callback = open_volume_callback;
|
||||
|
||||
while (true) {
|
||||
CMyComPtr<IInArchive> in_archive = nullptr;
|
||||
AString format_name;
|
||||
|
||||
result = OpenInArchive(
|
||||
arg_in_stream,
|
||||
arg_password,
|
||||
arg_filename,
|
||||
arg_open_volume_callback,
|
||||
in_archive,
|
||||
format_name
|
||||
);
|
||||
|
||||
if (result != S_OK || in_archive == nullptr) {
|
||||
if (in_archive != nullptr) {
|
||||
in_archive->Close();
|
||||
in_archive = nullptr;
|
||||
}
|
||||
if (result == S_OK) {
|
||||
result = E_INTERNAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
previous_archive = new InArchive(previous_archive, in_archive, format_name);
|
||||
|
||||
// Break if it has no entry or more than one entry
|
||||
UInt32 number = 0;
|
||||
previous_archive->GetNumberOfEntries(number);
|
||||
if (number != 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Break if it's not IInArchiveGetStream
|
||||
CMyComPtr<IInArchiveGetStream> archive_get_stream;
|
||||
in_archive->QueryInterface(IID_IInArchiveGetStream, (void**)&archive_get_stream);
|
||||
if (archive_get_stream == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Break if GetStream returns null
|
||||
CMyComPtr<ISequentialInStream> sequential_in_stream;
|
||||
archive_get_stream->GetStream(0, &sequential_in_stream);
|
||||
if (sequential_in_stream == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Break if the stream is not IInStream
|
||||
arg_in_stream = nullptr;
|
||||
sequential_in_stream->QueryInterface(IID_IInStream, (void**)&arg_in_stream);
|
||||
if (arg_in_stream == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear arguments for the first archive
|
||||
arg_password = nullptr;
|
||||
arg_filename = nullptr;
|
||||
arg_open_volume_callback = nullptr;
|
||||
}
|
||||
|
||||
*archive = previous_archive;
|
||||
return *archive != nullptr ? S_OK : result;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_P7ZIP_H__
|
||||
#define __A7ZIP_P7ZIP_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <include_windows/windows.h>
|
||||
#include <Common/MyBuffer.h>
|
||||
#include <Common/MyCom.h>
|
||||
#include <Common/MyString.h>
|
||||
|
||||
#include <7zip/ICoder.h>
|
||||
|
||||
#include "InArchive.h"
|
||||
#include "OpenVolumeCallback.h"
|
||||
|
||||
namespace a7zip {
|
||||
namespace SevenZip {
|
||||
|
||||
HRESULT Initialize();
|
||||
HRESULT OpenArchive(
|
||||
CMyComPtr<IInStream>& stream,
|
||||
BSTR password,
|
||||
BSTR filename,
|
||||
CMyComPtr<OpenVolumeCallback>& callback,
|
||||
InArchive** archive
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //__A7ZIP_P7ZIP_H__
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __A7ZIP_UTILS_H__
|
||||
#define __A7ZIP_UTILS_H__
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE (4 * 1024)
|
||||
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
#define E_NOT_INITIALIZED ((HRESULT)0x82210000L)
|
||||
#define E_INTERNAL ((HRESULT)0x82210001L)
|
||||
#define E_UNKNOWN_ERROR ((HRESULT)0x82210002L)
|
||||
|
||||
#define E_CLASS_NOT_FOUND ((HRESULT)0x82220000L)
|
||||
#define E_METHOD_NOT_FOUND ((HRESULT)0x82220001L)
|
||||
#define E_JAVA_EXCEPTION ((HRESULT)0x82220002L)
|
||||
#define E_FAILED_CONSTRUCT ((HRESULT)0x82220003L)
|
||||
#define E_FAILED_REGISTER ((HRESULT)0x82220004L)
|
||||
#define E_FAILED_UNREGISTER ((HRESULT)0x82220005L)
|
||||
|
||||
#define E_INCONSISTENT_PROP_TYPE ((HRESULT)0x82240000L)
|
||||
#define E_EMPTY_PROP ((HRESULT)0x82240001L)
|
||||
#define E_UNKNOWN_FORMAT ((HRESULT)0x82240002L)
|
||||
#define E_UNSUPPORTED_EXTRACT_MODE ((HRESULT)0x82240003L)
|
||||
#define E_NO_OUT_STREAM ((HRESULT)0x82240004L)
|
||||
|
||||
#define E_UNSUPPORTED_METHOD ((HRESULT)0x82250000L)
|
||||
#define E_DATA_ERROR ((HRESULT)0x82250001L)
|
||||
#define E_DATA_ERROR_ENCRYPTED ((HRESULT)0x82250002L)
|
||||
#define E_CRC_ERROR ((HRESULT)0x82250003L)
|
||||
#define E_CRC_ERROR_ENCRYPTED ((HRESULT)0x82250004L)
|
||||
#define E_UNAVAILABLE ((HRESULT)0x82250005L)
|
||||
#define E_UNEXPECTED_END ((HRESULT)0x82250006L)
|
||||
#define E_DATA_AFTER_END ((HRESULT)0x82250007L)
|
||||
#define E_IS_NOT_ARC ((HRESULT)0x82250008L)
|
||||
#define E_HEADERS_ERROR ((HRESULT)0x82250009L)
|
||||
#define E_WRONG_PASSWORD ((HRESULT)0x82250010L)
|
||||
#define E_NO_PASSWORD ((HRESULT)0x82250011L)
|
||||
|
||||
#define CLEAR_IF_EXCEPTION_PENDING(ENV) \
|
||||
do { \
|
||||
if ((ENV)->ExceptionCheck()) { \
|
||||
(ENV)->ExceptionDescribe(); \
|
||||
(ENV)->ExceptionClear(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(ENV) \
|
||||
do { \
|
||||
if ((ENV)->ExceptionCheck()) { \
|
||||
(ENV)->ExceptionDescribe(); \
|
||||
(ENV)->ExceptionClear(); \
|
||||
return E_JAVA_EXCEPTION; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RETURN_JNI_ERR_IF_NOT_ZERO(VALUE) \
|
||||
do { \
|
||||
if ((VALUE) != 0) { \
|
||||
return JNI_ERR; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RETURN_SAME_IF_NOT_ZERO(VALUE) \
|
||||
do { \
|
||||
auto __temp__ = (VALUE); \
|
||||
if (__temp__ != 0) { \
|
||||
return __temp__; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CONTINUE_IF_NOT_ZERO(VALUE) \
|
||||
do { \
|
||||
if ((VALUE) != 0) { \
|
||||
continue; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif //__A7ZIP_UTILS_H__
|
||||