优化TXT导出

This commit is contained in:
Horis
2022-09-23 22:59:29 +08:00
parent 99276cdb9d
commit 19af2f7258
15 changed files with 176 additions and 63 deletions
@@ -113,6 +113,7 @@ object PreferKey {
const val showAddToShelfAlert = "showAddToShelfAlert"
const val asyncLoadImage = "asyncLoadImage"
const val ignoreAudioFocus = "ignoreAudioFocus"
const val parallelExportBook = "parallelExportBook"
const val cPrimary = "colorPrimary"
const val cAccent = "colorAccent"
@@ -81,33 +81,35 @@ class ContentProcessor private constructor(
reSegment: Boolean = true
): List<String> {
var mContent = content
//去除重复标题
try {
val name = Pattern.quote(book.name)
val title = Pattern.quote(chapter.title)
val titleRegex = "^(\\s|\\p{P}|${name})*${title}(\\s)*".toRegex()
mContent = mContent.replace(titleRegex, "")
} catch (e: Exception) {
AppLog.put("去除重复标题出错\n${e.localizedMessage}", e)
}
if (reSegment && book.getReSegment()) {
//重新分段
mContent = ContentHelp.reSegment(mContent, chapter.title)
}
if (chineseConvert) {
//简繁转换
if (content != "null") {
//去除重复标题
try {
when (AppConfig.chineseConverterType) {
1 -> mContent = ChineseUtils.t2s(mContent)
2 -> mContent = ChineseUtils.s2t(mContent)
}
val name = Pattern.quote(book.name)
val title = Pattern.quote(chapter.title)
val titleRegex = "^(\\s|\\p{P}|${name})*${title}(\\s)*".toRegex()
mContent = mContent.replace(titleRegex, "")
} catch (e: Exception) {
appCtx.toastOnUi("简繁转换出错")
AppLog.put("去除重复标题出错\n${e.localizedMessage}", e)
}
if (reSegment && book.getReSegment()) {
//重新分段
mContent = ContentHelp.reSegment(mContent, chapter.title)
}
if (chineseConvert) {
//简繁转换
try {
when (AppConfig.chineseConverterType) {
1 -> mContent = ChineseUtils.t2s(mContent)
2 -> mContent = ChineseUtils.s2t(mContent)
}
} catch (e: Exception) {
appCtx.toastOnUi("简繁转换出错")
}
}
if (useReplace && book.getUseReplaceRule()) {
//替换
mContent = replaceContent(mContent)
}
}
if (useReplace && book.getUseReplaceRule()) {
//替换
mContent = replaceContent(mContent)
}
if (includeTitle) {
//重新添加标题
@@ -255,6 +255,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
appCtx.putPrefBoolean(PreferKey.exportPictureFile, value)
}
var parallelExportBook: Boolean
get() = appCtx.getPrefBoolean(PreferKey.parallelExportBook, false)
set(value) {
appCtx.putPrefBoolean(PreferKey.parallelExportBook, value)
}
var changeSourceCheckAuthor: Boolean
get() = appCtx.getPrefBoolean(PreferKey.changeSourceCheckAuthor)
set(value) {
@@ -0,0 +1,54 @@
package io.legado.app.help.coroutine
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.PriorityBlockingQueue
class OrderCoroutine<T>(val threadCount: Int) {
private val taskList = ConcurrentLinkedQueue<suspend CoroutineScope.() -> T>()
private val taskResultMap = ConcurrentHashMap<Int, T>()
private val finishTaskIndex = PriorityBlockingQueue<Int>()
private val mutex = Mutex()
private suspend fun start() = coroutineScope {
var taskIndex = 0
for (i in 1..threadCount) {
launch {
while (true) {
ensureActive()
val task: suspend CoroutineScope.() -> T
val curIndex: Int
mutex.withLock {
task = taskList.poll() ?: return@launch
curIndex = taskIndex++
}
taskResultMap[curIndex] = task.invoke(this)
finishTaskIndex.add(curIndex)
}
}
}
}
fun submit(block: suspend CoroutineScope.() -> T) {
taskList.add(block)
}
suspend fun collect(block: (index: Int, result: T) -> Unit) = withContext(IO) {
var index = 0
val taskSize = taskList.size
launch { start() }
while (index < taskSize) {
ensureActive()
if (finishTaskIndex.peek() == index) {
finishTaskIndex.poll()
block.invoke(index, taskResultMap.remove(index)!!)
index++
}
}
}
}
@@ -94,6 +94,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
menu.findItem(R.id.menu_export_no_chapter_name)?.isChecked = AppConfig.exportNoChapterName
menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav
menu.findItem(R.id.menu_export_pics_file)?.isChecked = AppConfig.exportPictureFile
menu.findItem(R.id.menu_parallel_export)?.isChecked = AppConfig.parallelExportBook
menu.findItem(R.id.menu_export_type)?.title =
"${getString(R.string.export_type)}(${getTypeName()})"
menu.findItem(R.id.menu_export_charset)?.title =
@@ -131,6 +132,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
R.id.menu_export_no_chapter_name -> AppConfig.exportNoChapterName = !item.isChecked
R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked
R.id.menu_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked
R.id.menu_parallel_export -> AppConfig.parallelExportBook = !item.isChecked
R.id.menu_export_folder -> {
selectExportFolder(-1)
}
@@ -22,9 +22,9 @@ import io.legado.app.help.AppWebDav
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.OrderCoroutine
import io.legado.app.utils.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import me.ag2s.epublib.domain.*
@@ -104,10 +104,13 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
val stringBuilder = StringBuilder()
val exportToWebDav = AppConfig.exportToWebDav
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
getAllContents(book) { text, srcList ->
bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
stringBuilder.append(text)
if (exportToWebDav) {
stringBuilder.append(text)
}
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) {
@@ -133,9 +136,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
val stringBuilder = StringBuilder()
val exportToWebDav = AppConfig.exportToWebDav
getAllContents(book) { text, srcList ->
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
stringBuilder.append(text)
if (exportToWebDav) {
stringBuilder.append(text)
}
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) {
@@ -171,37 +177,61 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
)
}"
append(qy, null)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
coroutineContext.ensureActive()
upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content ->
val content1 = contentProcessor
.getContent(
book,
chapter,
content ?: "null",
includeTitle = !AppConfig.exportNoChapterName,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).joinToString("\n")
if (AppConfig.exportPictureFile) {
//txt导出图片文件
val srcList = arrayListOf<Triple<String, Int, String>>()
content?.split("\n")?.forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) {
matcher.group(1)?.let {
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
srcList.add(Triple(chapter.title, index, src))
}
if (AppConfig.parallelExportBook) {
val oc =
OrderCoroutine<Pair<String, ArrayList<Triple<String, Int, String>>?>>(AppConfig.threadCount)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->
oc.submit { getExportData(book, chapter, contentProcessor, useReplace) }
}
oc.collect { index, result ->
upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index
append.invoke(result.first, result.second)
}
} else {
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
coroutineContext.ensureActive()
upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index
val result = getExportData(book, chapter, contentProcessor, useReplace)
append.invoke(result.first, result.second)
}
}
}
private suspend fun getExportData(
book: Book,
chapter: BookChapter,
contentProcessor: ContentProcessor,
useReplace: Boolean
): Pair<String, ArrayList<Triple<String, Int, String>>?> {
BookHelp.getContent(book, chapter).let { content ->
val content1 = contentProcessor
.getContent(
book,
chapter,
content ?: "null",
includeTitle = !AppConfig.exportNoChapterName,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).joinToString("\n")
if (AppConfig.exportPictureFile) {
//txt导出图片文件
val srcList = arrayListOf<Triple<String, Int, String>>()
content?.split("\n")?.forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) {
matcher.group(1)?.let {
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
srcList.add(Triple(chapter.title, index, src))
}
}
append.invoke("\n\n$content1", srcList)
} else {
append.invoke("\n\n$content1", null)
}
return Pair("\n\n$content1", srcList)
} else {
return Pair("\n\n$content1", null)
}
}
}
@@ -422,7 +452,10 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
.asBitmap()
.load(book.getDisplayCover())
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
val stream = ByteArrayOutputStream()
resource.compress(Bitmap.CompressFormat.JPEG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
@@ -491,8 +524,10 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
while (matcher.find()) {
matcher.group(1)?.let {
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
val originalHref = "${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
val href = "Images/${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
val originalHref =
"${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
val href =
"Images/${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
val vFile = BookHelp.getImage(book, src)
val fp = FileResourceProvider(vFile.parent)
if (vFile.exists()) {
@@ -3,9 +3,9 @@ package io.legado.app.utils
import androidx.core.os.postDelayed
import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.CrashHandler
import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx
import kotlin.concurrent.thread
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -16,7 +16,7 @@ import kotlin.coroutines.resumeWithException
suspend fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): String {
val charSequence = this
return suspendCancellableCoroutine { block ->
val thread = thread {
val coroutine = Coroutine.async {
try {
val result = regex.replace(charSequence, replacement)
block.resume(result)
@@ -25,14 +25,14 @@ suspend fun CharSequence.replace(regex: Regex, replacement: String, timeout: Lon
}
}
mainHandler.postDelayed(timeout) {
if (thread.isAlive) {
if (coroutine.isActive) {
val timeoutMsg = "替换超时,3秒后还未结束将重启应用\n替换规则$regex\n替换内容:${this}"
val exception = RegexTimeoutException(timeoutMsg)
block.cancel(exception)
appCtx.longToastOnUi(timeoutMsg)
CrashHandler.saveCrashInfo2File(exception)
mainHandler.postDelayed(3000) {
if (thread.isAlive) {
if (coroutine.isActive) {
appCtx.restart()
}
}