diff --git a/app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt b/app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt index 1ed21f167..716126b55 100644 --- a/app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt +++ b/app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt @@ -54,6 +54,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() { /** * 判断Cronet是否安装完成 */ + @Synchronized fun install(): Boolean { if (cacheInstall) { return true diff --git a/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt b/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt index 409152490..a5bfaa8fc 100644 --- a/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt @@ -1,5 +1,7 @@ package io.legado.app.utils +import java.io.IOException + val Throwable.msg: String get() { val stackTrace = stackTraceToString() @@ -8,4 +10,10 @@ val Throwable.msg: String stackTrace.isNotEmpty() -> stackTrace else -> lMsg } - } \ No newline at end of file + } + +fun Throwable.rethrowAsIOException(): IOException { + val newException = IOException(this.message) + newException.initCause(this) + throw newException +} \ No newline at end of file diff --git a/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java b/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java index 6fd7e668e..7401b00f9 100644 --- a/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java +++ b/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java @@ -1,8 +1,9 @@ package me.ag2s.epublib.zip; +import static me.ag2s.utils.ThrowableUtils.rethrowAsIOException; + import android.content.Context; import android.net.Uri; -import android.os.Build; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.OsConstants; @@ -22,12 +23,18 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; -import me.ag2s.epublib.util.AndroidCloseGuard; - +import me.ag2s.utils.AndroidCloseGuard; +@SuppressWarnings("unused") public class AndroidRandomReadableFile implements DataInput, Closeable { private final ParcelFileDescriptor pfd; private FileInputStream fis; + private final Object lock = new Object(); + + /** + * 读取基本类型的buffer + */ + private final byte[] readBuffer = new byte[8]; private long pos = 0; private final AndroidCloseGuard guard = AndroidCloseGuard.getInstance(); @@ -35,7 +42,6 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException { pfd = context.getContentResolver().openFileDescriptor(treeUri, "r"); fis = new FileInputStream(pfd.getFileDescriptor()); - //dis = new DataInputStream(fis); guard.open("close"); } @@ -45,18 +51,42 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { return pfd.getFileDescriptor(); } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file. + * + *

The {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will always be equal to + * this object's file-pointer offset as returned by the {@link + * #getFilePointer getFilePointer} method. Changing this object's + * file-pointer offset, whether explicitly or by reading or writing bytes, + * will change the position of the channel, and vice versa. Changing the + * file's length via this object will change the length seen via the file + * channel, and vice versa. + * + * @return the file channel associated with this file + * @spec JSR-51 + * @since 1.4 + */ public final FileChannel getChannel() { - if (fis == null || pos != getPos()) { - fis = new FileInputStream(pfd.getFileDescriptor()); + synchronized (lock) { + if (fis == null || pos != getPos()) { + fis = new FileInputStream(pfd.getFileDescriptor()); + } } + return fis.getChannel(); } public final FileInputStream getFileInputStream() { - if (fis == null || this.pos != getPos()) { - this.pos = getPos(); - fis = new FileInputStream(pfd.getFileDescriptor()); + synchronized (lock) { + if (fis == null || this.pos != getPos()) { + this.pos = getPos(); + fis = new FileInputStream(pfd.getFileDescriptor()); + } } + return fis; } @@ -69,8 +99,23 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { * @throws IOException if an I/O error occurs. */ public int read() throws IOException { - byte[] b = new byte[1]; - return (read(b, 0, 1) != -1) ? b[0] & 0xff : -1; + return (read(readBuffer, 0, 1) != -1) ? readBuffer[0] & 0xff : -1; + } + + /** + * Reads a sub array as a sequence of bytes. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the number of bytes to read. + * @throws IOException If an I/O error has occurred. + */ + private int readBytes(byte[] b, int off, int len) throws IOException { + try { + return android.system.Os.read(pfd.getFileDescriptor(), b, off, len); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); + } } @@ -86,8 +131,7 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { * @throws IOException if an I/O error occurs. */ public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - + return readBytes(b, 0, b.length); } /** @@ -109,11 +153,7 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { * @throws IOException if an I/O error occurs. */ public int read(byte[] b, int off, int len) throws IOException { - try { - return android.system.Os.read(pfd.getFileDescriptor(), b, off, len); - } catch (Exception e) { - throw new IOException(e); - } + return readBytes(b, off, len); } @@ -126,8 +166,8 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { public void seek(long pos) throws IOException { try { android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET); - } catch (Exception e) { - throw new IOException(e); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); } } @@ -135,21 +175,37 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { public long length() throws IOException { try { return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END); - } catch (Exception e) { - throw new IOException(e); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); } } + /** + * Returns the current offset in this file. + * + * @return the offset from the beginning of the file, in bytes, + * at which the next read or write occurs. + * @throws IOException if an I/O error occurs. + */ + public long getFilePointer() throws IOException { + try { + return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); + } + } + public long getPos() { try { return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR); - } catch (Exception e) { + } catch (ErrnoException e) { return -1; } } + /** * Reads some bytes from an input * stream and stores them into the buffer @@ -192,13 +248,7 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { */ @Override public void readFully(byte[] b) throws IOException { - try { - android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length); - } catch (ErrnoException e) { - e.printStackTrace(); - } - - + readFully(b, 0, b.length); } /** @@ -246,12 +296,13 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { */ @Override public void readFully(byte[] b, int off, int len) throws IOException { - try { - android.system.Os.read(pfd.getFileDescriptor(), b, off, len); - //syncInputStream(); - } catch (ErrnoException e) { - throw new IOException(e); - } + int n = 0; + do { + int count = this.read(b, off + n, len - n); + if (count < 0) + throw new EOFException(); + n += count; + } while (n < len); } /** @@ -277,15 +328,33 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { */ @Override public int skipBytes(int n) throws IOException { - try { - byte[] b = new byte[n]; - return android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length); - } catch (Exception e) { - return -1; + long pos; + long len; + long newpos; + + if (n <= 0) { + return 0; + } + pos = getFilePointer(); + len = length(); + newpos = pos + n; + if (newpos > len) { + newpos = len; } + seek(newpos); + + /* return the actual number of bytes skipped */ + return (int) (newpos - pos); +// try { +// byte[] b = new byte[n]; +// return android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length); +// } catch (Exception e) { +// return -1; +// } } + /** * Reads one input byte and returns * {@code true} if that byte is nonzero, @@ -357,7 +426,6 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { return ch; } - private final byte[] readBuffer = new byte[8]; /** * Reads two input bytes and returns @@ -432,7 +500,7 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { @Override public char readChar() throws IOException { readFully(readBuffer, 0, 2); - return (char) ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(); + return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asCharBuffer().get(); } /** @@ -543,73 +611,60 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { return Double.longBitsToDouble(readLong()); } - private char[] lineBuffer; /** - * See the general contract of the readLine - * method of DataInput. - *

- * Bytes - * for this operation are read from the contained - * input stream. + * Reads the next line of text from this file. This method successively + * reads bytes from the file, starting at the current file pointer, + * until it reaches a line terminator or the end + * of the file. Each byte is converted into a character by taking the + * byte's value for the lower eight bits of the character and setting the + * high eight bits of the character to zero. This method does not, + * therefore, support the full Unicode character set. * - * @return the next line of text from this input stream. + *

A line of text is terminated by a carriage-return character + * ({@code '\u005Cr'}), a newline character ({@code '\u005Cn'}), a + * carriage-return character immediately followed by a newline character, + * or the end of the file. Line-terminating characters are discarded and + * are not included as part of the string returned. + * + *

This method blocks until a newline character is read, a carriage + * return and the byte following it are read (to see if it is a newline), + * the end of the file is reached, or an exception is thrown. + * + * @return the next line of text from this file, or null if end + * of file is encountered before even one byte is read. * @throws IOException if an I/O error occurs. - * @see java.io.BufferedReader#readLine() - * @see java.io.FilterInputStream##in - * @deprecated This method does not properly convert bytes to characters. - * As of JDK 1.1, the preferred way to read lines of text is via the - * BufferedReader.readLine() method. Programs that use the - * DataInputStream class to read lines can be converted to use - * the BufferedReader class by replacing code of the form: - *

-     *     DataInputStream d = new DataInputStream(in);
-     * 
- * with: - *
-     *     BufferedReader d
-     *          = new BufferedReader(new InputStreamReader(in));
-     * 
*/ - @Deprecated + @Override public String readLine() throws IOException { - char[] buf = lineBuffer; - - if (buf == null) { - buf = lineBuffer = new char[128]; - } + StringBuilder input = new StringBuilder(); + int c = -1; + boolean eol = false; - int room = buf.length; - int offset = 0; - int c; - - loop: - while (true) { - switch (c = this.read()) { + while (!eol) { + switch (c = read()) { case -1: case '\n': - break loop; - + eol = true; + break; case '\r': - int c2 = this.read(); - break loop; - - default: - if (--room < 0) { - buf = new char[offset + 128]; - room = buf.length - offset - 1; - System.arraycopy(lineBuffer, 0, buf, 0, offset); - lineBuffer = buf; + eol = true; + long cur = getFilePointer(); + if ((read()) != '\n') { + seek(cur); } - buf[offset++] = (char) c; + break; + default: + input.append((char) c); break; } } - if ((c == -1) && (offset == 0)) { + + if ((c == -1) && (input.length() == 0)) { return null; } - return String.copyValueOf(buf, 0, offset); + return input.toString(); } /** @@ -641,9 +696,6 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { guard.close(); - if (Build.VERSION.SDK_INT >= 28) { - //Reference.reachabilityFence(this); - } try { if (fis != null) { fis.close(); @@ -669,6 +721,7 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { guard.warnIfOpen(); + close(); } finally { diff --git a/epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java b/epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java similarity index 95% rename from epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java rename to epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java index 8e2d0ca50..a318315ca 100644 --- a/epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java +++ b/epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java @@ -1,4 +1,4 @@ -package me.ag2s.epublib.util; +package me.ag2s.utils; import android.os.Build; import android.util.CloseGuard; diff --git a/epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java b/epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java similarity index 92% rename from epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java rename to epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java index ba8b963e3..0472bfcc6 100644 --- a/epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java +++ b/epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java @@ -1,4 +1,4 @@ -package me.ag2s.epublib.util; +package me.ag2s.utils; import android.os.Build; @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; public interface AndroidCloseGuard { - public static AndroidCloseGuard getInstance() { + static AndroidCloseGuard getInstance() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return new Android11CloseGuard(); } else { diff --git a/epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java b/epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java similarity index 98% rename from epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java rename to epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java index 8daee7837..deda4f3e4 100644 --- a/epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java +++ b/epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java @@ -1,4 +1,4 @@ -package me.ag2s.epublib.util; +package me.ag2s.utils; import androidx.annotation.NonNull; diff --git a/epublib/src/main/java/me/ag2s/utils/ThrowableUtils.java b/epublib/src/main/java/me/ag2s/utils/ThrowableUtils.java new file mode 100644 index 000000000..a5e7b1685 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/utils/ThrowableUtils.java @@ -0,0 +1,16 @@ +package me.ag2s.utils; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +public class ThrowableUtils { + + + public static @NonNull + IOException rethrowAsIOException(Throwable throwable) throws IOException { + IOException newException = new IOException(throwable.getMessage()); + newException.initCause(throwable); + throw newException; + } +}