implement mihonapp/mihon#326 (#1104)
* implement mihonapp/mihon#326 Archives are now being read from channels Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * disable parallelisms for loading into memory * switched to mutex * detekt changes * more detekt baseline changes --------- Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
@@ -36,6 +36,7 @@ dependencies {
|
||||
implementation(libs.image.decoder)
|
||||
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.bundles.archive)
|
||||
|
||||
api(kotlinx.coroutines.core)
|
||||
api(kotlinx.serialization.json)
|
||||
|
||||
@@ -3,25 +3,15 @@ package eu.kanade.tachiyomi.util.storage
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import logcat.LogPriority
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.exception.ZipException
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream
|
||||
import net.lingala.zip4j.io.outputstream.ZipOutputStream
|
||||
import net.lingala.zip4j.model.LocalFileHeader
|
||||
import net.lingala.zip4j.model.ZipParameters
|
||||
import net.lingala.zip4j.model.enums.AesKeyStrength
|
||||
import net.lingala.zip4j.model.enums.EncryptionMethod
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -136,12 +126,15 @@ object CbzCrypto {
|
||||
}
|
||||
|
||||
fun getDecryptedPasswordCbz(): CharArray {
|
||||
return decrypt(securityPreferences.cbzPassword().get(), ALIAS_CBZ).toCharArray()
|
||||
val encryptedPassword = securityPreferences.cbzPassword().get()
|
||||
if (encryptedPassword.isBlank()) error("This archive is encrypted please set a password")
|
||||
|
||||
return decrypt(encryptedPassword, ALIAS_CBZ).toCharArray()
|
||||
}
|
||||
|
||||
private fun generateAndEncryptSqlPw() {
|
||||
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
val password = (1..32).map {
|
||||
val password = (1..SQL_PASSWORD_LENGTH).map {
|
||||
charPool[SecureRandom().nextInt(charPool.size)]
|
||||
}.joinToString("", transform = { it.toString() })
|
||||
securityPreferences.sqlPassword().set(encrypt(password, encryptionCipherSql))
|
||||
@@ -152,27 +145,6 @@ object CbzCrypto {
|
||||
return decrypt(securityPreferences.sqlPassword().get(), ALIAS_SQL).toByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that returns true when the supplied password
|
||||
* can Successfully decrypt the supplied zip archive
|
||||
* not very elegant but this is the solution recommended by the maintainer for checking passwords
|
||||
* a real password check will likely be implemented in the future though
|
||||
*/
|
||||
fun checkCbzPassword(zip4j: ZipFile, password: CharArray): Boolean {
|
||||
try {
|
||||
zip4j.setPassword(password)
|
||||
zip4j.use { zip ->
|
||||
zip.getInputStream(zip.fileHeaders.firstOrNull())
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${zip4j.file.name} in: ${zip4j.file.parentFile?.name}"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isPasswordSet(): Boolean {
|
||||
return securityPreferences.cbzPassword().get().isNotEmpty()
|
||||
}
|
||||
@@ -228,133 +200,6 @@ object CbzCrypto {
|
||||
}
|
||||
return String(bytes).contains(DEFAULT_COVER_NAME, ignoreCase = true)
|
||||
}
|
||||
|
||||
fun UniFile.isEncryptedZip(): Boolean {
|
||||
return try {
|
||||
val stream = ZipInputStream(this.openInputStream())
|
||||
stream.nextEntry
|
||||
stream.close()
|
||||
false
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
true
|
||||
} else throw zipException
|
||||
}
|
||||
}
|
||||
|
||||
fun UniFile.testCbzPassword(): Boolean {
|
||||
return try {
|
||||
val stream = ZipInputStream(this.openInputStream())
|
||||
stream.setPassword(getDecryptedPasswordCbz())
|
||||
stream.nextEntry
|
||||
stream.close()
|
||||
true
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
false
|
||||
} else throw zipException
|
||||
}
|
||||
}
|
||||
|
||||
fun UniFile.addStreamToZip(inputStream: InputStream, filename: String, password: CharArray? = null) {
|
||||
val zipOutputStream =
|
||||
if (password != null) ZipOutputStream(this.openOutputStream(), password)
|
||||
else ZipOutputStream(this.openOutputStream())
|
||||
|
||||
val zipParameters = ZipParameters()
|
||||
zipParameters.fileNameInZip = filename
|
||||
|
||||
if (password != null) setZipParametersEncrypted(zipParameters)
|
||||
zipOutputStream.putNextEntry(zipParameters)
|
||||
|
||||
zipOutputStream.use { output ->
|
||||
inputStream.use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun UniFile.addFilesToZip(files: List<UniFile>, password: CharArray? = null) {
|
||||
val zipOutputStream =
|
||||
if (password != null) ZipOutputStream(this.openOutputStream(), password)
|
||||
else ZipOutputStream(this.openOutputStream())
|
||||
|
||||
|
||||
files.forEach {
|
||||
val zipParameters = ZipParameters()
|
||||
if (password != null) setZipParametersEncrypted(zipParameters)
|
||||
zipParameters.fileNameInZip = it.name
|
||||
|
||||
zipOutputStream.putNextEntry(zipParameters)
|
||||
|
||||
it.openInputStream().use { input ->
|
||||
input.copyTo(zipOutputStream)
|
||||
}
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
zipOutputStream.close()
|
||||
}
|
||||
|
||||
fun UniFile.getZipInputStream(filename: String): InputStream? {
|
||||
val zipInputStream = ZipInputStream(this.openInputStream())
|
||||
var fileHeader: LocalFileHeader?
|
||||
|
||||
if (this.isEncryptedZip()) zipInputStream.setPassword(getDecryptedPasswordCbz())
|
||||
|
||||
try {
|
||||
while (run {
|
||||
fileHeader = zipInputStream.nextEntry
|
||||
fileHeader != null
|
||||
}) {
|
||||
if (fileHeader?.fileName == filename) return zipInputStream
|
||||
}
|
||||
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${this.name} in: ${this.parentFile?.name}"
|
||||
}
|
||||
} else throw zipException
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun UniFile.getCoverStreamFromZip(): InputStream? {
|
||||
val zipInputStream = ZipInputStream(this.openInputStream())
|
||||
var fileHeader: LocalFileHeader?
|
||||
val fileHeaderList: MutableList<LocalFileHeader?> = mutableListOf()
|
||||
|
||||
if (this.isEncryptedZip()) zipInputStream.setPassword(getDecryptedPasswordCbz())
|
||||
|
||||
try {
|
||||
while (run {
|
||||
fileHeader = zipInputStream.nextEntry
|
||||
fileHeader != null
|
||||
}) {
|
||||
fileHeaderList.add(fileHeader)
|
||||
}
|
||||
|
||||
var coverHeader = fileHeaderList
|
||||
.mapNotNull { it }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) }
|
||||
|
||||
|
||||
val coverStream = coverHeader?.fileName?.let { this.getZipInputStream(it) }
|
||||
if (coverStream != null) {
|
||||
if (!ImageUtil.isImage(coverHeader?.fileName) { coverStream }) coverHeader = null
|
||||
}
|
||||
return coverHeader?.fileName?.let { getZipInputStream(it) }
|
||||
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${this.name} in: ${this.parentFile?.name}"
|
||||
}
|
||||
return null
|
||||
} else throw zipException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val BUFFER_SIZE = 2048
|
||||
@@ -369,4 +214,6 @@ private const val CRYPTO_SETTINGS = "$ALGORITHM/$BLOCK_MODE/$PADDING"
|
||||
private const val KEYSTORE = "AndroidKeyStore"
|
||||
private const val ALIAS_CBZ = "cbzPw"
|
||||
private const val ALIAS_SQL = "sqlPw"
|
||||
|
||||
private const val SQL_PASSWORD_LENGTH = 32
|
||||
// SY <--
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
package eu.kanade.tachiyomi.util.storage
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.hippo.unifile.UniFile
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.core.common.storage.openReadOnlyChannel
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* Wrapper over ZipFile to load files in epub format.
|
||||
*/
|
||||
class EpubFile(file: File) : Closeable {
|
||||
// SY -->
|
||||
class EpubFile(file: UniFile, context: Context) : Closeable {
|
||||
|
||||
private val tempFileManager: UniFileTempFileManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Zip file of this epub.
|
||||
*/
|
||||
private val zip = ZipFile(file)
|
||||
private val zip = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
ZipFile(tempFileManager.createTempFile(file))
|
||||
} else {
|
||||
ZipFile(file.openReadOnlyChannel(context))
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Path separator used by this epub.
|
||||
@@ -33,14 +47,14 @@ class EpubFile(file: File) : Closeable {
|
||||
/**
|
||||
* Returns an input stream for reading the contents of the specified zip file entry.
|
||||
*/
|
||||
fun getInputStream(entry: ZipEntry): InputStream {
|
||||
fun getInputStream(entry: ZipArchiveEntry): InputStream {
|
||||
return zip.getInputStream(entry)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zip file entry for the specified name, or null if not found.
|
||||
*/
|
||||
fun getEntry(name: String): ZipEntry? {
|
||||
fun getEntry(name: String): ZipArchiveEntry? {
|
||||
return zip.getEntry(name)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
package tachiyomi.core.common.storage
|
||||
|
||||
import android.content.Context
|
||||
import android.os.ParcelFileDescriptor
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import logcat.LogPriority
|
||||
import net.lingala.zip4j.exception.ZipException
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream
|
||||
import net.lingala.zip4j.io.outputstream.ZipOutputStream
|
||||
import net.lingala.zip4j.model.LocalFileHeader
|
||||
import net.lingala.zip4j.model.ZipParameters
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
val UniFile.extension: String?
|
||||
get() = name?.substringAfterLast('.')
|
||||
@@ -10,3 +25,201 @@ val UniFile.nameWithoutExtension: String?
|
||||
|
||||
val UniFile.displayablePath: String
|
||||
get() = filePath ?: uri.toString()
|
||||
|
||||
fun UniFile.openReadOnlyChannel(context: Context): FileChannel {
|
||||
return ParcelFileDescriptor.AutoCloseInputStream(context.contentResolver.openFileDescriptor(uri, "r")).channel
|
||||
// SY -->
|
||||
}
|
||||
|
||||
fun UniFile.isEncryptedZip(): Boolean {
|
||||
return try {
|
||||
val stream = ZipInputStream(this.openInputStream())
|
||||
stream.nextEntry
|
||||
stream.close()
|
||||
false
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
true
|
||||
} else {
|
||||
throw zipException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UniFile.testCbzPassword(): Boolean {
|
||||
return try {
|
||||
val stream = ZipInputStream(this.openInputStream())
|
||||
stream.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
stream.nextEntry
|
||||
stream.close()
|
||||
true
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
false
|
||||
} else {
|
||||
throw zipException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UniFile.addStreamToZip(inputStream: InputStream, filename: String, password: CharArray? = null) {
|
||||
val zipOutputStream =
|
||||
if (password != null) {
|
||||
ZipOutputStream(this.openOutputStream(), password)
|
||||
} else {
|
||||
ZipOutputStream(this.openOutputStream())
|
||||
}
|
||||
|
||||
val zipParameters = ZipParameters()
|
||||
zipParameters.fileNameInZip = filename
|
||||
|
||||
if (password != null) CbzCrypto.setZipParametersEncrypted(zipParameters)
|
||||
zipOutputStream.putNextEntry(zipParameters)
|
||||
|
||||
zipOutputStream.use { output ->
|
||||
inputStream.use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips encrypted or unencrypted zip files using zip4j.
|
||||
* The caller is responsible to ensure, that the file this is called from is a zip archive
|
||||
*/
|
||||
fun UniFile.unzip(destination: File, onlyCopyImages: Boolean = false) {
|
||||
destination.mkdirs()
|
||||
if (!destination.isDirectory) return
|
||||
|
||||
val zipInputStream = ZipInputStream(this.openInputStream())
|
||||
var fileHeader: LocalFileHeader?
|
||||
|
||||
if (this.isEncryptedZip()) {
|
||||
zipInputStream.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
}
|
||||
try {
|
||||
while (
|
||||
run {
|
||||
fileHeader = zipInputStream.nextEntry
|
||||
fileHeader != null
|
||||
}
|
||||
) {
|
||||
val tmpFile = File("${destination.absolutePath}/${fileHeader!!.fileName}")
|
||||
|
||||
if (onlyCopyImages) {
|
||||
if (!fileHeader!!.isDirectory && ImageUtil.isImage(fileHeader!!.fileName)) {
|
||||
tmpFile.createNewFile()
|
||||
tmpFile.outputStream().buffered().use { tmpOut ->
|
||||
zipInputStream.buffered().copyTo(tmpOut)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!fileHeader!!.isDirectory && ImageUtil.isImage(fileHeader!!.fileName)) {
|
||||
tmpFile.createNewFile()
|
||||
tmpFile
|
||||
.outputStream()
|
||||
.buffered()
|
||||
.use { zipInputStream.buffered().copyTo(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
zipInputStream.close()
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${this.name} in: ${this.parentFile?.name}"
|
||||
}
|
||||
} else {
|
||||
throw zipException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UniFile.addFilesToZip(files: List<UniFile>, password: CharArray? = null) {
|
||||
val zipOutputStream =
|
||||
if (password != null) {
|
||||
ZipOutputStream(this.openOutputStream(), password)
|
||||
} else {
|
||||
ZipOutputStream(this.openOutputStream())
|
||||
}
|
||||
|
||||
files.forEach {
|
||||
val zipParameters = ZipParameters()
|
||||
if (password != null) CbzCrypto.setZipParametersEncrypted(zipParameters)
|
||||
zipParameters.fileNameInZip = it.name
|
||||
|
||||
zipOutputStream.putNextEntry(zipParameters)
|
||||
|
||||
it.openInputStream().use { input ->
|
||||
input.copyTo(zipOutputStream)
|
||||
}
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
zipOutputStream.close()
|
||||
}
|
||||
|
||||
fun UniFile.getZipInputStream(filename: String): InputStream? {
|
||||
val zipInputStream = ZipInputStream(this.openInputStream())
|
||||
var fileHeader: LocalFileHeader?
|
||||
|
||||
if (this.isEncryptedZip()) zipInputStream.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
|
||||
try {
|
||||
while (
|
||||
run {
|
||||
fileHeader = zipInputStream.nextEntry
|
||||
fileHeader != null
|
||||
}
|
||||
) {
|
||||
if (fileHeader?.fileName == filename) return zipInputStream
|
||||
}
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${this.name} in: ${this.parentFile?.name}"
|
||||
}
|
||||
} else {
|
||||
throw zipException
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun UniFile.getCoverStreamFromZip(): InputStream? {
|
||||
val zipInputStream = ZipInputStream(this.openInputStream())
|
||||
var fileHeader: LocalFileHeader?
|
||||
val fileHeaderList: MutableList<LocalFileHeader?> = mutableListOf()
|
||||
|
||||
if (this.isEncryptedZip()) zipInputStream.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
|
||||
try {
|
||||
while (
|
||||
run {
|
||||
fileHeader = zipInputStream.nextEntry
|
||||
fileHeader != null
|
||||
}
|
||||
) {
|
||||
fileHeaderList.add(fileHeader)
|
||||
}
|
||||
var coverHeader = fileHeaderList
|
||||
.mapNotNull { it }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) }
|
||||
|
||||
val coverStream = coverHeader?.fileName?.let { this.getZipInputStream(it) }
|
||||
if (coverStream != null) {
|
||||
if (!ImageUtil.isImage(coverHeader?.fileName) { coverStream }) coverHeader = null
|
||||
}
|
||||
return coverHeader?.fileName?.let { getZipInputStream(it) }
|
||||
} catch (zipException: ZipException) {
|
||||
if (zipException.type == ZipException.Type.WRONG_PASSWORD) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Wrong CBZ archive password for: ${this.name} in: ${this.parentFile?.name}"
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
throw zipException
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@@ -26,8 +26,6 @@ import androidx.core.graphics.red
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.hippo.unifile.UniFile
|
||||
import logcat.LogPriority
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.FileHeader
|
||||
import tachiyomi.decoder.Format
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
import java.io.BufferedInputStream
|
||||
@@ -133,20 +131,8 @@ object ImageUtil {
|
||||
*
|
||||
* @return true if the width is greater than the height
|
||||
*/
|
||||
fun isWideImage(
|
||||
imageStream: BufferedInputStream,
|
||||
// SY -->
|
||||
zip4jFile: ZipFile?,
|
||||
zip4jEntry: FileHeader?,
|
||||
// SY <--
|
||||
): Boolean {
|
||||
val options = extractImageOptions(
|
||||
imageStream,
|
||||
// SY -->
|
||||
zip4jFile,
|
||||
zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
fun isWideImage(imageStream: BufferedInputStream): Boolean {
|
||||
val options = extractImageOptions(imageStream)
|
||||
return options.outWidth > options.outHeight
|
||||
}
|
||||
|
||||
@@ -271,19 +257,9 @@ object ImageUtil {
|
||||
*
|
||||
* @return true if the height:width ratio is greater than 3.
|
||||
*/
|
||||
private fun isTallImage(
|
||||
imageStream: InputStream,
|
||||
// SY -->
|
||||
zip4jFile: ZipFile?,
|
||||
zip4jEntry: FileHeader?,
|
||||
// SY <--
|
||||
): Boolean {
|
||||
private fun isTallImage(imageStream: InputStream): Boolean {
|
||||
val options = extractImageOptions(
|
||||
imageStream,
|
||||
// SY -->
|
||||
zip4jFile,
|
||||
zip4jEntry,
|
||||
// SY <--
|
||||
resetAfterExtraction = false,
|
||||
)
|
||||
|
||||
@@ -297,18 +273,9 @@ object ImageUtil {
|
||||
tmpDir: UniFile,
|
||||
imageFile: UniFile,
|
||||
filenamePrefix: String,
|
||||
// SY -->
|
||||
zip4jFile: ZipFile?,
|
||||
zip4jEntry: FileHeader?,
|
||||
// SY <--
|
||||
): Boolean {
|
||||
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(
|
||||
imageFile.openInputStream(),
|
||||
// SY -->
|
||||
zip4jFile,
|
||||
zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
if (isAnimatedAndSupported(imageFile.openInputStream()) ||
|
||||
!isTallImage(imageFile.openInputStream())
|
||||
) {
|
||||
return true
|
||||
}
|
||||
@@ -321,10 +288,6 @@ object ImageUtil {
|
||||
|
||||
val options = extractImageOptions(
|
||||
imageFile.openInputStream(),
|
||||
// SY -->
|
||||
zip4jFile,
|
||||
zip4jEntry,
|
||||
// SY <--
|
||||
resetAfterExtraction = false,
|
||||
).apply {
|
||||
inJustDecodeBounds = false
|
||||
@@ -641,17 +604,8 @@ object ImageUtil {
|
||||
*/
|
||||
private fun extractImageOptions(
|
||||
imageStream: InputStream,
|
||||
// SY -->
|
||||
zip4jFile: ZipFile?,
|
||||
zip4jEntry: FileHeader?,
|
||||
// SY <--
|
||||
resetAfterExtraction: Boolean = true,
|
||||
): BitmapFactory.Options {
|
||||
// SY -->
|
||||
// zip4j does currently not support mark() and reset()
|
||||
if (zip4jFile != null && zip4jEntry != null) return extractImageOptionsZip4j(zip4jFile, zip4jEntry)
|
||||
// SY <--
|
||||
|
||||
imageStream.mark(Int.MAX_VALUE)
|
||||
|
||||
val imageBytes = imageStream.readBytes()
|
||||
@@ -661,16 +615,6 @@ object ImageUtil {
|
||||
return options
|
||||
}
|
||||
|
||||
// SY -->
|
||||
private fun extractImageOptionsZip4j(zip4jFile: ZipFile?, zip4jEntry: FileHeader?): BitmapFactory.Options {
|
||||
zip4jFile?.getInputStream(zip4jEntry).use { imageStream ->
|
||||
val imageBytes = imageStream?.readBytes()
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
imageBytes?.size?.let { BitmapFactory.decodeByteArray(imageBytes, 0, it, options) }
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates random exif metadata used as padding to make
|
||||
* the size of files inside CBZ archives unique
|
||||
|
||||
Reference in New Issue
Block a user