From 82f0d0af17e5502904a55bd5b407c103f640160a Mon Sep 17 00:00:00 2001 From: Discut Date: Mon, 22 May 2023 22:52:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(CustomExportDialog):=20=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/cache/CacheViewModel.kt | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index acd859b5c..8fbd74c7d 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri +import android.util.ArraySet import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.MutableLiveData import com.bumptech.glide.Glide @@ -41,6 +42,7 @@ import java.nio.charset.Charset import java.nio.file.* import java.util.* import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.ArrayList import kotlin.coroutines.coroutineContext @@ -248,7 +250,55 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } } + /** + * 解析范围字符串 + * + * @param scope 范围字符串 + * @return 范围 + * + * @since 2023/5/22 + * @author Discut + */ + private fun paresScope(scope: String): IntArray { + val split = scope.split(",") + val result = ArraySet() + for (s in split) { + val v = s.split("-") + if (v.size != 2) { + result.add(s.toInt() - 1) + continue + } + val left = v[0].toInt(); + val right = v[1].toInt(); + if (left > right) + return IntArray(0) + for (i in left..right) + result.add(i - 1) + } + return result.toIntArray() + } + + //////////////////Start EPUB + /** + * 导出Epub 自定义导出范围 + * + * @param path 导出路径 + * @param book 书籍 + * @param size 每本Epub包含的章节 + * @param scope 导出范围 + * @author Discut + * @since 2023/5/22 + */ + fun exportEPUB(path: String, book: Book, size: Int = 1, scope: String) { + if (exportProgress.contains(book.bookUrl)) return + CustomExporter(this).let { + it.scope = paresScope(scope) + it.size = size + it.exportEPUB(path, book) + } + } + /** * 导出Epub */ @@ -373,6 +423,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { ) ) } + else -> { //其他格式文件当做资源文件 folder.listFiles().forEach { @@ -575,4 +626,183 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } //////end of EPUB + + /** + * 自定义Exporter + */ + class CustomExporter(private val context: CacheViewModel) { + var scope: IntArray = IntArray(0); + var size: Int = 1; + + fun exportEPUB( + path: String, + book: Book + ) { + context.exportProgress[book.bookUrl] = 0 + context.exportMsg.remove(book.bookUrl) + context.upAdapterLiveData.sendValue(book.bookUrl) + context.execute { + context.mutex.withLock { + while (context.exportNumber > 0) { + delay(1000) + } + context.exportNumber++ + } + if (path.isContentScheme()) { + val uri = Uri.parse(path) + val doc = DocumentFile.fromTreeUri(context.context, uri) + ?: throw NoStackTraceException("获取导出文档失败") + book.totalChapterNum + // TODO + exportEpub(doc, book) + } else { + context.exportEpub(File(path).createFolderIfNotExist(), book) + } + }.onError { + context.exportProgress.remove(book.bookUrl) + context.exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR" + context.upAdapterLiveData.postValue(book.bookUrl) + it.printStackTrace() + AppLog.put("导出epub书籍<${book.name}>出错\n${it.localizedMessage}", it) + }.onSuccess { + context.exportProgress.remove(book.bookUrl) + context.exportMsg[book.bookUrl] = context.context.getString(R.string.export_success) + context.upAdapterLiveData.postValue(book.bookUrl) + }.onFinally { + context.exportNumber-- + } + } + + /** + * 导出 epub + * + * from [io.legado.app.ui.book.cache.CacheViewModel.exportEpub] + */ + private suspend fun exportEpub(doc: DocumentFile, book: Book) { + val (contentModel, epubList) = createEpubs(doc, book) + epubList.forEachIndexed { index, epubBook -> + //设置正文 + this.setEpubContent(contentModel, book, epubBook, index) + save2Drive(book.name+index+".epub", epubBook, doc) + } + + } + + /** + * 设置epub正文 + * + * from [io.legado.app.ui.book.cache.CacheViewModel.setEpubContent] + */ + private suspend fun setEpubContent( + contentModel: String, + book: Book, + epubBook: EpubBook, + epubBookIndex: Int + ) { + + //正文 + val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() + val contentProcessor = ContentProcessor.get(book.name, book.origin) + var chapterList: MutableList = ArrayList(); + appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> + if (scope.indexOf(index) >= 0) { + chapterList.add(chapter) + } + } + chapterList = chapterList.subList(epubBookIndex * size, (epubBookIndex + 1) * size) + chapterList.forEachIndexed { index, chapter -> + coroutineContext.ensureActive() + context.upAdapterLiveData.postValue(book.bookUrl) + context.exportProgress[book.bookUrl] = index + BookHelp.getContent(book, chapter).let { content -> + var content1 = context.fixPic( + epubBook, + book, + content ?: if (chapter.isVolume) "" else "null", + chapter + ) + content1 = contentProcessor + .getContent( + book, + chapter, + content1, + includeTitle = false, + useReplace = useReplace, + chineseConvert = false, + reSegment = false + ).toString() + val title = chapter.run { + // 不导出vip标识 + isVip = false + getDisplayTitle( + contentProcessor.getTitleReplaceRules(), + useReplace = useReplace + ) + } + epubBook.addSection( + title, + ResourceUtil.createChapterResource( + title.replace("\uD83D\uDD12", ""), + content1, + contentModel, + "Text/chapter_${index}.html" + ) + ) + } + } + } + + /** + * 创建多个epub 对象 + */ + private fun createEpubs(doc: DocumentFile, book: Book): Pair> { + val paresNumOfEpub = paresNumOfEpub(scope.size, size) + val result: MutableList = ArrayList(paresNumOfEpub) + var contentModel = ""; + for (i in 1..paresNumOfEpub) { + val filename = book.getExportFileName("epub") + DocumentUtils.delete(doc, filename) + val epubBook = EpubBook() + epubBook.version = "2.0" + //set metadata + context.setEpubMetadata(book, epubBook) + //set cover + context.setCover(book, epubBook) + //set css + contentModel = context.setAssets(doc, book, epubBook) + + // add epubBook + result.add(epubBook) + } + return Pair(contentModel, result) + } + + /** + * 保存文件到 设备 + */ + private suspend fun save2Drive(filename: String, epubBook: EpubBook, doc: DocumentFile) { + DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> + context.context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> + EpubWriter().write(epubBook, bookOs) + } + if (AppConfig.exportToWebDav) { + // 导出到webdav + AppWebDav.exportWebDav(bookDoc.uri, filename) + } + } + + } + + /** + * 解析 分割epub后的数量 + */ + private fun paresNumOfEpub(total: Int, size: Int): Int { + val i = total % size + var result = total / size; + if (i > 0) { + result++; + } + return result; + } + } } \ No newline at end of file