From 8b94997cde96978ea60d6017f27777880f6d5bcb Mon Sep 17 00:00:00 2001 From: tarsin Date: Sun, 18 Dec 2022 12:52:30 +0800 Subject: [PATCH] Minor Refactor --- .../java/com/hippo/ehviewer/EhApplication.kt | 2 + .../hippo/ehviewer/client/EhTagDatabase.kt | 249 ++++++++---------- .../com/hippo/ehviewer/ui/MainActivity.java | 2 - .../hippo/ehviewer/ui/fragment/EhFragment.kt | 4 +- .../ehviewer/ui/scene/GalleryDetailScene.java | 4 +- .../hippo/ehviewer/ui/scene/SearchBarScene.kt | 8 +- 6 files changed, 116 insertions(+), 153 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt index 813e57f8a..f648e7078 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt @@ -43,6 +43,7 @@ import com.hippo.ehviewer.client.EhDns import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhRequestBuilder import com.hippo.ehviewer.client.EhSSLSocketFactory +import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhX509TrustManager import com.hippo.ehviewer.client.data.GalleryDetail @@ -114,6 +115,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF EhDB.initialize(this) EhEngine.initialize() BitmapUtils.initialize(this) + EhTagDatabase.update() // Locales can be managed by system automatically above Snow Cone v2 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt index 9b7f42bbc..2733216c6 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt +++ b/app/src/main/java/com/hippo/ehviewer/client/EhTagDatabase.kt @@ -16,17 +16,20 @@ package com.hippo.ehviewer.client import android.content.Context -import androidx.core.util.Pair import com.hippo.ehviewer.AppConfig +import com.hippo.ehviewer.EhApplication import com.hippo.ehviewer.EhApplication.Companion.okHttpClient import com.hippo.ehviewer.R import com.hippo.util.ExceptionUtils import com.hippo.util.HashCodeUtils -import com.hippo.util.IoThreadPoolExecutor import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.IOUtils +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okio.BufferedSource @@ -41,38 +44,36 @@ import java.io.IOException import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.NoSuchAlgorithmException -import java.util.concurrent.locks.Lock -import java.util.concurrent.locks.ReentrantLock -class EhTagDatabase(private val name: String, source: BufferedSource) { - private val tags: JSONObject? - private var tagList: ArrayList>? = null +object EhTagDatabase { + private lateinit var tags: JSONObject + private lateinit var tagList: ArrayList> - init { - var tmpTags: JSONObject? = null + fun isInitialized(): Boolean { + return this::tags.isInitialized && this::tagList.isInitialized + } + + private fun updateData(source: BufferedSource) { try { - tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8)) - } catch (e: JSONException) { - e.printStackTrace() - } - tags = tmpTags - if (tags != null) { - tagList = ArrayList() - tags.keys().forEachRemaining { prefix: String -> - val values = tags.optJSONObject(prefix) + val tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8)) + val tmpTagList = arrayListOf>() + tmpTags.keys().forEachRemaining { prefix: String -> + val values = tmpTags.optJSONObject(prefix) values?.let { it.keys().forEachRemaining { tag: String -> - tagList!!.add(Pair("$prefix:$tag", values.optString(tag))) + tmpTagList.add(Pair("$prefix:$tag", values.optString(tag))) } } } - } else { - tagList = null + tags = tmpTags + tagList = tmpTagList + } catch (e: JSONException) { + e.printStackTrace() } } fun getTranslation(prefix: String?, tag: String?): String? { - return tags!!.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() } + return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() } } /* Construct a cold flow for tag database suggestions */ @@ -81,7 +82,7 @@ class EhTagDatabase(private val name: String, source: BufferedSource) { translate: Boolean, exactly: Boolean = false ): Flow> = flow { - tagList?.forEach { + tagList.forEach { val tag = it.first val hint = if (translate) it.second else null val index = tag.indexOf(':') @@ -108,126 +109,102 @@ class EhTagDatabase(private val name: String, source: BufferedSource) { return text != null && text.replace(" ", "").contains(key.replace(" ", "")) } - companion object { - private val NAMESPACE_TO_PREFIX: MutableMap = HashMap() + private val NAMESPACE_TO_PREFIX = HashMap().also { + it["artist"] = "a" + it["cosplayer"] = "cos" + it["character"] = "c" + it["female"] = "f" + it["group"] = "g" + it["language"] = "l" + it["male"] = "m" + it["mixed"] = "x" + it["other"] = "o" + it["parody"] = "p" + it["reclass"] = "r" + } - // TODO more lock for different language - private val lock: Lock = ReentrantLock() + @JvmStatic + fun namespaceToPrefix(namespace: String): String? { + return NAMESPACE_TO_PREFIX[namespace] + } - @JvmStatic - @Volatile - var instance: EhTagDatabase? = null - private set + private fun getMetadata(context: Context): Array? { + return context.resources.getStringArray(R.array.tag_translation_metadata) + .takeIf { it.size == 4 } + } - init { - NAMESPACE_TO_PREFIX["artist"] = "a" - NAMESPACE_TO_PREFIX["cosplayer"] = "cos" - NAMESPACE_TO_PREFIX["character"] = "c" - NAMESPACE_TO_PREFIX["female"] = "f" - NAMESPACE_TO_PREFIX["group"] = "g" - NAMESPACE_TO_PREFIX["language"] = "l" - NAMESPACE_TO_PREFIX["male"] = "m" - NAMESPACE_TO_PREFIX["mixed"] = "x" - NAMESPACE_TO_PREFIX["other"] = "o" - NAMESPACE_TO_PREFIX["parody"] = "p" - NAMESPACE_TO_PREFIX["reclass"] = "r" + fun isTranslatable(): Boolean { + return EhApplication.application.resources.getBoolean(R.bool.tag_translatable) + } + + private fun getFileContent(file: File): String? { + try { + file.source().buffer().use { return it.readString(StandardCharsets.UTF_8) } + } catch (e: IOException) { + return null } + } - @JvmStatic - fun namespaceToPrefix(namespace: String): String? { - return NAMESPACE_TO_PREFIX[namespace] - } - - private fun getMetadata(context: Context): Array? { - val metadata = context.resources.getStringArray(R.array.tag_translation_metadata) - return if (metadata.size == 4) { - metadata - } else { - null - } - } - - fun isTranslatable(context: Context): Boolean { - return context.resources.getBoolean(R.bool.tag_translatable) - } - - private fun getFileContent(file: File): String? { - try { - file.source().buffer() - .use { source -> return source.readString(StandardCharsets.UTF_8) } - } catch (e: IOException) { - return null - } - } - - private fun getFileSha1(file: File): String? { - try { - FileInputStream(file).use { `is` -> - val digest = MessageDigest.getInstance("SHA-1") - var n: Int - val buffer = ByteArray(4 * 1024) - while (`is`.read(buffer).also { n = it } != -1) { - digest.update(buffer, 0, n) - } - return HashCodeUtils.bytesToHexString(digest.digest()) + private fun getFileSha1(file: File): String? { + try { + FileInputStream(file).use { stream -> + val digest = MessageDigest.getInstance("SHA-1") + var n: Int + val buffer = ByteArray(4 * 1024) + while (stream.read(buffer).also { n = it } != -1) { + digest.update(buffer, 0, n) } - } catch (e: IOException) { - return null - } catch (e: NoSuchAlgorithmException) { - return null + return HashCodeUtils.bytesToHexString(digest.digest()) } + } catch (e: IOException) { + return null + } catch (e: NoSuchAlgorithmException) { + return null } + } - private fun checkData(sha1File: File, dataFile: File): Boolean { - val s1 = getFileContent(sha1File) ?: return false - val s2 = getFileSha1(dataFile) ?: return false - return s1 == s2 - } + private fun checkData(sha1File: File, dataFile: File): Boolean { + val s1 = getFileContent(sha1File) ?: return false + val s2 = getFileSha1(dataFile) ?: return false + return s1 == s2 + } - private fun save(client: OkHttpClient, url: String, file: File): Boolean { - val request: Request = Request.Builder().url(url).build() - val call = client.newCall(request) - try { - call.execute().use { response -> - if (!response.isSuccessful) { - return false - } - val body = response.body - body.byteStream() - .use { `is` -> FileOutputStream(file).use { os -> IOUtils.copy(`is`, os) } } - return true + private fun save(client: OkHttpClient, url: String, file: File): Boolean { + val request: Request = Request.Builder().url(url).build() + val call = client.newCall(request) + try { + call.execute().use { response -> + if (!response.isSuccessful) { + return false } - } catch (t: Throwable) { - t.printStackTrace() - ExceptionUtils.throwIfFatal(t) - return false + val body = response.body + body.byteStream() + .use { ins -> FileOutputStream(file).use { os -> IOUtils.copy(ins, os) } } + return true } + } catch (t: Throwable) { + FileUtils.delete(file) + t.printStackTrace() + ExceptionUtils.throwIfFatal(t) + return false } + } - @JvmStatic - fun update(context: Context) { - val urls = getMetadata(context) - if (urls == null || urls.size != 4) { - // Clear tags if it's not possible - instance = null - return - } + @OptIn(DelicateCoroutinesApi::class) + @Synchronized + @JvmStatic + fun update() { + val urls = getMetadata(EhApplication.application) + urls?.let { val sha1Name = urls[0] val sha1Url = urls[1] val dataName = urls[2] val dataUrl = urls[3] - // Clear tags if name if different - val tmp = instance - if (tmp != null && tmp.name != dataName) { - instance = null - } - IoThreadPoolExecutor.getInstance().execute { - if (!lock.tryLock()) { - return@execute - } + GlobalScope.launch(Dispatchers.IO) { try { - val dir = AppConfig.getFilesDir("tag-translations") ?: return@execute + val dir = AppConfig.getFilesDir("tag-translations") + checkNotNull(dir) // Check current sha1 and current data val sha1File = File(dir, sha1Name) @@ -238,10 +215,9 @@ class EhTagDatabase(private val name: String, source: BufferedSource) { } // Read current EhTagDatabase - if (instance == null && dataFile.exists()) { + if (!isInitialized() && dataFile.exists()) { try { - dataFile.source().buffer() - .use { source -> instance = EhTagDatabase(dataName, source) } + dataFile.source().buffer().use { updateData(it) } } catch (e: IOException) { FileUtils.delete(sha1File) FileUtils.delete(dataFile) @@ -251,30 +227,24 @@ class EhTagDatabase(private val name: String, source: BufferedSource) { // Save new sha1 val tempSha1File = File(dir, "$sha1Name.tmp") - if (!save(client, sha1Url, tempSha1File)) { - FileUtils.delete(tempSha1File) - return@execute - } + check(save(client, sha1Url, tempSha1File)) // Check new sha1 and current data if (checkData(tempSha1File, dataFile)) { // The data is the same FileUtils.delete(tempSha1File) - return@execute + return@launch } // Save new data val tempDataFile = File(dir, "$dataName.tmp") - if (!save(client, dataUrl, tempDataFile)) { - FileUtils.delete(tempDataFile) - return@execute - } + check(save(client, dataUrl, tempDataFile)) // Check new sha1 and new data if (!checkData(tempSha1File, tempDataFile)) { FileUtils.delete(tempSha1File) FileUtils.delete(tempDataFile) - return@execute + return@launch } // Replace current sha1 and current data with new sha1 and new data @@ -285,13 +255,10 @@ class EhTagDatabase(private val name: String, source: BufferedSource) { // Read new EhTagDatabase try { - dataFile.source().buffer() - .use { source -> instance = EhTagDatabase(dataName, source) } - } catch (e: IOException) { - // Ignore + dataFile.source().buffer().use { updateData(it) } + } catch (_: IOException) { } } finally { - lock.unlock() } } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java index c13371074..28857ef3f 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java @@ -309,8 +309,6 @@ public final class MainActivity extends StageActivity } else { onRestore(savedInstanceState); } - - EhTagDatabase.update(this); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.kt b/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.kt index 282d94b7f..fcd05f561 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.kt @@ -52,7 +52,7 @@ class EhFragment : BasePreferenceFragment() { thumbResolution!!.setSummaryProvider { getString(R.string.settings_eh_thumb_resolution_summary, (it as ListPreference).entry) } - if (!EhTagDatabase.isTranslatable(requireActivity())) { + if (!EhTagDatabase.isTranslatable()) { preferenceScreen.removePreference(showTagTranslations) preferenceScreen.removePreference(tagTranslationsSource!!) } @@ -80,7 +80,7 @@ class EhFragment : BasePreferenceFragment() { requireActivity().setResult(Activity.RESULT_OK) } else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) { if (java.lang.Boolean.TRUE == newValue) { - EhTagDatabase.update(requireActivity()) + EhTagDatabase.update() } } else if (Settings.KEY_BLACK_DARK_THEME == key) { if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 63b03efed..92d7aa44c 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -927,7 +927,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O mNoTags.setVisibility(View.GONE); } - EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.getInstance() : null; + EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.INSTANCE : null; int colorTag = ResourcesKt.resolveColor(getTheme(), R.attr.tagBackgroundColor); int colorName = ResourcesKt.resolveColor(getTheme(), R.attr.tagGroupBackgroundColor); for (GalleryTagGroup tg : tagGroups) { @@ -936,7 +936,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O mTags.addView(ll); String readableTagName = null; - if (ehTags != null) { + if (ehTags != null && ehTags.isInitialized()) { readableTagName = ehTags.getTranslation("n", tg.groupName); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt index e8a5017f6..339c788b5 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SearchBarScene.kt @@ -306,16 +306,12 @@ abstract class SearchBarScene : ToolbarScene() { mSearchView?.editText?.text?.toString()?.let { text -> mSuggestionProvider?.run { providerSuggestions(text)?.forEach { emit(it) } } mSearchDatabase.getSuggestions(text, 128).forEach { emit(KeywordSuggestion(it)) } - EhTagDatabase.instance?.run { + EhTagDatabase.takeIf { it.isInitialized() }?.run { if (!TextUtils.isEmpty(text) && !text.endsWith(" ")) { val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() if (s.isNotEmpty()) { val keyword = s[s.size - 1] - val translate = - Settings.getShowTagTranslations() && EhTagDatabase.isTranslatable( - requireContext() - ) - + val translate = Settings.getShowTagTranslations() && isTranslatable() suggestFlow(keyword, translate, true).collect { emit(TagSuggestion(it.first, it.second)) }