From ab63f6036c152b1c5cadb9455ae65fb5cf72ab67 Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 28 Nov 2023 08:59:45 -0500 Subject: [PATCH] Remove storage permissions Requires adjusting some file reading to first copy to a temporary file in cache that we have permissions to read from. This is only applicable for things like ZIP files where we need an actual File rather than just some Android content URI shenanigans. (cherry picked from commit 4fcdde4913df28bbd678ae1be4a2971ed77179d3) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt # source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt --- app/build.gradle.kts | 1 - app/src/main/AndroidManifest.xml | 4 - .../settings/screen/SettingsDataScreen.kt | 3 - .../permissions/PermissionRequestHelper.kt | 20 ---- app/src/main/java/eu/kanade/tachiyomi/App.kt | 6 -- .../kanade/tachiyomi/ui/browse/BrowseTab.kt | 4 - .../ui/reader/loader/ChapterLoader.kt | 13 +-- .../ui/reader/loader/DownloadPageLoader.kt | 3 +- .../ui/reader/loader/EpubPageLoader.kt | 4 +- .../ui/reader/loader/RarPageLoader.kt | 7 +- .../ui/reader/loader/ZipPageLoader.kt | 11 +-- .../main/java/exh/log/EnhancedFilePrinter.kt | 98 +++++-------------- .../kanade/tachiyomi/util/storage/EpubFile.kt | 6 +- .../core/storage/UniFileExtensions.kt | 28 +++++- gradle/compose.versions.toml | 1 - .../tachiyomi/source/local/LocalSource.kt | 26 ++--- .../source/local/image/LocalCoverManager.kt | 13 ++- 17 files changed, 97 insertions(+), 151 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/presentation/permissions/PermissionRequestHelper.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 12effa462..3d12eaea6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -165,7 +165,6 @@ dependencies { implementation(compose.ui.tooling.preview) implementation(compose.ui.util) implementation(compose.accompanist.webview) - implementation(compose.accompanist.permissions) implementation(compose.accompanist.systemuicontroller) lintChecks(compose.lintchecks) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f75531139..ccb3b15d0 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,9 +7,6 @@ - - - @@ -40,7 +37,6 @@ android:largeHeap="true" android:localeConfig="@xml/locales_config" android:networkSecurityConfig="@xml/network_security_config" - android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Tachiyomi"> diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 0d86cabbe..4ffd15d77 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -35,7 +35,6 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding -import eu.kanade.presentation.permissions.PermissionRequestHelper import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.backup.BackupFileValidator @@ -73,8 +72,6 @@ object SettingsDataScreen : SearchableSettings { val backupPreferences = Injekt.get() val storagePreferences = Injekt.get() - PermissionRequestHelper.requestStoragePermission() - return listOf( getStorageLocationPref(storagePreferences = storagePreferences), Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)), diff --git a/app/src/main/java/eu/kanade/presentation/permissions/PermissionRequestHelper.kt b/app/src/main/java/eu/kanade/presentation/permissions/PermissionRequestHelper.kt deleted file mode 100644 index 7ce28f9da..000000000 --- a/app/src/main/java/eu/kanade/presentation/permissions/PermissionRequestHelper.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.presentation.permissions - -import android.Manifest -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import com.google.accompanist.permissions.rememberPermissionState - -/** - * Launches request for [Manifest.permission.WRITE_EXTERNAL_STORAGE] permission - */ -object PermissionRequestHelper { - - @Composable - fun requestStoragePermission() { - val permissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) - LaunchedEffect(Unit) { - permissionState.launchPermissionRequest() - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 8f103c47f..374839eea 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -27,7 +27,6 @@ import com.elvishew.xlog.XLog import com.elvishew.xlog.printer.AndroidPrinter import com.elvishew.xlog.printer.Printer import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy -import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator import eu.kanade.domain.DomainModule import eu.kanade.domain.SYDomainModule @@ -67,7 +66,6 @@ import logcat.LogPriority import logcat.LogcatLogger import org.conscrypt.Conscrypt import tachiyomi.core.i18n.stringResource -import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.logcat import tachiyomi.domain.storage.service.StorageManager import tachiyomi.i18n.MR @@ -78,7 +76,6 @@ import uy.kohesive.injekt.injectLazy import java.security.Security import java.text.SimpleDateFormat import java.util.Locale -import kotlin.time.Duration.Companion.days class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { @@ -250,8 +247,6 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { val printers = mutableListOf(AndroidPrinter()) val logFolder = Injekt.get().getLogsDirectory() - ?.toFile() - ?.absolutePath if (logFolder != null) { val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) @@ -269,7 +264,6 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { flattener { timeMillis, level, tag, message -> "${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message" } - cleanStrategy = FileLastModifiedCleanStrategy(7.days.inWholeMilliseconds) backupStrategy = NeverBackupStrategy() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt index 7624cfa0b..ec9daa07c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt @@ -17,7 +17,6 @@ import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.core.preference.asState import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.TabbedScreen -import eu.kanade.presentation.permissions.PermissionRequestHelper import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel @@ -91,9 +90,6 @@ data class BrowseTab( onChangeSearchQuery = extensionsScreenModel::search, ) - // For local source - PermissionRequestHelper.requestStoragePermission() - LaunchedEffect(Unit) { (context as? MainActivity)?.ready = true } 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 570cd3720..d3364677f 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 @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import tachiyomi.core.i18n.stringResource +import tachiyomi.core.storage.toTempFile import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.model.Manga @@ -124,13 +125,13 @@ class ChapterLoader( source is LocalSource -> source.getFormat(chapter.chapter).let { format -> when (format) { is Format.Directory -> DirectoryPageLoader(format.file) - is Format.Zip -> ZipPageLoader(format.file) + is Format.Zip -> ZipPageLoader(format.file.toTempFile(context)) is Format.Rar -> try { - RarPageLoader(format.file) + RarPageLoader(format.file.toTempFile(context)) } catch (e: UnsupportedRarV5Exception) { error(context.stringResource(MR.strings.loader_rar5_error)) } - is Format.Epub -> EpubPageLoader(format.file) + is Format.Epub -> EpubPageLoader(format.file.toTempFile(context)) } } else -> error(context.stringResource(MR.strings.loader_not_implemented_error)) @@ -141,13 +142,13 @@ class ChapterLoader( source is LocalSource -> source.getFormat(chapter.chapter).let { format -> when (format) { is Format.Directory -> DirectoryPageLoader(format.file) - is Format.Zip -> ZipPageLoader(format.file) + is Format.Zip -> ZipPageLoader(format.file.toTempFile(context)) is Format.Rar -> try { - RarPageLoader(format.file) + RarPageLoader(format.file.toTempFile(context)) } catch (e: UnsupportedRarV5Exception) { error(context.stringResource(MR.strings.loader_rar5_error)) } - is Format.Epub -> EpubPageLoader(format.file) + is Format.Epub -> EpubPageLoader(format.file.toTempFile(context)) } } 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 d01706e3f..d2fe3de04 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,6 +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.domain.manga.model.Manga import uy.kohesive.injekt.injectLazy @@ -46,7 +47,7 @@ internal class DownloadPageLoader( } private suspend fun getPagesFromArchive(chapterPath: UniFile): List { - val loader = ZipPageLoader(chapterPath).also { zipPageLoader = it } + val loader = ZipPageLoader(chapterPath.toTempFile(context)).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 cd00e3756..324af51bf 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: UniFile) : PageLoader() { +internal class EpubPageLoader(file: File) : 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 86c91dc7a..1d8598fe9 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 @@ -8,7 +8,6 @@ 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 @@ -19,9 +18,9 @@ import java.io.PipedOutputStream /** * Loader used to load a chapter from a .rar or .cbr file. */ -internal class RarPageLoader(file: UniFile) : PageLoader() { +internal class RarPageLoader(file: File) : PageLoader() { - private val rar = Archive(file.toFile()) + private val rar = Archive(file) // SY --> private val context: Application by injectLazy() @@ -33,7 +32,7 @@ internal class RarPageLoader(file: UniFile) : PageLoader() { init { if (readerPreferences.cacheArchiveMangaOnDisk().get()) { tmpDir.mkdirs() - Archive(file.toFile()).use { rar -> + Archive(file).use { rar -> rar.fileHeaders.asSequence() .filterNot { it.isDirectory } .forEach { header -> 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 f9238f453..81c4d2509 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 @@ -9,7 +9,6 @@ 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 @@ -21,7 +20,7 @@ import net.lingala.zip4j.ZipFile as Zip4jFile /** * Loader used to load a chapter from a .zip or .cbz file. */ -internal class ZipPageLoader(file: UniFile) : PageLoader() { +internal class ZipPageLoader(file: File) : PageLoader() { // SY --> private val context: Application by injectLazy() @@ -29,12 +28,12 @@ internal class ZipPageLoader(file: UniFile) : PageLoader() { private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also { it.deleteRecursively() } - private val zip4j: Zip4jFile = Zip4jFile(file.toFile()) + private val zip4j: Zip4jFile = Zip4jFile(file) private val zip: ZipFile? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!zip4j.isEncrypted) ZipFile(file.toFile(), StandardCharsets.ISO_8859_1) else null + if (!zip4j.isEncrypted) ZipFile(file, StandardCharsets.ISO_8859_1) else null } else { - if (!zip4j.isEncrypted) ZipFile(file.toFile()) else null + if (!zip4j.isEncrypted) ZipFile(file) else null } init { @@ -42,7 +41,7 @@ internal class ZipPageLoader(file: UniFile) : PageLoader() { zip4j.charset = StandardCharsets.ISO_8859_1 } - Zip4jFile(file.toFile()).use { zip -> + Zip4jFile(file).use { zip -> if (zip.isEncrypted) { if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) { this.recycle() diff --git a/app/src/main/java/exh/log/EnhancedFilePrinter.kt b/app/src/main/java/exh/log/EnhancedFilePrinter.kt index e3269c2aa..0bd6fee20 100644 --- a/app/src/main/java/exh/log/EnhancedFilePrinter.kt +++ b/app/src/main/java/exh/log/EnhancedFilePrinter.kt @@ -3,14 +3,14 @@ package exh.log import com.elvishew.xlog.internal.DefaultsFactory import com.elvishew.xlog.printer.Printer import com.elvishew.xlog.printer.file.backup.BackupStrategy -import com.elvishew.xlog.printer.file.clean.CleanStrategy import com.elvishew.xlog.printer.file.naming.FileNameGenerator +import com.hippo.unifile.UniFile +import exh.log.EnhancedFilePrinter.Builder import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter import java.io.IOException import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingQueue +import kotlin.time.Duration.Companion.days import com.elvishew.xlog.flattener.Flattener2 as Flattener /** @@ -18,7 +18,7 @@ import com.elvishew.xlog.flattener.Flattener2 as Flattener * * Use the [Builder] to construct a [EnhancedFilePrinter] object. * - * @param folderPath The folder path of log file. + * @param folder The folder path of log file. * @param fileNameGenerator the file name generator for log file. * @param backupStrategy the backup strategy for log file. * @param cleanStrategy The clean strategy for log file. @@ -27,10 +27,9 @@ import com.elvishew.xlog.flattener.Flattener2 as Flattener */ @Suppress("unused") class EnhancedFilePrinter internal constructor( - private val folderPath: String, + private val folder: UniFile, private val fileNameGenerator: FileNameGenerator, private val backupStrategy: BackupStrategy, - private val cleanStrategy: CleanStrategy, private val flattener: Flattener, ) : Printer { /** @@ -41,16 +40,6 @@ class EnhancedFilePrinter internal constructor( @Volatile private var worker: Worker? = null - /** - * Make sure the folder of log file exists. - */ - private fun checkLogFolder() { - val folder = File(folderPath) - if (!folder.exists()) { - folder.mkdirs() - } - } - override fun println(logLevel: Int, tag: String, msg: String) { val timeMillis = System.currentTimeMillis() if (USE_WORKER) { @@ -68,8 +57,8 @@ class EnhancedFilePrinter internal constructor( * Do the real job of writing log to file. */ private fun doPrintln(timeMillis: Long, logLevel: Int, tag: String, msg: String) { - var lastFileName = writer.lastFileName - if (lastFileName == null || fileNameGenerator.isFileNameChangeable) { + val lastFileName = writer.lastFileName + if (fileNameGenerator.isFileNameChangeable) { val newFileName = fileNameGenerator.generateFileName(logLevel, System.currentTimeMillis()) require(!(newFileName == null || newFileName.trim { it <= ' ' }.isEmpty())) { "File name should not be empty." } if (newFileName != lastFileName) { @@ -77,37 +66,29 @@ class EnhancedFilePrinter internal constructor( writer.close() } cleanLogFilesIfNecessary() - if (writer.open(newFileName).not()) { + if (writer.open(folder.createFile(newFileName)!!).not()) { return } - lastFileName = newFileName - } - } - val lastFile = writer.file ?: return - if (backupStrategy.shouldBackup(lastFile)) { - // Backup the log file, and create a new log file. - writer.close() - val backupFile = File(folderPath, "$lastFileName.bak") - if (backupFile.exists()) { - backupFile.delete() - } - lastFile.renameTo(backupFile) - if (writer.open(lastFileName).not()) { - return } } val flattenedLog = flattener.flatten(timeMillis, logLevel, tag, msg).toString() writer.appendLog(flattenedLog) } + private val maxTimeMillis = 7.days.inWholeMilliseconds + private fun shouldClean(file: UniFile): Boolean { + val currentTimeMillis = System.currentTimeMillis() + val lastModified = file.lastModified() + return currentTimeMillis - lastModified > maxTimeMillis + } + /** * Clean log files if should clean follow strategy */ private fun cleanLogFilesIfNecessary() { - val logDir = File(folderPath) - logDir.listFiles().orEmpty() + folder.listFiles().orEmpty() .asSequence() - .filter { cleanStrategy.shouldClean(it) } + .filter { shouldClean(it) } .forEach { it.delete() } } @@ -115,7 +96,7 @@ class EnhancedFilePrinter internal constructor( * Builder for [EnhancedFilePrinter]. * @param folderPath the folder path of log file */ - class Builder(private val folderPath: String) { + class Builder(private val folder: UniFile) { /** * The file name generator for log file. */ @@ -126,11 +107,6 @@ class EnhancedFilePrinter internal constructor( */ var backupStrategy: BackupStrategy? = null - /** - * The clean strategy for log file. - */ - var cleanStrategy: CleanStrategy? = null - /** * The flattener when print a log. */ @@ -158,17 +134,6 @@ class EnhancedFilePrinter internal constructor( return this } - /** - * Set the clean strategy for log file. - * - * @param cleanStrategy the clean strategy for log file - * @return the builder - */ - fun cleanStrategy(cleanStrategy: CleanStrategy): Builder { - this.cleanStrategy = cleanStrategy - return this - } - /** * Set the flattener when print a log. * @@ -187,17 +152,16 @@ class EnhancedFilePrinter internal constructor( */ fun build(): EnhancedFilePrinter { return EnhancedFilePrinter( - folderPath, + folder, fileNameGenerator ?: DefaultsFactory.createFileNameGenerator(), backupStrategy ?: DefaultsFactory.createBackupStrategy(), - cleanStrategy ?: DefaultsFactory.createCleanStrategy(), flattener ?: DefaultsFactory.createFlattener2(), ) } companion object { - operator fun invoke(folderPath: String, block: Builder.() -> Unit): EnhancedFilePrinter { - return Builder(folderPath).apply(block).build() + operator fun invoke(folder: UniFile, block: Builder.() -> Unit): EnhancedFilePrinter { + return Builder(folder).apply(block).build() } } } @@ -271,9 +235,6 @@ class EnhancedFilePrinter internal constructor( * Get the name of last used log file. * @return the name of last used log file, maybe null */ - /** - * The file name of last used log file. - */ var lastFileName: String? = null private set /** @@ -281,10 +242,7 @@ class EnhancedFilePrinter internal constructor( * * @return the current log file, maybe null */ - /** - * The current log file. - */ - var file: File? = null + var file: UniFile? = null private set private var bufferedWriter: BufferedWriter? = null @@ -303,15 +261,10 @@ class EnhancedFilePrinter internal constructor( * @param newFileName the specific file name * @return true if opened successfully, false otherwise */ - fun open(newFileName: String): Boolean { + fun open(file: UniFile): Boolean { return try { - val file = File(folderPath, newFileName) - if (file.exists().not()) { - (file.parentFile ?: File(file.absolutePath.substringBeforeLast(File.separatorChar))).mkdirs() - file.createNewFile() - } - bufferedWriter = FileWriter(file, true).buffered() - lastFileName = newFileName + bufferedWriter = file.openOutputStream().bufferedWriter() + lastFileName = file.name this.file = file true } catch (e: Exception) { @@ -370,6 +323,5 @@ class EnhancedFilePrinter internal constructor( if (USE_WORKER) { worker = Worker() } - checkLogFolder() } } 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 7650f65b5..a00ee69e7 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,9 +1,7 @@ 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 @@ -13,12 +11,12 @@ import java.util.zip.ZipFile /** * Wrapper over ZipFile to load files in epub format. */ -class EpubFile(file: UniFile) : Closeable { +class EpubFile(file: File) : Closeable { /** * Zip file of this epub. */ - private val zip = ZipFile(file.toFile()) + private val zip = ZipFile(file) /** * Path separator used by this epub. diff --git a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt index 5343dfa3f..c5c2bbbc8 100644 --- a/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt +++ b/core/src/main/java/tachiyomi/core/storage/UniFileExtensions.kt @@ -1,6 +1,10 @@ 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? @@ -9,4 +13,26 @@ val UniFile.extension: String? val UniFile.nameWithoutExtension: String? get() = name?.substringBeforeLast('.') -fun UniFile.toFile(): File? = filePath?.let { File(it) } +fun UniFile.toTempFile(context: Context): File { + val inputStream = context.contentResolver.openInputStream(uri)!! + val tempFile = File.createTempFile( + nameWithoutExtension.orEmpty(), + 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/gradle/compose.versions.toml b/gradle/compose.versions.toml index 39074cb0c..ce7b074bf 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -22,7 +22,6 @@ material-core = { module = "androidx.compose.material:material" } glance = "androidx.glance:glance-appwidget:1.0.0" accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" } -accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" } lintchecks = { module = "com.slack.lint.compose:compose-lint-checks", version = "1.2.0" } \ No newline at end of file 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 911ac4d93..f02760355 100755 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -30,7 +30,7 @@ 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.storage.toTempFile import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat @@ -154,12 +154,12 @@ actual class LocalSource( // SY --> fun updateMangaInfo(manga: SManga) { - val directory = fileSystem.getFilesInBaseDirectory().map { File(it.toFile(), manga.url) }.find { - it.exists() + val directory = fileSystem.getFilesInBaseDirectory().map { it.createDirectory(manga.url) }.find { + it?.exists() == true } ?: return - val existingFileName = directory.listFiles()?.find { it.extension == "json" }?.name - val file = File(directory, existingFileName ?: "info.json") - file.outputStream().use { + val existingFile = directory.listFiles()?.find { it.extension == "json" } + val file = existingFile ?: directory.createFile("info.json") ?: return + file.openOutputStream().use { json.encodeToStream(manga.toJson(), it) } } @@ -199,7 +199,7 @@ actual class LocalSource( } // SY --> comicInfoArchiveFile != null -> { - val comicInfoArchive = ZipFile(comicInfoArchiveFile.toFile()) + val comicInfoArchive = ZipFile(comicInfoArchiveFile.toTempFile(context)) noXmlFile?.delete() if (CbzCrypto.checkCbzPassword(comicInfoArchive, CbzCrypto.getDecryptedPasswordCbz())) { @@ -269,7 +269,7 @@ actual class LocalSource( for (chapter in chapterArchives) { when (Format.valueOf(chapter)) { is Format.Zip -> { - ZipFile(chapter.toFile()).use { zip: ZipFile -> + ZipFile(chapter.toTempFile(context)).use { zip: ZipFile -> // SY --> if (zip.isEncrypted && !CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz()) ) { @@ -288,7 +288,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(chapter.toFile()).use { rar -> + JunrarArchive(chapter.toTempFile(context)).use { rar -> rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> rar.getInputStream(comicInfoFile).buffered().use { stream -> return copyComicInfoFile(stream, folderPath) @@ -354,7 +354,7 @@ actual class LocalSource( val format = Format.valueOf(chapterFile) if (format is Format.Epub) { - EpubFile(format.file).use { epub -> + EpubFile(format.file.toTempFile(context)).use { epub -> epub.fillMetadata(manga, this) } } @@ -413,7 +413,7 @@ actual class LocalSource( entry?.let { coverManager.update(manga, it.openInputStream()) } } is Format.Zip -> { - ZipFile(format.file.toFile()).use { zip -> + ZipFile(format.file.toTempFile(context)).use { zip -> // SY --> var encrypted = false if (zip.isEncrypted) { @@ -428,7 +428,7 @@ actual class LocalSource( } } is Format.Rar -> { - JunrarArchive(format.file.toFile()).use { archive -> + JunrarArchive(format.file.toTempFile(context)).use { archive -> val entry = archive.fileHeaders .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } @@ -437,7 +437,7 @@ actual class LocalSource( } } is Format.Epub -> { - EpubFile(format.file).use { epub -> + EpubFile(format.file.toTempFile(context)).use { epub -> val entry = epub.getImagesFromPages() .firstOrNull() ?.let { epub.getEntry(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 3e81855a5..c78e28815 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 @@ -8,7 +8,6 @@ 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 @@ -61,12 +60,22 @@ actual class LocalCoverManager( inputStream.use { input -> // SY --> if (encrypted) { - val zip4j = ZipFile(targetFile.toFile()) + val tempFile = File.createTempFile( + targetFile.nameWithoutExtension.orEmpty(), + null, + ) + val zip4j = ZipFile(tempFile) val zipParameters = ZipParameters() zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz()) CbzCrypto.setZipParametersEncrypted(zipParameters) zipParameters.fileNameInZip = DEFAULT_COVER_NAME zip4j.addStream(input, zipParameters) + zip4j.close() + targetFile.openOutputStream().use { output -> + tempFile.inputStream().use { input -> + input.copyTo(output) + } + } DiskUtil.createNoMediaFile(directory, context)