Skip to content

Commit

Permalink
Merge pull request #867 from lihenggui/fix/cannot_find_rules
Browse files Browse the repository at this point in the history
Use assets when there is no files in the internal storage
  • Loading branch information
lihenggui authored Jun 2, 2024
2 parents a50f8e1 + b467999 commit 90ae699
Showing 1 changed file with 50 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package com.merxury.blocker.core.data.respository.generalrule

import android.os.Build
import android.content.res.AssetManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import com.merxury.blocker.core.data.di.RuleBaseFolder
import com.merxury.blocker.core.data.respository.userdata.UserDataRepository
import com.merxury.blocker.core.di.FilesDir
Expand All @@ -34,6 +36,8 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.InputStream
import javax.inject.Inject

private const val RULES_FOLDER = "rules"
Expand All @@ -42,37 +46,64 @@ private const val RULE_NAME = "general.json"
internal class LocalGeneralRuleDataSource @Inject constructor(
private val json: Json,
private val userDataRepository: UserDataRepository,
private val assetManager: AssetManager,
@FilesDir private val filesDir: File,
@RuleBaseFolder private val ruleBaseFolder: String,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : GeneralRuleDataSource {
@OptIn(ExperimentalSerializationApi::class)

override fun getGeneralRules(): Flow<List<GeneralRule>> = flow {
val language = userDataRepository.getLibDisplayLanguage()
val ruleFile = getRuleFile(language)
ruleFile.inputStream().use { inputStream ->
val serializedRule = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// https://github.com/Kotlin/kotlinx.serialization/issues/2457
// Use decodeFromString instead of decodeFromStream to avoid the issue
val text = inputStream.bufferedReader().use { it.readText() }
json.decodeFromString<List<NetworkGeneralRule>>(text)
.map { it.asExternalModel() }
} else {
json.decodeFromStream<List<NetworkGeneralRule>>(inputStream)
.map { it.asExternalModel() }
}
ruleFile?.use { inputStream ->
val serializedRule = readContentFromStream(inputStream)
emit(serializedRule)
return@flow
}

// Fallback to assets
val assetRuleFile = getRuleFileFromAssets(language)
assetRuleFile?.use { inputStream ->
val serializedRule = readContentFromStream(inputStream)
emit(serializedRule)
return@flow
}
Timber.e("No general rule file found")
emit(emptyList())
}
.flowOn(ioDispatcher)

private suspend fun getRuleFile(language: String): File = withContext(ioDispatcher) {
@OptIn(ExperimentalSerializationApi::class)
private fun readContentFromStream(inputStream: InputStream) =
if (VERSION.SDK_INT <= VERSION_CODES.M) {
// https://github.com/Kotlin/kotlinx.serialization/issues/2457
// Use decodeFromString instead of decodeFromStream to avoid the issue
val text = inputStream.bufferedReader().use { it.readText() }
json.decodeFromString<List<NetworkGeneralRule>>(text)
.map { it.asExternalModel() }
} else {
json.decodeFromStream<List<NetworkGeneralRule>>(inputStream)
.map { it.asExternalModel() }
}

private suspend fun getRuleFileFromAssets(language: String): InputStream? =
withContext(ioDispatcher) {
Timber.d("Get rule file from assets, language: $language")
try {
return@withContext assetManager.open("$ruleBaseFolder/$RULES_FOLDER/$language/$RULE_NAME")
} catch (e: IOException) {
Timber.e(e, "Failed to get rule file from assets")
return@withContext null
}
}

private suspend fun getRuleFile(language: String): InputStream? = withContext(ioDispatcher) {
val ruleFile = filesDir.resolve(ruleBaseFolder)
.resolve(RULES_FOLDER)
.resolve(language)
.resolve(RULE_NAME)
if (ruleFile.exists()) {
return@withContext ruleFile
return@withContext ruleFile.inputStream()
}
// TODO should be removed in future
val lowercaseFolder = filesDir.resolve(ruleBaseFolder)
Expand All @@ -81,15 +112,16 @@ internal class LocalGeneralRuleDataSource @Inject constructor(
.resolve(RULE_NAME)
if (lowercaseFolder.exists()) {
Timber.i("Fallback to lowercase-language folder")
return@withContext lowercaseFolder
return@withContext lowercaseFolder.inputStream()
}
val oldRuleFile = filesDir.resolve(ruleBaseFolder)
.resolve("zh-cn")
.resolve(RULE_NAME)
if (oldRuleFile.exists()) {
Timber.i("Fallback to old version of rules without RULES_FOLDER")
return@withContext oldRuleFile
return@withContext oldRuleFile.inputStream()
}
throw IllegalStateException("Cannot find general rule in files folder. language: $language")
Timber.e("General rule file not found, language: $language")
return@withContext null
}
}

0 comments on commit 90ae699

Please sign in to comment.