From 4fcdde4913df28bbd678ae1be4a2971ed77179d3 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. --- app/build.gradle.kts | 1 - app/src/main/AndroidManifest.xml | 4 --- .../settings/screen/SettingsDataScreen.kt | 3 -- .../permissions/PermissionRequestHelper.kt | 20 ------------- .../kanade/tachiyomi/ui/browse/BrowseTab.kt | 4 --- .../ui/reader/loader/ChapterLoader.kt | 7 +++-- .../ui/reader/loader/DownloadPageLoader.kt | 3 +- .../ui/reader/loader/EpubPageLoader.kt | 4 +-- .../ui/reader/loader/RarPageLoader.kt | 7 ++--- .../ui/reader/loader/ZipPageLoader.kt | 9 +++--- .../kanade/tachiyomi/util/storage/EpubFile.kt | 6 ++-- .../core/storage/UniFileExtensions.kt | 28 ++++++++++++++++++- gradle/compose.versions.toml | 1 - .../tachiyomi/source/local/LocalSource.kt | 14 +++++----- 14 files changed, 51 insertions(+), 60 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 4bba73d1de..0f0917a051 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -164,7 +164,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 442a123f38..5c903960f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,9 +7,6 @@ - - - @@ -39,7 +36,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 26f0b9b048..b1bd8879c3 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 @@ -71,8 +70,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 7ce28f9dac..0000000000 --- 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/ui/browse/BrowseTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt index a05cba6202..2ce4685bb2 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 @@ -13,7 +13,6 @@ import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions 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 @@ -66,9 +65,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 4989b4704e..6a31ed0292 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,6 +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.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.model.Manga @@ -88,13 +89,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 4fb5adb6c4..3d385551d5 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 cd00e37561..324af51bf3 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 319179117d..056319d4e6 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 @@ -2,12 +2,11 @@ package eu.kanade.tachiyomi.ui.reader.loader 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.util.lang.compareToCaseInsensitiveNaturalOrder -import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.ImageUtil +import java.io.File import java.io.InputStream import java.io.PipedInputStream import java.io.PipedOutputStream @@ -15,9 +14,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) override var isLocal: Boolean = true 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 b63b325578..e04fe78e6a 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 @@ -1,24 +1,23 @@ package eu.kanade.tachiyomi.ui.reader.loader 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.util.lang.compareToCaseInsensitiveNaturalOrder -import tachiyomi.core.storage.toFile import tachiyomi.core.util.system.ImageUtil +import java.io.File import java.nio.charset.StandardCharsets import java.util.zip.ZipFile /** * Loader used to load a chapter from a .zip or .cbz file. */ -internal class ZipPageLoader(file: UniFile) : PageLoader() { +internal class ZipPageLoader(file: File) : PageLoader() { private val zip = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - ZipFile(file.toFile(), StandardCharsets.ISO_8859_1) + ZipFile(file, StandardCharsets.ISO_8859_1) } else { - ZipFile(file.toFile()) + ZipFile(file) } override var isLocal: Boolean = true 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 7650f65b5e..a00ee69e75 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 5343dfa3f8..c5c2bbbc86 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 39074cb0c1..ce7b074bf1 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 b2fa6731ca..1305001f50 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -26,7 +26,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 @@ -213,7 +213,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 -> zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile -> zip.getInputStream(comicInfoFile).buffered().use { stream -> return copyComicInfoFile(stream, folderPath) @@ -222,7 +222,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) @@ -272,7 +272,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) } } @@ -331,7 +331,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 -> 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 +340,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) } } @@ -349,7 +349,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) }