diff --git a/app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt b/app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt index cfa55a2b1..56f5b4bfd 100644 --- a/app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt +++ b/app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt @@ -2,6 +2,7 @@ package io.legado.app.help.http.cronet import io.legado.app.help.http.okHttpClient import io.legado.app.utils.DebugLog +import io.legado.app.utils.rethrowAsIOException import okhttp3.* import okhttp3.EventListener import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -123,7 +124,7 @@ abstract class AbsCallBack( //UrlResponseInfo可能为null override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) { DebugLog.i(javaClass.name, error.message.toString()) - mException = IOException(error.message, error) + mException = error.rethrowAsIOException() this.eventListener?.callFailed(mCall, error) responseCallback?.onFailure(mCall, error) } 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..a5604bfe3 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 @@ -55,9 +55,12 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() { * 判断Cronet是否安装完成 */ fun install(): Boolean { - if (cacheInstall) { - return true + synchronized(this) { + if (cacheInstall) { + return true + } } + if (AppConfig.isGooglePlay) { return false } 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 1e476191c..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,5 +1,7 @@ package me.ag2s.epublib.zip; +import static me.ag2s.utils.ThrowableUtils.rethrowAsIOException; + import android.content.Context; import android.net.Uri; import android.os.ParcelFileDescriptor; @@ -16,78 +18,156 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UTFDataFormatException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; +import me.ag2s.utils.AndroidCloseGuard; + +@SuppressWarnings("unused") public class AndroidRandomReadableFile implements DataInput, Closeable { - private ParcelFileDescriptor pfd; + private final ParcelFileDescriptor pfd; private FileInputStream fis; - private DataInputStream dis; - public long pos = 0; + private final Object lock = new Object(); + + /** + * 读取基本类型的buffer + */ + private final byte[] readBuffer = new byte[8]; + private long pos = 0; + + private final AndroidCloseGuard guard = AndroidCloseGuard.getInstance(); public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException { - try { - pfd = context.getContentResolver().openFileDescriptor(treeUri, "r"); - fis = new FileInputStream(pfd.getFileDescriptor()); - dis = new DataInputStream(fis); - } catch (FileNotFoundException e) { - throw e; - } + pfd = context.getContentResolver().openFileDescriptor(treeUri, "r"); + fis = new FileInputStream(pfd.getFileDescriptor()); + guard.open("close"); + } + public final FileDescriptor getFD() { 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 || 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;
}
+ /**
+ * Reads a byte of data from this input stream. This method blocks
+ * if no input is yet available.
+ *
+ * @return the next byte of data, or -1
if the end of the
+ * file is reached.
+ * @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;
}
- public int read(byte[] b) {
+ /**
+ * 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 read(b, 0, b.length);
- } catch (IOException e) {
- e.printStackTrace();
- return -1;
+ return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
+ } catch (ErrnoException e) {
+ throw rethrowAsIOException(e);
}
}
+
+ /**
+ * Reads up to b.length
bytes of data from this input
+ * stream into an array of bytes. This method blocks until some input
+ * is available.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the file has been reached.
+ * @throws IOException if an I/O error occurs.
+ */
+ public int read(byte[] b) throws IOException {
+ return readBytes(b, 0, b.length);
+ }
+
+ /**
+ * Reads up to len
bytes of data from this input stream
+ * into an array of bytes. If len
is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and 0
is returned.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array b
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the file has been reached.
+ * @throws NullPointerException If b
is null
.
+ * @throws IndexOutOfBoundsException If off
is negative,
+ * len
is negative, or len
is greater than
+ * b.length - off
+ * @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) {
- return -1;
- }
+ return readBytes(b, off, len);
}
private void syncInputStream() {
+ this.pos = getPos();
fis = new FileInputStream(pfd.getFileDescriptor());
- dis = new DataInputStream(fis);
}
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);
}
}
@@ -95,53 +175,199 @@ 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);
}
}
- public long getPos() {
+ /**
+ * 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 (Exception e) {
- return -1;
+ } catch (ErrnoException e) {
+ throw rethrowAsIOException(e);
}
-
}
- @Override
- public void readFully(byte[] b) throws IOException {
+ public long getPos() {
try {
- android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
+ return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
} catch (ErrnoException e) {
- e.printStackTrace();
+ return -1;
}
+ }
+
+ /**
+ * Reads some bytes from an input
+ * stream and stores them into the buffer
+ * array {@code b}. The number of bytes
+ * read is equal
+ * to the length of {@code b}.
+ *
+ * This method blocks until one of the + * following conditions occurs: + *
+ * If {@code b} is {@code null}, + * a {@code NullPointerException} is thrown. + * If {@code b.length} is zero, then + * no bytes are read. Otherwise, the first + * byte read is stored into element {@code b[0]}, + * the next one into {@code b[1]}, and + * so on. + * If an exception is thrown from + * this method, then it may be that some but + * not all bytes of {@code b} have been + * updated with data from the input stream. + * + * @param b the buffer into which the data is read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ + @Override + public void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); } + /** + * Reads {@code len} + * bytes from + * an input stream. + *
+ * This method + * blocks until one of the following conditions + * occurs: + *
+ * If {@code b} is {@code null}, + * a {@code NullPointerException} is thrown. + * If {@code off} is negative, or {@code len} + * is negative, or {@code off+len} is + * greater than the length of the array {@code b}, + * then an {@code IndexOutOfBoundsException} + * is thrown. + * If {@code len} is zero, + * then no bytes are read. Otherwise, the first + * byte read is stored into element {@code b[off]}, + * the next one into {@code b[off+1]}, + * and so on. The number of bytes read is, + * at most, equal to {@code len}. + * + * @param b the buffer into which the data is read. + * @param off an int specifying the offset into the data. + * @param len an int specifying the number of bytes to read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @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); } + /** + * Makes an attempt to skip over + * {@code n} bytes + * of data from the input + * stream, discarding the skipped bytes. However, + * it may skip + * over some smaller number of + * bytes, possibly zero. This may result from + * any of a + * number of conditions; reaching + * end of file before {@code n} bytes + * have been skipped is + * only one possibility. + * This method never throws an {@code EOFException}. + * The actual + * number of bytes skipped is returned. + * + * @param n the number of bytes to be skipped. + * @return the number of bytes actually skipped. + * @throws IOException if an I/O error occurs. + */ @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, + * {@code false} if that byte is zero. + * This method is suitable for reading + * the byte written by the {@code writeBoolean} + * method of interface {@code DataOutput}. + * + * @return the {@code boolean} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public boolean readBoolean() throws IOException { int ch = this.read(); @@ -151,6 +377,20 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads and returns one input byte. + * The byte is treated as a signed value in + * the range {@code -128} through {@code 127}, + * inclusive. + * This method is suitable for + * reading the byte written by the {@code writeByte} + * method of interface {@code DataOutput}. + * + * @return the 8-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public byte readByte() throws IOException { int ch = this.read(); @@ -160,6 +400,24 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads one input byte, zero-extends + * it to type {@code int}, and returns + * the result, which is therefore in the range + * {@code 0} + * through {@code 255}. + * This method is suitable for reading + * the byte written by the {@code writeByte} + * method of interface {@code DataOutput} + * if the argument to {@code writeByte} + * was intended to be a value in the range + * {@code 0} through {@code 255}. + * + * @return the unsigned 8-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readUnsignedByte() throws IOException { int ch = this.read(); @@ -168,32 +426,132 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { return ch; } - private final byte[] readBuffer = new byte[8]; + /** + * Reads two input bytes and returns + * a {@code short} value. Let {@code a} + * be the first byte read and {@code b} + * be the second byte. The value + * returned + * is: + *
{@code (short)((a << 8) | (b & 0xff)) + * }+ * This method + * is suitable for reading the bytes written + * by the {@code writeShort} method of + * interface {@code DataOutput}. + * + * @return the 16-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public short readShort() throws IOException { readFully(readBuffer, 0, 2); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(); } + /** + * Reads two input bytes and returns + * an {@code int} value in the range {@code 0} + * through {@code 65535}. Let {@code a} + * be the first byte read and + * {@code b} + * be the second byte. The value returned is: + *
{@code (((a & 0xff) << 8) | (b & 0xff)) + * }+ * This method is suitable for reading the bytes + * written by the {@code writeShort} method + * of interface {@code DataOutput} if + * the argument to {@code writeShort} + * was intended to be a value in the range + * {@code 0} through {@code 65535}. + * + * @return the unsigned 16-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readUnsignedShort() throws IOException { readFully(readBuffer, 0, 2); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get() & 0xffff; } + /** + * Reads two input bytes and returns a {@code char} value. + * Let {@code a} + * be the first byte read and {@code b} + * be the second byte. The value + * returned is: + *
{@code (char)((a << 8) | (b & 0xff)) + * }+ * This method + * is suitable for reading bytes written by + * the {@code writeChar} method of interface + * {@code DataOutput}. + * + * @return the {@code char} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @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(); } + /** + * Reads four input bytes and returns an + * {@code int} value. Let {@code a-d} + * be the first through fourth bytes read. The value returned is: + *
{@code + * (((a & 0xff) << 24) | ((b & 0xff) << 16) | + * ((c & 0xff) << 8) | (d & 0xff)) + * }+ * This method is suitable + * for reading bytes written by the {@code writeInt} + * method of interface {@code DataOutput}. + * + * @return the {@code int} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readInt() throws IOException { readFully(readBuffer, 0, 4); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get(); } + + /** + * Reads eight input bytes and returns + * a {@code long} value. Let {@code a-h} + * be the first through eighth bytes read. + * The value returned is: + *
{@code + * (((long)(a & 0xff) << 56) | + * ((long)(b & 0xff) << 48) | + * ((long)(c & 0xff) << 40) | + * ((long)(d & 0xff) << 32) | + * ((long)(e & 0xff) << 24) | + * ((long)(f & 0xff) << 16) | + * ((long)(g & 0xff) << 8) | + * ((long)(h & 0xff))) + * }+ *
+ * This method is suitable + * for reading bytes written by the {@code writeLong} + * method of interface {@code DataOutput}. + * + * @return the {@code long} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public long readLong() throws IOException { readFully(readBuffer, 0, 8); @@ -207,162 +565,167 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { ((readBuffer[7] & 255) << 0)); } + /** + * Reads four input bytes and returns + * a {@code float} value. It does this + * by first constructing an {@code int} + * value in exactly the manner + * of the {@code readInt} + * method, then converting this {@code int} + * value to a {@code float} in + * exactly the manner of the method {@code Float.intBitsToFloat}. + * This method is suitable for reading + * bytes written by the {@code writeFloat} + * method of interface {@code DataOutput}. + * + * @return the {@code float} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } + /** + * Reads eight input bytes and returns + * a {@code double} value. It does this + * by first constructing a {@code long} + * value in exactly the manner + * of the {@code readLong} + * method, then converting this {@code long} + * value to a {@code double} in exactly + * the manner of the method {@code Double.longBitsToDouble}. + * This method is suitable for reading + * bytes written by the {@code writeDouble} + * method of interface {@code DataOutput}. + * + * @return the {@code double} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } - private char[] lineBuffer; - @Deprecated + /** + * 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. + * + *
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.
+ */
+
@Override
public String readLine() throws IOException {
- char[] buf = lineBuffer;
-
- if (buf == null) {
- buf = lineBuffer = new char[128];
- }
-
- int room = buf.length;
- int offset = 0;
- int c;
+ StringBuilder input = new StringBuilder();
+ int c = -1;
+ boolean eol = false;
- 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();
}
+ /**
+ * See the general contract of the readUTF
+ * method of DataInput
.
+ *
+ * Bytes + * for this operation are read from the contained + * input stream. + * + * @return a Unicode string. + * @throws EOFException if this input stream reaches the end before + * reading all the bytes. + * @throws IOException the stream has been closed and the contained + * input stream does not support reading after close, or + * another I/O error occurs. + * @throws UTFDataFormatException if the bytes do not represent a valid + * modified UTF-8 encoding of a string. + * @see java.io.DataInputStream#readUTF(java.io.DataInput) + */ @Override public String readUTF() throws IOException { - if (pos != getPos()) { - syncInputStream(); - } - return dis.readUTF(); + return DataInputStream.readUTF(this); } -// public final static String readUTF(DataInput in) throws IOException { -// int utflen = in.readUnsignedShort(); -// byte[] bytearr = null; -// char[] chararr = null; -// if (in instanceof DataInputStream) { -// DataInputStream dis = (DataInputStream)in; -// if (dis.bytearr.length < utflen){ -// dis.bytearr = new byte[utflen*2]; -// dis.chararr = new char[utflen*2]; -// } -// chararr = dis.chararr; -// bytearr = dis.bytearr; -// } else { -// bytearr = new byte[utflen]; -// chararr = new char[utflen]; -// } -// -// int c, char2, char3; -// int count = 0; -// int chararr_count=0; -// -// in.readFully(bytearr, 0, utflen); -// -// while (count < utflen) { -// c = (int) bytearr[count] & 0xff; -// if (c > 127) break; -// count++; -// chararr[chararr_count++]=(char)c; -// } -// -// while (count < utflen) { -// c = (int) bytearr[count] & 0xff; -// switch (c >> 4) { -// case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: -// /* 0xxxxxxx*/ -// count++; -// chararr[chararr_count++]=(char)c; -// break; -// case 12: case 13: -// /* 110x xxxx 10xx xxxx*/ -// count += 2; -// if (count > utflen) -// throw new UTFDataFormatException( -// "malformed input: partial character at end"); -// char2 = (int) bytearr[count-1]; -// if ((char2 & 0xC0) != 0x80) -// throw new UTFDataFormatException( -// "malformed input around byte " + count); -// chararr[chararr_count++]=(char)(((c & 0x1F) << 6) | -// (char2 & 0x3F)); -// break; -// case 14: -// /* 1110 xxxx 10xx xxxx 10xx xxxx */ -// count += 3; -// if (count > utflen) -// throw new UTFDataFormatException( -// "malformed input: partial character at end"); -// char2 = (int) bytearr[count-2]; -// char3 = (int) bytearr[count-1]; -// if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) -// throw new UTFDataFormatException( -// "malformed input around byte " + (count-1)); -// chararr[chararr_count++]=(char)(((c & 0x0F) << 12) | -// ((char2 & 0x3F) << 6) | -// ((char3 & 0x3F) << 0)); -// break; -// default: -// /* 10xx xxxx, 1111 xxxx */ -// throw new UTFDataFormatException( -// "malformed input around byte " + count); -// } -// } -// // The number of chars produced may be less than utflen -// return new String(chararr, 0, chararr_count); -// } @Override public void close() throws IOException { - try { - dis.close(); - } catch (Exception e) { - e.printStackTrace(); - } + + guard.close(); try { - fis.close(); + if (fis != null) { + fis.close(); + } } catch (Exception e) { e.printStackTrace(); } try { - pfd.close(); + if (pfd != null) { + pfd.close(); + } + } catch (Exception e) { e.printStackTrace(); } } + + protected void finalize() throws Throwable { + try { + // Note that guard could be null if the constructor threw. + + guard.warnIfOpen(); + + + close(); + + } finally { + super.finalize(); + } + } } diff --git a/epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java b/epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java new file mode 100644 index 000000000..a318315ca --- /dev/null +++ b/epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java @@ -0,0 +1,33 @@ +package me.ag2s.utils; + +import android.os.Build; +import android.util.CloseGuard; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.R) +final class Android11CloseGuard implements AndroidCloseGuard { + @NonNull + private final CloseGuard mImpl; + + + public Android11CloseGuard() { + mImpl = new CloseGuard(); + } + + @Override + public void open(@NonNull String closeMethodName) { + mImpl.open(closeMethodName); + } + + @Override + public void close() { + mImpl.close(); + } + + @Override + public void warnIfOpen() { + mImpl.warnIfOpen(); + } +} diff --git a/epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java b/epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java new file mode 100644 index 000000000..0472bfcc6 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java @@ -0,0 +1,38 @@ +package me.ag2s.utils; + + +import android.os.Build; + +import androidx.annotation.NonNull; + +public interface AndroidCloseGuard { + + + static AndroidCloseGuard getInstance() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return new Android11CloseGuard(); + } else { + return new AndroidRefCloseGuard(); + } + } + + /** + * Initializes the instance with a warning that the caller should have explicitly called the + * {@code closeMethodName} method instead of relying on finalization. + * + * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen. + * @throws NullPointerException if closeMethodName is null. + */ + void open(@NonNull String closeMethodName); + + /** + * Marks this CloseGuard instance as closed to avoid warnings on finalization. + */ + void close(); + + /** + * Logs a warning if the caller did not properly cleanup by calling an explicit close method + * before finalization. + */ + void warnIfOpen(); +} diff --git a/epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java b/epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java new file mode 100644 index 000000000..deda4f3e4 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java @@ -0,0 +1,83 @@ +package me.ag2s.utils; + +import androidx.annotation.NonNull; + +import java.lang.reflect.Method; + +final class AndroidRefCloseGuard implements AndroidCloseGuard { + private static Object closeGuardInstance; + private static Method getMethod; + private static Method closeMethod; + private static Method openMethod; + private static Method warnIfOpenMethod; + + + public AndroidRefCloseGuard() { + + if (getMethod == null || closeMethod == null || openMethod == null || warnIfOpenMethod == null) { + try { + Class> closeGuardClass = Class.forName("dalvik.system.CloseGuard"); + getMethod = closeGuardClass.getMethod("get"); + closeMethod = closeGuardClass.getMethod("close"); + openMethod = closeGuardClass.getMethod("open", String.class); + warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen"); + } catch (Exception ignored) { + getMethod = null; + openMethod = null; + warnIfOpenMethod = null; + } + } + + + } + + Object createAndOpen(String closer) { + if (getMethod != null) { + try { + if (closeGuardInstance == null) { + closeGuardInstance = getMethod.invoke(null); + } + + openMethod.invoke(closeGuardInstance, closer); + return closeGuardInstance; + } catch (Exception ignored) { + } + } + return null; + } + + + boolean warnIfOpen(Object closeGuardInstance) { + boolean reported = false; + if (closeGuardInstance != null) { + try { + warnIfOpenMethod.invoke(closeGuardInstance); + reported = true; + } catch (Exception ignored) { + } + } + return reported; + } + + + @Override + public void open(@NonNull String closeMethodName) { + closeGuardInstance = createAndOpen(closeMethodName); + } + + @Override + public void close() { + if (closeGuardInstance != null) { + try { + closeMethod.invoke(closeMethod); + } catch (Exception ignored) { + } + } + + } + + @Override + public void warnIfOpen() { + warnIfOpen(closeGuardInstance); + } +} 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; + } +}