From ee28484b02a33ea5bd6f8fb9d5879e0af9797c69 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Tue, 20 Dec 2022 19:18:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/help/book/BookHelp.kt | 17 + .../app/help/glide/LegadoGlideModule.kt | 2 + .../io/legado/app/model/localBook/EpubFile.kt | 24 +- .../io/legado/app/model/localBook/PdfFile.kt | 7 +- .../src/main/java/me/ag2s/base/PfdHelper.java | 102 ++++ .../java/me/ag2s/base/ThrowableUtils.java | 16 + .../epublib/domain/EpubResourceProvider.java | 21 +- .../epublib/domain/ResourceInputStream.java | 31 +- .../java/me/ag2s/epublib/epub/EpubReader.java | 18 +- .../me/ag2s/epublib/epub/ResourcesLoader.java | 23 +- .../me/ag2s/epublib/util/ResourceUtil.java | 11 +- .../epublib/util/zip/AndroidZipEntry.java | 355 +++++++++++++ .../ag2s/epublib/util/zip/AndroidZipFile.java | 490 ++++++++++++++++++ .../ag2s/epublib/util/zip/ZipConstants.java | 61 +++ .../epublib/util/zip/ZipEntryWrapper.java | 75 +++ .../ag2s/epublib/util/zip/ZipException.java | 34 ++ .../ag2s/epublib/util/zip/ZipFileWrapper.java | 103 ++++ 17 files changed, 1339 insertions(+), 51 deletions(-) create mode 100644 epublib/src/main/java/me/ag2s/base/PfdHelper.java create mode 100644 epublib/src/main/java/me/ag2s/base/ThrowableUtils.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipEntry.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipFile.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/ZipConstants.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/ZipEntryWrapper.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/ZipException.java create mode 100644 epublib/src/main/java/me/ag2s/epublib/util/zip/ZipFileWrapper.java diff --git a/app/src/main/java/io/legado/app/help/book/BookHelp.kt b/app/src/main/java/io/legado/app/help/book/BookHelp.kt index 885ce274d..3979c2117 100644 --- a/app/src/main/java/io/legado/app/help/book/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/book/BookHelp.kt @@ -1,5 +1,6 @@ package io.legado.app.help.book +import android.os.ParcelFileDescriptor import androidx.documentfile.provider.DocumentFile import io.legado.app.constant.AppLog import io.legado.app.constant.AppPattern @@ -198,6 +199,22 @@ object BookHelp { return ZipFile(uri.path) } + /** + * 获取本地书籍文件的ParcelFileDescriptor + * + * @param book + * @return + */ + @Throws(IOException::class, FileNotFoundException::class) + fun getBookPFD(book: Book): ParcelFileDescriptor? { + val uri = book.getLocalUri() + return if (uri.isContentScheme()) { + appCtx.contentResolver.openFileDescriptor(uri, "r") + } else { + ParcelFileDescriptor.open(File(uri.path), ParcelFileDescriptor.MODE_READ_ONLY) + } + } + fun getChapterFiles(book: Book): HashSet { val fileNames = hashSetOf() if (book.isLocalTxt) { diff --git a/app/src/main/java/io/legado/app/help/glide/LegadoGlideModule.kt b/app/src/main/java/io/legado/app/help/glide/LegadoGlideModule.kt index 7460af55d..bed786283 100644 --- a/app/src/main/java/io/legado/app/help/glide/LegadoGlideModule.kt +++ b/app/src/main/java/io/legado/app/help/glide/LegadoGlideModule.kt @@ -8,10 +8,12 @@ import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.AppGlideModule import java.io.InputStream + @Suppress("unused") @GlideModule class LegadoGlideModule : AppGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.replace( GlideUrl::class.java, diff --git a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt index d16f4b975..b2d9061c7 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt @@ -2,6 +2,7 @@ package io.legado.app.model.localBook import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.os.ParcelFileDescriptor import android.text.TextUtils import io.legado.app.constant.AppLog import io.legado.app.data.entities.Book @@ -12,6 +13,7 @@ import me.ag2s.epublib.domain.EpubBook import me.ag2s.epublib.domain.Resource import me.ag2s.epublib.domain.TOCReference import me.ag2s.epublib.epub.EpubReader +import me.ag2s.epublib.util.zip.AndroidZipFile import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.jsoup.select.Elements @@ -65,9 +67,14 @@ class EpubFile(var book: Book) { } private var mCharset: Charset = Charset.defaultCharset() + + /** + *持有引用,避免被回收 + */ + private var fileDescriptor: ParcelFileDescriptor? = null private var epubBook: EpubBook? = null get() { - if (field != null) { + if (field != null && fileDescriptor != null) { return field } field = readEpub() @@ -107,8 +114,14 @@ class EpubFile(var book: Book) { private fun readEpub(): EpubBook? { return kotlin.runCatching { //ContentScheme拷贝到私有文件夹采用懒加载防止OOM - val zipFile = BookHelp.getEpubFile(book) - EpubReader().readEpubLazy(zipFile, "utf-8") + //val zipFile = BookHelp.getEpubFile(book) + BookHelp.getBookPFD(book)?.let { + fileDescriptor = it + val zipFile = AndroidZipFile(it, book.originName) + EpubReader().readEpubLazy(zipFile, "utf-8") + } + + }.onFailure { AppLog.put("读取Epub文件失败\n${it.localizedMessage}", it) it.printOnDebug() @@ -350,4 +363,9 @@ class EpubFile(var book: Book) { } } + + protected fun finalize() { + fileDescriptor?.close() + } + } diff --git a/app/src/main/java/io/legado/app/model/localBook/PdfFile.kt b/app/src/main/java/io/legado/app/model/localBook/PdfFile.kt index 16e50c14a..40ccca227 100644 --- a/app/src/main/java/io/legado/app/model/localBook/PdfFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/PdfFile.kt @@ -57,10 +57,13 @@ class PdfFile(var book: Book) { } + /** + *持有引用,避免被回收 + */ private var fileDescriptor: ParcelFileDescriptor? = null private var pdfRenderer: PdfRenderer? = null get() { - if (field != null) { + if (field != null && fileDescriptor != null) { return field } field = readPdf() @@ -180,7 +183,7 @@ class PdfFile(var book: Book) { val index = href.toInt() val bitmap = openPdfPage(pdfRenderer!!, index) if (bitmap != null) { - BitmapUtils.toInputStream(bitmap).also { bitmap.recycle() } + BitmapUtils.toInputStream(bitmap) } else { null } diff --git a/epublib/src/main/java/me/ag2s/base/PfdHelper.java b/epublib/src/main/java/me/ag2s/base/PfdHelper.java new file mode 100644 index 000000000..dfc601d3d --- /dev/null +++ b/epublib/src/main/java/me/ag2s/base/PfdHelper.java @@ -0,0 +1,102 @@ +package me.ag2s.base; + +import static me.ag2s.base.ThrowableUtils.rethrowAsIOException; + +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.OsConstants; + +import java.io.EOFException; +import java.io.IOException; + +/** + * 读取ParcelFileDescriptor的工具类 + */ +@SuppressWarnings("unused") +public final class PfdHelper { + + /** + * 读取基本类型的buffer + */ + private static final byte[] readBuffer = new byte[8]; + + public static void seek(ParcelFileDescriptor pfd, long pos) throws IOException { + try { + android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); + } + + } + + public static long getFilePointer(ParcelFileDescriptor pfd) throws IOException { + try { + return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); + } + } + + public static long length(ParcelFileDescriptor pfd) throws IOException { + try { + return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END); + } catch (ErrnoException e) { + throw rethrowAsIOException(e); + } + } + + private static int readBytes(ParcelFileDescriptor pfd, 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); + } + } + + public static int read(ParcelFileDescriptor pfd) throws IOException { + return (read(pfd, readBuffer, 0, 1) != -1) ? readBuffer[0] & 0xff : -1; + } + + public static int read(ParcelFileDescriptor pfd, byte[] b, int off, int len) throws IOException { + return readBytes(pfd, b, off, len); + } + + public static int read(ParcelFileDescriptor pfd, byte[] b) throws IOException { + return readBytes(pfd, b, 0, b.length); + } + + public static void readFully(ParcelFileDescriptor pfd, byte[] b) throws IOException { + readFully(pfd, b, 0, b.length); + } + + public static void readFully(ParcelFileDescriptor pfd, byte[] b, int off, int len) throws IOException { + int n = 0; + do { + int count = read(pfd, b, off + n, len - n); + if (count < 0) + throw new EOFException(); + n += count; + } while (n < len); + } + + + public static int skipBytes(ParcelFileDescriptor pfd, int n) throws IOException { + long pos; + long len; + long newpos; + + if (n <= 0) { + return 0; + } + pos = getFilePointer(pfd); + len = length(pfd); + newpos = pos + n; + if (newpos > len) { + newpos = len; + } + seek(pfd, newpos); + + /* return the actual number of bytes skipped */ + return (int) (newpos - pos); + } +} diff --git a/epublib/src/main/java/me/ag2s/base/ThrowableUtils.java b/epublib/src/main/java/me/ag2s/base/ThrowableUtils.java new file mode 100644 index 000000000..6b5555304 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/base/ThrowableUtils.java @@ -0,0 +1,16 @@ +package me.ag2s.base; + +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; + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java b/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java index 98940662b..a1b42c75c 100644 --- a/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java +++ b/epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java @@ -4,8 +4,9 @@ import androidx.annotation.NonNull; import java.io.IOException; import java.io.InputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; + +import me.ag2s.epublib.util.zip.ZipEntryWrapper; +import me.ag2s.epublib.util.zip.ZipFileWrapper; /** * @author jake @@ -13,24 +14,24 @@ import java.util.zip.ZipFile; public class EpubResourceProvider implements LazyResourceProvider { - private final String epubFilename; + private final ZipFileWrapper zipFileWrapper; - public EpubResourceProvider(String epubFilename) { - this.epubFilename = epubFilename; + public EpubResourceProvider(ZipFileWrapper zipFileWrapper) { + this.zipFileWrapper = zipFileWrapper; } @Override public InputStream getResourceStream(@NonNull String href) throws IOException { - ZipFile zipFile = new ZipFile(epubFilename); - ZipEntry zipEntry = zipFile.getEntry(href); + //ZipFile zipFile = new ZipFile(epubFilename); + ZipEntryWrapper zipEntry = zipFileWrapper.getEntry(href); if (zipEntry == null) { - zipFile.close(); + //zipFile.close(); throw new IllegalStateException( - "Cannot find entry " + href + " in epub file " + epubFilename); + "Cannot find entry " + href + " in epub file " + zipFileWrapper); } - return new ResourceInputStream(zipFile.getInputStream(zipEntry), zipFile); + return new ResourceInputStream(zipFileWrapper.getInputStream(zipEntry)); } } diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java index d6aa68f86..b8b8b9953 100644 --- a/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java +++ b/epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java @@ -3,34 +3,31 @@ package me.ag2s.epublib.domain; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.zip.ZipFile; /** - * A wrapper class for closing a ZipFile object when the InputStream derived + * A wrapper class for closing a AndroidZipFile object when the InputStream derived * from it is closed. * * @author ttopalov */ public class ResourceInputStream extends FilterInputStream { - private final ZipFile zipFile; + //private final ZipFile zipFile; - /** - * Constructor. - * - * @param in - * The InputStream object. - * @param zipFile - * The ZipFile object. - */ - public ResourceInputStream(InputStream in, ZipFile zipFile) { - super(in); - this.zipFile = zipFile; - } + /** + * Constructor. + * + * @param in The InputStream object. + */ + public ResourceInputStream(InputStream in) { + super(in); + //this.zipFile = zipFile; + } @Override public void close() throws IOException { - super.close(); - zipFile.close(); + super.close(); + + //zipFile.close(); } } diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java index 7f74b836f..5a8b1b8ad 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java @@ -22,6 +22,8 @@ import me.ag2s.epublib.domain.Resource; import me.ag2s.epublib.domain.Resources; import me.ag2s.epublib.util.ResourceUtil; import me.ag2s.epublib.util.StringUtil; +import me.ag2s.epublib.util.zip.AndroidZipFile; +import me.ag2s.epublib.util.zip.ZipFileWrapper; /** * Reads an epub file. @@ -72,12 +74,17 @@ public class EpubReader { return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes)); } + public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding) + throws IOException { + return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes)); + } + public EpubBook readEpub(@NonNull ZipInputStream in, @NonNull String encoding) throws IOException { return readEpub(ResourcesLoader.loadResources(in, encoding)); } public EpubBook readEpub(ZipFile in, String encoding) throws IOException { - return readEpub(ResourcesLoader.loadResources(in, encoding)); + return readEpub(ResourcesLoader.loadResources(new ZipFileWrapper(in), encoding)); } /** @@ -92,7 +99,14 @@ public class EpubReader { public EpubBook readEpubLazy(@NonNull ZipFile zipFile, @NonNull String encoding, @NonNull List lazyLoadedTypes) throws IOException { Resources resources = ResourcesLoader - .loadResources(zipFile, encoding, lazyLoadedTypes); + .loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes); + return readEpub(resources); + } + + public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding, + @NonNull List lazyLoadedTypes) throws IOException { + Resources resources = ResourcesLoader + .loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes); return readEpub(resources); } diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java b/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java index 8c8bdd72e..5f034856d 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java @@ -9,7 +9,6 @@ import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipException; -import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import me.ag2s.epublib.domain.EpubResourceProvider; @@ -21,6 +20,8 @@ import me.ag2s.epublib.domain.Resource; import me.ag2s.epublib.domain.Resources; import me.ag2s.epublib.util.CollectionUtil; import me.ag2s.epublib.util.ResourceUtil; +import me.ag2s.epublib.util.zip.ZipEntryWrapper; +import me.ag2s.epublib.util.zip.ZipFileWrapper; /** @@ -34,32 +35,32 @@ public class ResourcesLoader { /** - * Loads the entries of the zipFile as resources. + * Loads the entries of the zipFileWrapper as resources. *

* The MediaTypes that are in the lazyLoadedTypes will not get their * contents loaded, but are stored as references to entries into the - * ZipFile and are loaded on demand by the Resource system. + * AndroidZipFile and are loaded on demand by the Resource system. * - * @param zipFile import epub zipfile + * @param zipFileWrapper import epub zipfile * @param defaultHtmlEncoding epub xhtml default encoding * @param lazyLoadedTypes lazyLoadedTypes * @return Resources * @throws IOException IOException */ public static Resources loadResources( - ZipFile zipFile, + ZipFileWrapper zipFileWrapper, String defaultHtmlEncoding, List lazyLoadedTypes ) throws IOException { LazyResourceProvider resourceProvider = - new EpubResourceProvider(zipFile.getName()); + new EpubResourceProvider(zipFileWrapper); Resources result = new Resources(); - Enumeration entries = zipFile.entries(); + Enumeration entries = zipFileWrapper.entries(); while (entries.hasMoreElements()) { - ZipEntry zipEntry = entries.nextElement(); + ZipEntryWrapper zipEntry = new ZipEntryWrapper(entries.nextElement()); if (zipEntry == null || zipEntry.isDirectory()) { continue; @@ -73,7 +74,7 @@ public class ResourcesLoader { resource = new LazyResource(resourceProvider, zipEntry.getSize(), href); } else { resource = ResourceUtil - .createResource(zipEntry, zipFile.getInputStream(zipEntry)); + .createResource(zipEntry.getName(), zipFileWrapper.getInputStream(zipEntry)); /*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/ if (href.endsWith("opf")) { String string = new String(resource.getData()) @@ -136,7 +137,7 @@ public class ResourcesLoader { String href = zipEntry.getName(); // store resource - Resource resource = ResourceUtil.createResource(zipEntry, zipInputStream); + Resource resource = ResourceUtil.createResource(zipEntry.getName(), zipInputStream); ///*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/ if (href.endsWith("opf")) { String string = new String(resource.getData()) @@ -184,7 +185,7 @@ public class ResourcesLoader { * @return Resources * @throws IOException IOException */ - public static Resources loadResources(ZipFile zipFile, String defaultHtmlEncoding) throws IOException { + public static Resources loadResources(ZipFileWrapper zipFile, String defaultHtmlEncoding) throws IOException { List ls = new ArrayList<>(); return loadResources(zipFile, defaultHtmlEncoding, ls); } diff --git a/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java index faf074333..0050af67d 100644 --- a/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java +++ b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; -import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.parsers.DocumentBuilder; @@ -102,20 +101,20 @@ public class ResourceUtil { /** * Creates a resource out of the given zipEntry and zipInputStream. * - * @param zipEntry v + * @param name v * @param zipInputStream v * @return a resource created out of the given zipEntry and zipInputStream. * @throws IOException v */ - public static Resource createResource(ZipEntry zipEntry, + public static Resource createResource(String name, ZipInputStream zipInputStream) throws IOException { - return new Resource(zipInputStream, zipEntry.getName()); + return new Resource(zipInputStream, name); } - public static Resource createResource(ZipEntry zipEntry, + public static Resource createResource(String name, InputStream zipInputStream) throws IOException { - return new Resource(zipInputStream, zipEntry.getName()); + return new Resource(zipInputStream, name); } diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipEntry.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipEntry.java new file mode 100644 index 000000000..eb2812efb --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipEntry.java @@ -0,0 +1,355 @@ + + + +package me.ag2s.epublib.util.zip; + +import java.util.Calendar; +import java.util.Date; +import java.util.zip.ZipOutputStream; + +/** + * This class represents a member of a zip archive. ZipFile and + * ZipInputStream will give you instances of this class as information + * about the members in an archive. On the other hand ZipOutputStream + * needs an instance of this class to create a new member. + * + * @author Jochen Hoenicke + */ +public class AndroidZipEntry implements ZipConstants, Cloneable { + private static final int KNOWN_SIZE = 1; + private static final int KNOWN_CSIZE = 2; + private static final int KNOWN_CRC = 4; + private static final int KNOWN_TIME = 8; + + private static Calendar cal; + + private final String name; + private int size; + private int compressedSize; + private int crc; + private int dostime; + private short known = 0; + private short method = -1; + private byte[] extra = null; + private String comment = null; + + int flags; /* used by ZipOutputStream */ + int offset; /* used by ZipFile and ZipOutputStream */ + + + /** + * Compression method. This method doesn't compress at all. + */ + public final static int STORED = 0; + /** + * Compression method. This method uses the Deflater. + */ + public final static int DEFLATED = 8; + + /** + * Creates a zip entry with the given name. + * + * @param name the name. May include directory components separated + * by '/'. + * @throws NullPointerException when name is null. + * @throws IllegalArgumentException when name is bigger then 65535 chars. + */ + public AndroidZipEntry(String name) { + int length = name.length(); + if (length > 65535) + throw new IllegalArgumentException("name length is " + length); + this.name = name; + } + + /** + * Creates a copy of the given zip entry. + * + * @param e the entry to copy. + */ + public AndroidZipEntry(AndroidZipEntry e) { + name = e.name; + known = e.known; + size = e.size; + compressedSize = e.compressedSize; + crc = e.crc; + dostime = e.dostime; + method = e.method; + extra = e.extra; + comment = e.comment; + } + + final void setDOSTime(int dostime) { + this.dostime = dostime; + known |= KNOWN_TIME; + } + + final int getDOSTime() { + if ((known & KNOWN_TIME) == 0) + return 0; + else + return dostime; + } + + /** + * Creates a copy of this zip entry. + */ + /** + * Clones the entry. + */ + public Object clone() { + try { + // The JCL says that the `extra' field is also copied. + AndroidZipEntry clone = (AndroidZipEntry) super.clone(); + if (extra != null) + clone.extra = (byte[]) extra.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * Returns the entry name. The path components in the entry are + * always separated by slashes ('/'). + */ + public String getName() { + return name; + } + + /** + * Sets the time of last modification of the entry. + * + * @time the time of last modification of the entry. + */ + public void setTime(long time) { + Calendar cal = getCalendar(); + synchronized (cal) { + cal.setTime(new Date(time * 1000L)); + dostime = (cal.get(Calendar.YEAR) - 1980 & 0x7f) << 25 + | (cal.get(Calendar.MONTH) + 1) << 21 + | (cal.get(Calendar.DAY_OF_MONTH)) << 16 + | (cal.get(Calendar.HOUR_OF_DAY)) << 11 + | (cal.get(Calendar.MINUTE)) << 5 + | (cal.get(Calendar.SECOND)) >> 1; + } + dostime = (int) (dostime / 1000L); + this.known |= KNOWN_TIME; + } + + /** + * Gets the time of last modification of the entry. + * + * @return the time of last modification of the entry, or -1 if unknown. + */ + public long getTime() { + if ((known & KNOWN_TIME) == 0) + return -1; + + int sec = 2 * (dostime & 0x1f); + int min = (dostime >> 5) & 0x3f; + int hrs = (dostime >> 11) & 0x1f; + int day = (dostime >> 16) & 0x1f; + int mon = ((dostime >> 21) & 0xf) - 1; + int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */ + + try { + cal = getCalendar(); + synchronized (cal) { + cal.set(year, mon, day, hrs, min, sec); + return cal.getTime().getTime(); + } + } catch (RuntimeException ex) { + /* Ignore illegal time stamp */ + known &= ~KNOWN_TIME; + return -1; + } + } + + private static synchronized Calendar getCalendar() { + if (cal == null) + cal = Calendar.getInstance(); + + return cal; + } + + /** + * Sets the size of the uncompressed data. + * + * @throws IllegalArgumentException if size is not in 0..0xffffffffL + */ + public void setSize(long size) { + if ((size & 0xffffffff00000000L) != 0) + throw new IllegalArgumentException(); + this.size = (int) size; + this.known |= KNOWN_SIZE; + } + + /** + * Gets the size of the uncompressed data. + * + * @return the size or -1 if unknown. + */ + public long getSize() { + return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L; + } + + /** + * Sets the size of the compressed data. + * + * @throws IllegalArgumentException if size is not in 0..0xffffffffL + */ + public void setCompressedSize(long csize) { + if ((csize & 0xffffffff00000000L) != 0) + throw new IllegalArgumentException(); + this.compressedSize = (int) csize; + this.known |= KNOWN_CSIZE; + } + + /** + * Gets the size of the compressed data. + * + * @return the size or -1 if unknown. + */ + public long getCompressedSize() { + return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L; + } + + /** + * Sets the crc of the uncompressed data. + * + * @throws IllegalArgumentException if crc is not in 0..0xffffffffL + */ + public void setCrc(long crc) { + if ((crc & 0xffffffff00000000L) != 0) + throw new IllegalArgumentException(); + this.crc = (int) crc; + this.known |= KNOWN_CRC; + } + + /** + * Gets the crc of the uncompressed data. + * + * @return the crc or -1 if unknown. + */ + public long getCrc() { + return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L; + } + + /** + * Sets the compression method. Only DEFLATED and STORED are + * supported. + * + * @throws IllegalArgumentException if method is not supported. + * @see ZipOutputStream#DEFLATED + * @see ZipOutputStream#STORED + */ + public void setMethod(int method) { + if (method != ZipOutputStream.STORED + && method != ZipOutputStream.DEFLATED) + throw new IllegalArgumentException(); + this.method = (short) method; + } + + /** + * Gets the compression method. + * + * @return the compression method or -1 if unknown. + */ + public int getMethod() { + return method; + } + + /** + * Sets the extra data. + * + * @throws IllegalArgumentException if extra is longer than 0xffff bytes. + */ + public void setExtra(byte[] extra) { + if (extra == null) { + this.extra = null; + return; + } + + if (extra.length > 0xffff) + throw new IllegalArgumentException(); + this.extra = extra; + try { + int pos = 0; + while (pos < extra.length) { + int sig = (extra[pos++] & 0xff) + | (extra[pos++] & 0xff) << 8; + int len = (extra[pos++] & 0xff) + | (extra[pos++] & 0xff) << 8; + if (sig == 0x5455) { + /* extended time stamp */ + int flags = extra[pos]; + if ((flags & 1) != 0) { + long time = ((extra[pos + 1] & 0xff) + | (extra[pos + 2] & 0xff) << 8 + | (extra[pos + 3] & 0xff) << 16 + | (extra[pos + 4] & 0xff) << 24); + setTime(time); + } + } + pos += len; + } + } catch (ArrayIndexOutOfBoundsException ex) { + /* be lenient */ + return; + } + } + + /** + * Gets the extra data. + * + * @return the extra data or null if not set. + */ + public byte[] getExtra() { + return extra; + } + + /** + * Sets the entry comment. + * + * @throws IllegalArgumentException if comment is longer than 0xffff. + */ + public void setComment(String comment) { + if (comment != null && comment.length() > 0xffff) + throw new IllegalArgumentException(); + this.comment = comment; + } + + /** + * Gets the comment. + * + * @return the comment or null if not set. + */ + public String getComment() { + return comment; + } + + /** + * Gets true, if the entry is a directory. This is solely + * determined by the name, a trailing slash '/' marks a directory. + */ + public boolean isDirectory() { + int nlen = name.length(); + return nlen > 0 && name.charAt(nlen - 1) == '/'; + } + + /** + * Gets the string representation of this AndroidZipEntry. This is just + * the name as returned by getName(). + */ + public String toString() { + return name; + } + + /** + * Gets the hashCode of this AndroidZipEntry. This is just the hashCode + * of the name. Note that the equals method isn't changed, though. + */ + public int hashCode() { + return name.hashCode(); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipFile.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipFile.java new file mode 100644 index 000000000..fb4143d38 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/AndroidZipFile.java @@ -0,0 +1,490 @@ + + +package me.ag2s.epublib.util.zip; + +import static me.ag2s.base.PfdHelper.seek; + +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; + +import java.io.BufferedInputStream; +import java.io.DataInput; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipOutputStream; + +import me.ag2s.base.PfdHelper; + +/** + * This class represents a Zip archive. You can ask for the contained + * entries, or get an input stream for a file entry. The entry is + * automatically decompressed. + *

+ * This class is thread safe: You can open input streams for arbitrary + * entries in different threads. + * + * @author Jochen Hoenicke + * @author Artur Biesiadowski + */ +public class AndroidZipFile implements ZipConstants { + + /** + * Mode flag to open a zip file for reading. + */ + public static final int OPEN_READ = 0x1; + + /** + * Mode flag to delete a zip file after reading. + */ + public static final int OPEN_DELETE = 0x4; + + // Name of this zip file. + private final String name; + + // File from which zip entries are read. + //private final RandomAccessFile raf; + + private final ParcelFileDescriptor pfd; + + // The entries of this zip file when initialized and not yet closed. + private HashMap entries; + + private boolean closed = false; + + /** + * Opens a Zip file with the given name for reading. + * + * @throws IOException if a i/o error occured. + * @throws ZipException if the file doesn't contain a valid zip + * archive. + */ + public AndroidZipFile(@NonNull ParcelFileDescriptor pfd, String name) throws ZipException, IOException { + this.pfd = pfd; + this.name = name; + } + + /** + * Opens a Zip file reading the given File. + * + * @throws IOException if a i/o error occured. + * @throws ZipException if the file doesn't contain a valid zip + * archive. + */ + public AndroidZipFile(File file) throws ZipException, IOException { + this.pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + this.name = file.getPath(); + } + + /** + * Opens a Zip file reading the given File in the given mode. + *

+ * If the OPEN_DELETE mode is specified, the zip file will be deleted at + * some time moment after it is opened. It will be deleted before the zip + * file is closed or the Virtual Machine exits. + *

+ * The contents of the zip file will be accessible until it is closed. + *

+ * The OPEN_DELETE mode is currently unimplemented in this library + * + * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE + * @throws IOException if a i/o error occured. + * @throws ZipException if the file doesn't contain a valid zip + * archive. + * @since JDK1.3 + */ +// public AndroidZipFile(File file, int mode) throws ZipException, IOException { +// if ((mode & OPEN_DELETE) != 0) { +// throw new IllegalArgumentException +// ("OPEN_DELETE mode not supported yet in net.sf.jazzlib.AndroidZipFile"); +// } +// this.raf = new RandomAccessFile(file, "r"); +// this.name = file.getPath(); +// } + + /** + * Read an unsigned short in little endian byte order from the given + * DataInput stream using the given byte buffer. + * + * @param di DataInput stream to read from. + * @param b the byte buffer to read in (must be at least 2 bytes long). + * @return The value read. + * @throws IOException if a i/o error occured. + * @throws EOFException if the file ends prematurely + */ + private final int readLeShort(DataInput di, byte[] b) throws IOException { + di.readFully(b, 0, 2); + return (b[0] & 0xff) | (b[1] & 0xff) << 8; + } + + private final int readLeShort(ParcelFileDescriptor pfd, byte[] b) throws IOException { + PfdHelper.readFully(pfd, b, 0, 2);//di.readFully(b, 0, 2); + return (b[0] & 0xff) | (b[1] & 0xff) << 8; + } + + /** + * Read an int in little endian byte order from the given + * DataInput stream using the given byte buffer. + * + * @param di DataInput stream to read from. + * @param b the byte buffer to read in (must be at least 4 bytes long). + * @return The value read. + * @throws IOException if a i/o error occured. + * @throws EOFException if the file ends prematurely + */ + private final int readLeInt(DataInput di, byte[] b) throws IOException { + di.readFully(b, 0, 4); + return ((b[0] & 0xff) | (b[1] & 0xff) << 8) + | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16; + } + + private final int readLeInt(ParcelFileDescriptor pfd, byte[] b) throws IOException { + PfdHelper.readFully(pfd, b, 0, 4);//di.readFully(b, 0, 4); + return ((b[0] & 0xff) | (b[1] & 0xff) << 8) + | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16; + } + + + /** + * Read an unsigned short in little endian byte order from the given + * byte buffer at the given offset. + * + * @param b the byte array to read from. + * @param off the offset to read from. + * @return The value read. + */ + private final int readLeShort(byte[] b, int off) { + return (b[off] & 0xff) | (b[off + 1] & 0xff) << 8; + } + + /** + * Read an int in little endian byte order from the given + * byte buffer at the given offset. + * + * @param b the byte array to read from. + * @param off the offset to read from. + * @return The value read. + */ + private final int readLeInt(byte[] b, int off) { + return ((b[off] & 0xff) | (b[off + 1] & 0xff) << 8) + | ((b[off + 2] & 0xff) | (b[off + 3] & 0xff) << 8) << 16; + } + + + /** + * Read the central directory of a zip file and fill the entries + * array. This is called exactly once when first needed. It is called + * while holding the lock on raf. + * + * @throws IOException if a i/o error occured. + * @throws ZipException if the central directory is malformed + */ + private void readEntries() throws ZipException, IOException { + /* Search for the End Of Central Directory. When a zip comment is + * present the directory may start earlier. + * FIXME: This searches the whole file in a very slow manner if the + * file isn't a zip file. + */ + //long pos = raf.length() - ENDHDR; + long pos = PfdHelper.length(pfd) - ENDHDR; + byte[] ebs = new byte[CENHDR]; + + do { + if (pos < 0) + throw new ZipException + ("central directory not found, probably not a zip file: " + name); + //raf.seek(pos--); + seek(pfd, pos--); + } + //while (readLeInt(raf, ebs) != ENDSIG); + while (readLeInt(pfd, ebs) != ENDSIG); + + if (PfdHelper.skipBytes(pfd, ENDTOT - ENDNRD) != ENDTOT - ENDNRD) + throw new EOFException(name); + //int count = readLeShort(raf, ebs); + int count = readLeShort(pfd, ebs); + if (PfdHelper.skipBytes(pfd, ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ) + throw new EOFException(name); + int centralOffset = readLeInt(pfd, ebs); + + entries = new HashMap<>(count + count / 2); + //raf.seek(centralOffset); + seek(pfd, centralOffset); + + byte[] buffer = new byte[16]; + for (int i = 0; i < count; i++) { + //raf.readFully(ebs); + PfdHelper.readFully(pfd, ebs); + if (readLeInt(ebs, 0) != CENSIG) + throw new ZipException("Wrong Central Directory signature: " + name); + + int method = readLeShort(ebs, CENHOW); + int dostime = readLeInt(ebs, CENTIM); + int crc = readLeInt(ebs, CENCRC); + int csize = readLeInt(ebs, CENSIZ); + int size = readLeInt(ebs, CENLEN); + int nameLen = readLeShort(ebs, CENNAM); + int extraLen = readLeShort(ebs, CENEXT); + int commentLen = readLeShort(ebs, CENCOM); + + int offset = readLeInt(ebs, CENOFF); + + int needBuffer = Math.max(nameLen, commentLen); + if (buffer.length < needBuffer) + buffer = new byte[needBuffer]; + + PfdHelper.readFully(pfd, buffer, 0, nameLen); + String name = new String(buffer, 0, 0, nameLen); + + AndroidZipEntry entry = new AndroidZipEntry(name); + entry.setMethod(method); + entry.setCrc(crc & 0xffffffffL); + entry.setSize(size & 0xffffffffL); + entry.setCompressedSize(csize & 0xffffffffL); + entry.setTime(dostime); + if (extraLen > 0) { + byte[] extra = new byte[extraLen]; + PfdHelper.readFully(pfd, extra); + entry.setExtra(extra); + } + if (commentLen > 0) { + PfdHelper.readFully(pfd, buffer, 0, commentLen); + entry.setComment(new String(buffer, 0, commentLen)); + } + entry.offset = offset; + //ZipEntryHelper.setOffset(entry,offset); + + //entry. = offset; + entries.put(name, entry); + } + } + + /** + * Closes the AndroidZipFile. This also closes all input streams given by + * this class. After this is called, no further method should be + * called. + * + * @throws IOException if a i/o error occured. + */ + public void close() throws IOException { + synchronized (pfd) { + closed = true; + entries = null; + pfd.close(); + } + } + + /** + * Calls the close() method when this AndroidZipFile has not yet + * been explicitly closed. + */ + protected void finalize() throws IOException { + if (!closed && pfd != null) close(); + } + + /** + * Returns an enumeration of all Zip entries in this Zip file. + */ + public Enumeration entries() { + try { + return new ZipEntryEnumeration(getEntries().values().iterator()); + } catch (IOException ioe) { + return null; + } + } + + /** + * Checks that the AndroidZipFile is still open and reads entries when necessary. + * + * @throws IllegalStateException when the AndroidZipFile has already been closed. + * @throws java, IOEexception when the entries could not be read. + */ + private HashMap getEntries() throws IOException { + synchronized (pfd) { + if (closed) + throw new IllegalStateException("AndroidZipFile has closed: " + name); + + if (entries == null) + readEntries(); + + return entries; + } + } + + /** + * Searches for a zip entry in this archive with the given name. + * + * @param name name. May contain directory components separated by + * slashes ('/'). + * @return the zip entry, or null if no entry with that name exists. + */ + public AndroidZipEntry getEntry(String name) { + try { + HashMap entries = getEntries(); + AndroidZipEntry entry = entries.get(name); + return entry != null ? (AndroidZipEntry) entry.clone() : null; + } catch (IOException ioe) { + return null; + } + } + + + //access should be protected by synchronized(raf) + private final byte[] locBuf = new byte[LOCHDR]; + + /** + * Checks, if the local header of the entry at index i matches the + * central directory, and returns the offset to the data. + * + * @param entry to check. + * @return the start offset of the (compressed) data. + * @throws IOException if a i/o error occured. + * @throws ZipException if the local header doesn't match the + * central directory header + */ + private long checkLocalHeader(AndroidZipEntry entry) throws IOException { + synchronized (pfd) { + seek(pfd, entry.offset); + PfdHelper.readFully(pfd, locBuf); + + if (readLeInt(locBuf, 0) != LOCSIG) + throw new ZipException("Wrong Local header signature: " + name); + + if (entry.getMethod() != readLeShort(locBuf, LOCHOW)) + throw new ZipException("Compression method mismatch: " + name); + + if (entry.getName().length() != readLeShort(locBuf, LOCNAM)) + throw new ZipException("file name length mismatch: " + name); + + int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT); + return entry.offset + LOCHDR + extraLen; + } + } + + /** + * Creates an input stream reading the given zip entry as + * uncompressed data. Normally zip entry should be an entry + * returned by getEntry() or entries(). + * + * @param entry the entry to create an InputStream for. + * @return the input stream. + * @throws IOException if a i/o error occured. + * @throws ZipException if the Zip archive is malformed. + */ + public InputStream getInputStream(AndroidZipEntry entry) throws IOException { + HashMap entries = getEntries(); + String name = entry.getName(); + AndroidZipEntry zipEntry = (AndroidZipEntry) entries.get(name); + if (zipEntry == null) + throw new NoSuchElementException(name); + + long start = checkLocalHeader((AndroidZipEntry) zipEntry); + int method = zipEntry.getMethod(); + InputStream is = new BufferedInputStream(new PartialInputStream + (pfd, start, zipEntry.getCompressedSize())); + switch (method) { + case ZipOutputStream.STORED: + return is; + case ZipOutputStream.DEFLATED: + return new InflaterInputStream(is, new Inflater(true)); + default: + throw new ZipException("Unknown compression method " + method); + } + } + + /** + * Returns the (path) name of this zip file. + */ + public String getName() { + return name; + } + + /** + * Returns the number of entries in this zip file. + */ + public int size() { + try { + return getEntries().size(); + } catch (IOException ioe) { + return 0; + } + } + + private static class ZipEntryEnumeration implements Enumeration { + private final Iterator elements; + + public ZipEntryEnumeration(Iterator elements) { + this.elements = elements; + } + + public boolean hasMoreElements() { + return elements.hasNext(); + } + + public AndroidZipEntry nextElement() { + /* We return a clone, just to be safe that the user doesn't + * change the entry. + */ + return (AndroidZipEntry) (elements.next()).clone(); + } + } + + private static class PartialInputStream extends InputStream { + private final ParcelFileDescriptor pfd; + long filepos, end; + + public PartialInputStream(ParcelFileDescriptor pfd, long start, long len) { + this.pfd = pfd; + filepos = start; + end = start + len; + } + + public int available() { + long amount = end - filepos; + if (amount > Integer.MAX_VALUE) + return Integer.MAX_VALUE; + return (int) amount; + } + + public int read() throws IOException { + if (filepos == end) + return -1; + synchronized (pfd) { + seek(pfd, filepos++); + return PfdHelper.read(pfd); + } + } + + public int read(byte[] b, int off, int len) throws IOException { + if (len > end - filepos) { + len = (int) (end - filepos); + if (len == 0) + return -1; + } + synchronized (pfd) { + seek(pfd, filepos); + int count = PfdHelper.read(pfd, b, off, len); + if (count > 0) + filepos += len; + return count; + } + } + + public long skip(long amount) { + if (amount < 0) + throw new IllegalArgumentException(); + if (amount > end - filepos) + amount = end - filepos; + filepos += amount; + return amount; + } + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipConstants.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipConstants.java new file mode 100644 index 000000000..a805a5757 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipConstants.java @@ -0,0 +1,61 @@ + + +package me.ag2s.epublib.util.zip; + +interface ZipConstants { + /* The local file header */ + int LOCHDR = 30; + int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); + + int LOCVER = 4; + int LOCFLG = 6; + int LOCHOW = 8; + int LOCTIM = 10; + int LOCCRC = 14; + int LOCSIZ = 18; + int LOCLEN = 22; + int LOCNAM = 26; + int LOCEXT = 28; + + /* The Data descriptor */ + int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + int EXTHDR = 16; + + int EXTCRC = 4; + int EXTSIZ = 8; + int EXTLEN = 12; + + /* The central directory file header */ + int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); + int CENHDR = 46; + + int CENVEM = 4; + int CENVER = 6; + int CENFLG = 8; + int CENHOW = 10; + int CENTIM = 12; + int CENCRC = 16; + int CENSIZ = 20; + int CENLEN = 24; + int CENNAM = 28; + int CENEXT = 30; + int CENCOM = 32; + int CENDSK = 34; + int CENATT = 36; + int CENATX = 38; + int CENOFF = 42; + + /* The entries in the end of central directory */ + int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); + int ENDHDR = 22; + + /* The following two fields are missing in SUN JDK */ + int ENDNRD = 4; + int ENDDCD = 6; + int ENDSUB = 8; + int ENDTOT = 10; + int ENDSIZ = 12; + int ENDOFF = 16; + int ENDCOM = 20; +} + diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipEntryWrapper.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipEntryWrapper.java new file mode 100644 index 000000000..5611bec2f --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipEntryWrapper.java @@ -0,0 +1,75 @@ +package me.ag2s.epublib.util.zip; + +import androidx.annotation.NonNull; + +import java.util.zip.ZipEntry; + +public class ZipEntryWrapper { + @NonNull + private final Object zipEntry; + + public void checkType() { + + if (zipEntry instanceof java.util.zip.ZipEntry || zipEntry instanceof AndroidZipEntry) { + } else { + throw new RuntimeException("使用了不支持的类"); + } + } + + public ZipEntryWrapper(@NonNull ZipEntry zipEntry) { + this.zipEntry = zipEntry; + } + + public ZipEntryWrapper(@NonNull AndroidZipEntry zipEntry) { + this.zipEntry = zipEntry; + } + + public ZipEntryWrapper(@NonNull Object element) { + + this.zipEntry = element; + checkType(); + } + + public boolean isDirectory() { + checkType(); + if (zipEntry instanceof ZipEntry) { + return ((ZipEntry) zipEntry).isDirectory(); + } + if (zipEntry instanceof AndroidZipEntry) { + return ((AndroidZipEntry) zipEntry).isDirectory(); + } + return true; + } + + public ZipEntry getZipEntry() { + return (ZipEntry) zipEntry; + } + + public AndroidZipEntry getAndroidZipEntry() { + return (AndroidZipEntry) zipEntry; + } + + public String getName() { + checkType(); + if (zipEntry instanceof ZipEntry) { + return ((ZipEntry) zipEntry).getName(); + } + if (zipEntry instanceof AndroidZipEntry) { + return ((AndroidZipEntry) zipEntry).getName(); + } + return null; + } + + public long getSize() { + checkType(); + if (zipEntry instanceof ZipEntry) { + return ((ZipEntry) zipEntry).getSize(); + } + if (zipEntry instanceof AndroidZipEntry) { + return ((AndroidZipEntry) zipEntry).getSize(); + } + return -1; + } + + +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipException.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipException.java new file mode 100644 index 000000000..11ca4d052 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipException.java @@ -0,0 +1,34 @@ + + +package me.ag2s.epublib.util.zip; + +import java.io.IOException; + +/** + * Thrown during the creation or input of a zip file. + * + * @author Jochen Hoenicke + * @author Per Bothner + * @status updated to 1.4 + */ +public class ZipException extends IOException { + /** + * Compatible with JDK 1.0+. + */ + private static final long serialVersionUID = 8000196834066748623L; + + /** + * Create an exception without a message. + */ + public ZipException() { + } + + /** + * Create an exception with a message. + * + * @param msg the message + */ + public ZipException(String msg) { + super(msg); + } +} diff --git a/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipFileWrapper.java b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipFileWrapper.java new file mode 100644 index 000000000..2cd7fcb53 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/epublib/util/zip/ZipFileWrapper.java @@ -0,0 +1,103 @@ +package me.ag2s.epublib.util.zip; + + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.zip.ZipFile; + +/** + * 对ZipFile的包装 + */ + +public class ZipFileWrapper { + @NonNull + private final Object zipFile; + + + public void checkType() { + if (zipFile instanceof java.util.zip.ZipFile || zipFile instanceof AndroidZipFile) { + } else { + throw new RuntimeException("使用了不支持的类"); + } + } + + public ZipFileWrapper(@NonNull ZipFile zipFile) { + this.zipFile = zipFile; + checkType(); + } + + public ZipFileWrapper(@NonNull AndroidZipFile zipFile) { + this.zipFile = zipFile; + checkType(); + } + + public String getName() { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + return ((ZipFile) zipFile).getName(); + } else if (zipFile instanceof AndroidZipFile) { + return ((AndroidZipFile) zipFile).getName(); + } else { + return null; + } + } + + public String getComment() { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + return ((ZipFile) zipFile).getComment(); + } else if (zipFile instanceof AndroidZipFile) { + return ((AndroidZipFile) zipFile).getName(); + } else { + return null; + } + } + + public ZipEntryWrapper getEntry(String name) { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + return new ZipEntryWrapper(((ZipFile) zipFile).getEntry(name)); + } else if (zipFile instanceof AndroidZipFile) { + return new ZipEntryWrapper(((AndroidZipFile) zipFile).getEntry(name)); + } else { + return null; + } + } + + public Enumeration entries() { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + return ((ZipFile) zipFile).entries(); + + } else if (zipFile instanceof AndroidZipFile) { + return ((AndroidZipFile) zipFile).entries(); + } else { + return null; + } + } + + public InputStream getInputStream(ZipEntryWrapper entry) throws IOException { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + return ((ZipFile) zipFile).getInputStream(entry.getZipEntry()); + } else if (zipFile instanceof AndroidZipFile) { + return ((AndroidZipFile) zipFile).getInputStream(entry.getAndroidZipEntry()); + } else { + return null; + } + } + + public void close() throws IOException { + checkType(); + if (zipFile instanceof java.util.zip.ZipFile) { + ((ZipFile) zipFile).close(); + } else if (zipFile instanceof AndroidZipFile) { + ((AndroidZipFile) zipFile).close(); + } + } + + +}