优化TXT导出
This commit is contained in:
@@ -113,6 +113,7 @@ object PreferKey {
|
|||||||
const val showAddToShelfAlert = "showAddToShelfAlert"
|
const val showAddToShelfAlert = "showAddToShelfAlert"
|
||||||
const val asyncLoadImage = "asyncLoadImage"
|
const val asyncLoadImage = "asyncLoadImage"
|
||||||
const val ignoreAudioFocus = "ignoreAudioFocus"
|
const val ignoreAudioFocus = "ignoreAudioFocus"
|
||||||
|
const val parallelExportBook = "parallelExportBook"
|
||||||
|
|
||||||
const val cPrimary = "colorPrimary"
|
const val cPrimary = "colorPrimary"
|
||||||
const val cAccent = "colorAccent"
|
const val cAccent = "colorAccent"
|
||||||
|
|||||||
@@ -81,33 +81,35 @@ class ContentProcessor private constructor(
|
|||||||
reSegment: Boolean = true
|
reSegment: Boolean = true
|
||||||
): List<String> {
|
): List<String> {
|
||||||
var mContent = content
|
var mContent = content
|
||||||
//去除重复标题
|
if (content != "null") {
|
||||||
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) {
|
|
||||||
//简繁转换
|
|
||||||
try {
|
try {
|
||||||
when (AppConfig.chineseConverterType) {
|
val name = Pattern.quote(book.name)
|
||||||
1 -> mContent = ChineseUtils.t2s(mContent)
|
val title = Pattern.quote(chapter.title)
|
||||||
2 -> mContent = ChineseUtils.s2t(mContent)
|
val titleRegex = "^(\\s|\\p{P}|${name})*${title}(\\s)*".toRegex()
|
||||||
}
|
mContent = mContent.replace(titleRegex, "")
|
||||||
} catch (e: Exception) {
|
} 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) {
|
if (includeTitle) {
|
||||||
//重新添加标题
|
//重新添加标题
|
||||||
|
|||||||
@@ -255,6 +255,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
appCtx.putPrefBoolean(PreferKey.exportPictureFile, value)
|
appCtx.putPrefBoolean(PreferKey.exportPictureFile, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var parallelExportBook: Boolean
|
||||||
|
get() = appCtx.getPrefBoolean(PreferKey.parallelExportBook, false)
|
||||||
|
set(value) {
|
||||||
|
appCtx.putPrefBoolean(PreferKey.parallelExportBook, value)
|
||||||
|
}
|
||||||
|
|
||||||
var changeSourceCheckAuthor: Boolean
|
var changeSourceCheckAuthor: Boolean
|
||||||
get() = appCtx.getPrefBoolean(PreferKey.changeSourceCheckAuthor)
|
get() = appCtx.getPrefBoolean(PreferKey.changeSourceCheckAuthor)
|
||||||
set(value) {
|
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_no_chapter_name)?.isChecked = AppConfig.exportNoChapterName
|
||||||
menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav
|
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_export_pics_file)?.isChecked = AppConfig.exportPictureFile
|
||||||
|
menu.findItem(R.id.menu_parallel_export)?.isChecked = AppConfig.parallelExportBook
|
||||||
menu.findItem(R.id.menu_export_type)?.title =
|
menu.findItem(R.id.menu_export_type)?.title =
|
||||||
"${getString(R.string.export_type)}(${getTypeName()})"
|
"${getString(R.string.export_type)}(${getTypeName()})"
|
||||||
menu.findItem(R.id.menu_export_charset)?.title =
|
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_no_chapter_name -> AppConfig.exportNoChapterName = !item.isChecked
|
||||||
R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !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_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked
|
||||||
|
R.id.menu_parallel_export -> AppConfig.parallelExportBook = !item.isChecked
|
||||||
R.id.menu_export_folder -> {
|
R.id.menu_export_folder -> {
|
||||||
selectExportFolder(-1)
|
selectExportFolder(-1)
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-35
@@ -22,9 +22,9 @@ import io.legado.app.help.AppWebDav
|
|||||||
import io.legado.app.help.BookHelp
|
import io.legado.app.help.BookHelp
|
||||||
import io.legado.app.help.ContentProcessor
|
import io.legado.app.help.ContentProcessor
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
|
import io.legado.app.help.coroutine.OrderCoroutine
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.ensureActive
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import me.ag2s.epublib.domain.*
|
import me.ag2s.epublib.domain.*
|
||||||
@@ -104,10 +104,13 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
|
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
|
||||||
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
|
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
|
val exportToWebDav = AppConfig.exportToWebDav
|
||||||
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
|
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
|
||||||
getAllContents(book) { text, srcList ->
|
getAllContents(book) { text, srcList ->
|
||||||
bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
|
bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
|
||||||
stringBuilder.append(text)
|
if (exportToWebDav) {
|
||||||
|
stringBuilder.append(text)
|
||||||
|
}
|
||||||
srcList?.forEach {
|
srcList?.forEach {
|
||||||
val vFile = BookHelp.getImage(book, it.third)
|
val vFile = BookHelp.getImage(book, it.third)
|
||||||
if (vFile.exists()) {
|
if (vFile.exists()) {
|
||||||
@@ -133,9 +136,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
val bookPath = FileUtils.getPath(file, filename)
|
val bookPath = FileUtils.getPath(file, filename)
|
||||||
val bookFile = FileUtils.createFileWithReplace(bookPath)
|
val bookFile = FileUtils.createFileWithReplace(bookPath)
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
|
val exportToWebDav = AppConfig.exportToWebDav
|
||||||
getAllContents(book) { text, srcList ->
|
getAllContents(book) { text, srcList ->
|
||||||
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
|
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
|
||||||
stringBuilder.append(text)
|
if (exportToWebDav) {
|
||||||
|
stringBuilder.append(text)
|
||||||
|
}
|
||||||
srcList?.forEach {
|
srcList?.forEach {
|
||||||
val vFile = BookHelp.getImage(book, it.third)
|
val vFile = BookHelp.getImage(book, it.third)
|
||||||
if (vFile.exists()) {
|
if (vFile.exists()) {
|
||||||
@@ -171,37 +177,61 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
)
|
)
|
||||||
}"
|
}"
|
||||||
append(qy, null)
|
append(qy, null)
|
||||||
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
|
if (AppConfig.parallelExportBook) {
|
||||||
coroutineContext.ensureActive()
|
val oc =
|
||||||
upAdapterLiveData.postValue(book.bookUrl)
|
OrderCoroutine<Pair<String, ArrayList<Triple<String, Int, String>>?>>(AppConfig.threadCount)
|
||||||
exportProgress[book.bookUrl] = index
|
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->
|
||||||
BookHelp.getContent(book, chapter).let { content ->
|
oc.submit { getExportData(book, chapter, contentProcessor, useReplace) }
|
||||||
val content1 = contentProcessor
|
}
|
||||||
.getContent(
|
oc.collect { index, result ->
|
||||||
book,
|
upAdapterLiveData.postValue(book.bookUrl)
|
||||||
chapter,
|
exportProgress[book.bookUrl] = index
|
||||||
content ?: "null",
|
append.invoke(result.first, result.second)
|
||||||
includeTitle = !AppConfig.exportNoChapterName,
|
}
|
||||||
useReplace = useReplace,
|
} else {
|
||||||
chineseConvert = false,
|
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
|
||||||
reSegment = false
|
coroutineContext.ensureActive()
|
||||||
).joinToString("\n")
|
upAdapterLiveData.postValue(book.bookUrl)
|
||||||
if (AppConfig.exportPictureFile) {
|
exportProgress[book.bookUrl] = index
|
||||||
//txt导出图片文件
|
val result = getExportData(book, chapter, contentProcessor, useReplace)
|
||||||
val srcList = arrayListOf<Triple<String, Int, String>>()
|
append.invoke(result.first, result.second)
|
||||||
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))
|
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()
|
.asBitmap()
|
||||||
.load(book.getDisplayCover())
|
.load(book.getDisplayCover())
|
||||||
.into(object : CustomTarget<Bitmap>() {
|
.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()
|
val stream = ByteArrayOutputStream()
|
||||||
resource.compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
resource.compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
||||||
val byteArray: ByteArray = stream.toByteArray()
|
val byteArray: ByteArray = stream.toByteArray()
|
||||||
@@ -491,8 +524,10 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
matcher.group(1)?.let {
|
matcher.group(1)?.let {
|
||||||
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
|
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
|
||||||
val originalHref = "${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
|
val originalHref =
|
||||||
val href = "Images/${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
|
"${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
|
||||||
|
val href =
|
||||||
|
"Images/${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}"
|
||||||
val vFile = BookHelp.getImage(book, src)
|
val vFile = BookHelp.getImage(book, src)
|
||||||
val fp = FileResourceProvider(vFile.parent)
|
val fp = FileResourceProvider(vFile.parent)
|
||||||
if (vFile.exists()) {
|
if (vFile.exists()) {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package io.legado.app.utils
|
|||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import io.legado.app.exception.RegexTimeoutException
|
import io.legado.app.exception.RegexTimeoutException
|
||||||
import io.legado.app.help.CrashHandler
|
import io.legado.app.help.CrashHandler
|
||||||
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import kotlin.coroutines.resumeWithException
|
|||||||
suspend fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): String {
|
suspend fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): String {
|
||||||
val charSequence = this
|
val charSequence = this
|
||||||
return suspendCancellableCoroutine { block ->
|
return suspendCancellableCoroutine { block ->
|
||||||
val thread = thread {
|
val coroutine = Coroutine.async {
|
||||||
try {
|
try {
|
||||||
val result = regex.replace(charSequence, replacement)
|
val result = regex.replace(charSequence, replacement)
|
||||||
block.resume(result)
|
block.resume(result)
|
||||||
@@ -25,14 +25,14 @@ suspend fun CharSequence.replace(regex: Regex, replacement: String, timeout: Lon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mainHandler.postDelayed(timeout) {
|
mainHandler.postDelayed(timeout) {
|
||||||
if (thread.isAlive) {
|
if (coroutine.isActive) {
|
||||||
val timeoutMsg = "替换超时,3秒后还未结束将重启应用\n替换规则$regex\n替换内容:${this}"
|
val timeoutMsg = "替换超时,3秒后还未结束将重启应用\n替换规则$regex\n替换内容:${this}"
|
||||||
val exception = RegexTimeoutException(timeoutMsg)
|
val exception = RegexTimeoutException(timeoutMsg)
|
||||||
block.cancel(exception)
|
block.cancel(exception)
|
||||||
appCtx.longToastOnUi(timeoutMsg)
|
appCtx.longToastOnUi(timeoutMsg)
|
||||||
CrashHandler.saveCrashInfo2File(exception)
|
CrashHandler.saveCrashInfo2File(exception)
|
||||||
mainHandler.postDelayed(3000) {
|
mainHandler.postDelayed(3000) {
|
||||||
if (thread.isAlive) {
|
if (coroutine.isActive) {
|
||||||
appCtx.restart()
|
appCtx.restart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,12 @@
|
|||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_parallel_export"
|
||||||
|
android:title="@string/parallel_export_book"
|
||||||
|
android:checkable="true"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_export_folder"
|
android:id="@+id/menu_export_folder"
|
||||||
android:title="@string/export_folder"
|
android:title="@string/export_folder"
|
||||||
|
|||||||
@@ -1035,4 +1035,5 @@
|
|||||||
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1038,4 +1038,5 @@
|
|||||||
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1038,4 +1038,5 @@
|
|||||||
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1035,4 +1035,5 @@
|
|||||||
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1037,4 +1037,5 @@
|
|||||||
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1037,4 +1037,5 @@
|
|||||||
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1038,4 +1038,5 @@
|
|||||||
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
|
||||||
<string name="net_no_group">网络为分组</string>
|
<string name="net_no_group">网络为分组</string>
|
||||||
<string name="local_no_group">本地未分组</string>
|
<string name="local_no_group">本地未分组</string>
|
||||||
|
<string name="parallel_export_book">多线程导出TXT</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user