From fe3f95d1600bc251932a10e27f24d8a90ce1e98d Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 13 Jul 2025 10:04:18 +0300 Subject: [PATCH] Cache custom covers (close #1492) --- .../override/OverrideConfigViewModel.kt | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt index 241491fc5..701a0556e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt @@ -1,23 +1,39 @@ package org.koitharu.kotatsu.settings.override +import android.content.Context +import androidx.core.net.toUri import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.withContext +import okio.buffer +import okio.sink import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.model.MangaOverride +import org.koitharu.kotatsu.core.util.MimeTypes import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call +import org.koitharu.kotatsu.core.util.ext.isFileUri +import org.koitharu.kotatsu.core.util.ext.openSource import org.koitharu.kotatsu.core.util.ext.require +import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull +import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.md5 +import java.io.File import javax.inject.Inject +private const val DIR_COVERS = "covers" + @HiltViewModel class OverrideConfigViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + @ApplicationContext private val context: Context, private val dataRepository: MangaDataRepository, ) : BaseViewModel() { @@ -34,9 +50,12 @@ class OverrideConfigViewModel @Inject constructor( fun save(title: String?) { launchLoadingJob(Dispatchers.Default) { - val override = checkNotNull(data.value).second.copy( - title = title, - ) + val override = checkNotNull(data.value).second.let { + it.copy( + title = title, + coverUrl = it.coverUrl?.cachedFile(), + ) + } dataRepository.setOverride(manga, override) onSaved.call(Unit) } @@ -49,5 +68,33 @@ class OverrideConfigViewModel @Inject constructor( ) } + private suspend fun String.cachedFile(): String { + val uri = toUriOrNull() + if (uri == null || uri.isFileUri()) { + return this + } + val cacheDir = context.getExternalFilesDir(DIR_COVERS) ?: return this + val cr = context.contentResolver + val ext = cr.getType(uri)?.toMimeTypeOrNull()?.let { + MimeTypes.getExtension(it) + } + val fileName = buildString { + append(this@cachedFile.md5()) + if (!ext.isNullOrEmpty()) { + append('.') + append(ext) + } + } + return withContext(Dispatchers.IO) { + val dest = File(cacheDir, fileName) + cr.openSource(uri).use { source -> + dest.sink().buffer().use { sink -> + sink.writeAll(source) + } + } + dest + }.toUri().toString() + } + private fun emptyOverride() = MangaOverride(null, null, null) }