Horis 1 rok temu
rodzic
commit
f6bd4a1704

+ 2 - 3
app/src/main/java/io/legado/app/help/glide/ImageLoader.kt

@@ -12,7 +12,6 @@ import io.legado.app.utils.isAbsUrl
 import io.legado.app.utils.isContentScheme
 import io.legado.app.utils.isDataUrl
 import io.legado.app.utils.lifecycle
-import splitties.init.appCtx
 import java.io.File
 
 //https://bumptech.github.io/glide/doc/generatedapi.html
@@ -37,8 +36,8 @@ object ImageLoader {
         }
     }
 
-    fun load(lifecycle: Lifecycle, path: String?): RequestBuilder<Drawable> {
-        val requestManager = Glide.with(appCtx).lifecycle(lifecycle)
+    fun load(context: Context, lifecycle: Lifecycle, path: String?): RequestBuilder<Drawable> {
+        val requestManager = Glide.with(context).lifecycle(lifecycle)
         return when {
             path.isNullOrEmpty() -> requestManager.load(path)
             path.isDataUrl() -> requestManager.load(path)

+ 112 - 268
app/src/main/java/io/legado/app/service/ExportBookService.kt

@@ -2,12 +2,7 @@ package io.legado.app.service
 
 import android.annotation.SuppressLint
 import android.content.Intent
-import android.graphics.Bitmap
-import android.net.Uri
-import android.os.Build
-import android.util.ArraySet
 import androidx.core.app.NotificationCompat
-import androidx.documentfile.provider.DocumentFile
 import androidx.lifecycle.lifecycleScope
 import com.bumptech.glide.Glide
 import io.legado.app.R
@@ -31,7 +26,6 @@ import io.legado.app.help.book.isLocalModified
 import io.legado.app.help.config.AppConfig
 import io.legado.app.model.localBook.LocalBook
 import io.legado.app.ui.book.cache.CacheActivity
-import io.legado.app.utils.DocumentUtils
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.FileUtils
 import io.legado.app.utils.HtmlFormatter
@@ -39,21 +33,19 @@ import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.NetworkUtils
 import io.legado.app.utils.activityPendingIntent
 import io.legado.app.utils.cnCompare
-import io.legado.app.utils.createFolderIfNotExist
+import io.legado.app.utils.createFileIfNotExist
+import io.legado.app.utils.delete
 import io.legado.app.utils.find
-import io.legado.app.utils.isContentScheme
 import io.legado.app.utils.list
 import io.legado.app.utils.mapAsync
 import io.legado.app.utils.mapAsyncIndexed
-import io.legado.app.utils.outputStream
+import io.legado.app.utils.openOutputStream
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.servicePendingIntent
 import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.writeBytes
-import kotlinx.coroutines.Deferred
+import io.legado.app.utils.writeFile
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.flow.collectIndexed
@@ -65,6 +57,7 @@ import me.ag2s.epublib.domain.Date
 import me.ag2s.epublib.domain.EpubBook
 import me.ag2s.epublib.domain.FileResourceProvider
 import me.ag2s.epublib.domain.LazyResource
+import me.ag2s.epublib.domain.LazyResourceProvider
 import me.ag2s.epublib.domain.Metadata
 import me.ag2s.epublib.domain.Resource
 import me.ag2s.epublib.domain.TOCReference
@@ -73,8 +66,6 @@ import me.ag2s.epublib.epub.EpubWriterProcessor
 import me.ag2s.epublib.util.ResourceUtil
 import splitties.init.appCtx
 import splitties.systemservices.notificationManager
-import java.io.ByteArrayOutputStream
-import java.io.File
 import java.nio.charset.Charset
 import java.util.concurrent.ConcurrentHashMap
 import kotlin.coroutines.coroutineContext
@@ -197,15 +188,15 @@ class ExportBookService : BaseService() {
                     upExportNotification()
                     if (exportConfig.type == "epub") {
                         if (exportConfig.epubScope.isNullOrBlank()) {
-                            exportEPUB(exportConfig.path, book)
+                            exportEpub(exportConfig.path, book)
                         } else {
                             CustomExporter(
-                                paresScope(exportConfig.epubScope),
+                                exportConfig.epubScope,
                                 exportConfig.epubSize
                             ).export(exportConfig.path, book)
                         }
                     } else {
-                        export(exportConfig.path, book)
+                        exportTxt(exportConfig.path, book)
                     }
                     exportMsg[book.bookUrl] = getString(R.string.export_success)
                 } catch (e: Throwable) {
@@ -239,40 +230,33 @@ class ExportBookService : BaseService() {
         val src: String
     )
 
-    private suspend fun export(path: String, book: Book) {
+    private suspend fun exportTxt(path: String, book: Book) {
         exportMsg.remove(book.bookUrl)
         postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-        if (path.isContentScheme()) {
-            val uri = Uri.parse(path)
-            val doc = DocumentFile.fromTreeUri(this@ExportBookService, uri)
-                ?: throw NoStackTraceException("获取导出文档失败")
-            export(doc, book)
-        } else {
-            export(File(path).createFolderIfNotExist(), book)
-        }
+        val fileDoc = FileDoc.fromDir(path)
+        exportTxt(fileDoc, book)
     }
 
-    private suspend fun export(doc: DocumentFile, book: Book) {
+    private suspend fun exportTxt(fileDoc: FileDoc, book: Book) {
         val filename = book.getExportFileName("txt")
-        DocumentUtils.delete(doc, filename)
-        val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
-            ?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
+        fileDoc.find(filename)?.delete()
+
+        val bookDoc = fileDoc.createFileIfNotExist(filename)
         val charset = Charset.forName(AppConfig.exportCharset)
-        contentResolver.openOutputStream(bookDoc.uri, "wa")?.bufferedWriter(charset)?.use { bw ->
+        bookDoc.openOutputStream().getOrThrow().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,
+                        fileDoc.createFileIfNotExist(
                             "${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg",
                             subDirs = arrayOf(
                                 "${book.name}_${book.author}",
                                 "images",
                                 it.chapterTitle
                             )
-                        )?.writeBytes(this, vFile.readBytes())
+                        ).writeFile(vFile)
                     }
                 }
             }
@@ -283,34 +267,6 @@ class ExportBookService : BaseService() {
         }
     }
 
-    private suspend fun export(file: File, book: Book) {
-        val filename = book.getExportFileName("txt")
-        val bookPath = FileUtils.getPath(file, filename)
-        val bookFile = FileUtils.createFileWithReplace(bookPath)
-        val charset = Charset.forName(AppConfig.exportCharset)
-        val bw = bookFile.outputStream(true).bufferedWriter(charset)
-        bw.use {
-            getAllContents(book) { text, srcList ->
-                it.write(text)
-                srcList?.forEach {
-                    val vFile = BookHelp.getImage(book, it.src)
-                    if (vFile.exists()) {
-                        FileUtils.createFileIfNotExist(
-                            file,
-                            "${book.name}_${book.author}",
-                            "images",
-                            it.chapterTitle,
-                            "${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg"
-                        ).writeBytes(vFile.readBytes())
-                    }
-                }
-            }
-        }
-        if (AppConfig.exportToWebDav) {
-            AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename) // 导出到webdav
-        }
-    }
-
     private suspend fun getAllContents(
         book: Book,
         append: (text: String, srcList: ArrayList<SrcData>?) -> Unit
@@ -381,87 +337,20 @@ class ExportBookService : BaseService() {
         }
     }
 
-    /**
-     * 解析范围字符串
-     *
-     * @param scope 范围字符串
-     * @return 范围
-     *
-     * @since 2023/5/22
-     * @author Discut
-     */
-    @SuppressLint("ObsoleteSdkInt")
-    private fun paresScope(scope: String): Set<Int> {
-        val split = scope.split(",")
-
-        @Suppress("RemoveExplicitTypeArguments")
-        val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            ArraySet<Int>()
-        } else {
-            HashSet<Int>()
-        }
-        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) {
-                AppLog.put("Error expression : $s; left > right")
-                continue
-            }
-            for (i in left..right)
-                result.add(i - 1)
-        }
-        return result
-    }
-
     /**
      * 导出Epub
      */
-    private suspend fun exportEPUB(path: String, book: Book) {
+    private suspend fun exportEpub(path: String, book: Book) {
         exportMsg.remove(book.bookUrl)
         postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-        if (path.isContentScheme()) {
-            val uri = Uri.parse(path)
-            val doc = DocumentFile.fromTreeUri(this@ExportBookService, uri)
-                ?: throw NoStackTraceException("获取导出文档失败")
-            exportEpub(doc, book)
-        } else {
-            exportEpub(File(path).createFolderIfNotExist(), book)
-        }
+        val fileDoc = FileDoc.fromDir(path)
+        exportEpub(fileDoc, book)
     }
 
-    private suspend fun exportEpub(doc: DocumentFile, book: Book) {
+    private suspend fun exportEpub(fileDoc: FileDoc, book: Book) {
         val filename = book.getExportFileName("epub")
-        DocumentUtils.delete(doc, filename)
-        val epubBook = EpubBook()
-        epubBook.version = "2.0"
-        //set metadata
-        setEpubMetadata(book, epubBook)
-        //set cover
-        setCover(book, epubBook)
-        //set css
-        val contentModel = setAssets(doc, book, epubBook)
-
-        //设置正文
-        setEpubContent(contentModel, book, epubBook)
-        DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
-            contentResolver.openOutputStream(bookDoc.uri, "wa")?.buffered().use { bookOs ->
-                EpubWriter().write(epubBook, bookOs)
-            }
-            if (AppConfig.exportToWebDav) {
-                // 导出到webdav
-                AppWebDav.exportWebDav(bookDoc.uri, filename)
-            }
-        }
-    }
+        fileDoc.find(filename)?.delete()
 
-
-    private suspend fun exportEpub(file: File, book: Book) {
-        val filename = book.getExportFileName("epub")
         val epubBook = EpubBook()
         epubBook.version = "2.0"
         //set metadata
@@ -469,29 +358,22 @@ class ExportBookService : BaseService() {
         //set cover
         setCover(book, epubBook)
         //set css
-        val contentModel = setAssets(file, book, epubBook)
+        val contentModel = setAssets(fileDoc, book, epubBook)
 
-        val bookPath = FileUtils.getPath(file, filename)
-        val bookFile = FileUtils.createFileWithReplace(bookPath)
         //设置正文
         setEpubContent(contentModel, book, epubBook)
-        bookFile.outputStream().buffered().use {
-            EpubWriter().write(epubBook, it)
+
+        val bookDoc = fileDoc.createFileIfNotExist(filename)
+        bookDoc.openOutputStream().getOrThrow().buffered().use { bookOs ->
+            EpubWriter().write(epubBook, bookOs)
         }
+
         if (AppConfig.exportToWebDav) {
             // 导出到webdav
-            AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename)
+            AppWebDav.exportWebDav(bookDoc.uri, filename)
         }
     }
 
-    private fun setAssets(doc: DocumentFile, book: Book, epubBook: EpubBook): String {
-        return setAssets(FileDoc.fromDocumentFile(doc), book, epubBook)
-    }
-
-    private fun setAssets(file: File, book: Book, epubBook: EpubBook): String {
-        return setAssets(FileDoc.fromFile(file), book, epubBook)
-    }
-
     private fun setAssets(doc: FileDoc, book: Book, epubBook: EpubBook): String {
         val customPath = doc.find("Asset")
         val contentModel = if (customPath == null) {//使用内置模板
@@ -618,16 +500,15 @@ class ExportBookService : BaseService() {
 
     private fun setCover(book: Book, epubBook: EpubBook) {
         kotlin.runCatching {
-            val bitmap = Glide.with(this)
-                .asBitmap()
+            val file = Glide.with(this)
+                .asFile()
                 .load(book.getDisplayCover())
                 .submit()
                 .get()
-            val byteArray = ByteArrayOutputStream().use {
-                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
-                it.toByteArray()
+            val provider = LazyResourceProvider { _ ->
+                file.inputStream()
             }
-            epubBook.coverImage = Resource(byteArray, "Images/cover.jpg")
+            epubBook.coverImage = LazyResource(provider, "Images/cover.jpg")
         }.onFailure {
             AppLog.put("获取书籍封面出错\n${it.localizedMessage}", it)
         }
@@ -758,7 +639,9 @@ class ExportBookService : BaseService() {
      * @param scope 导出范围
      * @param size epub 文件包含最大章节数
      */
-    inner class CustomExporter(private var scope: Set<Int>, private val size: Int) {
+    inner class CustomExporter(scopeStr: String, private val size: Int) {
+
+        private var scope = parseScope(scopeStr)
 
         /**
          * 导出Epub
@@ -775,78 +658,35 @@ class ExportBookService : BaseService() {
             val currentTimeMillis = System.currentTimeMillis()
             val count = appDb.bookChapterDao.getChapterCount(book.bookUrl)
             scope = scope.filter { it < count }.toHashSet()
-            when (path.isContentScheme()) {
-                true -> {
-                    val uri = Uri.parse(path)
-                    val doc = DocumentFile.fromTreeUri(this@ExportBookService, uri)
-                        ?: throw NoStackTraceException("获取导出文档失败")
-                    val (contentModel, epubList) = createEpubs(book, doc)
-                    val asyncBlocks = ArrayList<Deferred<Unit>>(epubList.size)
-                    var progressBar = 0.0
-                    epubList.forEachIndexed { index, ep ->
-                        val (filename, epubBook) = ep
-                        coroutineScope {
-                            val asyncBlock = async {
-                                //设置正文
-                                setEpubContent(
-                                    contentModel,
-                                    book,
-                                    epubBook,
-                                    index
-                                ) { _, _ ->
-                                    // 将章节写入内存时更新进度条
-                                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-                                    progressBar += book.totalChapterNum.toDouble() / scope.size / 2
-                                    exportProgress[book.bookUrl] = progressBar.toInt()
-                                }
-                                save2Drive(filename, epubBook, doc) { total, _ ->
-                                    //写入硬盘时更新进度条
-                                    progressBar += book.totalChapterNum.toDouble() / epubList.size / total / 2
-                                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-                                    exportProgress[book.bookUrl] = progressBar.toInt()
-                                }
-                            }
-                            asyncBlocks.add(asyncBlock)
-                        }
-                    }
-                    asyncBlocks.forEach { it.await() }
-                }
 
-                false -> {
-                    val file = File(path).createFolderIfNotExist()
-                    val (contentModel, epubList) = createEpubs(book, null)
-                    val asyncBlocks = ArrayList<Deferred<Unit>>(epubList.size)
-                    var progressBar = 0.0
-                    epubList.forEachIndexed { index, ep ->
-                        val (filename, epubBook) = ep
-                        coroutineScope {
-                            val asyncBlock = async {
-                                //设置正文
-                                setEpubContent(
-                                    contentModel,
-                                    book,
-                                    epubBook,
-                                    index
-                                ) { _, _ ->
-                                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-                                    exportProgress[book.bookUrl] =
-                                        exportProgress[book.bookUrl]?.plus(book.totalChapterNum / scope.size)
-                                            ?: 1
-                                }
-                                save2Drive(filename, epubBook, file) { total, _ ->
-                                    //设置进度
-                                    progressBar += book.totalChapterNum.toDouble() / epubList.size / total / 2
-                                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
-                                    exportProgress[book.bookUrl] = progressBar.toInt()
-                                }
-                            }
-                            asyncBlocks.add(asyncBlock)
-                        }
-                    }
-                    asyncBlocks.forEach { it.await() }
+            val fileDoc = FileDoc.fromDir(path)
+
+            val (contentModel, epubList) = createEpubs(book, fileDoc)
+            var progressBar = 0.0
+            epubList.forEachIndexed { index, ep ->
+                val (filename, epubBook) = ep
+                //设置正文
+                setEpubContent(
+                    contentModel,
+                    book,
+                    epubBook,
+                    index
+                ) { _, _ ->
+                    // 将章节写入内存时更新进度条
+                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
+                    progressBar += book.totalChapterNum.toDouble() / scope.size / 2
+                    exportProgress[book.bookUrl] = progressBar.toInt()
+                }
+                save2Drive(filename, epubBook, fileDoc) { total, _ ->
+                    //写入硬盘时更新进度条
+                    progressBar += book.totalChapterNum.toDouble() / epubList.size / total / 2
+                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
+                    exportProgress[book.bookUrl] = progressBar.toInt()
                 }
             }
-            AppLog.put("分割导出书籍 ${book.name} 一共耗时 ${System.currentTimeMillis() - currentTimeMillis}")
+
+            val elapsed = System.currentTimeMillis() - currentTimeMillis
+            AppLog.put("分割导出书籍 ${book.name} 一共耗时 $elapsed")
         }
 
 
@@ -878,7 +718,7 @@ class ExportBookService : BaseService() {
                 }
             }
             // val totalChapterNum = book.totalChapterNum / scope.size
-            if (chapterList.size == 0) {
+            if (chapterList.isEmpty()) {
                 throw RuntimeException("书籍<${book.name}>(${epubBookIndex + 1})未找到章节信息")
             }
             chapterList = chapterList.subList(
@@ -930,23 +770,22 @@ class ExportBookService : BaseService() {
          * 创建多个epub 对象
          *
          * 分割epub时,一个书籍需要创建多个epub对象
-         * @param doc 导出文档
          * @param book 书籍
+         * @param fileDoc 导出文件夹文档
          *
          * @return <内容模板字符串, <epub文件名, epub对象>>
          */
         private fun createEpubs(
             book: Book,
-            doc: DocumentFile?,
+            fileDoc: FileDoc
         ): Pair<String, List<Pair<String, EpubBook>>> {
             val paresNumOfEpub = paresNumOfEpub(scope.size, size)
             val result: MutableList<Pair<String, EpubBook>> = ArrayList(paresNumOfEpub)
             var contentModel = ""
             for (i in 1..paresNumOfEpub) {
                 val filename = book.getExportFileName("epub", i)
-                doc?.let {
-                    DocumentUtils.delete(it, filename)
-                }
+                fileDoc.find(filename)?.delete()
+
                 val epubBook = EpubBook()
                 epubBook.version = "2.0"
                 //set metadata
@@ -954,9 +793,7 @@ class ExportBookService : BaseService() {
                 //set cover
                 setCover(book, epubBook)
                 //set css
-                contentModel = doc?.let {
-                    setAssets(it, book, epubBook)
-                } ?: setAssets(book, epubBook)
+                contentModel = setAssets(fileDoc, book, epubBook)
 
                 // add epubBook
                 result.add(Pair(filename, epubBook))
@@ -970,47 +807,23 @@ class ExportBookService : BaseService() {
         private suspend fun save2Drive(
             filename: String,
             epubBook: EpubBook,
-            doc: DocumentFile,
+            fileDoc: FileDoc,
             callback: (total: Int, progress: Int) -> Unit
         ) {
-            DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
-                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, bookOs)
-                }
-                if (AppConfig.exportToWebDav) {
-                    // 导出到webdav
-                    AppWebDav.exportWebDav(bookDoc.uri, filename)
-                }
+            val bookDoc = fileDoc.createFileIfNotExist(filename)
+            bookDoc.openOutputStream().getOrThrow().buffered().use { bookOs ->
+                EpubWriter()
+                    .setCallback(object : EpubWriterProcessor.Callback {
+                        override fun onProgressing(total: Int, progress: Int) {
+                            callback(total, progress)
+                        }
+                    })
+                    .write(epubBook, bookOs)
             }
-        }
 
-        /**
-         * 保存文件到 设备
-         */
-        private suspend fun save2Drive(
-            filename: String,
-            epubBook: EpubBook,
-            file: File,
-            callback: (total: Int, progress: Int) -> Unit
-        ) {
-            val bookPath = FileUtils.getPath(file, filename)
-            val bookFile = FileUtils.createFileWithReplace(bookPath)
-            EpubWriter()
-                .setCallback(object : EpubWriterProcessor.Callback {
-                    override fun onProgressing(total: Int, progress: Int) {
-                        callback(total, progress)
-                    }
-                })
-                .write(epubBook, bookFile.outputStream().buffered())
             if (AppConfig.exportToWebDav) {
                 // 导出到webdav
-                AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename)
+                AppWebDav.exportWebDav(bookDoc.uri, filename)
             }
         }
 
@@ -1028,5 +841,36 @@ class ExportBookService : BaseService() {
             }
             return result
         }
+
+        /**
+         * 解析范围字符串
+         *
+         * @param scope 范围字符串
+         * @return 范围
+         *
+         * @since 2023/5/22
+         * @author Discut
+         */
+        private fun parseScope(scope: String): Set<Int> {
+            val split = scope.split(",")
+
+            val result = linkedSetOf<Int>()
+            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) {
+                    AppLog.put("Error expression : $s; left > right")
+                    continue
+                }
+                for (i in left..right)
+                    result.add(i - 1)
+            }
+            return result
+        }
     }
 }

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

@@ -89,7 +89,7 @@ class TextChapterLayout(
 
     var exception: Throwable? = null
 
-    var channel = Channel<TextPage>(Int.MAX_VALUE)
+    var channel = Channel<TextPage>(Channel.UNLIMITED)
 
 
     init {

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt

@@ -32,7 +32,7 @@ class RssAdapter(context: Context, val callBack: CallBack, val lifecycle: Lifecy
             tvName.text = item.sourceName
             val options = RequestOptions()
                 .set(OkHttpModelLoader.sourceOriginOption, item.sourceUrl)
-            ImageLoader.load(lifecycle, item.sourceIcon)
+            ImageLoader.load(context, lifecycle, item.sourceIcon)
                 .apply(options)
                 .centerCrop()
                 .placeholder(R.drawable.image_rss)

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt

@@ -198,7 +198,7 @@ class CoverImageView @JvmOverloads constructor(
                 options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)
             }
             val builder = if (lifecycle != null) {
-                ImageLoader.load(lifecycle, path)
+                ImageLoader.load(context, lifecycle, path)
             } else {
                 ImageLoader.load(context, path)//Glide自动识别http://,content://和file://
             }

+ 13 - 0
app/src/main/java/io/legado/app/utils/FileDocExtensions.kt

@@ -8,6 +8,7 @@ import android.database.Cursor
 import android.net.Uri
 import android.os.ParcelFileDescriptor
 import android.provider.DocumentsContract
+import androidx.core.net.toUri
 import androidx.documentfile.provider.DocumentFile
 import io.legado.app.exception.NoStackTraceException
 import splitties.init.appCtx
@@ -67,6 +68,10 @@ data class FileDoc(
 
     companion object {
 
+        fun fromDir(path: String): FileDoc {
+            return fromUri(path.toUri(), true)
+        }
+
         fun fromUri(uri: Uri, isDir: Boolean): FileDoc {
             if (uri.isContentScheme()) {
                 val doc = if (isDir) {
@@ -284,6 +289,14 @@ fun FileDoc.writeText(text: String) {
     }
 }
 
+fun FileDoc.writeFile(file: File) {
+    openOutputStream().getOrThrow().use { out ->
+        file.inputStream().use {
+            it.copyTo(out)
+        }
+    }
+}
+
 fun FileDoc.delete() {
     asFile()?.let {
         FileUtils.delete(it, true)

+ 9 - 0
app/src/main/java/io/legado/app/utils/RequestManagerExtensions.kt

@@ -3,9 +3,18 @@ package io.legado.app.utils
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import com.bumptech.glide.Glide
 import com.bumptech.glide.RequestManager
+import splitties.init.appCtx
+
+private val applicationRequestManager by lazy {
+    Glide.with(appCtx)
+}
 
 fun RequestManager.lifecycle(lifecycle: Lifecycle): RequestManager {
+    if (this === applicationRequestManager) {
+        return this
+    }
 
     val observer = object : DefaultLifecycleObserver {
         override fun onResume(owner: LifecycleOwner) = onStart()