kunfei 4 лет назад
Родитель
Сommit
75b5387297
36 измененных файлов с 213 добавлено и 151 удалено
  1. 1 2
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  2. 3 1
      app/src/main/java/io/legado/app/api/controller/BookSourceController.kt
  3. 8 8
      app/src/main/java/io/legado/app/data/entities/BaseSource.kt
  4. 3 3
      app/src/main/java/io/legado/app/data/entities/Book.kt
  5. 2 2
      app/src/main/java/io/legado/app/data/entities/BookChapter.kt
  6. 21 11
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  7. 2 2
      app/src/main/java/io/legado/app/data/entities/RssArticle.kt
  8. 1 1
      app/src/main/java/io/legado/app/data/entities/RssStar.kt
  9. 2 2
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  10. 11 8
      app/src/main/java/io/legado/app/help/DefaultData.kt
  11. 1 1
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  12. 10 6
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  13. 25 15
      app/src/main/java/io/legado/app/help/SourceAnalyzer.kt
  14. 6 5
      app/src/main/java/io/legado/app/help/ThemeConfig.kt
  15. 1 3
      app/src/main/java/io/legado/app/help/storage/AppWebDav.kt
  16. 1 1
      app/src/main/java/io/legado/app/help/storage/BackupRestore.kt
  17. 2 5
      app/src/main/java/io/legado/app/help/storage/OldReplace.kt
  18. 4 3
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  19. 9 6
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  20. 26 25
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  21. 2 2
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  22. 1 1
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  23. 4 4
      app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt
  24. 1 1
      app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleDialog.kt
  25. 2 5
      app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt
  26. 2 2
      app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleViewModel.kt
  27. 1 1
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt
  28. 4 3
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
  29. 1 4
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  30. 2 2
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt
  31. 3 1
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
  32. 41 0
      app/src/main/java/io/legado/app/utils/DataExtensions.kt
  33. 4 11
      app/src/main/java/io/legado/app/utils/GsonExtensions.kt
  34. 2 2
      app/src/main/java/io/legado/app/utils/IntentExtensions.kt
  35. 2 1
      app/src/main/java/io/legado/app/web/socket/BookSourceDebugWebSocket.kt
  36. 2 1
      app/src/main/java/io/legado/app/web/socket/RssSourceDebugWebSocket.kt

+ 1 - 2
app/src/main/java/io/legado/app/api/controller/BookController.kt

@@ -166,9 +166,8 @@ object BookController {
      * 保存书籍
      */
     fun saveBook(postData: String?): ReturnData {
-        val book = GSON.fromJsonObject<Book>(postData)
         val returnData = ReturnData()
-        if (book != null) {
+        GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->
             book.save()
             AppWebDav.uploadBookProgress(book)
             if (ReadBook.book?.bookUrl == book.bookUrl) {

+ 3 - 1
app/src/main/java/io/legado/app/api/controller/BookSourceController.kt

@@ -73,11 +73,13 @@ object BookSourceController {
 
     fun deleteSources(postData: String?): ReturnData {
         kotlin.runCatching {
-            GSON.fromJsonArray<BookSource>(postData)?.let {
+            GSON.fromJsonArray<BookSource>(postData).getOrThrow()?.let {
                 it.forEach { source ->
                     appDb.bookSourceDao.delete(source)
                 }
             }
+        }.onFailure {
+            return ReturnData().setErrorMsg(it.localizedMessage ?: "数据格式错误")
         }
         return ReturnData().setData("已执行"/*okSources*/)
     }

+ 8 - 8
app/src/main/java/io/legado/app/data/entities/BaseSource.kt

@@ -8,10 +8,7 @@ import io.legado.app.help.AppConfig
 import io.legado.app.help.CacheManager
 import io.legado.app.help.JsExtensions
 import io.legado.app.help.http.CookieStore
-import io.legado.app.utils.EncoderUtils
-import io.legado.app.utils.GSON
-import io.legado.app.utils.fromJsonArray
-import io.legado.app.utils.fromJsonObject
+import io.legado.app.utils.*
 import javax.script.SimpleBindings
 
 /**
@@ -30,7 +27,10 @@ interface BaseSource : JsExtensions {
     fun getKey(): String
 
     fun loginUi(): List<RowUi>? {
-        return GSON.fromJsonArray(loginUi)
+        return GSON.fromJsonArray<RowUi>(loginUi)
+            .onFailure {
+                it.printOnDebug()
+            }.getOrNull()
     }
 
     fun getLoginJs(): String? {
@@ -64,7 +64,7 @@ interface BaseSource : JsExtensions {
                         evalJS(it.substring(4, it.lastIndexOf("<"))).toString()
                     else -> it
                 }
-            )?.let { map ->
+            ).getOrNull()?.let { map ->
                 putAll(map)
             }
         }
@@ -84,7 +84,7 @@ interface BaseSource : JsExtensions {
 
     fun getLoginHeaderMap(): Map<String, String>? {
         val cache = getLoginHeader() ?: return null
-        return GSON.fromJsonObject(cache)
+        return GSON.fromJsonObject<Map<String, String>>(cache).getOrNull()
     }
 
     /**
@@ -117,7 +117,7 @@ interface BaseSource : JsExtensions {
     }
 
     fun getLoginInfoMap(): Map<String, String>? {
-        return GSON.fromJsonObject(getLoginInfo())
+        return GSON.fromJsonObject<Map<String, String>>(getLoginInfo()).getOrNull()
     }
 
     /**

+ 3 - 3
app/src/main/java/io/legado/app/data/entities/Book.kt

@@ -92,8 +92,8 @@ data class Book(
     @delegate:Transient
     @delegate:Ignore
     @IgnoredOnParcel
-    override val variableMap by lazy {
-        GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
+    override val variableMap: HashMap<String, String> by lazy {
+        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
     }
 
     override fun putVariable(key: String, value: String?) {
@@ -307,6 +307,6 @@ data class Book(
         fun readConfigToString(config: ReadConfig?): String = GSON.toJson(config)
 
         @TypeConverter
-        fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json)
+        fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json).getOrNull()
     }
 }

+ 2 - 2
app/src/main/java/io/legado/app/data/entities/BookChapter.kt

@@ -50,8 +50,8 @@ data class BookChapter(
     @delegate:Transient
     @delegate:Ignore
     @IgnoredOnParcel
-    override val variableMap by lazy {
-        GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
+    override val variableMap: HashMap<String, String> by lazy {
+        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
     }
 
     override fun putVariable(key: String, value: String?) {

+ 21 - 11
app/src/main/java/io/legado/app/data/entities/BookSource.kt

@@ -108,7 +108,7 @@ data class BookSource(
                     }
                 }
                 if (ruleStr.isJsonArray()) {
-                    GSON.fromJsonArray<ExploreKind>(ruleStr)?.let {
+                    GSON.fromJsonArray<ExploreKind>(ruleStr).getOrThrow()?.let {
                         kinds.addAll(it)
                     }
                 } else {
@@ -219,34 +219,44 @@ data class BookSource(
     class Converters {
 
         @TypeConverter
-        fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule)
+        fun exploreRuleToString(exploreRule: ExploreRule?): String =
+            GSON.toJson(exploreRule)
 
         @TypeConverter
-        fun stringToExploreRule(json: String?) = GSON.fromJsonObject<ExploreRule>(json)
+        fun stringToExploreRule(json: String?) =
+            GSON.fromJsonObject<ExploreRule>(json).getOrNull()
 
         @TypeConverter
-        fun searchRuleToString(searchRule: SearchRule?): String = GSON.toJson(searchRule)
+        fun searchRuleToString(searchRule: SearchRule?): String =
+            GSON.toJson(searchRule)
 
         @TypeConverter
-        fun stringToSearchRule(json: String?) = GSON.fromJsonObject<SearchRule>(json)
+        fun stringToSearchRule(json: String?) =
+            GSON.fromJsonObject<SearchRule>(json).getOrNull()
 
         @TypeConverter
-        fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String = GSON.toJson(bookInfoRule)
+        fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String =
+            GSON.toJson(bookInfoRule)
 
         @TypeConverter
-        fun stringToBookInfoRule(json: String?) = GSON.fromJsonObject<BookInfoRule>(json)
+        fun stringToBookInfoRule(json: String?) =
+            GSON.fromJsonObject<BookInfoRule>(json).getOrNull()
 
         @TypeConverter
-        fun tocRuleToString(tocRule: TocRule?): String = GSON.toJson(tocRule)
+        fun tocRuleToString(tocRule: TocRule?): String =
+            GSON.toJson(tocRule)
 
         @TypeConverter
-        fun stringToTocRule(json: String?) = GSON.fromJsonObject<TocRule>(json)
+        fun stringToTocRule(json: String?) =
+            GSON.fromJsonObject<TocRule>(json).getOrNull()
 
         @TypeConverter
-        fun contentRuleToString(contentRule: ContentRule?): String = GSON.toJson(contentRule)
+        fun contentRuleToString(contentRule: ContentRule?): String =
+            GSON.toJson(contentRule)
 
         @TypeConverter
-        fun stringToContentRule(json: String?) = GSON.fromJsonObject<ContentRule>(json)
+        fun stringToContentRule(json: String?) =
+            GSON.fromJsonObject<ContentRule>(json).getOrNull()
 
     }
 }

+ 2 - 2
app/src/main/java/io/legado/app/data/entities/RssArticle.kt

@@ -36,8 +36,8 @@ data class RssArticle(
     @delegate:Transient
     @delegate:Ignore
     @IgnoredOnParcel
-    override val variableMap by lazy {
-        GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
+    override val variableMap: HashMap<String, String> by lazy {
+        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
     }
 
     override fun putVariable(key: String, value: String?) {

+ 1 - 1
app/src/main/java/io/legado/app/data/entities/RssStar.kt

@@ -29,7 +29,7 @@ data class RssStar(
     @delegate:Ignore
     @IgnoredOnParcel
     override val variableMap by lazy {
-        GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
+        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
     }
 
     override fun putVariable(key: String, value: String?) {

+ 2 - 2
app/src/main/java/io/legado/app/data/entities/SearchBook.kt

@@ -59,8 +59,8 @@ data class SearchBook(
     @delegate:Transient
     @delegate:Ignore
     @IgnoredOnParcel
-    override val variableMap by lazy {
-        GSON.fromJsonObject<HashMap<String, String>>(variable) ?: HashMap()
+    override val variableMap: HashMap<String, String> by lazy {
+        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: HashMap()
     }
 
     override fun putVariable(key: String, value: String?) {

+ 11 - 8
app/src/main/java/io/legado/app/help/DefaultData.kt

@@ -28,7 +28,8 @@ object DefaultData {
             appCtx.assets.open("defaultData${File.separator}${ReadBookConfig.configFileName}")
                 .readBytes()
         )
-        GSON.fromJsonArray(json)!!
+        GSON.fromJsonArray<ReadBookConfig.Config>(json).getOrNull()
+            ?: emptyList()
     }
 
     val txtTocRules: List<TxtTocRule> by lazy {
@@ -36,7 +37,7 @@ object DefaultData {
             appCtx.assets.open("defaultData${File.separator}$txtTocRuleFileName")
                 .readBytes()
         )
-        GSON.fromJsonArray(json)!!
+        GSON.fromJsonArray<TxtTocRule>(json).getOrNull() ?: emptyList()
     }
 
     val themeConfigs: List<ThemeConfig.Config> by lazy {
@@ -44,15 +45,17 @@ object DefaultData {
             appCtx.assets.open("defaultData${File.separator}${ThemeConfig.configFileName}")
                 .readBytes()
         )
-        GSON.fromJsonArray(json)!!
+        GSON.fromJsonArray<ThemeConfig.Config>(json).getOrNull() ?: emptyList()
     }
 
     val rssSources: List<RssSource> by lazy {
-        val json = String(
-            appCtx.assets.open("defaultData${File.separator}rssSources.json")
-                .readBytes()
-        )
-        RssSource.fromJsonArray(json)
+        kotlin.runCatching {
+            val json = String(
+                appCtx.assets.open("defaultData${File.separator}rssSources.json")
+                    .readBytes()
+            )
+            RssSource.fromJsonArray(json)
+        }.getOrDefault(emptyList())
     }
 
     fun importDefaultHttpTTS() {

+ 1 - 1
app/src/main/java/io/legado/app/help/JsExtensions.kt

@@ -97,7 +97,7 @@ interface JsExtensions {
 
     fun connect(urlStr: String, header: String?): StrResponse {
         return runBlocking {
-            val headerMap = GSON.fromJsonObject<Map<String, String>>(header)
+            val headerMap = GSON.fromJsonObject<Map<String, String>>(header).getOrNull()
             val analyzeUrl = AnalyzeUrl(urlStr, headerMapF = headerMap, source = getSource())
             kotlin.runCatching {
                 analyzeUrl.getStrResponseAwait()

+ 10 - 6
app/src/main/java/io/legado/app/help/ReadBookConfig.kt

@@ -6,15 +6,17 @@ import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
 import androidx.annotation.Keep
 import io.legado.app.R
+import io.legado.app.constant.AppLog
 import io.legado.app.constant.PreferKey
 import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.model.NoStackTraceException
 import io.legado.app.ui.book.read.page.provider.ChapterProvider
 import io.legado.app.utils.*
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.withContext
 import splitties.init.appCtx
-
 import java.io.File
+import java.io.IOException
 
 /**
  * 阅读界面配置
@@ -60,9 +62,9 @@ object ReadBookConfig {
         if (configFile.exists()) {
             try {
                 val json = configFile.readText()
-                configs = GSON.fromJsonArray(json)
+                configs = GSON.fromJsonArray<Config>(json).getOrThrow()
             } catch (e: Exception) {
-                e.printOnDebug()
+                AppLog.put("读取排版配置文件出错", e)
             }
         }
         (configs ?: DefaultData.readConfigs).let {
@@ -77,7 +79,7 @@ object ReadBookConfig {
         if (configFile.exists()) {
             try {
                 val json = configFile.readText()
-                c = GSON.fromJsonObject(json)
+                c = GSON.fromJsonObject<Config>(json).getOrThrow()
             } catch (e: Exception) {
                 e.printOnDebug()
             }
@@ -325,7 +327,7 @@ object ReadBookConfig {
         }
 
     fun getExportConfig(): Config {
-        val exportConfig = GSON.fromJsonObject<Config>(GSON.toJson(durConfig))!!
+        val exportConfig = durConfig.deepCopy()
         if (shareLayout) {
             exportConfig.textFont = shareConfig.textFont
             exportConfig.textBold = shareConfig.textBold
@@ -364,6 +366,7 @@ object ReadBookConfig {
         return exportConfig
     }
 
+    @Throws(IOException::class)
     suspend fun import(byteArray: ByteArray): Config {
         return withContext(IO) {
             val configZipPath = FileUtils.getPath(appCtx.externalCache, "readConfig.zip")
@@ -376,7 +379,8 @@ object ReadBookConfig {
             ZipUtils.unzipFile(zipFile, FileUtils.createFolderIfNotExist(configDirPath))
             val configDir = FileUtils.createFolderIfNotExist(configDirPath)
             val configFile = configDir.getFile(configFileName)
-            val config: Config = GSON.fromJsonObject(configFile.readText())!!
+            val config: Config = GSON.fromJsonObject<Config>(configFile.readText()).getOrThrow()
+                ?: throw NoStackTraceException("排版配置格式错误")
             if (config.textFont.isNotEmpty()) {
                 val fontName = FileUtils.getName(config.textFont)
                 val fontPath =

+ 25 - 15
app/src/main/java/io/legado/app/help/SourceAnalyzer.kt

@@ -3,6 +3,7 @@ package io.legado.app.help
 import androidx.annotation.Keep
 import com.jayway.jsonpath.JsonPath
 import io.legado.app.constant.AppConst
+import io.legado.app.constant.AppLog
 import io.legado.app.constant.BookType
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.rule.*
@@ -31,11 +32,10 @@ object SourceAnalyzer {
 
     fun jsonToBookSource(json: String): BookSource? {
         val source = BookSource()
-        val sourceAny = try {
-            GSON.fromJsonObject<BookSourceAny>(json.trim())
-        } catch (e: Exception) {
-            null
-        }
+        val sourceAny = GSON.fromJsonObject<BookSourceAny>(json.trim())
+            .onFailure {
+                AppLog.put("转化书源出错", it)
+            }.getOrNull()
         try {
             if (sourceAny?.ruleToc == null) {
                 source.apply {
@@ -132,30 +132,40 @@ object SourceAnalyzer {
                 source.weight = sourceAny.weight
                 source.exploreUrl = sourceAny.exploreUrl
                 source.ruleExplore = if (sourceAny.ruleExplore is String) {
-                    GSON.fromJsonObject(sourceAny.ruleExplore.toString())
+                    GSON.fromJsonObject<ExploreRule>(sourceAny.ruleExplore.toString())
+                        .getOrNull()
                 } else {
-                    GSON.fromJsonObject(GSON.toJson(sourceAny.ruleExplore))
+                    GSON.fromJsonObject<ExploreRule>(GSON.toJson(sourceAny.ruleExplore))
+                        .getOrNull()
                 }
                 source.searchUrl = sourceAny.searchUrl
                 source.ruleSearch = if (sourceAny.ruleSearch is String) {
-                    GSON.fromJsonObject(sourceAny.ruleSearch.toString())
+                    GSON.fromJsonObject<SearchRule>(sourceAny.ruleSearch.toString())
+                        .getOrNull()
                 } else {
-                    GSON.fromJsonObject(GSON.toJson(sourceAny.ruleSearch))
+                    GSON.fromJsonObject<SearchRule>(GSON.toJson(sourceAny.ruleSearch))
+                        .getOrNull()
                 }
                 source.ruleBookInfo = if (sourceAny.ruleBookInfo is String) {
-                    GSON.fromJsonObject(sourceAny.ruleBookInfo.toString())
+                    GSON.fromJsonObject<BookInfoRule>(sourceAny.ruleBookInfo.toString())
+                        .getOrNull()
                 } else {
-                    GSON.fromJsonObject(GSON.toJson(sourceAny.ruleBookInfo))
+                    GSON.fromJsonObject<BookInfoRule>(GSON.toJson(sourceAny.ruleBookInfo))
+                        .getOrNull()
                 }
                 source.ruleToc = if (sourceAny.ruleToc is String) {
-                    GSON.fromJsonObject(sourceAny.ruleToc.toString())
+                    GSON.fromJsonObject<TocRule>(sourceAny.ruleToc.toString())
+                        .getOrNull()
                 } else {
-                    GSON.fromJsonObject(GSON.toJson(sourceAny.ruleToc))
+                    GSON.fromJsonObject<TocRule>(GSON.toJson(sourceAny.ruleToc))
+                        .getOrNull()
                 }
                 source.ruleContent = if (sourceAny.ruleContent is String) {
-                    GSON.fromJsonObject(sourceAny.ruleContent.toString())
+                    GSON.fromJsonObject<ContentRule>(sourceAny.ruleContent.toString())
+                        .getOrNull()
                 } else {
-                    GSON.fromJsonObject(GSON.toJson(sourceAny.ruleContent))
+                    GSON.fromJsonObject<ContentRule>(GSON.toJson(sourceAny.ruleContent))
+                        .getOrNull()
                 }
             }
         } catch (e: Exception) {

+ 6 - 5
app/src/main/java/io/legado/app/help/ThemeConfig.kt

@@ -83,10 +83,11 @@ object ThemeConfig {
     }
 
     fun addConfig(json: String): Boolean {
-        GSON.fromJsonObject<Config>(json.trim { it < ' ' })?.let {
-            addConfig(it)
-            return true
-        }
+        GSON.fromJsonObject<Config>(json.trim { it < ' ' }).getOrNull()
+            ?.let {
+                addConfig(it)
+                return true
+            }
         return false
     }
 
@@ -106,7 +107,7 @@ object ThemeConfig {
         if (configFile.exists()) {
             kotlin.runCatching {
                 val json = configFile.readText()
-                return GSON.fromJsonArray(json)
+                return GSON.fromJsonArray<Config>(json).getOrThrow()
             }.onFailure {
                 it.printOnDebug()
             }

+ 1 - 3
app/src/main/java/io/legado/app/help/storage/AppWebDav.kt

@@ -172,9 +172,7 @@ object AppWebDav {
             WebDav(url).download()?.let { byteArray ->
                 val json = String(byteArray)
                 if (json.isJson()) {
-                    GSON.fromJsonObject<BookProgress>(json)?.let {
-                        return it
-                    }
+                    return GSON.fromJsonObject<BookProgress>(json).getOrNull()
                 }
             }
         }

+ 1 - 1
app/src/main/java/io/legado/app/help/storage/BackupRestore.kt

@@ -13,7 +13,7 @@ abstract class BackupRestore {
     val ignoreConfig: HashMap<String, Boolean> by lazy {
         val file = FileUtils.createFileIfNotExist(ignoreConfigPath)
         val json = file.readText()
-        GSON.fromJsonObject<HashMap<String, Boolean>>(json) ?: hashMapOf()
+        GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()
     }
 
     //忽略key

+ 2 - 5
app/src/main/java/io/legado/app/help/storage/OldReplace.kt

@@ -20,12 +20,9 @@ object OldReplace {
     }
 
     private fun jsonToReplaceRule(json: String): ReplaceRule? {
-        var replaceRule: ReplaceRule? = null
+        val replaceRule: ReplaceRule? = GSON.fromJsonObject<ReplaceRule>(json.trim()).getOrNull()
         runCatching {
-            replaceRule = GSON.fromJsonObject<ReplaceRule>(json.trim())
-        }
-        runCatching {
-            if (replaceRule == null || replaceRule?.pattern.isNullOrBlank()) {
+            if (replaceRule == null || replaceRule.pattern.isBlank()) {
                 val jsonItem = jsonPath.parse(json.trim())
                 val rule = ReplaceRule()
                 rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis()

+ 4 - 3
app/src/main/java/io/legado/app/help/storage/Restore.kt

@@ -6,6 +6,7 @@ import androidx.documentfile.provider.DocumentFile
 import io.legado.app.BuildConfig
 import io.legado.app.R
 import io.legado.app.constant.AppConst.androidId
+import io.legado.app.constant.AppLog
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.PreferKey
 import io.legado.app.data.appDb
@@ -20,7 +21,6 @@ import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.withContext
 import splitties.init.appCtx
-
 import java.io.File
 
 
@@ -193,9 +193,10 @@ object Restore : BackupRestore() {
         try {
             val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)
             val json = file.readText()
-            return GSON.fromJsonArray(json)
+            return GSON.fromJsonArray<T>(json).getOrThrow()
         } catch (e: Exception) {
-            e.printOnDebug()
+            AppLog.put("读取文件出错\n${e.localizedMessage}", e)
+            appCtx.toastOnUi("读取文件出错\n${e.localizedMessage}")
         }
         return null
     }

+ 9 - 6
app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt

@@ -341,8 +341,11 @@ class AnalyzeRule(
         val putMatcher = putPattern.matcher(vRuleStr)
         while (putMatcher.find()) {
             vRuleStr = vRuleStr.replace(putMatcher.group(), "")
-            val map = GSON.fromJsonObject<Map<String, String>>(putMatcher.group(1))
-            map?.let { putMap.putAll(map) }
+            GSON.fromJsonObject<Map<String, String>>(putMatcher.group(1))
+                .getOrNull()
+                ?.let {
+                    putMap.putAll(it)
+                }
         }
         return vRuleStr
     }
@@ -379,7 +382,7 @@ class AnalyzeRule(
      * 分解规则生成规则列表
      */
     fun splitSourceRule(ruleStr: String?, allInOne: Boolean = false): List<SourceRule> {
-        if (ruleStr.isNullOrEmpty()) return ArrayList<SourceRule>()
+        if (ruleStr.isNullOrEmpty()) return emptyList()
         val ruleList = ArrayList<SourceRule>()
         var mMode: Mode = Mode.Default
         var start = 0
@@ -597,9 +600,9 @@ class AnalyzeRule(
 
         private fun isRule(ruleStr: String): Boolean {
             return ruleStr.startsWith('@') //js首个字符不可能是@,除非是装饰器,所以@开头规定为规则
-                    || ruleStr.startsWith("$.")
-                    || ruleStr.startsWith("$[")
-                    || ruleStr.startsWith("//")
+                || ruleStr.startsWith("$.")
+                || ruleStr.startsWith("$[")
+                || ruleStr.startsWith("//")
         }
     }
 

+ 26 - 25
app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt

@@ -167,34 +167,35 @@ class AnalyzeUrl(
             baseUrl = it
         }
         if (urlNoOption.length != ruleUrl.length) {
-            GSON.fromJsonObject<UrlOption>(ruleUrl.substring(urlMatcher.end()))?.let { option ->
-                option.method?.let {
-                    if (it.equals("POST", true)) method = RequestMethod.POST
-                }
-                option.headers?.let { headers ->
-                    if (headers is Map<*, *>) {
-                        headers.forEach { entry ->
-                            headerMap[entry.key.toString()] = entry.value.toString()
+            GSON.fromJsonObject<UrlOption>(ruleUrl.substring(urlMatcher.end())).getOrNull()
+                ?.let { option ->
+                    option.method?.let {
+                        if (it.equals("POST", true)) method = RequestMethod.POST
+                    }
+                    option.headers?.let { headers ->
+                        if (headers is Map<*, *>) {
+                            headers.forEach { entry ->
+                                headerMap[entry.key.toString()] = entry.value.toString()
+                            }
+                        } else if (headers is String) {
+                            GSON.fromJsonObject<Map<String, String>>(headers).getOrNull()
+                                ?.let { headerMap.putAll(it) }
                         }
-                    } else if (headers is String) {
-                        GSON.fromJsonObject<Map<String, String>>(headers)
-                            ?.let { headerMap.putAll(it) }
                     }
-                }
-                option.body?.let {
-                    body = if (it is String) it else GSON.toJson(it)
-                }
-                type = option.type
-                charset = option.charset
-                retry = option.retry
-                useWebView = option.webView?.toString()?.isNotBlank() == true
-                webJs = option.webJs
-                option.js?.let { jsStr ->
-                    evalJS(jsStr, url)?.toString()?.let {
-                        url = it
+                    option.body?.let {
+                        body = if (it is String) it else GSON.toJson(it)
+                    }
+                    type = option.type
+                    charset = option.charset
+                    retry = option.retry
+                    useWebView = option.webView?.toString()?.isNotBlank() == true
+                    webJs = option.webJs
+                    option.js?.let { jsStr ->
+                        evalJS(jsStr, url)?.toString()?.let {
+                            url = it
+                        }
                     }
                 }
-            }
         }
         headerMap[UA_NAME] ?: let {
             headerMap[UA_NAME] = AppConfig.userAgent
@@ -510,7 +511,7 @@ class AnalyzeUrl(
     suspend fun upload(fileName: String, file: Any, contentType: String): StrResponse {
         return getProxyClient(proxy).newCallStrResponse(retry) {
             url(urlNoQuery)
-            val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body)!!
+            val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body).getOrNull()!!
             bodyMap.forEach { entry ->
                 if (entry.value.toString() == "fileRequest") {
                     bodyMap[entry.key] = mapOf(

+ 2 - 2
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -141,8 +141,8 @@ object LocalBook {
                         //将文件名注入到脚步的src变量中
                         SimpleBindings().also { it["src"] = tempFileName }
                     ).toString()
-                    val bookMess =
-                        GSON.fromJsonObject<HashMap<String, String>>(jsonStr) ?: HashMap()
+                    val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)
+                        .getOrThrow() ?: HashMap()
                     name = bookMess["name"] ?: tempFileName
                     author = bookMess["author"]?.takeIf { it.length != tempFileName.length } ?: ""
                 } catch (e: Exception) {

+ 1 - 1
app/src/main/java/io/legado/app/service/TTSReadAloudService.kt

@@ -37,7 +37,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
     @Synchronized
     private fun initTts() {
         ttsInitFinish = false
-        val engine = GSON.fromJsonObject<SelectItem<String>>(ReadAloud.ttsEngine)?.value
+        val engine = GSON.fromJsonObject<SelectItem<String>>(ReadAloud.ttsEngine).getOrNull()?.value
         textToSpeech = if (engine.isNullOrBlank()) {
             TextToSpeech(this, this)
         } else {

+ 4 - 4
app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt

@@ -19,11 +19,11 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode
     fun importTextTocRule(json: String, finally: (title: String, msg: String) -> Unit) {
         execute {
             if (json.isJsonArray()) {
-                GSON.fromJsonArray<TxtTocRule>(json)?.let {
+                GSON.fromJsonArray<TxtTocRule>(json).getOrThrow()?.let {
                     appDb.txtTocRuleDao.insert(*it.toTypedArray())
                 } ?: throw NoStackTraceException("格式不对")
             } else {
-                GSON.fromJsonObject<TxtTocRule>(json)?.let {
+                GSON.fromJsonObject<TxtTocRule>(json).getOrThrow()?.let {
                     appDb.txtTocRuleDao.insert(it)
                 } ?: throw NoStackTraceException("格式不对")
             }
@@ -63,11 +63,11 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode
     fun importTheme(json: String, finally: (title: String, msg: String) -> Unit) {
         execute {
             if (json.isJsonArray()) {
-                GSON.fromJsonArray<ThemeConfig.Config>(json)?.forEach {
+                GSON.fromJsonArray<ThemeConfig.Config>(json).getOrThrow()?.forEach {
                     ThemeConfig.addConfig(it)
                 } ?: throw NoStackTraceException("格式不对")
             } else {
-                GSON.fromJsonObject<ThemeConfig.Config>(json)?.let {
+                GSON.fromJsonObject<ThemeConfig.Config>(json).getOrThrow()?.let {
                     ThemeConfig.addConfig(it)
                 } ?: throw NoStackTraceException("格式不对")
             }

+ 1 - 1
app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleDialog.kt

@@ -181,7 +181,7 @@ class ImportReplaceRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_vi
 
     override fun onCodeSave(code: String, requestId: String?) {
         requestId?.toInt()?.let {
-            GSON.fromJsonObject<ReplaceRule>(code)?.let { rule ->
+            GSON.fromJsonObject<ReplaceRule>(code).getOrNull()?.let { rule ->
                 viewModel.allRules[it] = rule
                 adapter.setItem(it, rule)
             }

+ 2 - 5
app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt

@@ -6,7 +6,6 @@ import android.view.Menu
 import android.view.MenuItem
 import androidx.activity.viewModels
 import androidx.recyclerview.widget.ItemTouchHelper
-import com.google.android.material.snackbar.Snackbar
 import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.data.appDb
@@ -22,8 +21,8 @@ import io.legado.app.ui.widget.recycler.ItemTouchCallback
 import io.legado.app.ui.widget.recycler.VerticalDivider
 import io.legado.app.utils.ACache
 import io.legado.app.utils.setEdgeEffectColor
-import io.legado.app.utils.snackbar
 import io.legado.app.utils.splitNotBlank
+import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.launch
@@ -184,10 +183,8 @@ class TxtTocRuleActivity : VMBaseActivity<ActivityTxtTocRuleBinding, TxtTocRuleV
                         cacheUrls.add(0, it)
                         aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
                     }
-                    Snackbar.make(binding.root, R.string.importing, Snackbar.LENGTH_INDEFINITE)
-                        .show()
                     viewModel.importOnLine(it) { msg ->
-                        binding.root.snackbar(msg)
+                        toastOnUi(msg)
                     }
                 }
             }

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleViewModel.kt

@@ -42,14 +42,14 @@ class TxtTocRuleViewModel(app: Application) : BaseViewModel(app) {
             okHttpClient.newCallResponseBody {
                 url(url)
             }.text("utf-8").let { json ->
-                GSON.fromJsonArray<TxtTocRule>(json)?.let {
+                GSON.fromJsonArray<TxtTocRule>(json).getOrThrow()?.let {
                     appDb.txtTocRuleDao.insert(*it.toTypedArray())
                 }
             }
         }.onSuccess {
             finally("导入成功")
         }.onError {
-            finally("导入失败")
+            finally("导入失败\n${it.localizedMessage}")
         }
     }
 

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt

@@ -65,7 +65,7 @@ class ReadAloudConfigDialog : DialogFragment() {
                     return appDb.httpTTSDao.getName(ttsEngine.toLong())
                         ?: getString(R.string.system_tts)
                 }
-                return GSON.fromJsonObject<SelectItem<String>>(ttsEngine)?.title
+                return GSON.fromJsonObject<SelectItem<String>>(ttsEngine).getOrNull()?.title
                     ?: getString(R.string.system_tts)
             }
 

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt

@@ -95,8 +95,8 @@ class SpeakEngineDialog(val callBack: CallBack) : BaseDialogFragment(R.layout.di
                     labelSys.visible()
                     cbName.text = engine.label
                     cbName.tag = engine.name
-                    cbName.isChecked =
-                        GSON.fromJsonObject<SelectItem<String>>(ttsEngine)?.value == cbName.tag
+                    cbName.isChecked = GSON.fromJsonObject<SelectItem<String>>(ttsEngine)
+                        .getOrNull()?.value == cbName.tag
                     cbName.setOnClickListener {
                         upTts(GSON.toJson(SelectItem(engine.label, engine.name)))
                     }
@@ -192,7 +192,8 @@ class SpeakEngineDialog(val callBack: CallBack) : BaseDialogFragment(R.layout.di
     private fun upTts(tts: String) {
         ttsEngine = tts
         sysTtsViews.forEach {
-            it.isChecked = GSON.fromJsonObject<SelectItem<String>>(ttsEngine)?.value == it.tag
+            it.isChecked = GSON.fromJsonObject<SelectItem<String>>(ttsEngine)
+                .getOrNull()?.value == it.tag
         }
         adapter.notifyItemRangeChanged(adapter.getHeaderCount(), adapter.itemCount)
     }

+ 1 - 4
app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt

@@ -10,7 +10,6 @@ import androidx.appcompat.widget.Toolbar
 import androidx.fragment.app.viewModels
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.snackbar.Snackbar
 import io.legado.app.R
 import io.legado.app.base.BaseDialogFragment
 import io.legado.app.base.adapter.ItemViewHolder
@@ -154,10 +153,8 @@ class TocRegexDialog() : BaseDialogFragment(R.layout.dialog_toc_regex),
                         cacheUrls.add(0, it)
                         aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
                     }
-                    Snackbar.make(binding.toolBar, R.string.importing, Snackbar.LENGTH_INDEFINITE)
-                        .show()
                     viewModel.importOnLine(it) { msg ->
-                        binding.toolBar.snackbar(msg)
+                        toastOnUi(msg)
                     }
                 }
             }

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt

@@ -33,14 +33,14 @@ class TocRegexViewModel(application: Application) : BaseViewModel(application) {
             okHttpClient.newCallResponseBody {
                 url(url)
             }.text("utf-8").let { json ->
-                GSON.fromJsonArray<TxtTocRule>(json)?.let {
+                GSON.fromJsonArray<TxtTocRule>(json).getOrThrow()?.let {
                     appDb.txtTocRuleDao.insert(*it.toTypedArray())
                 }
             }
         }.onSuccess {
             finally("导入成功")
         }.onError {
-            finally("导入失败")
+            finally("导入失败\n${it.localizedMessage}")
         }
     }
 

+ 3 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -126,7 +126,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
     private fun importBookshelfByJson(json: String, groupId: Long) {
         execute {
             val bookSources = appDb.bookSourceDao.allEnabled
-            GSON.fromJsonArray<Map<String, String?>>(json)?.forEach { bookInfo ->
+            GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow()?.forEach { bookInfo ->
                 if (!isActive) return@execute
                 val name = bookInfo["name"] ?: ""
                 val author = bookInfo["author"] ?: ""
@@ -143,6 +143,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
                         }
                 }
             }
+        }.onError {
+            it.printOnDebug()
         }.onFinally {
             context.toastOnUi(R.string.success)
         }

+ 41 - 0
app/src/main/java/io/legado/app/utils/DataExtensions.kt

@@ -0,0 +1,41 @@
+package io.legado.app.utils
+
+import kotlin.reflect.KClass
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+
+@Suppress("UNCHECKED_CAST")
+fun <T : Any> T.deepCopy(): T {
+    //如果不是数据类,直接返回
+    if (!this::class.isData) {
+        return this
+    }
+
+    //拿到构造函数
+    return this::class.primaryConstructor!!.let { primaryConstructor ->
+        //转换类型
+        //memberProperties 返回非扩展属性中的第一个并将构造函数赋值给其
+        //最终value=第一个参数类型的对象
+
+        //如果当前类(这里的当前类指的是参数对应的类型,比如说这里如果非基本类型时)是数据类
+
+        //最终返回一个新的映射map,即返回一个属性值重新组合的map,并调用callBy返回指定的对象
+        primaryConstructor.parameters.associate { parameter ->
+            //转换类型
+            //memberProperties 返回非扩展属性中的第一个并将构造函数赋值给其
+            //最终value=第一个参数类型的对象
+            val value = (this::class as KClass<T>).memberProperties.first {
+                it.name == parameter.name
+            }.get(this)
+
+            //如果当前类(这里的当前类指的是参数对应的类型,比如说这里如果非基本类型时)是数据类
+            if ((parameter.type.classifier as? KClass<*>)?.isData == true) {
+                parameter to value?.deepCopy()
+            } else {
+                parameter to value
+            }
+
+            //最终返回一个新的映射map,即返回一个属性值重新组合的map,并调用callBy返回指定的对象
+        }.let(primaryConstructor::callBy)
+    }
+}

+ 4 - 11
app/src/main/java/io/legado/app/utils/GsonExtensions.kt

@@ -4,7 +4,6 @@ import com.google.gson.*
 import com.google.gson.internal.LinkedTreeMap
 import com.google.gson.reflect.TypeToken
 import com.google.gson.stream.JsonWriter
-
 import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.lang.reflect.ParameterizedType
@@ -26,22 +25,16 @@ val GSON: Gson by lazy {
 
 inline fun <reified T> genericType(): Type = object : TypeToken<T>() {}.type
 
-inline fun <reified T> Gson.fromJsonObject(json: String?): T? {//可转成任意类型
-    json ?: return null
+inline fun <reified T> Gson.fromJsonObject(json: String?): Result<T?> {
     return kotlin.runCatching {
         fromJson(json, genericType<T>()) as? T
-    }.onFailure {
-        DebugLog.e("GSON解析出错", json, it)
-    }.getOrNull()
+    }
 }
 
-inline fun <reified T> Gson.fromJsonArray(json: String?): List<T>? {
-    json ?: return null
+inline fun <reified T> Gson.fromJsonArray(json: String?): Result<List<T>?> {
     return kotlin.runCatching {
         fromJson(json, ParameterizedTypeImpl(T::class.java)) as? List<T>
-    }.onFailure {
-        DebugLog.e("GSON解析出错", json, it)
-    }.getOrNull()
+    }
 }
 
 fun Gson.writeToOutputStream(out: OutputStream, any: Any) {

+ 2 - 2
app/src/main/java/io/legado/app/utils/IntentExtensions.kt

@@ -12,10 +12,10 @@ fun Intent.putJson(key: String, any: Any?) {
 
 inline fun <reified T> Intent.getJsonObject(key: String): T? {
     val value = getStringExtra(key)
-    return GSON.fromJsonObject<T>(value)
+    return GSON.fromJsonObject<T>(value).getOrNull()
 }
 
 inline fun <reified T> Intent.getJsonArray(key: String): List<T>? {
     val value = getStringExtra(key)
-    return GSON.fromJsonArray(value)
+    return GSON.fromJsonArray<T>(value).getOrNull()
 }

+ 2 - 1
app/src/main/java/io/legado/app/web/socket/BookSourceDebugWebSocket.kt

@@ -49,7 +49,8 @@ class BookSourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
                     close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, "调试结束", false)
                     return@launch
                 }
-                val debugBean = GSON.fromJsonObject<Map<String, String>>(message.textPayload)
+                val debugBean =
+                    GSON.fromJsonObject<Map<String, String>>(message.textPayload).getOrNull()
                 if (debugBean != null) {
                     val tag = debugBean["tag"]
                     val key = debugBean["key"]

+ 2 - 1
app/src/main/java/io/legado/app/web/socket/RssSourceDebugWebSocket.kt

@@ -49,7 +49,8 @@ class RssSourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
                     close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, "调试结束", false)
                     return@launch
                 }
-                val debugBean = GSON.fromJsonObject<Map<String, String>>(message.textPayload)
+                val debugBean =
+                    GSON.fromJsonObject<Map<String, String>>(message.textPayload).getOrNull()
                 if (debugBean != null) {
                     val tag = debugBean["tag"]
                     if (tag.isNullOrBlank()) {