Workaround for image search (#768)

* Workaround for image search

* Cleanup
This commit is contained in:
FooIbar authored and GitHub committed 2024-02-20 15:22:51 +00:00
1 parent 953e216dde
commit 84299b4db7
21 files changed
+44 -142

No files matched your search

@@ -25,6 +25,8 @@ import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.StatusCodeException
import com.hippo.ehviewer.util.copyTo
import com.hippo.unifile.asUniFile
import com.hippo.unifile.sha1
import eu.kanade.tachiyomi.util.system.logcat
import io.ktor.client.HttpClient
import io.ktor.client.request.prepareGet
@@ -42,8 +44,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.tarsin.coroutines.runSuspendCatching
import okio.BufferedSource
import okio.HashingSink.Companion.sha1
import okio.blackholeSink
import okio.buffer
import okio.source
import splitties.init.appCtx
@@ -163,19 +163,8 @@ object EhTagDatabase : CoroutineScope {
}.getOrNull()
}
private fun getFileSha1(file: File): String? {
return runCatching {
file.source().buffer().use { source ->
sha1(blackholeSink()).use {
source.readAll(it)
it.hash.hex()
}
}
}.getOrNull()
}
private fun checkData(sha1: String?, data: File): Boolean {
return sha1 != null && sha1 == getFileSha1(data)
return sha1 != null && sha1 == data.asUniFile().sha1()
}
private suspend fun save(client: HttpClient, url: String, file: File) {
@@ -139,7 +139,7 @@ object EhUrl {
fun getTagDefinitionUrl(tag: String) = "https://ehwiki.org/wiki/" + tag.replace(' ', '_')
}
inline fun ehUrl(path: String?, host: String = EhUrl.domain, builder: ParametersBuilder.() -> Unit) = ehUrl(
inline fun ehUrl(path: String? = null, host: String = EhUrl.domain, builder: ParametersBuilder.() -> Unit) = ehUrl(
path = if (path.isNullOrBlank()) emptyList() else listOf(path),
host = host,
parameters = Parameters.build(builder),
@@ -47,8 +47,6 @@ data class ListUrlBuilder(
var pageFrom: Int = -1,
var pageTo: Int = -1,
var imagePath: String? = null,
var isUseSimilarityScan: Boolean = false,
var isOnlySearchCovers: Boolean = false,
) : Parcelable {
fun setIndex(index: String, isNext: Boolean = true) {
@@ -274,7 +272,6 @@ data class ListUrlBuilder(
} ?: it
}
addQueryParameterIfNotBlank("f_search", query)
addQueryParameterIfNotBlank("f_shash", hash)
addQueryParameterIfNotBlank("prev", mPrev)
addQueryParameterIfNotBlank("next", mNext)
addQueryParameterIfNotBlank("seek", mJumpTo)
@@ -328,7 +325,9 @@ data class ListUrlBuilder(
}
MODE_WHATS_HOT -> EhUrl.popularUrl
MODE_IMAGE_SEARCH -> EhUrl.imageSearchUrl
MODE_IMAGE_SEARCH -> ehUrl {
addQueryParameter("f_shash", requireNotNull(hash))
}.buildString()
MODE_TOPLIST -> {
ehUrl("toplist.php", EhUrl.DOMAIN_E) {
addQueryParameter("tl", requireNotNull(mKeyword))
@@ -17,12 +17,10 @@
*/
package com.hippo.ehviewer.image
import android.content.Context
import android.graphics.Bitmap
import android.graphics.ColorSpace
import android.graphics.Rect
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import arrow.core.Either
@@ -40,7 +38,6 @@ import coil3.request.allowHardware
import coil3.request.colorSpace
import coil3.size.Precision
import coil3.size.Scale
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.jni.isGif
import com.hippo.ehviewer.jni.mmap
@@ -96,7 +93,6 @@ class Image private constructor(image: CoilImage, private val src: AutoCloseable
}
companion object {
private val imageSearchMaxSize = appCtx.resources.getDimensionPixelOffset(R.dimen.image_search_max_size)
private val targetWidth = appCtx.resources.displayMetrics.widthPixels * 2
private val targetHeight = appCtx.resources.displayMetrics.heightPixels * 2
@@ -166,20 +162,6 @@ class Image private constructor(image: CoilImage, private val src: AutoCloseable
logcat(it)
}.getOrNull()
}
suspend fun Context.decodeBitmap(uri: Uri): Bitmap {
val req = imageRequest {
memoryCachePolicy(CachePolicy.DISABLED)
data(uri)
size(imageSearchMaxSize)
scale(Scale.FILL)
}
val image = when (val result = appCtx.imageLoader.execute(req)) {
is SuccessResult -> result.image
is ErrorResult -> throw result.throwable
}
return (image as BitmapImage).bitmap
}
}
}
@@ -20,7 +20,6 @@ import android.app.assist.AssistContent
import android.content.Intent
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
@@ -104,7 +103,6 @@ import com.hippo.ehviewer.download.DownloadService
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.filled.Subscriptions
import com.hippo.ehviewer.image.Image.Companion.decodeBitmap
import com.hippo.ehviewer.ui.destinations.DownloadScreenDestination
import com.hippo.ehviewer.ui.destinations.DownloadsScreenDestination
import com.hippo.ehviewer.ui.destinations.FavouritesScreenDestination
@@ -127,20 +125,18 @@ import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.LocalTouchSlopProvider
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.updater.AppUpdater
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.addTextToClipboard
import com.hippo.ehviewer.util.getParcelableExtraCompat
import com.hippo.ehviewer.util.getUrlFromClipboard
import com.hippo.ehviewer.util.isAtLeastQ
import com.hippo.ehviewer.util.isAtLeastS
import com.hippo.unifile.asUniFile
import com.hippo.unifile.sha1
import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.rememberNavHostEngine
import com.ramcosta.composedestinations.utils.currentDestinationAsState
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import java.io.File
import java.io.FileOutputStream
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
@@ -190,18 +186,6 @@ class MainActivity : EhActivity() {
}
}
private suspend fun saveImageToTempFile(uri: Uri): File? {
return runSuspendCatching {
val bitmap = decodeBitmap(uri)
val temp = AppConfig.createTempFile()
FileOutputStream(temp).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }
temp
}.getOrElse {
logcat(it)
null
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intentChannel.trySend(intent)
@@ -280,13 +264,11 @@ class MainActivity : EhActivity() {
} else if (type != null && type.startsWith("image/")) {
val uri = intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)
if (null != uri) {
val temp = withIOContext { saveImageToTempFile(uri) }
if (null != temp) {
withIOContext { uri.asUniFile().sha1() }?.let {
navController.navigate(
ListUrlBuilder(
mode = ListUrlBuilder.MODE_IMAGE_SEARCH,
imagePath = temp.path,
isUseSimilarityScan = true,
hash = it,
).asDst(),
)
}
@@ -1,15 +1,13 @@
package com.hippo.ehviewer.ui.main
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.Card
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -19,25 +17,20 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
@Composable
fun ImageSearch(
imagePath: String,
image: Uri?,
onSelectImage: () -> Unit,
uss: Boolean,
onUssChecked: (Boolean) -> Unit,
osc: Boolean,
onOscChecked: (Boolean) -> Unit,
) = Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AnimatedVisibility(visible = imagePath.isNotBlank(), modifier = Modifier.align(Alignment.CenterHorizontally)) {
AnimatedVisibility(visible = image != null, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card {
val maxSize = dimensionResource(id = R.dimen.image_search_max_size)
AsyncImage(
model = imagePath,
model = image,
contentDescription = null,
modifier = Modifier.sizeIn(maxWidth = maxSize, maxHeight = maxSize),
)
@@ -49,18 +42,4 @@ fun ImageSearch(
) {
Text(text = stringResource(id = R.string.select_image))
}
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
Row {
LabeledCheckbox(
checked = uss,
onCheckedChange = onUssChecked,
label = stringResource(id = R.string.search_uss),
)
LabeledCheckbox(
checked = osc,
onCheckedChange = onOscChecked,
label = stringResource(id = R.string.search_osc),
)
}
}
}
@@ -593,10 +593,10 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
icon = Icons.Default.ImageSearch,
text = stringResource(id = R.string.search_cover),
onClick = {
val key = galleryDetail.thumbKey.orEmpty()
val key = galleryDetail.thumbKey!!
navigator.navigate(
ListUrlBuilder(
mode = ListUrlBuilder.MODE_NORMAL,
mode = ListUrlBuilder.MODE_IMAGE_SEARCH,
hash = key.substringAfterLast('/').substringBefore('-'),
).asDst(),
)
@@ -1,6 +1,5 @@
package com.hippo.ehviewer.ui.screen
import android.graphics.Bitmap
import android.net.Uri
import android.view.ViewConfiguration
import androidx.activity.result.contract.ActivityResultContracts
@@ -110,7 +109,6 @@ import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.dao.QuickSearch
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.filled.GoTo
import com.hippo.ehviewer.image.Image.Companion.decodeBitmap
import com.hippo.ehviewer.ui.LocalSideSheetState
import com.hippo.ehviewer.ui.LocalSnackBarHostState
import com.hippo.ehviewer.ui.MainActivity
@@ -135,17 +133,17 @@ import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
import com.hippo.ehviewer.ui.tools.foldToLoadResult
import com.hippo.ehviewer.ui.tools.observed
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.hippo.ehviewer.util.findActivity
import com.hippo.ehviewer.util.pickVisualMedia
import com.hippo.unifile.asUniFile
import com.hippo.unifile.sha1
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.spec.Direction
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import java.io.File
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlinx.coroutines.delay
@@ -186,9 +184,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
var category by rememberSaveable { mutableIntStateOf(Settings.searchCategory) }
var searchMethod by rememberSaveable { mutableIntStateOf(1) }
var advancedSearchOption by rememberSaveable { mutableStateOf(AdvancedSearchOption()) }
var useSimilarityScan by rememberSaveable { mutableStateOf(false) }
var searchCoverOnly by rememberSaveable { mutableStateOf(false) }
var imagePath by rememberSaveable { mutableStateOf("") }
var imageUri by rememberSaveable { mutableStateOf<Uri?>(null) }
LaunchedEffect(urlBuilder) {
if (urlBuilder.mode == MODE_SUBSCRIPTION) searchMethod = 2
@@ -261,16 +257,8 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
}
}
runSuspendCatching {
if (MODE_IMAGE_SEARCH == urlBuilder.mode) {
EhEngine.imageSearch(
File(urlBuilder.imagePath!!),
urlBuilder.isUseSimilarityScan,
urlBuilder.isOnlySearchCovers,
)
} else {
val url = urlBuilder.build()
EhEngine.getGalleryList(url)
}
val url = urlBuilder.build()
EhEngine.getGalleryList(url)
}.foldToLoadResult { result ->
urlBuilder.mJumpTo = null
LoadResult.Page(result.galleryInfoList, result.prev, result.next)
@@ -551,18 +539,12 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
builder.pageFrom = advancedSearchOption.fromPage
builder.pageTo = advancedSearchOption.toPage
} else {
builder.mode = MODE_IMAGE_SEARCH
if (imagePath.isBlank()) {
if (imageUri == null) {
snackbarState.showSnackbar(selectImageFirst)
return@ret
}
val uri = Uri.parse(imagePath)
val temp = AppConfig.createTempFile() ?: return@ret
val bitmap = context.decodeBitmap(uri)
temp.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }
builder.imagePath = temp.path
builder.isUseSimilarityScan = useSimilarityScan
builder.isOnlySearchCovers = searchCoverOnly
builder.mode = MODE_IMAGE_SEARCH
builder.hash = imageUri!!.asUniFile().sha1() ?: return@ret
}
when (oldMode) {
MODE_TOPLIST, MODE_WHATS_HOT -> {
@@ -648,17 +630,12 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
style = MaterialTheme.typography.titleMedium,
)
ImageSearch(
imagePath = imagePath,
image = imageUri,
onSelectImage = {
coroutineScope.launch {
val image = context.pickVisualMedia(ActivityResultContracts.PickVisualMedia.ImageOnly)
if (image != null) imagePath = image.toString()
imageUri = context.pickVisualMedia(ActivityResultContracts.PickVisualMedia.ImageOnly)
}
},
uss = useSimilarityScan,
onUssChecked = { useSimilarityScan = it },
osc = searchCoverOnly,
onOscChecked = { searchCoverOnly = it },
)
}
}
@@ -5,10 +5,15 @@ import android.os.Environment
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import android.os.ParcelFileDescriptor.AutoCloseOutputStream
import android.provider.DocumentsContract
import eu.kanade.tachiyomi.util.system.logcat
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import okio.HashingSink
import okio.Path
import okio.blackholeSink
import okio.buffer
import okio.source
/**
* Use Native IO/NIO directly if possible, unless you need process file content on JVM!
@@ -24,6 +29,18 @@ fun UniFile.openOutputStream(): FileOutputStream {
return AutoCloseOutputStream(openFileDescriptor("w"))
}
fun UniFile.sha1() = runCatching {
openInputStream().source().buffer().use { source ->
HashingSink.sha1(blackholeSink()).use {
source.readAll(it)
it.hash.hex()
}
}
}.getOrElse {
logcat(it)
null
}
fun File.asUniFile() = UniFile.fromFile(this)
fun Path.asUniFile() = toFile().asUniFile()
-2
View File
@@ -65,8 +65,6 @@
<string name="search_sfu">Uploader</string>
<string name="search_sft">Etikett</string>
<string name="select_image">Bild wählen</string>
<string name="search_uss">Ähnlichkeitscan aktivieren</string>
<string name="search_osc">Nur nach Umschläge suchen</string>
<string name="select_image_first">Wähle ein Bild</string>
<string name="add_to_favourites">Zu den Favoriten hinzufügen</string>
<string name="remove_from_favourites">Aus den Favoriten entfernen</string>
-2
View File
@@ -76,8 +76,6 @@
<string name="search_sto">Mostrar solo galerías con torrents</string>
<string name="search_sr">Valoración mínima:</string>
<string name="select_image">Seleccionar imagen</string>
<string name="search_uss">Usar búsqueda similar</string>
<string name="search_osc">Buscar solo portadas</string>
<string name="select_image_first">Seleccione la imagen primero</string>
<string name="search_sp_to">hasta</string>
<string name="search_sf">Deshabilitar filtros para:</string>
-2
View File
@@ -86,8 +86,6 @@
<string name="search_sft">Tag</string>
<string name="select_image">Sélectionner une image</string>
<string name="search_uss">Activer le scannage de similarité</string>
<string name="search_osc">Chercher seulement les couverture</string>
<string name="select_image_first">Sélectionnez une image</string>
<string name="add_to_favourites">Ajouter à favori</string>
-2
View File
@@ -85,8 +85,6 @@
<string name="search_sft">タグ</string>
<string name="select_image">画像を選択</string>
<string name="search_uss">類似検索を有効化</string>
<string name="search_osc">カバーのみを検索</string>
<string name="select_image_first">画像を選択してください</string>
<string name="add_to_favourites">お気に入りに追加</string>
-2
View File
@@ -82,8 +82,6 @@
<string name="search_sr">최소 평점:</string>
<string name="select_image">이미지 선택</string>
<string name="search_uss">유사한 이미지 검색</string>
<string name="search_osc">커버만 검색</string>
<string name="select_image_first">먼저 이미지를 선택해 주세요.</string>
<string name="add_to_favourites">즐겨찾기에 추가</string>
@@ -28,7 +28,6 @@
<string name="star_4">4 stjerner</string>
<string name="no_torrents">Ingen torrenter</string>
<string name="search_sfl">Språk</string>
<string name="search_osc">Kun søk i omslag</string>
<string name="guest_mode">Fortsett uten å logge inn</string>
<string name="favored_times">♥ %d</string>
<string name="torrent_count">Torrent (%d)</string>
-2
View File
@@ -82,8 +82,6 @@
<string name="search_sr">เรตติ้งขั้นต่ำ:</string>
<string name="select_image">เลือกรูปภาพ</string>
<string name="search_uss">ใช้สแกนหาความเหมือน</string>
<string name="search_osc">ค้นหาเฉพาะหน้าปก</string>
<string name="select_image_first">โปรดเลือกรูปภาพก่อน</string>
<string name="add_to_favourites">เพิ่มเข้าในรายการโปรด</string>
-2
View File
@@ -52,8 +52,6 @@
<string name="search_sfu">Yükleyen</string>
<string name="search_sft">Etiketler</string>
<string name="select_image">Görsel Seç</string>
<string name="search_uss">Benzerlik taramasını kullan</string>
<string name="search_osc">Yalnızca kapak ara</string>
<string name="select_image_first">Lütfen önce görsel seç</string>
<string name="add_to_favourites">Favorilere ekle</string>
<string name="remove_from_favourites">Favorilerden kaldır</string>
@@ -78,8 +78,6 @@
<string name="search_sfu">上传者</string>
<string name="search_sft">标签</string>
<string name="select_image">选择图片</string>
<string name="search_uss">使用相似搜索</string>
<string name="search_osc">只搜索封面</string>
<string name="select_image_first">请先选择图片</string>
<string name="add_to_favourites">收藏</string>
<string name="remove_from_favourites">移除收藏</string>
@@ -90,8 +90,6 @@
<string name="search_sft">標籤</string>
<string name="select_image">選擇圖片</string>
<string name="search_uss">使用相似搜索</string>
<string name="search_osc">只搜索封面</string>
<string name="select_image_first">請先選擇圖片</string>
<string name="add_to_favourites">收藏</string>
@@ -78,8 +78,6 @@
<string name="search_sfu">上傳者</string>
<string name="search_sft">標籤</string>
<string name="select_image">選擇圖片</string>
<string name="search_uss">使用相似性搜尋</string>
<string name="search_osc">只搜尋封面</string>
<string name="select_image_first">請先選擇圖片</string>
<string name="add_to_favourites">收藏</string>
<string name="remove_from_favourites">移除收藏</string>
-2
View File
@@ -118,8 +118,6 @@
<string name="search_sfu">Uploader</string>
<string name="search_sft">Tags</string>
<string name="select_image">Select image</string>
<string name="search_uss">Use similarity scan</string>
<string name="search_osc">Only search covers</string>
<string name="select_image_first">Please select image first</string>
<string name="add_to_favourites">Add to favourites</string>
<string name="remove_from_favourites">Remove from favourites</string>