优化TXT导出

pull/2337/head
Horis 2 years ago
parent 332b951535
commit ac95d46058
  1. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 2
      app/src/main/java/io/legado/app/help/ContentProcessor.kt
  3. 6
      app/src/main/java/io/legado/app/help/config/AppConfig.kt
  4. 54
      app/src/main/java/io/legado/app/help/coroutine/OrderCoroutine.kt
  5. 2
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  6. 51
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  7. 8
      app/src/main/java/io/legado/app/utils/RegexExtensions.kt
  8. 6
      app/src/main/res/menu/book_cache.xml
  9. 1
      app/src/main/res/values-es-rES/strings.xml
  10. 1
      app/src/main/res/values-ja-rJP/strings.xml
  11. 1
      app/src/main/res/values-pt-rBR/strings.xml
  12. 1
      app/src/main/res/values-zh-rHK/strings.xml
  13. 1
      app/src/main/res/values-zh-rTW/strings.xml
  14. 1
      app/src/main/res/values-zh/strings.xml
  15. 1
      app/src/main/res/values/strings.xml

@ -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,6 +81,7 @@ class ContentProcessor private constructor(
reSegment: Boolean = true
): List<String> {
var mContent = content
if (content != "null") {
//去除重复标题
try {
val name = Pattern.quote(book.name)
@ -109,6 +110,7 @@ class ContentProcessor private constructor(
//替换
mContent = replaceContent(mContent)
}
}
if (includeTitle) {
//重新添加标题
mContent = chapter.getDisplayTitle(

@ -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)))
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))
if (exportToWebDav) {
stringBuilder.append(text)
}
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) {
@ -171,10 +177,35 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
)
}"
append(qy, null)
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(
@ -198,10 +229,9 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}
}
}
append.invoke("\n\n$content1", srcList)
return Pair("\n\n$content1", srcList)
} else {
append.invoke("\n\n$content1", null)
}
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()
}
}

@ -49,6 +49,12 @@
android:checkable="true"
app:showAsAction="never" />
<item
android:id="@+id/menu_parallel_export"
android:title="@string/parallel_export_book"
android:checkable="true"
app:showAsAction="never" />
<item
android:id="@+id/menu_export_folder"
android:title="@string/export_folder"

@ -1035,4 +1035,5 @@
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1038,4 +1038,5 @@
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1038,4 +1038,5 @@
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1035,4 +1035,5 @@
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1037,4 +1037,5 @@
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1037,4 +1037,5 @@
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

@ -1038,4 +1038,5 @@
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
<string name="net_no_group">网络为分组</string>
<string name="local_no_group">本地未分组</string>
<string name="parallel_export_book">多线程导出TXT</string>
</resources>

Loading…
Cancel
Save