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 2b261e511..95fcc9aad 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -30,7 +30,7 @@ import net.zetetic.database.sqlcipher.SupportOpenHelperFactory import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML -import tachiyomi.core.provider.AndroidStorageFolderProvider +import tachiyomi.core.storage.AndroidStorageFolderProvider import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.Database import tachiyomi.data.DatabaseHandler @@ -153,7 +153,7 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { ImageSaver(app) } addSingletonFactory { AndroidStorageFolderProvider(app) } - addSingletonFactory { LocalSourceFileSystem(get()) } + addSingletonFactory { LocalSourceFileSystem(app, get()) } addSingletonFactory { LocalCoverManager(app, get()) } // SY --> diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt index 257ff5dd4..51136226b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.system.isDevFlavor import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.PreferenceStore -import tachiyomi.core.provider.AndroidStorageFolderProvider +import tachiyomi.core.storage.AndroidStorageFolderProvider import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.library.service.LibraryPreferences diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt index 837986b28..2a11f74e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt @@ -1,25 +1,24 @@ package eu.kanade.tachiyomi.ui.reader.loader +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import tachiyomi.core.util.system.ImageUtil -import java.io.File -import java.io.FileInputStream /** * Loader used to load a chapter from a directory given on [file]. */ -internal class DirectoryPageLoader(val file: File) : PageLoader() { +internal class DirectoryPageLoader(val file: UniFile) : PageLoader() { override var isLocal: Boolean = true override suspend fun getPages(): List { return file.listFiles() - ?.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } - ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + ?.filter { !it.isDirectory && ImageUtil.isImage(it.name) { it.openInputStream() } } + ?.sortedWith { f1, f2 -> f1.name.orEmpty().compareToCaseInsensitiveNaturalOrder(f2.name.orEmpty()) } ?.mapIndexed { i, file -> - val streamFn = { FileInputStream(file) } + val streamFn = { file.openInputStream() } ReaderPage(i).apply { stream = streamFn status = Page.State.READY 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 b20c81e24..d01706e3f 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 @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.injectLazy -import java.io.File /** * Loader used to load a chapter from the downloaded chapters. @@ -47,7 +46,7 @@ internal class DownloadPageLoader( } private suspend fun getPagesFromArchive(chapterPath: UniFile): List { - val loader = ZipPageLoader(File(chapterPath.filePath!!)).also { zipPageLoader = it } + val loader = ZipPageLoader(chapterPath).also { zipPageLoader = it } return loader.getPages() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index 324af51bf..cd00e3756 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -1,14 +1,14 @@ package eu.kanade.tachiyomi.ui.reader.loader +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.storage.EpubFile -import java.io.File /** * Loader used to load a chapter from a .epub file. */ -internal class EpubPageLoader(file: File) : PageLoader() { +internal class EpubPageLoader(file: UniFile) : PageLoader() { private val epub = EpubFile(file) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt index 2c8bfc9f9..86c91dc7a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt @@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.ui.reader.loader import android.app.Application import com.github.junrar.Archive import com.github.junrar.rarfile.FileHeader +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.ImageUtil import uy.kohesive.injekt.injectLazy import java.io.File @@ -17,9 +19,9 @@ import java.io.PipedOutputStream /** * Loader used to load a chapter from a .rar or .cbr file. */ -internal class RarPageLoader(file: File) : PageLoader() { +internal class RarPageLoader(file: UniFile) : PageLoader() { - private val rar = Archive(file) + private val rar = Archive(file.toFile()) // SY --> private val context: Application by injectLazy() @@ -31,7 +33,7 @@ internal class RarPageLoader(file: File) : PageLoader() { init { if (readerPreferences.cacheArchiveMangaOnDisk().get()) { tmpDir.mkdirs() - Archive(file).use { rar -> + Archive(file.toFile()).use { rar -> rar.fileHeaders.asSequence() .filterNot { it.isDirectory } .forEach { header -> @@ -52,7 +54,7 @@ internal class RarPageLoader(file: File) : PageLoader() { override suspend fun getPages(): List { // SY --> if (readerPreferences.cacheArchiveMangaOnDisk().get()) { - return DirectoryPageLoader(tmpDir).getPages() + return DirectoryPageLoader(UniFile.fromFile(tmpDir)!!).getPages() } // SY <-- return rar.fileHeaders.asSequence() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt index 4a61c6b13..f9238f453 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt @@ -2,12 +2,14 @@ package eu.kanade.tachiyomi.ui.reader.loader import android.app.Application import android.os.Build +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.CbzCrypto import tachiyomi.core.i18n.stringResource +import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.ImageUtil import tachiyomi.i18n.sy.SYMR import uy.kohesive.injekt.injectLazy @@ -19,7 +21,7 @@ import net.lingala.zip4j.ZipFile as Zip4jFile /** * Loader used to load a chapter from a .zip or .cbz file. */ -internal class ZipPageLoader(file: File) : PageLoader() { +internal class ZipPageLoader(file: UniFile) : PageLoader() { // SY --> private val context: Application by injectLazy() @@ -27,12 +29,12 @@ internal class ZipPageLoader(file: File) : PageLoader() { private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also { it.deleteRecursively() } - private val zip4j: Zip4jFile = Zip4jFile(file) + private val zip4j: Zip4jFile = Zip4jFile(file.toFile()) private val zip: ZipFile? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!zip4j.isEncrypted) ZipFile(file, StandardCharsets.ISO_8859_1) else null + if (!zip4j.isEncrypted) ZipFile(file.toFile(), StandardCharsets.ISO_8859_1) else null } else { - if (!zip4j.isEncrypted) ZipFile(file) else null + if (!zip4j.isEncrypted) ZipFile(file.toFile()) else null } init { @@ -40,7 +42,7 @@ internal class ZipPageLoader(file: File) : PageLoader() { zip4j.charset = StandardCharsets.ISO_8859_1 } - Zip4jFile(file).use { zip -> + Zip4jFile(file.toFile()).use { zip -> if (zip.isEncrypted) { if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) { this.recycle() @@ -79,7 +81,7 @@ internal class ZipPageLoader(file: File) : PageLoader() { override suspend fun getPages(): List { if (readerPreferences.cacheArchiveMangaOnDisk().get()) { - return DirectoryPageLoader(tmpDir).getPages() + return DirectoryPageLoader(UniFile.fromFile(tmpDir)!!).getPages() } if (zip == null) { diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt index a00ee69e7..7650f65b5 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -1,7 +1,9 @@ package eu.kanade.tachiyomi.util.storage +import com.hippo.unifile.UniFile import org.jsoup.Jsoup import org.jsoup.nodes.Document +import tachiyomi.core.storage.toFile import java.io.Closeable import java.io.File import java.io.InputStream @@ -11,12 +13,12 @@ import java.util.zip.ZipFile /** * Wrapper over ZipFile to load files in epub format. */ -class EpubFile(file: File) : Closeable { +class EpubFile(file: UniFile) : Closeable { /** * Zip file of this epub. */ - private val zip = ZipFile(file) + private val zip = ZipFile(file.toFile()) /** * Path separator used by this epub. diff --git a/core/src/main/java/tachiyomi/core/provider/AndroidStorageFolderProvider.kt b/core/src/main/java/tachiyomi/core/storage/AndroidStorageFolderProvider.kt similarity index 94% rename from core/src/main/java/tachiyomi/core/provider/AndroidStorageFolderProvider.kt rename to core/src/main/java/tachiyomi/core/storage/AndroidStorageFolderProvider.kt index fc2859868..a5d48a49d 100644 --- a/core/src/main/java/tachiyomi/core/provider/AndroidStorageFolderProvider.kt +++ b/core/src/main/java/tachiyomi/core/storage/AndroidStorageFolderProvider.kt @@ -1,4 +1,4 @@ -package tachiyomi.core.provider +package tachiyomi.core.storage import android.content.Context import android.os.Environment diff --git a/core/src/main/java/tachiyomi/core/provider/FolderProvider.kt b/core/src/main/java/tachiyomi/core/storage/FolderProvider.kt similarity index 76% rename from core/src/main/java/tachiyomi/core/provider/FolderProvider.kt rename to core/src/main/java/tachiyomi/core/storage/FolderProvider.kt index b4e124cee..decd1c378 100644 --- a/core/src/main/java/tachiyomi/core/provider/FolderProvider.kt +++ b/core/src/main/java/tachiyomi/core/storage/FolderProvider.kt @@ -1,4 +1,4 @@ -package tachiyomi.core.provider +package tachiyomi.core.storage import java.io.File diff --git a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt index 9a869c883..5343dfa3f 100644 --- a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt +++ b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt @@ -1,9 +1,12 @@ package tachiyomi.core.storage import com.hippo.unifile.UniFile +import java.io.File val UniFile.extension: String? get() = name?.substringAfterLast('.') val UniFile.nameWithoutExtension: String? get() = name?.substringBeforeLast('.') + +fun UniFile.toFile(): File? = filePath?.let { File(it) } diff --git a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt index 7b56c7b7f..f43ce636f 100644 --- a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt +++ b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt @@ -43,7 +43,8 @@ import kotlin.math.min object ImageUtil { - fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean { + fun isImage(name: String?, openStream: (() -> InputStream)? = null): Boolean { + if (name == null) return false // SY --> if (File(name).extension.equals("cbi", ignoreCase = true)) return true // SY <-- diff --git a/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt b/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt index a426caaa4..e930ebaa4 100644 --- a/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt @@ -1,7 +1,7 @@ package tachiyomi.domain.storage.service import tachiyomi.core.preference.PreferenceStore -import tachiyomi.core.provider.FolderProvider +import tachiyomi.core.storage.FolderProvider class StoragePreferences( private val folderProvider: FolderProvider, 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 3caf17bfe..6573c0047 100755 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -26,6 +26,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.extension +import tachiyomi.core.storage.nameWithoutExtension +import tachiyomi.core.storage.toFile import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat @@ -41,7 +44,6 @@ import tachiyomi.source.local.metadata.fillChapterMetadata import tachiyomi.source.local.metadata.fillMangaMetadata import uy.kohesive.injekt.injectLazy import java.io.File -import java.io.FileInputStream import java.io.InputStream import java.nio.charset.StandardCharsets import kotlin.time.Duration.Companion.days @@ -96,14 +98,14 @@ actual class LocalSource( .filter { it.isDirectory && /* SY --> */ ( - !it.name.startsWith('.') || + !it.name.orEmpty().startsWith('.') || allowLocalSourceHiddenFolders ) /* SY <-- */ } .distinctBy { it.name } .filter { // Filter by query or last modified if (lastModifiedLimit == 0L) { - it.name.contains(query, ignoreCase = true) + it.name.orEmpty().contains(query, ignoreCase = true) } else { it.lastModified() >= lastModifiedLimit } @@ -113,16 +115,16 @@ actual class LocalSource( when (filter) { is OrderBy.Popular -> { mangaDirs = if (filter.state!!.ascending) { - mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }) + mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name.orEmpty() }) } else { - mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }) + mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name.orEmpty() }) } } is OrderBy.Latest -> { mangaDirs = if (filter.state!!.ascending) { - mangaDirs.sortedBy(File::lastModified) + mangaDirs.sortedBy(UniFile::lastModified) } else { - mangaDirs.sortedByDescending(File::lastModified) + mangaDirs.sortedByDescending(UniFile::lastModified) } } @@ -135,13 +137,13 @@ actual class LocalSource( // Transform mangaDirs to list of SManga val mangas = mangaDirs.map { mangaDir -> SManga.create().apply { - title = mangaDir.name - url = mangaDir.name + title = mangaDir.name.orEmpty() + url = mangaDir.name.orEmpty() // Try to find the cover - coverManager.find(mangaDir.name) - ?.takeIf(File::exists) - ?.let { thumbnail_url = it.absolutePath } + coverManager.find(mangaDir.name.orEmpty()) + ?.takeIf(UniFile::exists) + ?.let { thumbnail_url = it.uri.toString() } } } @@ -170,7 +172,7 @@ actual class LocalSource( // SY --> fun updateMangaInfo(manga: SManga) { - val directory = fileSystem.getFilesInBaseDirectories().map { File(it, manga.url) }.find { + val directory = fileSystem.getFilesInBaseDirectory().map { File(it.toFile(), manga.url) }.find { it.exists() } ?: return val existingFileName = directory.listFiles()?.find { it.extension == "json" }?.name @@ -188,7 +190,7 @@ actual class LocalSource( // Manga details related override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext { coverManager.find(manga.url)?.let { - manga.thumbnail_url = it.absolutePath + manga.thumbnail_url = it.uri.toString() } // Augment manga details based on metadata files @@ -211,11 +213,11 @@ actual class LocalSource( // Top level ComicInfo.xml comicInfoFile != null -> { noXmlFile?.delete() - setMangaDetailsFromComicInfoFile(comicInfoFile.inputStream(), manga) + setMangaDetailsFromComicInfoFile(comicInfoFile.openInputStream(), manga) } // SY --> comicInfoArchiveFile != null -> { - val comicInfoArchive = ZipFile(comicInfoArchiveFile) + val comicInfoArchive = ZipFile(comicInfoArchiveFile.toFile()) noXmlFile?.delete() if (CbzCrypto.checkCbzPassword(comicInfoArchive, CbzCrypto.getDecryptedPasswordCbz())) { @@ -229,7 +231,7 @@ actual class LocalSource( // Old custom JSON format // TODO: remove support for this entirely after a while legacyJsonDetailsFile != null -> { - json.decodeFromStream(legacyJsonDetailsFile.inputStream()).run { + json.decodeFromStream(legacyJsonDetailsFile.openInputStream()).run { title?.let { manga.title = it } author?.let { manga.author = it } artist?.let { manga.artist = it } @@ -239,7 +241,7 @@ actual class LocalSource( } // Replace with ComicInfo.xml file val comicInfo = manga.getComicInfo() - UniFile.fromFile(mangaDir) + mangaDir ?.createFile(COMIC_INFO_FILE) ?.openOutputStream() ?.use { @@ -255,7 +257,7 @@ actual class LocalSource( .filter(Archive::isSupported) .toList() - val folderPath = mangaDir?.absolutePath + val folderPath = mangaDir?.filePath val copiedFile = copyComicInfoFileFromArchive(chapterArchives, folderPath) // SY --> @@ -281,11 +283,11 @@ actual class LocalSource( return@withIOContext manga } - private fun copyComicInfoFileFromArchive(chapterArchives: List, folderPath: String?): File? { + private fun copyComicInfoFileFromArchive(chapterArchives: List, folderPath: String?): File? { for (chapter in chapterArchives) { when (Format.valueOf(chapter)) { is Format.Zip -> { - ZipFile(chapter).use { zip: ZipFile -> + ZipFile(chapter.toFile()).use { zip: ZipFile -> // SY --> if (zip.isEncrypted && !CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz()) ) { @@ -304,7 +306,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(chapter).use { rar -> + JunrarArchive(chapter.toFile()).use { rar -> rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> rar.getInputStream(comicInfoFile).buffered().use { stream -> return copyComicInfoFile(stream, folderPath) @@ -359,9 +361,9 @@ actual class LocalSource( SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" name = if (chapterFile.isDirectory) { - chapterFile.name + chapterFile.name.orEmpty() } else { - chapterFile.nameWithoutExtension + chapterFile.nameWithoutExtension.orEmpty() } date_upload = chapterFile.lastModified() chapter_number = ChapterRecognition @@ -391,8 +393,8 @@ actual class LocalSource( fun getFormat(chapter: SChapter): Format { try { - return File(fileSystem.getBaseDirectory(), chapter.url) - .takeIf { it.exists() } + return fileSystem.getBaseDirectory() + ?.findFile(chapter.url) ?.let(Format.Companion::valueOf) ?: throw Exception(context.stringResource(MR.strings.chapter_not_found)) } catch (e: Format.UnknownFormatException) { @@ -402,18 +404,24 @@ actual class LocalSource( } } - private fun updateCover(chapter: SChapter, manga: SManga): File? { + private fun updateCover(chapter: SChapter, manga: SManga): UniFile? { return try { when (val format = getFormat(chapter)) { is Format.Directory -> { val entry = format.file.listFiles() - ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } + ?.sortedWith { f1, f2 -> + f1.name.orEmpty().compareToCaseInsensitiveNaturalOrder( + f2.name.orEmpty(), + ) + } + ?.find { + !it.isDirectory && ImageUtil.isImage(it.name) { it.openInputStream() } + } - entry?.let { coverManager.update(manga, it.inputStream()) } + entry?.let { coverManager.update(manga, it.openInputStream()) } } is Format.Zip -> { - ZipFile(format.file).use { zip -> + ZipFile(format.file.toFile()).use { zip -> // SY --> var encrypted = false if (zip.isEncrypted) { @@ -428,7 +436,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(format.file).use { archive -> + JunrarArchive(format.file.toFile()).use { archive -> val entry = archive.fileHeaders .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt index 48808aaa0..9208934ba 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.util.storage.CbzCrypto import eu.kanade.tachiyomi.util.storage.DiskUtil import net.lingala.zip4j.ZipFile import net.lingala.zip4j.model.ZipParameters +import tachiyomi.core.storage.nameWithoutExtension +import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.ImageUtil import tachiyomi.source.local.io.LocalSourceFileSystem import java.io.File @@ -20,13 +22,13 @@ actual class LocalCoverManager( private val fileSystem: LocalSourceFileSystem, ) { - actual fun find(mangaUrl: String): File? { + actual fun find(mangaUrl: String): UniFile? { return fileSystem.getFilesInMangaDirectory(mangaUrl) // Get all file whose names start with "cover" .filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) } // Get the first actual image .firstOrNull { - ImageUtil.isImage(it.name) { it.inputStream() } || it.name == COVER_ARCHIVE_NAME + ImageUtil.isImage(it.name) { it.openInputStream() } || it.name == COVER_ARCHIVE_NAME } } @@ -36,7 +38,7 @@ actual class LocalCoverManager( // SY --> encrypted: Boolean, // SY <-- - ): File? { + ): UniFile? { val directory = fileSystem.getMangaDirectory(manga.url) if (directory == null) { inputStream.close() @@ -46,38 +48,38 @@ actual class LocalCoverManager( var targetFile = find(manga.url) if (targetFile == null) { // SY --> - if (encrypted) { - targetFile = File(directory.absolutePath, COVER_ARCHIVE_NAME) + targetFile = if (encrypted) { + directory.createFile(COVER_ARCHIVE_NAME) } else { - targetFile = File(directory.absolutePath, DEFAULT_COVER_NAME) - targetFile.createNewFile() + directory.createFile(DEFAULT_COVER_NAME) } // SY <-- } + targetFile!! + // It might not exist at this point - targetFile.parentFile?.mkdirs() inputStream.use { input -> // SY --> if (encrypted) { - val zip4j = ZipFile(targetFile) + val zip4j = ZipFile(targetFile.toFile()) val zipParameters = ZipParameters() zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz()) CbzCrypto.setZipParametersEncrypted(zipParameters) zipParameters.fileNameInZip = DEFAULT_COVER_NAME zip4j.addStream(input, zipParameters) - DiskUtil.createNoMediaFile(UniFile.fromFile(directory), context) + DiskUtil.createNoMediaFile(directory, context) - manga.thumbnail_url = zip4j.file.absolutePath - return zip4j.file + manga.thumbnail_url = targetFile.uri.toString() + return targetFile } else { // SY <-- - targetFile.outputStream().use { output -> + targetFile.openOutputStream().use { output -> input.copyTo(output) } - DiskUtil.createNoMediaFile(UniFile.fromFile(directory), context) - manga.thumbnail_url = targetFile.absolutePath + DiskUtil.createNoMediaFile(directory, context) + manga.thumbnail_url = targetFile.uri.toString() return targetFile } } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt index 9e53bc5e2..a5ce8a513 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt @@ -1,27 +1,31 @@ package tachiyomi.source.local.io -import tachiyomi.core.provider.FolderProvider -import java.io.File +import android.content.Context +import androidx.core.net.toUri +import com.hippo.unifile.UniFile +import tachiyomi.core.storage.FolderProvider actual class LocalSourceFileSystem( + private val context: Context, private val folderProvider: FolderProvider, ) { - actual fun getBaseDirectory(): File { - return File(folderProvider.directory(), "local") + actual fun getBaseDirectory(): UniFile? { + return UniFile.fromUri(context, folderProvider.path().toUri()) + ?.createDirectory("local") } - actual fun getFilesInBaseDirectory(): List { - return getBaseDirectory().listFiles().orEmpty().toList() + actual fun getFilesInBaseDirectory(): List { + return getBaseDirectory()?.listFiles().orEmpty().toList() } - actual fun getMangaDirectory(name: String): File? { + actual fun getMangaDirectory(name: String): UniFile? { return getFilesInBaseDirectory() // Get the first mangaDir or null .firstOrNull { it.isDirectory && it.name == name } } - actual fun getFilesInMangaDirectory(name: String): List { + actual fun getFilesInMangaDirectory(name: String): List { return getFilesInBaseDirectory() // Filter out ones that are not related to the manga and is not a directory .filter { it.isDirectory && it.name == name } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt index a9c3d5a59..71467660e 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/image/LocalCoverManager.kt @@ -1,14 +1,14 @@ package tachiyomi.source.local.image +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.source.model.SManga -import java.io.File import java.io.InputStream expect class LocalCoverManager { - fun find(mangaUrl: String): File? + fun find(mangaUrl: String): UniFile? // SY --> - fun update(manga: SManga, inputStream: InputStream, encrypted: Boolean = false): File? + fun update(manga: SManga, inputStream: InputStream, encrypted: Boolean = false): UniFile? // SY <-- } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt index b28ee60b5..a8f5a0740 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt @@ -1,12 +1,13 @@ package tachiyomi.source.local.io -import java.io.File +import com.hippo.unifile.UniFile +import tachiyomi.core.storage.extension object Archive { private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub") - fun isSupported(file: File): Boolean = with(file) { - return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES + fun isSupported(file: UniFile): Boolean { + return file.extension in SUPPORTED_ARCHIVE_TYPES } } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt index 53406b5de..0f29ae8ab 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt @@ -1,18 +1,19 @@ package tachiyomi.source.local.io -import java.io.File +import com.hippo.unifile.UniFile +import tachiyomi.core.storage.extension sealed interface Format { - data class Directory(val file: File) : Format - data class Zip(val file: File) : Format - data class Rar(val file: File) : Format - data class Epub(val file: File) : Format + data class Directory(val file: UniFile) : Format + data class Zip(val file: UniFile) : Format + data class Rar(val file: UniFile) : Format + data class Epub(val file: UniFile) : Format class UnknownFormatException : Exception() companion object { - fun valueOf(file: File) = with(file) { + fun valueOf(file: UniFile) = with(file) { when { isDirectory -> Directory(this) extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this) diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt index f7e46c415..5aa74d851 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/LocalSourceFileSystem.kt @@ -1,14 +1,14 @@ package tachiyomi.source.local.io -import java.io.File +import com.hippo.unifile.UniFile expect class LocalSourceFileSystem { - fun getBaseDirectory(): File + fun getBaseDirectory(): UniFile? - fun getFilesInBaseDirectory(): List + fun getFilesInBaseDirectory(): List - fun getMangaDirectory(name: String): File? + fun getMangaDirectory(name: String): UniFile? - fun getFilesInMangaDirectory(name: String): List + fun getFilesInMangaDirectory(name: String): List }