From de5d74a1f005d8eded098f02ccaae3cce1c91f1c Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:25:31 +0800 Subject: [PATCH] Remove more java.io usage --- .../com/hippo/ehviewer/spider/ComicInfo.kt | 21 ++++----- .../com/hippo/ehviewer/spider/SpiderDen.kt | 6 +-- .../com/hippo/ehviewer/spider/SpiderInfo.kt | 8 ++-- .../com/hippo/ehviewer/ui/CommonOperations.kt | 5 ++- .../com/hippo/ehviewer/updater/AppUpdater.kt | 7 ++- .../kotlin/com/hippo/ehviewer/util/FDUtils.kt | 33 +------------- .../com/hippo/ehviewer/util/FileUtils.kt | 14 ++++-- .../com/hippo/files/AndroidFileSystem.kt | 43 ++++++++++++++++--- app/src/main/kotlin/com/hippo/files/Paths.kt | 11 ++--- gradle/libs.versions.toml | 5 ++- settings.gradle.kts | 1 + 11 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt index 73272d9ba..3e56e8be8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt @@ -16,8 +16,9 @@ import com.hippo.ehviewer.client.data.TagNamespace.Male import com.hippo.ehviewer.client.data.TagNamespace.Mixed import com.hippo.ehviewer.client.data.TagNamespace.Other import com.hippo.ehviewer.client.data.TagNamespace.Parody -import com.hippo.files.inputStream -import com.hippo.files.outputStream +import com.hippo.ehviewer.ui.screen.implicit +import com.hippo.files.read +import com.hippo.files.write import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -25,12 +26,12 @@ import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import net.devrieze.xmlutil.serialization.kxio.decodeFromSource +import net.devrieze.xmlutil.serialization.kxio.encodeToSink import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion -import nl.adaptivity.xmlutil.newWriter import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XmlElement -import nl.adaptivity.xmlutil.xmlStreaming import okio.Path const val COMIC_INFO_FILE = "ComicInfo.xml" @@ -107,18 +108,14 @@ fun ComicInfo.toSimpleTags() = listOfNotNull( ).flatten().ifEmpty { null } fun ComicInfo.write(file: Path) { - file.outputStream().bufferedWriter().use { - xmlStreaming.newWriter(it).use { writer -> - xml.encodeToWriter(writer, ComicInfo.serializer(), this) - } + file.write { + xml.encodeToSink(this, implicit()) } } fun readComicInfo(file: Path): ComicInfo? = runCatching { - file.inputStream().bufferedReader().use { - xmlStreaming.newReader(it).use { reader -> - xml.decodeFromReader(ComicInfo.serializer(), reader) - } + file.read { + xml.decodeFromSource(this) } }.getOrNull() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt index 80e71dcd6..11a0e716f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -39,6 +39,7 @@ import com.hippo.ehviewer.download.tempDownloadDir import com.hippo.ehviewer.image.PathSource import com.hippo.ehviewer.jni.archiveFdBatch import com.hippo.ehviewer.util.FileUtils +import com.hippo.ehviewer.util.copyTo import com.hippo.ehviewer.util.sendTo import com.hippo.ehviewer.util.sha1 import com.hippo.files.delete @@ -49,7 +50,6 @@ import com.hippo.files.list import com.hippo.files.mkdirs import com.hippo.files.moveTo import com.hippo.files.openFileDescriptor -import com.hippo.files.outputStream import eu.kanade.tachiyomi.util.system.logcat import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.timeout @@ -221,9 +221,7 @@ class SpiderDen(val info: GalleryInfo) { val url = response.request.url.toString() val extension = MimeTypeMap.getFileExtensionFromUrl(url).ifEmpty { "jpg" } return saveResponseMeta(index, extension) { outFile -> - outFile.outputStream().use { - response.bodyAsChannel().copyTo(it.channel) - } + response.bodyAsChannel().copyTo(outFile) FileHashRegex.find(url)?.let { val expected = it.groupValues[1] val actual = outFile.sha1() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderInfo.kt index 53a5fb883..23d88f7eb 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderInfo.kt @@ -4,8 +4,8 @@ import com.hippo.ehviewer.coil.edit import com.hippo.ehviewer.coil.read import com.hippo.ehviewer.ktbuilder.diskCache import com.hippo.ehviewer.legacy.readLegacySpiderInfo -import com.hippo.files.outputStream import com.hippo.files.read +import com.hippo.files.write import eu.kanade.tachiyomi.util.system.logcat import kotlinx.io.readByteArray import kotlinx.serialization.Serializable @@ -37,15 +37,13 @@ private val cbor = Cbor { } fun SpiderInfo.write(file: Path) { - file.outputStream().use { - it.write(cbor.encodeToByteArray(this)) - } + file.write { write(cbor.encodeToByteArray(this)) } } fun SpiderInfo.saveToCache() { runSuspendCatching { spiderInfoCache.edit(gid.toString()) { - data.toFile().writeBytes(cbor.encodeToByteArray(this@saveToCache)) + data.write { write(cbor.encodeToByteArray(this@saveToCache)) } } }.onFailure { logcat(it) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt index 92d9b8455..2ff80ae3c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt @@ -67,8 +67,9 @@ import com.hippo.ehviewer.util.requestPermission import com.hippo.ehviewer.util.toEpochMillis import com.hippo.ehviewer.util.toLocalDateTime import com.hippo.files.delete +import com.hippo.files.exists import com.hippo.files.isDirectory -import com.hippo.files.outputStream +import com.hippo.files.write import com.ramcosta.composedestinations.navigation.DestinationsNavigator import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext @@ -89,7 +90,7 @@ private fun removeNoMediaFile(downloadDir: Path) { } private fun ensureNoMediaFile(downloadDir: Path) { - (downloadDir / ".nomedia").outputStream().close() + (downloadDir / ".nomedia").apply { if (!exists()) write {} } } private val lck = Mutex() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt b/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt index 5f180ea8e..fd0a0bc41 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/updater/AppUpdater.kt @@ -7,7 +7,7 @@ import com.hippo.ehviewer.client.executeAndParseAs import com.hippo.ehviewer.client.executeSafely import com.hippo.ehviewer.util.copyTo import com.hippo.ehviewer.util.ensureSuccess -import com.hippo.files.outputStream +import com.hippo.files.write import io.ktor.client.plugins.timeout import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.header @@ -18,6 +18,7 @@ import java.util.zip.ZipInputStream import kotlin.time.Duration.Companion.days import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import kotlinx.io.asSource import moe.tarsin.coroutines.runSuspendCatching import okio.Path import tachiyomi.data.release.GithubArtifacts @@ -79,9 +80,7 @@ object AppUpdater { response.bodyAsChannel().toInputStream().use { stream -> ZipInputStream(stream).use { zip -> zip.nextEntry - path.outputStream().use { - zip.copyTo(it) - } + path.write { transferFrom(zip.asSource()) } } } } else { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt index 7f70a58f9..2ca659651 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt @@ -1,35 +1,6 @@ package com.hippo.ehviewer.util -import android.os.Build -import android.system.Int64Ref -import android.system.Os -import androidx.annotation.RequiresApi -import com.hippo.files.inputStream -import com.hippo.files.openFileDescriptor -import com.hippo.files.outputStream -import eu.kanade.tachiyomi.util.system.logcat -import java.io.FileDescriptor +import com.hippo.files.SystemFileSystem import okio.Path -@RequiresApi(Build.VERSION_CODES.P) -private fun sendFileTotally(from: FileDescriptor, to: FileDescriptor): Long = Os.sendfile(to, from, Int64Ref(0), Long.MAX_VALUE) - -infix fun Path.sendTo(file: Path): Long { - if (isAtLeastP) { - // sendfile may fail on some devices - try { - return openFileDescriptor("r").use { src -> - file.openFileDescriptor("wt").use { dst -> - sendFileTotally(src.fileDescriptor, dst.fileDescriptor) - } - } - } catch (e: Exception) { - logcat("sendfile", e) - } - } - return inputStream().use { src -> - file.outputStream().use { dst -> - src.channel.transferTo(0, Long.MAX_VALUE, dst.channel) - } - } -} +infix fun Path.sendTo(file: Path) = SystemFileSystem.copy(this, file) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/FileUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/FileUtils.kt index 93f1aa41c..b2e6b973f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/FileUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/FileUtils.kt @@ -15,9 +15,10 @@ */ package com.hippo.ehviewer.util -import com.hippo.files.outputStream +import com.hippo.files.write import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.copyTo +import io.ktor.utils.io.InternalAPI +import io.ktor.utils.io.rethrowCloseCauseIfNeeded import java.io.File import java.util.Locale import kotlin.math.ln @@ -125,8 +126,13 @@ object FileUtils { fun getNameFromFilename(filename: String?) = filename?.substringBeforeLast('.')?.ifEmpty { null } } +@OptIn(InternalAPI::class) suspend fun ByteReadChannel.copyTo(file: Path) { - file.outputStream().use { - copyTo(it.channel) + file.write { + while (!isClosedForRead) { + transferFrom(readBuffer) + awaitContent() + } + rethrowCloseCauseIfNeeded() } } diff --git a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt index 4193ecd9c..3a2fbdd2e 100644 --- a/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt +++ b/app/src/main/kotlin/com/hippo/files/AndroidFileSystem.kt @@ -7,8 +7,13 @@ import android.os.ParcelFileDescriptor import android.provider.DocumentsContract import android.provider.DocumentsContract.Document import android.provider.MediaStore +import android.system.ErrnoException +import android.system.Int64Ref +import android.system.Os import android.webkit.MimeTypeMap import androidx.core.database.getLongOrNull +import kotlinx.io.asSink +import kotlinx.io.asSource import okio.FileHandle import okio.FileMetadata import okio.FileNotFoundException @@ -16,8 +21,7 @@ import okio.FileSystem import okio.IOException import okio.Path import okio.Sink -import okio.sink -import okio.source +import okio.Source import splitties.init.appCtx class AndroidFileSystem(context: Context) : FileSystem() { @@ -48,6 +52,26 @@ class AndroidFileSystem(context: Context) : FileSystem() { TODO("Not yet implemented") } + override fun copy(source: Path, target: Path) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + source.openFileDescriptor("r").use { src -> + target.openFileDescriptor("wt").use { dst -> + try { + Os.sendfile(dst.fileDescriptor, src.fileDescriptor, Int64Ref(0), Long.MAX_VALUE) + return + } catch (_: ErrnoException) {} + } + } + } + + // Fallback to transferTo if sendfile is not available or fails + source.inputStream().use { src -> + target.outputStream().use { dst -> + src.channel.transferTo(0, Long.MAX_VALUE, dst.channel) + } + } + } + override fun createDirectory(dir: Path, mustCreate: Boolean) { if (dir.isPhysicalFile()) { return physicalFileSystem.createDirectory(dir, mustCreate) @@ -177,11 +201,16 @@ class AndroidFileSystem(context: Context) : FileSystem() { } override fun sink(file: Path, mustCreate: Boolean): Sink { - if (mustCreate && exists(file)) throw IOException("$file already exists.") - return file.outputStream().sink() + TODO("Not yet implemented") } - override fun source(file: Path) = file.inputStream().source() + override fun source(file: Path): Source { + TODO("Not yet implemented") + } + + fun rawSink(file: Path) = file.outputStream().asSink() + + fun rawSource(file: Path) = file.inputStream().asSource() fun openFileDescriptor(path: Path, mode: String): ParcelFileDescriptor { if (path.isPhysicalFile()) { @@ -199,6 +228,10 @@ class AndroidFileSystem(context: Context) : FileSystem() { contentResolver.openFileDescriptor(path.toUri(), mode) }.getOrNull() ?: throw FileNotFoundException("Failed to open file: $path") } + + private fun Path.inputStream() = ParcelFileDescriptor.AutoCloseInputStream(openFileDescriptor(this, "r")) + + private fun Path.outputStream() = ParcelFileDescriptor.AutoCloseOutputStream(openFileDescriptor(this, "wt")) } private fun Path.isPhysicalFile() = toString().startsWith('/') diff --git a/app/src/main/kotlin/com/hippo/files/Paths.kt b/app/src/main/kotlin/com/hippo/files/Paths.kt index 9a81a5911..e772d5084 100644 --- a/app/src/main/kotlin/com/hippo/files/Paths.kt +++ b/app/src/main/kotlin/com/hippo/files/Paths.kt @@ -2,12 +2,9 @@ package com.hippo.files import android.content.ContentResolver import android.net.Uri -import android.os.ParcelFileDescriptor import androidx.core.net.toUri -import java.io.FileInputStream -import java.io.FileOutputStream +import kotlinx.io.Sink import kotlinx.io.Source -import kotlinx.io.asSource import kotlinx.io.buffered import okio.Path import okio.Path.Companion.toPath @@ -34,11 +31,9 @@ infix fun Path.moveTo(target: Path) = SystemFileSystem.atomicMove(this, target) fun Path.openFileDescriptor(mode: String) = SystemFileSystem.openFileDescriptor(this, mode) -fun Path.inputStream(): FileInputStream = ParcelFileDescriptor.AutoCloseInputStream(SystemFileSystem.openFileDescriptor(this, "r")) +inline fun Path.read(f: Source.() -> T) = SystemFileSystem.rawSource(this).buffered().use(f) -fun Path.outputStream(): FileOutputStream = ParcelFileDescriptor.AutoCloseOutputStream(SystemFileSystem.openFileDescriptor(this, "wt")) - -inline fun Path.read(f: Source.() -> T) = inputStream().asSource().buffered().use(f) +inline fun Path.write(f: Sink.() -> T) = SystemFileSystem.rawSink(this).buffered().use(f) fun Path.toUri(): Uri { val str = toString() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d59c40753..252a72fc6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ okhttp = "5.0.0-alpha.14" okio = "3.9.1" splitties = "3.0.0" telephoto = "0.14.0" -xmlutil = "0.90.3" +xmlutil = "0.90.4-SNAPSHOT" junit = "1.2.1" uiautomator = "2.4.0-alpha01" benchmark = "1.4.0-alpha06" @@ -112,6 +112,7 @@ serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso serialization-json-io = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-io", version.ref = "kotlinx-serialization" } serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xmlutil" } +serialization-xml-io = { module = "io.github.pdvrieze.xmlutil:serialization-io", version.ref = "xmlutil" } splitties-appctx = { module = "com.louiscad.splitties:splitties-appctx", version.ref = "splitties" } splitties-arch-room = { module = "com.louiscad.splitties:splitties-arch-room", version.ref = "splitties" } @@ -129,7 +130,7 @@ androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstall arrow = ["arrow-fx-coroutines", "arrow-functions", "arrow-resilience"] coil = ["coil-compose", "coil-gif", "coil-network"] compose = ["compose-foundation", "compose-material-icons-extended", "compose-material3", "compose-material3-adaptive", "compose-ui-util"] -kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-io", "serialization-xml"] +kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-io", "serialization-xml", "serialization-xml-io"] splitties = ["splitties-appctx", "splitties-arch-room", "splitties-preferences", "splitties-systemservices"] [plugins] diff --git a/settings.gradle.kts b/settings.gradle.kts index 6bd42df41..8c41309cb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") } }