From 3973f5ef70faf83e53ddb8ea989f8803afe97ed4 Mon Sep 17 00:00:00 2001 From: tarsin Date: Mon, 27 Feb 2023 18:13:00 +0800 Subject: [PATCH] Make UniFile actually backed by ParcelFileDescriptor, implement IO stream as extensions, Resolve #896 --- .../ehviewer/gallery/ArchivePageLoader.kt | 10 +-- .../com/hippo/ehviewer/spider/SpiderDen.kt | 10 ++- .../com/hippo/ehviewer/spider/SpiderInfo.kt | 2 + .../com/hippo/ehviewer/ui/CommonOperations.kt | 8 +- .../java/com/hippo/unifile/Contracts.java | 46 ++---------- .../java/com/hippo/unifile/MediaFile.java | 54 +++----------- .../main/java/com/hippo/unifile/RawFile.java | 34 ++++----- .../com/hippo/unifile/SingleDocumentFile.java | 40 ++-------- .../com/hippo/unifile/TreeDocumentFile.java | 32 +++----- .../main/java/com/hippo/unifile/UniFile.java | 35 +-------- .../main/java/com/hippo/unifile/UniFile.kt | 14 ++++ .../com/hippo/unifile/UriOutputStream.java | 73 ------------------- 12 files changed, 82 insertions(+), 276 deletions(-) create mode 100644 app/src/main/java/com/hippo/unifile/UniFile.kt delete mode 100644 app/src/main/java/com/hippo/unifile/UriOutputStream.java diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt index 290e2a8b3..b73661905 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt @@ -17,6 +17,7 @@ package com.hippo.ehviewer.gallery import android.content.Context import android.net.Uri +import android.os.ParcelFileDescriptor import com.hippo.UriArchiveAccessor import com.hippo.ehviewer.Settings import com.hippo.image.Image @@ -35,7 +36,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit -import java.io.FileOutputStream import java.io.IOException class ArchivePageLoader(context: Context, uri: Uri, passwdFlow: Flow) : PageLoader2(), @@ -137,16 +137,16 @@ class ArchivePageLoader(context: Context, uri: Uri, passwdFlow: Flow) : } override fun save(index: Int, file: UniFile): Boolean { - val stream: FileOutputStream + val fd: ParcelFileDescriptor try { - stream = file.openOutputStream() as FileOutputStream + fd = file.openFileDescriptor("w") } catch (e: IOException) { e.printStackTrace() return false } - archiveAccessor.extractToFd(index, stream.fd) + archiveAccessor.extractToFd(index, fd.fileDescriptor) try { - stream.close() + fd.close() } catch (e: IOException) { e.printStackTrace() return false diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index cb8743eec..cb04328cf 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -37,6 +37,8 @@ import com.hippo.ehviewer.gallery.SUPPORT_IMAGE_EXTENSIONS import com.hippo.image.Image.CloseableSource import com.hippo.unifile.RawFile import com.hippo.unifile.UniFile +import com.hippo.unifile.openInputStream +import com.hippo.unifile.openOutputStream import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.MathUtils import kotlinx.coroutines.currentCoroutineContext @@ -117,9 +119,9 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { sCache.read(key) { val extension = fixExtension("." + metadata.toFile().readText()) val file = dir.createFile(generateImageFilename(index, extension)) ?: return false - (file.openOutputStream() as FileOutputStream).use { outputStream -> + file.openFileDescriptor("w").use { outFd -> ParcelFileDescriptor.open(data.toFile(), MODE_READ_WRITE).use { - it sendTo outputStream.fd + it sendTo outFd } } } @@ -199,7 +201,7 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { findDownloadFileForIndex(index, extension)?.runSuspendCatching { openOutputStream().use { outputStream -> - (outputStream as FileOutputStream).channel.use { return doSave(it) == length } + outputStream.channel.use { return doSave(it) == length } } }?.onFailure { it.printStackTrace() @@ -254,7 +256,7 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { downloadDir?.let { uniFile -> runCatching { findImageFile(uniFile, index)?.openInputStream()?.use { - copy(it as FileInputStream) + copy(it) } }.onFailure { it.printStackTrace() diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt index 913dd97ca..090683610 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderInfo.kt @@ -4,6 +4,8 @@ import coil.disk.DiskCache import com.hippo.compat.readLegacySpiderInfo import com.hippo.ehviewer.EhApplication import com.hippo.unifile.UniFile +import com.hippo.unifile.openInputStream +import com.hippo.unifile.openOutputStream import kotlinx.serialization.Serializable import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.decodeFromByteArray diff --git a/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.kt b/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.kt index d5bf889fc..b7cd36328 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.kt @@ -18,11 +18,11 @@ package com.hippo.ehviewer.ui import android.app.Activity import android.content.Intent import android.os.Build +import android.os.ParcelFileDescriptor import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import com.hippo.app.ListCheckBoxDialogBuilder import com.hippo.ehviewer.EhApplication.Companion.application -import com.hippo.ehviewer.download.DownloadManager as downloadManager import com.hippo.ehviewer.EhApplication.Companion.favouriteStatusRouter import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R @@ -36,7 +36,7 @@ import com.hippo.unifile.UniFile import com.hippo.yorozuya.IOUtils import com.hippo.yorozuya.collect.LongList import java.io.IOException -import java.io.InputStream +import com.hippo.ehviewer.download.DownloadManager as downloadManager object CommonOperations { private fun doAddToFavorites( @@ -227,9 +227,9 @@ object CommonOperations { return } val noMedia = file.createFile(".nomedia") ?: return - var `is`: InputStream? = null + var `is`: ParcelFileDescriptor? = null try { - `is` = noMedia.openInputStream() + `is` = noMedia.openFileDescriptor("r") } catch (e: IOException) { // Ignore } finally { diff --git a/app/src/main/java/com/hippo/unifile/Contracts.java b/app/src/main/java/com/hippo/unifile/Contracts.java index 7a2a1e45f..32c560c0c 100644 --- a/app/src/main/java/com/hippo/unifile/Contracts.java +++ b/app/src/main/java/com/hippo/unifile/Contracts.java @@ -21,14 +21,13 @@ import android.content.Context; import android.database.Cursor; import android.graphics.ImageDecoder; import android.net.Uri; +import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; import com.hippo.image.Image; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; final class Contracts { private Contracts() { @@ -80,45 +79,12 @@ final class Contracts { } @NonNull - static OutputStream openOutputStream(Context context, Uri uri) throws IOException { - try { - OutputStream os = context.getContentResolver().openOutputStream(uri); - if (os == null) { - throw new IOException("Can't open OutputStream"); - } - return os; - } catch (Throwable e) { - Utils.throwIfFatal(e); - throw new IOException("Can't open OutputStream", e); - } - } - - @NonNull - static OutputStream openOutputStream(Context context, Uri uri, boolean append) throws IOException { - try { - OutputStream os = context.getContentResolver().openOutputStream(uri, append ? "wa" : "w"); - if (os == null) { - throw new IOException("Can't open OutputStream"); - } - return os; - } catch (Throwable e) { - Utils.throwIfFatal(e); - throw new IOException("Can't open OutputStream", e); - } - } - - @NonNull - static InputStream openInputStream(Context context, Uri uri) throws IOException { - try { - InputStream is = context.getContentResolver().openInputStream(uri); - if (is == null) { - throw new IOException("Can't open InputStream"); - } - return is; - } catch (Throwable e) { - Utils.throwIfFatal(e); - throw new IOException("Can't open InputStream", e); + static ParcelFileDescriptor openFileDescriptor(Context context, Uri uri, String mode) throws IOException { + ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, mode); + if (pfd == null) { + throw new IOException("Can't open ParcelFileDescriptor"); } + return pfd; } @NonNull diff --git a/app/src/main/java/com/hippo/unifile/MediaFile.java b/app/src/main/java/com/hippo/unifile/MediaFile.java index 5521e0258..973ae17dd 100644 --- a/app/src/main/java/com/hippo/unifile/MediaFile.java +++ b/app/src/main/java/com/hippo/unifile/MediaFile.java @@ -18,14 +18,13 @@ package com.hippo.unifile; import android.content.Context; import android.net.Uri; +import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; import com.hippo.image.Image; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; class MediaFile extends UniFile { @@ -76,14 +75,7 @@ class MediaFile extends UniFile { @Override public boolean isFile() { - InputStream is; - try { - is = openInputStream(); - } catch (IOException e) { - return false; - } - Utils.closeQuietly(is); - return true; + return DocumentsContractApi19.isFile(mContext, mUri); } @Override @@ -103,13 +95,12 @@ class MediaFile extends UniFile { @Override public boolean canWrite() { - OutputStream os; try { - os = openOutputStream(true); + var fd = openFileDescriptor("w"); + fd.close(); } catch (IOException e) { return false; } - Utils.closeQuietly(os); return true; } @@ -120,18 +111,7 @@ class MediaFile extends UniFile { @Override public boolean ensureFile() { - if (isFile()) { - return true; - } else { - OutputStream os; - try { - os = openOutputStream(); - } catch (IOException e) { - return false; - } - Utils.closeQuietly(os); - return true; - } + return isFile(); } @Override @@ -169,27 +149,15 @@ class MediaFile extends UniFile { return false; } - @NonNull - @Override - public OutputStream openOutputStream() throws IOException { - return UriOutputStream.create(mContext, mUri, "w"); - } - - @NonNull - @Override - public OutputStream openOutputStream(boolean append) throws IOException { - return UriOutputStream.create(mContext, mUri, append ? "wa" : "w"); - } - - @NonNull - @Override - public InputStream openInputStream() throws IOException { - return Contracts.openInputStream(mContext, mUri); - } - @NonNull @Override public Image.CloseableSource getImageSource() { return Contracts.getImageSource(mContext, mUri); } + + @NonNull + @Override + public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException { + return Contracts.openFileDescriptor(mContext, mUri, mode); + } } diff --git a/app/src/main/java/com/hippo/unifile/RawFile.java b/app/src/main/java/com/hippo/unifile/RawFile.java index 3fd8d5092..4941003e9 100644 --- a/app/src/main/java/com/hippo/unifile/RawFile.java +++ b/app/src/main/java/com/hippo/unifile/RawFile.java @@ -18,6 +18,7 @@ package com.hippo.unifile; import android.graphics.ImageDecoder; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.util.Log; import android.webkit.MimeTypeMap; @@ -26,10 +27,8 @@ import androidx.annotation.NonNull; import com.hippo.image.Image; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; @@ -227,7 +226,7 @@ public class RawFile extends UniFile { results.add(new RawFile(this, file)); } } - return results.toArray(new UniFile[results.size()]); + return results.toArray(new UniFile[0]); } @Override @@ -247,24 +246,6 @@ public class RawFile extends UniFile { } } - @Override - @NonNull - public OutputStream openOutputStream() throws IOException { - return new FileOutputStream(mFile); - } - - @Override - @NonNull - public OutputStream openOutputStream(boolean append) throws IOException { - return new FileOutputStream(mFile, append); - } - - @Override - @NonNull - public InputStream openInputStream() throws IOException { - return new FileInputStream(mFile); - } - @NonNull @Override public Image.CloseableSource getImageSource() { @@ -282,4 +263,15 @@ public class RawFile extends UniFile { } }; } + + @NonNull + @Override + public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException { + var md = ParcelFileDescriptor.parseMode(mode); + var pfd = ParcelFileDescriptor.open(mFile, md); + if (pfd == null) { + throw new IOException("Can't open ParcelFileDescriptor"); + } + return pfd; + } } diff --git a/app/src/main/java/com/hippo/unifile/SingleDocumentFile.java b/app/src/main/java/com/hippo/unifile/SingleDocumentFile.java index a03e1c8b1..c83a4fcde 100644 --- a/app/src/main/java/com/hippo/unifile/SingleDocumentFile.java +++ b/app/src/main/java/com/hippo/unifile/SingleDocumentFile.java @@ -18,14 +18,13 @@ package com.hippo.unifile; import android.content.Context; import android.net.Uri; +import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; import com.hippo.image.Image; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; class SingleDocumentFile extends UniFile { @@ -101,18 +100,7 @@ class SingleDocumentFile extends UniFile { @Override public boolean ensureFile() { - if (isFile()) { - return true; - } else { - OutputStream os; - try { - os = openOutputStream(); - } catch (IOException e) { - return false; - } - Utils.closeQuietly(os); - return true; - } + return isFile(); } @Override @@ -150,27 +138,15 @@ class SingleDocumentFile extends UniFile { return false; } - @NonNull - @Override - public OutputStream openOutputStream() throws IOException { - return Contracts.openOutputStream(mContext, mUri); - } - - @NonNull - @Override - public OutputStream openOutputStream(boolean append) throws IOException { - return Contracts.openOutputStream(mContext, mUri, append); - } - - @NonNull - @Override - public InputStream openInputStream() throws IOException { - return Contracts.openInputStream(mContext, mUri); - } - @NonNull @Override public Image.CloseableSource getImageSource() { return Contracts.getImageSource(mContext, mUri); } + + @NonNull + @Override + public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException { + return Contracts.openFileDescriptor(mContext, mUri, mode); + } } diff --git a/app/src/main/java/com/hippo/unifile/TreeDocumentFile.java b/app/src/main/java/com/hippo/unifile/TreeDocumentFile.java index d1efd410d..8c1447545 100644 --- a/app/src/main/java/com/hippo/unifile/TreeDocumentFile.java +++ b/app/src/main/java/com/hippo/unifile/TreeDocumentFile.java @@ -18,6 +18,7 @@ package com.hippo.unifile; import android.content.Context; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Log; import android.webkit.MimeTypeMap; @@ -27,8 +28,6 @@ import androidx.annotation.NonNull; import com.hippo.image.Image; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; class TreeDocumentFile extends UniFile { @@ -221,14 +220,13 @@ class TreeDocumentFile extends UniFile { final Uri[] result = DocumentsContractApi21.listFiles(mContext, mUri); final ArrayList results = new ArrayList<>(); - for (int i = 0, n = result.length; i < n; i++) { - Uri uri = result[i]; + for (Uri uri : result) { String name = getFilenameForUri(uri); if (filter.accept(this, name)) { results.add(new TreeDocumentFile(this, mContext, uri, name)); } } - return results.toArray(new UniFile[results.size()]); + return results.toArray(new UniFile[0]); } @Override @@ -249,27 +247,15 @@ class TreeDocumentFile extends UniFile { } } - @NonNull - @Override - public OutputStream openOutputStream() throws IOException { - return Contracts.openOutputStream(mContext, mUri); - } - - @NonNull - @Override - public OutputStream openOutputStream(boolean append) throws IOException { - return Contracts.openOutputStream(mContext, mUri, append); - } - - @NonNull - @Override - public InputStream openInputStream() throws IOException { - return Contracts.openInputStream(mContext, mUri); - } - @NonNull @Override public Image.CloseableSource getImageSource() { return Contracts.getImageSource(mContext, mUri); } + + @NonNull + @Override + public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException { + return Contracts.openFileDescriptor(mContext, mUri, mode); + } } diff --git a/app/src/main/java/com/hippo/unifile/UniFile.java b/app/src/main/java/com/hippo/unifile/UniFile.java index 3abaebaba..d98f207c6 100644 --- a/app/src/main/java/com/hippo/unifile/UniFile.java +++ b/app/src/main/java/com/hippo/unifile/UniFile.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; +import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -29,8 +30,6 @@ import com.hippo.image.Image; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -414,35 +413,9 @@ public abstract class UniFile { */ public abstract boolean renameTo(String displayName); - /** - * Open a stream on to the content associated with the file, clean it if it exists - * - * @return the {@link OutputStream} - * @throws IOException - */ - @NonNull - public abstract OutputStream openOutputStream() throws IOException; - - /** - * Open a stream on to the content associated with the file - * - * @param append {@code true} for do not clean it if it exists - * @return the {@link OutputStream} - * @throws IOException - */ - @NonNull - public abstract OutputStream openOutputStream(boolean append) throws IOException; - - /** - * Open a stream on to the content associated with the file - * - * @return the {@link InputStream} - * @throws IOException - */ - @NonNull - public abstract InputStream openInputStream() throws IOException; - - @NonNull public abstract Image.CloseableSource getImageSource(); + + @NonNull + public abstract ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException; } diff --git a/app/src/main/java/com/hippo/unifile/UniFile.kt b/app/src/main/java/com/hippo/unifile/UniFile.kt new file mode 100644 index 000000000..0b42da4b5 --- /dev/null +++ b/app/src/main/java/com/hippo/unifile/UniFile.kt @@ -0,0 +1,14 @@ +package com.hippo.unifile + +import android.os.ParcelFileDescriptor.AutoCloseInputStream +import android.os.ParcelFileDescriptor.AutoCloseOutputStream +import java.io.FileInputStream +import java.io.FileOutputStream + +fun UniFile.openInputStream(): FileInputStream { + return AutoCloseInputStream(openFileDescriptor("r")) +} + +fun UniFile.openOutputStream(): FileOutputStream { + return AutoCloseOutputStream(openFileDescriptor("w")) +} diff --git a/app/src/main/java/com/hippo/unifile/UriOutputStream.java b/app/src/main/java/com/hippo/unifile/UriOutputStream.java deleted file mode 100644 index 0a04052d3..000000000 --- a/app/src/main/java/com/hippo/unifile/UriOutputStream.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.unifile; - -/* - * Created by Hippo on 8/15/2016. - */ - -import android.content.Context; -import android.net.Uri; -import android.os.ParcelFileDescriptor; - -import androidx.annotation.NonNull; - -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -// The OutputStream from Context.getContentResolver().openOutputStream() -// and FileProvider uri may throw Exception when write. The Exception looks like: -// java.io.IOException: write failed: EBADF (Bad file descriptor) -// But UriOutputStream can avoid it on my Nexus 5 cm13. -// TODO need more test -class UriOutputStream extends FileOutputStream { - - private final ParcelFileDescriptor mPfd; - - private UriOutputStream(ParcelFileDescriptor pfd, FileDescriptor fd) { - super(fd); - mPfd = pfd; - } - - @NonNull - static OutputStream create(Context context, Uri uri, String mode) throws IOException { - ParcelFileDescriptor pfd; - try { - pfd = context.getContentResolver().openFileDescriptor(uri, mode); - } catch (Throwable e) { - Utils.throwIfFatal(e); - throw new IOException("Can't get ParcelFileDescriptor", e); - } - if (pfd == null) { - throw new IOException("Can't get ParcelFileDescriptor"); - } - FileDescriptor fd = pfd.getFileDescriptor(); - if (fd == null) { - throw new IOException("Can't get FileDescriptor"); - } - - return new UriOutputStream(pfd, fd); - } - - @Override - public void close() throws IOException { - mPfd.close(); - super.close(); - } -}