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:
Shamicen
2023-05-13 04:51:37 +02:00
committed by GitHub
parent 282a0c4e16
commit 291734a406
10 changed files with 81 additions and 154 deletions
@@ -390,13 +390,16 @@ actual class LocalSource(
is Format.Zip -> {
ZipFile(format.file).use { zip ->
// SY -->
if (zip.isEncrypted) zip.setPassword(CbzCrypto.getDecryptedPasswordCbz())
var encrypted = false
if (zip.isEncrypted) {
zip.setPassword(CbzCrypto.getDecryptedPasswordCbz())
encrypted = true
}
val entry = zip.fileHeaders.toList()
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip.getInputStream(it) } }
entry?.let { coverManager.update(manga, zip.getInputStream(it), encrypted) }
// SY <--
entry?.let { coverManager.update(manga, zip.getInputStream(it)) }
}
}
is Format.Rar -> {
@@ -2,58 +2,40 @@ package tachiyomi.source.local.image
import android.content.Context
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.DiskUtil
import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.ZipParameters
import tachiyomi.core.util.system.ImageUtil
import tachiyomi.source.local.io.LocalSourceFileSystem
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.io.InputStream
private const val DEFAULT_COVER_NAME = "cover.jpg"
// SY -->
private const val NO_COVER_FILE = ".nocover"
private const val CACHE_COVER_INTERNAL = ".cacheCoverInternal"
private const val LOCAL_CACHE_DIR = "covers/local"
// SY <--
private const val COVER_ARCHIVE_NAME = "cover.cbi"
actual class LocalCoverManager(
private val context: Context,
private val fileSystem: LocalSourceFileSystem,
// SY -->
private val coverCacheDir: File? = context.getExternalFilesDir(LOCAL_CACHE_DIR),
private val securityPreferences: SecurityPreferences = Injekt.get(),
// SY <--
) {
actual fun find(mangaUrl: String): File? {
return fileSystem.getFilesInMangaDirectory(mangaUrl)
// Get all file whose names start with 'cover'
// --> SY
.filter { (it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true)) || it.name == NO_COVER_FILE || it.name == CACHE_COVER_INTERNAL }
.filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) }
// Get the first actual image
.firstOrNull {
if (it.name != NO_COVER_FILE && it.name != CACHE_COVER_INTERNAL) {
ImageUtil.isImage(it.name) { it.inputStream() }
} else if (it.name == NO_COVER_FILE) {
true
} else if (it.name == CACHE_COVER_INTERNAL) {
return File("$coverCacheDir/${it.parentFile?.name}/$DEFAULT_COVER_NAME")
} else {
false
}
// SY <--
ImageUtil.isImage(it.name) { it.inputStream() } || it.name == COVER_ARCHIVE_NAME
}
}
actual fun update(
manga: SManga,
inputStream: InputStream,
// SY -->
encrypted: Boolean,
// SY <--
): File? {
val directory = fileSystem.getMangaDirectory(manga.url)
if (directory == null) {
@@ -64,48 +46,40 @@ actual class LocalCoverManager(
var targetFile = find(manga.url)
if (targetFile == null) {
// SY -->
targetFile = when (securityPreferences.localCoverLocation().get()) {
SecurityPreferences.CoverCacheLocation.INTERNAL -> File(directory.absolutePath, CACHE_COVER_INTERNAL)
SecurityPreferences.CoverCacheLocation.NEVER -> File(directory.absolutePath, NO_COVER_FILE)
SecurityPreferences.CoverCacheLocation.IN_MANGA_DIRECTORY -> File(directory.absolutePath, DEFAULT_COVER_NAME)
if (encrypted) {
targetFile = File(directory.absolutePath, COVER_ARCHIVE_NAME)
} else {
targetFile = File(directory.absolutePath, DEFAULT_COVER_NAME)
targetFile.createNewFile()
}
if (targetFile.parentFile?.parentFile?.name != "local") targetFile.parentFile?.mkdirs()
targetFile.createNewFile()
// SY <--
}
if (targetFile.name == NO_COVER_FILE) return null
if (securityPreferences.localCoverLocation().get() == SecurityPreferences.CoverCacheLocation.IN_MANGA_DIRECTORY) {
// SY <--
// It might not exist at this point
targetFile.parentFile?.mkdirs()
inputStream.use { input ->
// It might not exist at this point
targetFile.parentFile?.mkdirs()
inputStream.use { input ->
// SY -->
if (encrypted) {
val zip4j = ZipFile(targetFile)
val zipParameters = ZipParameters()
zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
CbzCrypto.setZipParametersEncrypted(zipParameters)
zipParameters.fileNameInZip = DEFAULT_COVER_NAME
zip4j.addStream(input, zipParameters)
DiskUtil.createNoMediaFile(UniFile.fromFile(directory), context)
manga.thumbnail_url = zip4j.file.absolutePath
return zip4j.file
} else {
// SY <--
targetFile.outputStream().use { output ->
input.copyTo(output)
}
DiskUtil.createNoMediaFile(UniFile.fromFile(directory), context)
manga.thumbnail_url = targetFile.absolutePath
return targetFile
// SY -->
}
} else if (securityPreferences.localCoverLocation().get() == SecurityPreferences.CoverCacheLocation.INTERNAL) {
// It might not exist at this point
targetFile.parentFile?.mkdirs()
val path = "$coverCacheDir/${targetFile.parentFile?.name}/$DEFAULT_COVER_NAME"
val outputFile = File(path)
outputFile.parentFile?.mkdirs()
outputFile.createNewFile()
inputStream.use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
manga.thumbnail_url = outputFile.absolutePath
return outputFile
} else {
return null
}
// SY <--
}
}
@@ -8,5 +8,7 @@ expect class LocalCoverManager {
fun find(mangaUrl: String): File?
fun update(manga: SManga, inputStream: InputStream): File?
// SY -->
fun update(manga: SManga, inputStream: InputStream, encrypted: Boolean = false): File?
// SY <--
}