This commit is contained in:
Horis
2024-01-28 17:09:25 +08:00
parent 5a32bfd414
commit cc9fbf4f8d
22 changed files with 350 additions and 160 deletions
@@ -20,7 +20,6 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@@ -206,10 +205,8 @@ object Backup {
withContext(IO) {
if (list.isNotEmpty()) {
val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)
FileOutputStream(file).use { fos ->
BufferedOutputStream(fos, 64 * 1024).use {
GSON.writeToOutputStream(it, list)
}
file.outputStream().buffered().use {
GSON.writeToOutputStream(it, list)
}
}
}
@@ -75,10 +75,8 @@ import me.ag2s.epublib.epub.EpubWriterProcessor
import me.ag2s.epublib.util.ResourceUtil
import splitties.init.appCtx
import splitties.systemservices.notificationManager
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.nio.charset.Charset
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.coroutineContext
@@ -257,23 +255,21 @@ class ExportBookService : BaseService() {
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
val charset = Charset.forName(AppConfig.exportCharset)
contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
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())
}
contentResolver.openOutputStream(bookDoc.uri, "wa")?.bufferedWriter(charset)?.use { bw ->
getAllContents(book) { text, srcList ->
bw.write(text)
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())
}
}
}
@@ -289,10 +285,10 @@ class ExportBookService : BaseService() {
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
val charset = Charset.forName(AppConfig.exportCharset)
val bos = BufferedOutputStream(bookFile.outputStream(true), 64 * 1024)
bos.use {
val bw = bookFile.outputStream(true).bufferedWriter(charset)
bw.use {
getAllContents(book) { text, srcList ->
bos.write(text.toByteArray(charset))
it.write(text)
srcList?.forEach {
val vFile = BookHelp.getImage(book, it.src)
if (vFile.exists()) {
@@ -315,7 +311,7 @@ class ExportBookService : BaseService() {
private suspend fun getAllContents(
book: Book,
append: (text: String, srcList: ArrayList<SrcData>?) -> Unit
) {
) = coroutineScope {
val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()
val contentProcessor = ContentProcessor.get(book.name, book.origin)
val qy = "${book.name}\n${
@@ -327,34 +323,27 @@ class ExportBookService : BaseService() {
)
}"
append(qy, null)
if (AppConfig.parallelExportBook) {
coroutineScope {
flow {
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->
val task = async(Default, start = CoroutineStart.LAZY) {
getExportData(book, chapter, contentProcessor, useReplace)
}
emit(task)
}
}.onEach { it.start() }
.buffer(AppConfig.threadCount)
.map { it.await() }
.withIndex()
.collect { (index, result) ->
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
exportProgress[book.bookUrl] = index
append.invoke(result.first, result.second)
}
}
val threads = if (AppConfig.parallelExportBook) {
AppConst.MAX_THREAD
} else {
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
coroutineContext.ensureActive()
0
}
flow {
appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->
val task = async(Default, start = CoroutineStart.LAZY) {
getExportData(book, chapter, contentProcessor, useReplace)
}
emit(task)
}
}.onEach { it.start() }
.buffer(threads)
.map { it.await() }
.withIndex()
.collect { (index, result) ->
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
exportProgress[book.bookUrl] = index
val result = getExportData(book, chapter, contentProcessor, useReplace)
append.invoke(result.first, result.second)
}
}
}
@@ -364,34 +353,33 @@ class ExportBookService : BaseService() {
contentProcessor: ContentProcessor,
useReplace: Boolean
): Pair<String, ArrayList<SrcData>?> {
BookHelp.getContent(book, chapter).let { content ->
val content1 = contentProcessor
.getContent(
book,
// 不导出vip标识
chapter.apply { isVip = false },
content ?: if (chapter.isVolume) "" else "null",
includeTitle = !AppConfig.exportNoChapterName,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).toString()
if (AppConfig.exportPictureFile) {
//txt导出图片文件
val srcList = arrayListOf<SrcData>()
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(SrcData(chapter.title, index, src))
}
val content = BookHelp.getContent(book, chapter)
val content1 = contentProcessor
.getContent(
book,
// 不导出vip标识
chapter.apply { isVip = false },
content ?: if (chapter.isVolume) "" else "null",
includeTitle = !AppConfig.exportNoChapterName,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).toString()
if (AppConfig.exportPictureFile) {
//txt导出图片文件
val srcList = arrayListOf<SrcData>()
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(SrcData(chapter.title, index, src))
}
}
return Pair("\n\n$content1", srcList)
} else {
return Pair("\n\n$content1", null)
}
return Pair("\n\n$content1", srcList)
} else {
return Pair("\n\n$content1", null)
}
}
@@ -463,8 +451,8 @@ class ExportBookService : BaseService() {
//设置正文
setEpubContent(contentModel, book, epubBook)
DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
EpubWriter().write(epubBook, BufferedOutputStream(bookOs))
contentResolver.openOutputStream(bookDoc.uri, "wa")?.buffered().use { bookOs ->
EpubWriter().write(epubBook, bookOs)
}
if (AppConfig.exportToWebDav) {
// 导出到webdav
@@ -489,8 +477,7 @@ class ExportBookService : BaseService() {
val bookFile = FileUtils.createFileWithReplace(bookPath)
//设置正文
setEpubContent(contentModel, book, epubBook)
@Suppress("BlockingMethodInNonBlockingContext")
EpubWriter().write(epubBook, BufferedOutputStream(FileOutputStream(bookFile)))
EpubWriter().write(epubBook, bookFile.outputStream().buffered())
if (AppConfig.exportToWebDav) {
// 导出到webdav
AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename)
@@ -647,59 +634,79 @@ class ExportBookService : BaseService() {
contentModel: String,
book: Book,
epubBook: EpubBook
) {
) = coroutineScope {
//正文
val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()
val contentProcessor = ContentProcessor.get(book.name, book.origin)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
coroutineContext.ensureActive()
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content ->
var content1 = fixPic(
epubBook,
book,
content ?: if (chapter.isVolume) "" else "null",
chapter
)
content1 = contentProcessor
.getContent(
val threads = if (AppConfig.parallelExportBook) {
AppConst.MAX_THREAD
} else {
0
}
flow {
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
val task = async(Default, start = CoroutineStart.LAZY) {
val content = BookHelp.getContent(book, chapter)
val (contentFix, resources) = fixPic(
book,
chapter,
content1,
includeTitle = false,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).toString()
val title = chapter.run {
// 不导出vip标识
isVip = false
getDisplayTitle(
contentProcessor.getTitleReplaceRules(),
useReplace = useReplace
content ?: if (chapter.isVolume) "" else "null",
chapter
)
}
epubBook.addSection(
title,
ResourceUtil.createChapterResource(
// 不导出vip标识
chapter.isVip = false
val content1 = contentProcessor
.getContent(
book,
chapter,
contentFix,
includeTitle = false,
useReplace = useReplace,
chineseConvert = false,
reSegment = false
).toString()
val title = chapter.run {
// 不导出vip标识
isVip = false
getDisplayTitle(
contentProcessor.getTitleReplaceRules(),
useReplace = useReplace
)
}
val chapterResource = ResourceUtil.createChapterResource(
title.replace("\uD83D\uDD12", ""),
content1,
contentModel,
"Text/chapter_${index}.html"
)
)
ExportChapter(title, chapterResource, resources)
}
emit(task)
}
}.onEach { it.start() }
.buffer(threads)
.map { it.await() }
.withIndex()
.collect { (index, exportChapter) ->
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
exportProgress[book.bookUrl] = index
epubBook.resources.addAll(exportChapter.resources)
epubBook.addSection(exportChapter.title, exportChapter.chapterResource)
}
}
}
data class ExportChapter(
val title: String,
val chapterResource: Resource,
val resources: ArrayList<Resource>
)
private fun fixPic(
epubBook: EpubBook,
book: Book,
content: String,
chapter: BookChapter
): String {
): Pair<String, ArrayList<Resource>> {
val data = StringBuilder("")
val resources = arrayListOf<Resource>()
content.split("\n").forEach { text ->
var text1 = text
val matcher = AppPattern.imgPattern.matcher(text)
@@ -714,14 +721,14 @@ class ExportBookService : BaseService() {
val fp = FileResourceProvider(vFile.parent)
if (vFile.exists()) {
val img = LazyResource(fp, href, originalHref)
epubBook.resources.add(img)
resources.add(img)
}
text1 = text1.replace(src, "../${href}")
}
}
data.append(text1).append("\n")
}
return data.toString()
return data.toString() to resources
}
private fun setEpubMetadata(book: Book, epubBook: EpubBook) {
@@ -875,17 +882,17 @@ class ExportBookService : BaseService() {
coroutineContext.ensureActive()
updateProgress(chapterList, index)
BookHelp.getContent(book, chapter).let { content ->
var content1 = fixPic(
epubBook,
val (contentFix, resources) = fixPic(
book,
content ?: if (chapter.isVolume) "" else "null",
chapter
)
content1 = contentProcessor
epubBook.resources.addAll(resources)
val content1 = contentProcessor
.getContent(
book,
chapter,
content1,
contentFix,
includeTitle = false,
useReplace = useReplace,
chineseConvert = false,
@@ -960,14 +967,14 @@ class ExportBookService : BaseService() {
callback: (total: Int, progress: Int) -> Unit
) {
DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
contentResolver.openOutputStream(bookDoc.uri, "wa")?.buffered().use { bookOs ->
EpubWriter()
.setCallback(object : EpubWriterProcessor.Callback {
override fun onProgressing(total: Int, progress: Int) {
callback(total, progress)
}
})
.write(epubBook, BufferedOutputStream(bookOs))
.write(epubBook, bookOs)
}
if (AppConfig.exportToWebDav) {
// 导出到webdav
@@ -987,14 +994,13 @@ class ExportBookService : BaseService() {
) {
val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath)
@Suppress("BlockingMethodInNonBlockingContext")
EpubWriter()
.setCallback(object : EpubWriterProcessor.Callback {
override fun onProgressing(total: Int, progress: Int) {
callback(total, progress)
}
})
.write(epubBook, BufferedOutputStream(FileOutputStream(bookFile)))
.write(epubBook, bookFile.outputStream().buffered())
if (AppConfig.exportToWebDav) {
// 导出到webdav
AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename)
@@ -22,9 +22,7 @@ import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.writeToOutputStream
import kotlinx.coroutines.delay
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
class BookshelfManageViewModel(application: Application) : BaseViewModel(application) {
@@ -64,17 +62,14 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica
}
}
@Suppress("BlockingMethodInNonBlockingContext")
fun saveAllUseBookSourceToFile(success: (file: File) -> Unit) {
execute {
val path = "${context.filesDir}/shareBookSource.json"
FileUtils.delete(path)
val file = FileUtils.createFileWithReplace(path)
val sources = appDb.bookDao.getAllUseBookSource()
FileOutputStream(file).use { out ->
BufferedOutputStream(out, 64 * 1024).use {
GSON.writeToOutputStream(it, sources)
}
file.outputStream().buffered().use {
GSON.writeToOutputStream(it, sources)
}
file
}.onSuccess {
@@ -9,11 +9,16 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.entities.toBookSource
import io.legado.app.help.config.SourceConfig
import io.legado.app.utils.*
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.cnCompare
import io.legado.app.utils.outputStream
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.writeToOutputStream
import splitties.init.appCtx
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
/**
* 书源管理数据修改
@@ -121,16 +126,13 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}
}
@Suppress("BlockingMethodInNonBlockingContext")
private fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {
execute {
val path = "${context.filesDir}/shareBookSource.json"
FileUtils.delete(path)
val file = FileUtils.createFileWithReplace(path)
FileOutputStream(file).use { out ->
BufferedOutputStream(out, 64 * 1024).use {
GSON.writeToOutputStream(it, sources)
}
file.outputStream().buffered().use {
GSON.writeToOutputStream(it, sources)
}
file
}.onSuccess {
@@ -0,0 +1,30 @@
package io.legado.app.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import org.mozilla.javascript.Context
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
inline fun <T> suspendContinuation(crossinline block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val cx = Context.enter()
try {
val pending = cx.captureContinuation()
pending.applicationState = suspend {
supervisorScope {
block()
}
}
throw pending
} catch (e: IllegalStateException) {
return runBlocking { block() }
} finally {
Context.exit()
}
}