Implemented local cover encryption (#881)
* Implemented local cover encryption and made coil capable of reading encrypted cover archives * add check that the file is not an image before determining that it is a zip file
This commit is contained in:
-46
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.presentation.more.settings.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -25,7 +24,6 @@ import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -57,13 +55,8 @@ import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
object SettingsSecurityScreen : SearchableSettings {
|
||||
|
||||
@@ -168,45 +161,6 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
},
|
||||
enabled = isCbzPasswordSet,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.localCoverLocation(),
|
||||
title = stringResource(R.string.save_local_manga_covers),
|
||||
entries = SecurityPreferences.CoverCacheLocation.values()
|
||||
.associateWith { stringResource(it.titleResId) },
|
||||
enabled = passwordProtectDownloads,
|
||||
onValueChanged = {
|
||||
try {
|
||||
withIOContext {
|
||||
CbzCrypto.deleteLocalCoverCache(context)
|
||||
CbzCrypto.deleteLocalCoverSystemFiles(context)
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
context.toast(e.toString(), Toast.LENGTH_SHORT).show()
|
||||
false
|
||||
}
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.delete_cached_local_source_covers),
|
||||
subtitle = stringResource(R.string.delete_cached_local_source_covers_subtitle),
|
||||
onClick = {
|
||||
try {
|
||||
CbzCrypto.deleteLocalCoverCache(context)
|
||||
CbzCrypto.deleteLocalCoverSystemFiles(context)
|
||||
context.toast(R.string.successfully_deleted_all_locally_cached_covers, Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
context.toast(R.string.something_went_wrong_deleting_your_cover_images, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
},
|
||||
enabled = produceState(false) {
|
||||
withIOContext {
|
||||
value = context.getExternalFilesDir("covers/local")?.absolutePath?.let { File(it).listFiles()?.isNotEmpty() } == true
|
||||
}
|
||||
}.value,
|
||||
),
|
||||
kotlin.run {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||
|
||||
@@ -214,7 +214,7 @@ class PreferenceModule(val application: Application) : InjektModule {
|
||||
SourcePreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
SecurityPreferences(get(), application.applicationContext)
|
||||
SecurityPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
LibraryPreferences(get())
|
||||
|
||||
@@ -9,6 +9,9 @@ import coil.decode.ImageDecoderDecoder
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.SourceResult
|
||||
import coil.request.Options
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.FileHeader
|
||||
import okio.BufferedSource
|
||||
import tachiyomi.core.util.system.ImageUtil
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
@@ -19,9 +22,24 @@ import tachiyomi.decoder.ImageDecoder
|
||||
class TachiyomiImageDecoder(private val resources: ImageSource, private val options: Options) : Decoder {
|
||||
|
||||
override suspend fun decode(): DecodeResult {
|
||||
val decoder = resources.sourceOrNull()?.use {
|
||||
ImageDecoder.newInstance(it.inputStream())
|
||||
// SY -->
|
||||
var zip4j: ZipFile? = null
|
||||
var entry: FileHeader? = null
|
||||
|
||||
if (resources.sourceOrNull()?.peek()?.use { CbzCrypto.detectCoverImageArchive(it.inputStream()) } == true) {
|
||||
if (resources.source().peek().use { ImageUtil.findImageType(it.inputStream()) == null }) {
|
||||
zip4j = ZipFile(resources.file().toFile().absolutePath)
|
||||
entry = zip4j.fileHeaders.firstOrNull { it.fileName.equals(CbzCrypto.DEFAULT_COVER_NAME, ignoreCase = true) }
|
||||
|
||||
if (zip4j.isEncrypted) zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
}
|
||||
}
|
||||
val decoder = resources.sourceOrNull()?.use {
|
||||
zip4j.use { zipFile ->
|
||||
ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream())
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" }
|
||||
|
||||
@@ -45,6 +63,9 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
||||
|
||||
private fun isApplicable(source: BufferedSource): Boolean {
|
||||
val type = source.peek().inputStream().use {
|
||||
// SY -->
|
||||
if (CbzCrypto.detectCoverImageArchive(it)) return true
|
||||
// SY <--
|
||||
ImageUtil.findImageType(it)
|
||||
}
|
||||
return when (type) {
|
||||
|
||||
Reference in New Issue
Block a user