diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 4e0866dade..ef0f65fff1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -27,6 +27,7 @@ import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML import tachiyomi.core.storage.AndroidStorageFolderProvider +import tachiyomi.core.storage.UniFileTempFileManager import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.Database import tachiyomi.data.DatabaseHandler @@ -111,6 +112,8 @@ class AppModule(val app: Application) : InjektModule { ProtoBuf } + addSingletonFactory { UniFileTempFileManager(app) } + addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { CoverCache(app) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 37116eeefa..789d26e3e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.runBlocking import logcat.LogPriority import tachiyomi.core.preference.toggle +import tachiyomi.core.storage.UniFileTempFileManager import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withIOContext @@ -85,6 +86,7 @@ class ReaderViewModel @JvmOverloads constructor( private val sourceManager: SourceManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), private val downloadProvider: DownloadProvider = Injekt.get(), + private val tempFileManager: UniFileTempFileManager = Injekt.get(), private val imageSaver: ImageSaver = Injekt.get(), preferences: BasePreferences = Injekt.get(), val readerPreferences: ReaderPreferences = Injekt.get(), @@ -269,7 +271,7 @@ class ReaderViewModel @JvmOverloads constructor( val context = Injekt.get() val source = sourceManager.getOrStub(manga.source) - loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source) + loader = ChapterLoader(context, downloadManager, downloadProvider, tempFileManager, manga, source) loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id }) Result.success(true) @@ -904,6 +906,7 @@ class ReaderViewModel @JvmOverloads constructor( private fun deletePendingChapters() { viewModelScope.launchNonCancellable { downloadManager.deletePendingChapters() + tempFileManager.deleteTempFiles() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 6a31ed0292..b8f97c5f42 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import tachiyomi.core.i18n.stringResource -import tachiyomi.core.storage.toTempFile +import tachiyomi.core.storage.UniFileTempFileManager import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.model.Manga @@ -24,6 +24,7 @@ class ChapterLoader( private val context: Context, private val downloadManager: DownloadManager, private val downloadProvider: DownloadProvider, + private val tempFileManager: UniFileTempFileManager, private val manga: Manga, private val source: Source, ) { @@ -85,17 +86,24 @@ class ChapterLoader( skipCache = true, ) return when { - isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider) + isDownloaded -> DownloadPageLoader( + chapter, + manga, + source, + downloadManager, + downloadProvider, + tempFileManager, + ) source is LocalSource -> source.getFormat(chapter.chapter).let { format -> when (format) { is Format.Directory -> DirectoryPageLoader(format.file) - is Format.Zip -> ZipPageLoader(format.file.toTempFile(context)) + is Format.Zip -> ZipPageLoader(tempFileManager.createTempFile(format.file)) is Format.Rar -> try { - RarPageLoader(format.file.toTempFile(context)) + RarPageLoader(tempFileManager.createTempFile(format.file)) } catch (e: UnsupportedRarV5Exception) { error(context.stringResource(MR.strings.loader_rar5_error)) } - is Format.Epub -> EpubPageLoader(format.file.toTempFile(context)) + is Format.Epub -> EpubPageLoader(tempFileManager.createTempFile(format.file)) } } source is HttpSource -> HttpPageLoader(chapter, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index 3d385551d5..775b493b3b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import tachiyomi.core.storage.toTempFile +import tachiyomi.core.storage.UniFileTempFileManager import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.injectLazy @@ -23,6 +23,7 @@ internal class DownloadPageLoader( private val source: Source, private val downloadManager: DownloadManager, private val downloadProvider: DownloadProvider, + private val tempFileManager: UniFileTempFileManager, ) : PageLoader() { private val context: Application by injectLazy() @@ -46,8 +47,8 @@ internal class DownloadPageLoader( zipPageLoader?.recycle() } - private suspend fun getPagesFromArchive(chapterPath: UniFile): List { - val loader = ZipPageLoader(chapterPath.toTempFile(context)).also { zipPageLoader = it } + private suspend fun getPagesFromArchive(file: UniFile): List { + val loader = ZipPageLoader(tempFileManager.createTempFile(file)).also { zipPageLoader = it } return loader.getPages() } diff --git a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt index 8e2bf43fcf..afe60ed353 100644 --- a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt +++ b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt @@ -1,11 +1,6 @@ package tachiyomi.core.storage -import android.content.Context -import android.os.Build -import android.os.FileUtils import com.hippo.unifile.UniFile -import java.io.BufferedOutputStream -import java.io.File val UniFile.extension: String? get() = name?.substringAfterLast('.') @@ -15,27 +10,3 @@ val UniFile.nameWithoutExtension: String? val UniFile.displayablePath: String get() = filePath ?: uri.toString() - -fun UniFile.toTempFile(context: Context): File { - val inputStream = context.contentResolver.openInputStream(uri)!! - val tempFile = File.createTempFile( - nameWithoutExtension.orEmpty().padEnd(3), // Prefix must be 3+ chars - null, - ) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - FileUtils.copy(inputStream, tempFile.outputStream()) - } else { - BufferedOutputStream(tempFile.outputStream()).use { tmpOut -> - inputStream.use { input -> - val buffer = ByteArray(8192) - var count: Int - while (input.read(buffer).also { count = it } > 0) { - tmpOut.write(buffer, 0, count) - } - } - } - } - - return tempFile -} diff --git a/core/src/main/java/tachiyomi/core/storage/UniFileTempFileManager.kt b/core/src/main/java/tachiyomi/core/storage/UniFileTempFileManager.kt new file mode 100644 index 0000000000..9384946135 --- /dev/null +++ b/core/src/main/java/tachiyomi/core/storage/UniFileTempFileManager.kt @@ -0,0 +1,44 @@ +package tachiyomi.core.storage + +import android.content.Context +import android.os.Build +import android.os.FileUtils +import com.hippo.unifile.UniFile +import java.io.BufferedOutputStream +import java.io.File + +class UniFileTempFileManager( + private val context: Context, +) { + + private val dir = File(context.externalCacheDir, "tmp").also { it.mkdir() } + + fun createTempFile(file: UniFile): File { + val inputStream = context.contentResolver.openInputStream(file.uri)!! + val tempFile = File.createTempFile( + file.nameWithoutExtension.orEmpty().padEnd(3), // Prefix must be 3+ chars + null, + dir, + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + FileUtils.copy(inputStream, tempFile.outputStream()) + } else { + BufferedOutputStream(tempFile.outputStream()).use { tmpOut -> + inputStream.use { input -> + val buffer = ByteArray(8192) + var count: Int + while (input.read(buffer).also { count = it } > 0) { + tmpOut.write(buffer, 0, count) + } + } + } + } + + return tempFile + } + + fun deleteTempFiles() { + dir.deleteRecursively() + } +} diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index 6177e77468..d92f939007 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -24,9 +24,9 @@ import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.copyFromComicInfo import tachiyomi.core.metadata.comicinfo.getComicInfo import tachiyomi.core.metadata.tachiyomi.MangaDetails +import tachiyomi.core.storage.UniFileTempFileManager import tachiyomi.core.storage.extension import tachiyomi.core.storage.nameWithoutExtension -import tachiyomi.core.storage.toTempFile import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat @@ -56,6 +56,7 @@ actual class LocalSource( private val json: Json by injectLazy() private val xml: XML by injectLazy() + private val tempFileManager: UniFileTempFileManager by injectLazy() private val POPULAR_FILTERS = FilterList(OrderBy.Popular(context)) private val LATEST_FILTERS = FilterList(OrderBy.Latest(context)) @@ -213,7 +214,7 @@ actual class LocalSource( for (chapter in chapterArchives) { when (Format.valueOf(chapter)) { is Format.Zip -> { - ZipFile(chapter.toTempFile(context)).use { zip: ZipFile -> + ZipFile(tempFileManager.createTempFile(chapter)).use { zip: ZipFile -> zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile -> zip.getInputStream(comicInfoFile).buffered().use { stream -> return copyComicInfoFile(stream, folderPath) @@ -222,7 +223,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(chapter.toTempFile(context)).use { rar -> + JunrarArchive(tempFileManager.createTempFile(chapter)).use { rar -> rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> rar.getInputStream(comicInfoFile).buffered().use { stream -> return copyComicInfoFile(stream, folderPath) @@ -272,7 +273,7 @@ actual class LocalSource( val format = Format.valueOf(chapterFile) if (format is Format.Epub) { - EpubFile(format.file.toTempFile(context)).use { epub -> + EpubFile(tempFileManager.createTempFile(format.file)).use { epub -> epub.fillMetadata(manga, this) } } @@ -331,7 +332,7 @@ actual class LocalSource( entry?.let { coverManager.update(manga, it.openInputStream()) } } is Format.Zip -> { - ZipFile(format.file.toTempFile(context)).use { zip -> + ZipFile(tempFileManager.createTempFile(format.file)).use { zip -> val entry = zip.entries().toList() .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } @@ -340,7 +341,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(format.file.toTempFile(context)).use { archive -> + JunrarArchive(tempFileManager.createTempFile(format.file)).use { archive -> val entry = archive.fileHeaders .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } @@ -349,7 +350,7 @@ actual class LocalSource( } } is Format.Epub -> { - EpubFile(format.file.toTempFile(context)).use { epub -> + EpubFile(tempFileManager.createTempFile(format.file)).use { epub -> val entry = epub.getImagesFromPages() .firstOrNull() ?.let { epub.getEntry(it) }