Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei 2023-11-29 11:27:44 +08:00
commit 6060014c74
15 changed files with 189 additions and 61 deletions

View File

@ -250,8 +250,8 @@
## 对外提供api
-keep class io.legado.app.api.ReturnData{*;}
# Apache Commons Compress
-keep class org.apache.commons.compress.archivers.** {*;}
# 繁简转换
-keep class com.github.liuyueyi.quick.transfer.** {*;}
#-------------------Cronet------------------------------------

View File

@ -7,7 +7,6 @@ import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import com.github.liuyueyi.quick.transfer.ChineseUtils
import com.github.liuyueyi.quick.transfer.constants.TransType
import com.jeremyliao.liveeventbus.LiveEventBus
import io.legado.app.base.AppContextWrapper
@ -31,6 +30,7 @@ import io.legado.app.help.http.okHttpClient
import io.legado.app.help.source.SourceHelp
import io.legado.app.help.storage.Backup
import io.legado.app.model.BookCover
import io.legado.app.utils.ChineseUtils
import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.launch
@ -73,7 +73,10 @@ class App : Application() {
Backup.clearCache()
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> ChineseUtils.preLoad(true, TransType.TRADITIONAL_TO_SIMPLE)
1 -> launch {
ChineseUtils.fixT2sDict()
}
2 -> ChineseUtils.preLoad(true, TransType.SIMPLE_TO_TRADITIONAL)
}
//调整排序序号

View File

@ -5,7 +5,6 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import com.github.liuyueyi.quick.transfer.ChineseUtils
import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern

View File

@ -4,8 +4,10 @@ import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Looper
import android.webkit.WebSettings
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.LocalConfig
@ -37,9 +39,26 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
* uncaughtException 回调函数
*/
override fun uncaughtException(thread: Thread, ex: Throwable) {
ReadAloud.stop(context)
handleException(ex)
mDefaultHandler?.uncaughtException(thread, ex)
if (shouldAbsorb(ex)) {
AppLog.put("发生未捕获的异常\n${ex.localizedMessage}", ex)
Looper.loop()
} else {
ReadAloud.stop(context)
handleException(ex)
mDefaultHandler?.uncaughtException(thread, ex)
}
}
private fun shouldAbsorb(e: Throwable): Boolean {
return when {
e::class.simpleName == "CannotDeliverBroadcastException" -> true
e is SecurityException && e.message?.contains(
"nor current process has android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS",
true
) == true -> true
else -> false
}
}
/**
@ -107,7 +126,8 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
val time = format.format(Date())
val fileName = "crash-$time-$timestamp.log"
try {
val backupPath = AppConfig.backupPath ?: throw NoStackTraceException("备份路径未配置")
val backupPath = AppConfig.backupPath
?: throw NoStackTraceException("备份路径未配置")
val uri = Uri.parse(backupPath)
val fileDoc = FileDoc.fromUri(uri, true)
fileDoc.createFileIfNotExist(fileName, "crash")

View File

@ -5,7 +5,7 @@ import android.webkit.WebSettings
import androidx.annotation.Keep
import cn.hutool.core.codec.Base64
import cn.hutool.core.util.HexUtil
import com.github.liuyueyi.quick.transfer.ChineseUtils
import io.legado.app.utils.ChineseUtils
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.constant.AppLog

View File

@ -1,6 +1,5 @@
package io.legado.app.help.book
import com.github.liuyueyi.quick.transfer.ChineseUtils
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern.spaceRegex
import io.legado.app.data.appDb
@ -10,6 +9,7 @@ import io.legado.app.data.entities.ReplaceRule
import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.utils.ChineseUtils
import io.legado.app.utils.escapeRegex
import io.legado.app.utils.replace
import io.legado.app.utils.stackTraceStr

View File

@ -41,6 +41,7 @@ import io.legado.app.utils.activityPendingIntent
import io.legado.app.utils.cnCompare
import io.legado.app.utils.createFolderIfNotExist
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.outputStream
import io.legado.app.utils.postEvent
import io.legado.app.utils.readBytes
import io.legado.app.utils.readText
@ -140,7 +141,7 @@ class ExportBookService : BaseService() {
startForeground(NotificationId.ExportBookService, notification.build())
}
private fun upExportNotification() {
private fun upExportNotification(finish: Boolean = false) {
val notification = NotificationCompat.Builder(this, AppConst.channelIdDownload)
.setSmallIcon(R.drawable.ic_export)
.setSubText(getString(R.string.export_book))
@ -149,7 +150,7 @@ class ExportBookService : BaseService() {
.setContentText(notificationContentText)
.setDeleteIntent(servicePendingIntent<ExportBookService>(IntentAction.stop))
.setGroup(groupKey)
if (exportJob?.isActive == true) {
if (!finish) {
notification.setOngoing(true)
notification.addAction(
R.drawable.ic_stop_black_24dp,
@ -168,7 +169,7 @@ class ExportBookService : BaseService() {
while (true) {
val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let {
notificationContentText = "导出完成"
upExportNotification()
upExportNotification(true)
return@launch
}
exportProgress[bookUrl] = 0
@ -230,21 +231,24 @@ class ExportBookService : BaseService() {
DocumentUtils.delete(doc, filename)
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
val charset = Charset.forName(AppConfig.exportCharset)
contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
getAllContents(book) { text, srcList ->
bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.src)
if (vFile.exists()) {
DocumentUtils.createFileIfNotExist(
doc,
"${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg",
subDirs = arrayOf(
"${book.name}_${book.author}",
"images",
it.chapterTitle
)
)?.writeBytes(this, vFile.readBytes())
BufferedOutputStream(bookOs, 64 * 1024).use { bos ->
getAllContents(book) { text, srcList ->
bos.write(text.toByteArray(charset))
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.src)
if (vFile.exists()) {
DocumentUtils.createFileIfNotExist(
doc,
"${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg",
subDirs = arrayOf(
"${book.name}_${book.author}",
"images",
it.chapterTitle
)
)?.writeBytes(this, vFile.readBytes())
}
}
}
}
@ -259,18 +263,22 @@ class ExportBookService : BaseService() {
val filename = book.getExportFileName("txt")
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
getAllContents(book) { text, srcList ->
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.src)
if (vFile.exists()) {
FileUtils.createFileIfNotExist(
file,
"${book.name}_${book.author}",
"images",
it.chapterTitle,
"${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg"
).writeBytes(vFile.readBytes())
val charset = Charset.forName(AppConfig.exportCharset)
val bos = BufferedOutputStream(bookFile.outputStream(true), 64 * 1024)
bos.use {
getAllContents(book) { text, srcList ->
bos.write(text.toByteArray(charset))
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.src)
if (vFile.exists()) {
FileUtils.createFileIfNotExist(
file,
"${book.name}_${book.author}",
"images",
it.chapterTitle,
"${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg"
).writeBytes(vFile.readBytes())
}
}
}
}

View File

@ -249,10 +249,11 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
val fontPath = ReadBookConfig.textFont
if (fontPath.isNotEmpty()) {
val fontName = FileUtils.getName(fontPath)
val fontBytes = fontPath.parseToUri().readBytes(requireContext())
fontBytes.let {
val fontInputStream =
fontPath.parseToUri().inputStream(requireContext()).getOrNull()
fontInputStream?.use {
val fontExportFile = FileUtils.createFileIfNotExist(configDir, fontName)
fontExportFile.writeBytes(it)
it.copyTo(fontExportFile.outputStream())
exportFiles.add(fontExportFile)
}
}
@ -261,8 +262,10 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
val bgFile = File(ReadBookConfig.durConfig.bgStr)
if (bgFile.exists()) {
val bgExportFile = File(FileUtils.getPath(configDir, bgName))
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
if (!bgExportFile.exists()) {
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
}
}
}
if (ReadBookConfig.durConfig.bgTypeNight == 2) {
@ -270,8 +273,10 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
val bgFile = File(ReadBookConfig.durConfig.bgStrNight)
if (bgFile.exists()) {
val bgExportFile = File(FileUtils.getPath(configDir, bgName))
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
if (!bgExportFile.exists()) {
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
}
}
}
if (ReadBookConfig.durConfig.bgTypeEInk == 2) {
@ -279,8 +284,10 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
val bgFile = File(ReadBookConfig.durConfig.bgStrEInk)
if (bgFile.exists()) {
val bgExportFile = File(FileUtils.getPath(configDir, bgName))
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
if (!bgExportFile.exists()) {
bgFile.copyTo(bgExportFile)
exportFiles.add(bgExportFile)
}
}
}
val configZipPath = FileUtils.getPath(requireContext().externalCache, configFileName)
@ -288,14 +295,17 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(requireContext(), uri)?.let { treeDoc ->
treeDoc.findFile(exportFileName)?.delete()
treeDoc.createFile("", exportFileName)
?.writeBytes(requireContext(), File(configZipPath).readBytes())
val out = treeDoc.createFile("", exportFileName)?.openOutputStream()
out?.use {
File(configZipPath).inputStream().use {
it.copyTo(out)
}
}
}
} else {
val exportPath = FileUtils.getPath(File(uri.path!!), exportFileName)
FileUtils.delete(exportPath)
FileUtils.createFileIfNotExist(exportPath)
.writeBytes(File(configZipPath).readBytes())
File(configZipPath).copyTo(FileUtils.createFileIfNotExist(exportPath))
}
}
}.onSuccess {

View File

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.core.view.get
import com.github.liuyueyi.quick.transfer.ChineseUtils
import com.github.liuyueyi.quick.transfer.constants.TransType
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment

View File

@ -2,7 +2,6 @@ package io.legado.app.ui.book.searchContent
import android.app.Application
import com.github.liuyueyi.quick.transfer.ChineseUtils
import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
@ -10,13 +9,14 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.ChineseUtils
import kotlinx.coroutines.ensureActive
import kotlin.coroutines.coroutineContext
class SearchContentViewModel(application: Application) : BaseViewModel(application) {
var bookUrl: String = ""
var book: Book? = null
var contentProcessor: ContentProcessor? = null
private var contentProcessor: ContentProcessor? = null
var lastQuery: String = ""
var searchResultCounts = 0
val cacheChapterNames = hashSetOf<String>()

View File

@ -246,7 +246,7 @@ class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {
}
else -> {
binding.root.longSnackbar("跳转其它应用", "确认") {
binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) {
openUrl(url)
}
return true

View File

@ -0,0 +1,45 @@
package io.legado.app.utils
import com.github.liuyueyi.quick.transfer.ChineseUtils
import com.github.liuyueyi.quick.transfer.constants.TransType
import com.github.liuyueyi.quick.transfer.dictionary.DictionaryContainer
object ChineseUtils {
private var fixed = false
fun s2t(content: String): String {
return ChineseUtils.s2t(content)
}
fun t2s(content: String): String {
if (!fixed) {
fixed = true
fixT2sDict()
}
return ChineseUtils.t2s(content)
}
fun preLoad(async: Boolean, vararg transType: TransType) {
ChineseUtils.preLoad(async, *transType)
}
fun unLoad(vararg transType: TransType) {
ChineseUtils.unLoad(*transType)
}
fun fixT2sDict() {
val dict = DictionaryContainer.getInstance().getDictionary(TransType.TRADITIONAL_TO_SIMPLE)
dict.chars.run {
remove('劈')
remove('脊')
}
kotlin.runCatching {
dict.dict.run {
remove("支援")
remove("路易斯")
}
}
}
}

View File

@ -212,7 +212,7 @@ fun FileDoc.createFileIfNotExist(
vararg subDirs: String
): FileDoc {
return if (uri.isContentScheme()) {
val documentFile = DocumentFile.fromTreeUri(appCtx, uri)!!
val documentFile = asDocumentFile()!!
val tmp = DocumentUtils.createFileIfNotExist(documentFile, fileName, *subDirs)!!
FileDoc.fromDocumentFile(tmp)
} else {
@ -226,7 +226,7 @@ fun FileDoc.createFolderIfNotExist(
vararg subDirs: String
): FileDoc {
return if (uri.isContentScheme()) {
val documentFile = DocumentFile.fromTreeUri(appCtx, uri)!!
val documentFile = asDocumentFile()!!
val tmp = DocumentUtils.createFolderIfNotExist(documentFile, *subDirs)!!
FileDoc.fromDocumentFile(tmp)
} else {
@ -257,7 +257,7 @@ fun FileDoc.exists(
vararg subDirs: String
): Boolean {
return if (uri.isContentScheme()) {
DocumentUtils.exists(DocumentFile.fromTreeUri(appCtx, uri)!!, fileName, *subDirs)
DocumentUtils.exists(asDocumentFile()!!, fileName, *subDirs)
} else {
val path = FileUtils.getPath(uri.path!!, *subDirs) + File.separator + fileName
FileUtils.exist(path)
@ -266,7 +266,7 @@ fun FileDoc.exists(
fun FileDoc.exists(): Boolean {
return if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(appCtx, uri)!!.exists()
asDocumentFile()!!.exists()
} else {
FileUtils.exist(uri.path!!)
}

View File

@ -4,6 +4,7 @@ package io.legado.app.utils
import android.net.Uri
import java.io.File
import java.io.FileOutputStream
fun File.getFile(vararg subDirFiles: String): File {
val path = FileUtils.getPath(this, *subDirFiles)
@ -79,3 +80,7 @@ fun File.checkWrite(): Boolean {
false
}
}
fun File.outputStream(append: Boolean = false): FileOutputStream {
return FileOutputStream(this, append)
}

View File

@ -0,0 +1,39 @@
package io.legado.app.utils
import com.github.liuyueyi.quick.transfer.Trie
import com.github.liuyueyi.quick.transfer.TrieNode
import java.util.HashMap
fun <T> Trie<T>.getRoot(): TrieNode<T> {
val rootField = javaClass.getDeclaredField("root")
.apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
return rootField.get(this) as TrieNode<T>
}
fun <T> TrieNode<T>.getChildren(): HashMap<Char, TrieNode<T>> {
val childrenField = javaClass.getDeclaredField("children")
.apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
return childrenField.get(this) as HashMap<Char, TrieNode<T>>
}
fun <T> Trie<T>.remove(value: String) {
var node = getRoot()
val nodes = arrayListOf<TrieNode<T>>()
val chars = value.toCharArray()
for (c in chars) {
nodes.add(node)
node = node.getChildren()[c] ?: break
if (!node.isLeaf) {
continue
}
for ((ch, n) in chars.reversed().zip(nodes.reversed())) {
val children = n.getChildren()
children.remove(ch)
if (children.isNotEmpty()) {
break
}
}
}
}