Remove tmp chapter files after exiting reader

This commit is contained in:
arkon 2024-01-04 18:02:40 -05:00
parent 8a7d6a328a
commit 4e221397ce
7 changed files with 76 additions and 45 deletions

View file

@ -27,6 +27,7 @@ import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.storage.AndroidStorageFolderProvider import tachiyomi.core.storage.AndroidStorageFolderProvider
import tachiyomi.core.storage.UniFileTempFileManager
import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.Database import tachiyomi.data.Database
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
@ -111,6 +112,8 @@ class AppModule(val app: Application) : InjektModule {
ProtoBuf ProtoBuf
} }
addSingletonFactory { UniFileTempFileManager(app) }
addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { ChapterCache(app, get()) }
addSingletonFactory { CoverCache(app) } addSingletonFactory { CoverCache(app) }

View file

@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.preference.toggle import tachiyomi.core.preference.toggle
import tachiyomi.core.storage.UniFileTempFileManager
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
@ -85,6 +86,7 @@ class ReaderViewModel @JvmOverloads constructor(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val downloadProvider: DownloadProvider = Injekt.get(), private val downloadProvider: DownloadProvider = Injekt.get(),
private val tempFileManager: UniFileTempFileManager = Injekt.get(),
private val imageSaver: ImageSaver = Injekt.get(), private val imageSaver: ImageSaver = Injekt.get(),
preferences: BasePreferences = Injekt.get(), preferences: BasePreferences = Injekt.get(),
val readerPreferences: ReaderPreferences = Injekt.get(), val readerPreferences: ReaderPreferences = Injekt.get(),
@ -269,7 +271,7 @@ class ReaderViewModel @JvmOverloads constructor(
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(manga.source) 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 }) loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
Result.success(true) Result.success(true)
@ -904,6 +906,7 @@ class ReaderViewModel @JvmOverloads constructor(
private fun deletePendingChapters() { private fun deletePendingChapters() {
viewModelScope.launchNonCancellable { viewModelScope.launchNonCancellable {
downloadManager.deletePendingChapters() downloadManager.deletePendingChapters()
tempFileManager.deleteTempFiles()
} }
} }

View file

@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import tachiyomi.core.i18n.stringResource 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.lang.withIOContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -24,6 +24,7 @@ class ChapterLoader(
private val context: Context, private val context: Context,
private val downloadManager: DownloadManager, private val downloadManager: DownloadManager,
private val downloadProvider: DownloadProvider, private val downloadProvider: DownloadProvider,
private val tempFileManager: UniFileTempFileManager,
private val manga: Manga, private val manga: Manga,
private val source: Source, private val source: Source,
) { ) {
@ -85,17 +86,24 @@ class ChapterLoader(
skipCache = true, skipCache = true,
) )
return when { 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 -> source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
when (format) { when (format) {
is Format.Directory -> DirectoryPageLoader(format.file) 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 { is Format.Rar -> try {
RarPageLoader(format.file.toTempFile(context)) RarPageLoader(tempFileManager.createTempFile(format.file))
} catch (e: UnsupportedRarV5Exception) { } catch (e: UnsupportedRarV5Exception) {
error(context.stringResource(MR.strings.loader_rar5_error)) 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) source is HttpSource -> HttpPageLoader(chapter, source)

View file

@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage 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 tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -23,6 +23,7 @@ internal class DownloadPageLoader(
private val source: Source, private val source: Source,
private val downloadManager: DownloadManager, private val downloadManager: DownloadManager,
private val downloadProvider: DownloadProvider, private val downloadProvider: DownloadProvider,
private val tempFileManager: UniFileTempFileManager,
) : PageLoader() { ) : PageLoader() {
private val context: Application by injectLazy() private val context: Application by injectLazy()
@ -46,8 +47,8 @@ internal class DownloadPageLoader(
zipPageLoader?.recycle() zipPageLoader?.recycle()
} }
private suspend fun getPagesFromArchive(chapterPath: UniFile): List<ReaderPage> { private suspend fun getPagesFromArchive(file: UniFile): List<ReaderPage> {
val loader = ZipPageLoader(chapterPath.toTempFile(context)).also { zipPageLoader = it } val loader = ZipPageLoader(tempFileManager.createTempFile(file)).also { zipPageLoader = it }
return loader.getPages() return loader.getPages()
} }

View file

@ -1,11 +1,6 @@
package tachiyomi.core.storage package tachiyomi.core.storage
import android.content.Context
import android.os.Build
import android.os.FileUtils
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import java.io.BufferedOutputStream
import java.io.File
val UniFile.extension: String? val UniFile.extension: String?
get() = name?.substringAfterLast('.') get() = name?.substringAfterLast('.')
@ -15,27 +10,3 @@ val UniFile.nameWithoutExtension: String?
val UniFile.displayablePath: String val UniFile.displayablePath: String
get() = filePath ?: uri.toString() 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
}

View file

@ -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()
}
}

View file

@ -24,9 +24,9 @@ import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
import tachiyomi.core.metadata.comicinfo.getComicInfo import tachiyomi.core.metadata.comicinfo.getComicInfo
import tachiyomi.core.metadata.tachiyomi.MangaDetails import tachiyomi.core.metadata.tachiyomi.MangaDetails
import tachiyomi.core.storage.UniFileTempFileManager
import tachiyomi.core.storage.extension import tachiyomi.core.storage.extension
import tachiyomi.core.storage.nameWithoutExtension import tachiyomi.core.storage.nameWithoutExtension
import tachiyomi.core.storage.toTempFile
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.ImageUtil
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
@ -56,6 +56,7 @@ actual class LocalSource(
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val xml: XML by injectLazy() private val xml: XML by injectLazy()
private val tempFileManager: UniFileTempFileManager by injectLazy()
private val POPULAR_FILTERS = FilterList(OrderBy.Popular(context)) private val POPULAR_FILTERS = FilterList(OrderBy.Popular(context))
private val LATEST_FILTERS = FilterList(OrderBy.Latest(context)) private val LATEST_FILTERS = FilterList(OrderBy.Latest(context))
@ -213,7 +214,7 @@ actual class LocalSource(
for (chapter in chapterArchives) { for (chapter in chapterArchives) {
when (Format.valueOf(chapter)) { when (Format.valueOf(chapter)) {
is Format.Zip -> { 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.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
zip.getInputStream(comicInfoFile).buffered().use { stream -> zip.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath) return copyComicInfoFile(stream, folderPath)
@ -222,7 +223,7 @@ actual class LocalSource(
} }
} }
is Format.Rar -> { 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.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
rar.getInputStream(comicInfoFile).buffered().use { stream -> rar.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath) return copyComicInfoFile(stream, folderPath)
@ -272,7 +273,7 @@ actual class LocalSource(
val format = Format.valueOf(chapterFile) val format = Format.valueOf(chapterFile)
if (format is Format.Epub) { if (format is Format.Epub) {
EpubFile(format.file.toTempFile(context)).use { epub -> EpubFile(tempFileManager.createTempFile(format.file)).use { epub ->
epub.fillMetadata(manga, this) epub.fillMetadata(manga, this)
} }
} }
@ -331,7 +332,7 @@ actual class LocalSource(
entry?.let { coverManager.update(manga, it.openInputStream()) } entry?.let { coverManager.update(manga, it.openInputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile(format.file.toTempFile(context)).use { zip -> ZipFile(tempFileManager.createTempFile(format.file)).use { zip ->
val entry = zip.entries().toList() val entry = zip.entries().toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
@ -340,7 +341,7 @@ actual class LocalSource(
} }
} }
is Format.Rar -> { is Format.Rar -> {
JunrarArchive(format.file.toTempFile(context)).use { archive -> JunrarArchive(tempFileManager.createTempFile(format.file)).use { archive ->
val entry = archive.fileHeaders val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
@ -349,7 +350,7 @@ actual class LocalSource(
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file.toTempFile(context)).use { epub -> EpubFile(tempFileManager.createTempFile(format.file)).use { epub ->
val entry = epub.getImagesFromPages() val entry = epub.getImagesFromPages()
.firstOrNull() .firstOrNull()
?.let { epub.getEntry(it) } ?.let { epub.getEntry(it) }