mirror of
https://github.com/geometer/FBReaderJ.git
synced 2025-10-03 17:59:33 +02:00
fixed zip deflating on android 4.0 devices
This commit is contained in:
parent
e10597aadb
commit
46ef1eaeb4
4 changed files with 218 additions and 226 deletions
|
@ -7,52 +7,41 @@
|
||||||
|
|
||||||
#define SIZE 10
|
#define SIZE 10
|
||||||
|
|
||||||
static jobject keys[SIZE] = { 0 };
|
static z_stream* ourStreams[SIZE] = { 0 };
|
||||||
static z_stream* values[SIZE] = { 0 };
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
jboolean Java_org_amse_ys_zip_DeflatingDecompressor_startInflating(JNIEnv *env, jobject thiz) {
|
jint Java_org_amse_ys_zip_DeflatingDecompressor_startInflating(JNIEnv *env, jobject thiz) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < SIZE; ++i) {
|
for (i = 0; i < SIZE; ++i) {
|
||||||
if (keys[i] == 0) {
|
if (ourStreams[i] == 0) {
|
||||||
keys[i] = thiz;
|
ourStreams[i] = new z_stream;
|
||||||
values[i] = new z_stream;
|
memset(ourStreams[i], 0, sizeof(z_stream));
|
||||||
memset(values[i], 0, sizeof(z_stream));
|
inflateInit2(ourStreams[i], -MAX_WBITS);
|
||||||
inflateInit2(values[i], -MAX_WBITS);
|
return i;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
void Java_org_amse_ys_zip_DeflatingDecompressor_endInflating(JNIEnv *env, jobject thiz) {
|
void Java_org_amse_ys_zip_DeflatingDecompressor_endInflating(JNIEnv *env, jobject thiz, jint inflatorId) {
|
||||||
int i;
|
if (inflatorId >= 0 && inflatorId < SIZE) {
|
||||||
for (i = 0; i < SIZE; ++i) {
|
inflateEnd(ourStreams[inflatorId]);
|
||||||
if (keys[i] == thiz) {
|
delete ourStreams[inflatorId];
|
||||||
keys[i] = 0;
|
ourStreams[inflatorId] = 0;
|
||||||
inflateEnd(values[i]);
|
|
||||||
delete values[i];
|
|
||||||
values[i] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns (endFlag << 32) + ((used inLength) << 16) + outLength
|
// returns (endFlag << 32) + ((used inLength) << 16) + outLength
|
||||||
extern "C"
|
extern "C"
|
||||||
jlong Java_org_amse_ys_zip_DeflatingDecompressor_inflate(JNIEnv *env, jobject thiz, jbyteArray in, jint inOffset, jint inLength, jbyteArray out) {
|
jlong Java_org_amse_ys_zip_DeflatingDecompressor_inflate(JNIEnv *env, jobject thiz, jint inflatorId, jbyteArray in, jint inOffset, jint inLength, jbyteArray out) {
|
||||||
int i;
|
if (inflatorId < 0 || inflatorId >= SIZE) {
|
||||||
z_stream *stream = 0;
|
|
||||||
for (i = 0; i < SIZE; ++i) {
|
|
||||||
if (keys[i] == thiz) {
|
|
||||||
stream = values[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stream == 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
z_stream *stream = ourStreams[inflatorId];
|
||||||
|
if (stream == 0) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
jbyte* inStart = env->GetByteArrayElements(in, 0);
|
jbyte* inStart = env->GetByteArrayElements(in, 0);
|
||||||
jbyte* outStart = env->GetByteArrayElements(out, 0);
|
jbyte* outStart = env->GetByteArrayElements(out, 0);
|
||||||
|
@ -71,5 +60,5 @@ jlong Java_org_amse_ys_zip_DeflatingDecompressor_inflate(JNIEnv *env, jobject th
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return -2;
|
return -3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ public abstract class Decompressor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Decompressor init(MyBufferedInputStream is, LocalFileHeader header) throws IOException {
|
static Decompressor init(MyBufferedInputStream is, LocalFileHeader header) throws IOException {
|
||||||
switch (header.CompressionMethod) {
|
switch (header.CompressionMethod) {
|
||||||
case 0:
|
case 0:
|
||||||
return new NoCompressionDecompressor(is, header);
|
return new NoCompressionDecompressor(is, header);
|
||||||
|
|
|
@ -7,10 +7,10 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
System.loadLibrary("DeflatingDecompressor");
|
System.loadLibrary("DeflatingDecompressor");
|
||||||
}
|
}
|
||||||
|
|
||||||
// common variables
|
// common variables
|
||||||
private MyBufferedInputStream myStream;
|
private MyBufferedInputStream myStream;
|
||||||
private int myCompressedAvailable;
|
private int myCompressedAvailable;
|
||||||
private int myAvailable;
|
private int myAvailable;
|
||||||
|
|
||||||
private static final int IN_BUFFER_SIZE = 2048;
|
private static final int IN_BUFFER_SIZE = 2048;
|
||||||
private static final int OUT_BUFFER_SIZE = 32768;
|
private static final int OUT_BUFFER_SIZE = 32768;
|
||||||
|
@ -22,21 +22,21 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
private int myOutBufferOffset;
|
private int myOutBufferOffset;
|
||||||
private int myOutBufferLength;
|
private int myOutBufferLength;
|
||||||
|
|
||||||
private boolean myInflatingInProgress;
|
private volatile int myInflatorId = -1;
|
||||||
|
|
||||||
public DeflatingDecompressor(MyBufferedInputStream inputStream, LocalFileHeader header) throws IOException {
|
public DeflatingDecompressor(MyBufferedInputStream inputStream, LocalFileHeader header) throws IOException {
|
||||||
super();
|
super();
|
||||||
reset(inputStream, header);
|
reset(inputStream, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(MyBufferedInputStream inputStream, LocalFileHeader header) throws IOException {
|
void reset(MyBufferedInputStream inputStream, LocalFileHeader header) throws IOException {
|
||||||
if (myInflatingInProgress) {
|
if (myInflatorId != -1) {
|
||||||
endInflating();
|
endInflating(myInflatorId);
|
||||||
myInflatingInProgress = false;
|
myInflatorId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
myStream = inputStream;
|
myStream = inputStream;
|
||||||
myCompressedAvailable = header.CompressedSize;
|
myCompressedAvailable = header.CompressedSize;
|
||||||
if (myCompressedAvailable <= 0) {
|
if (myCompressedAvailable <= 0) {
|
||||||
myCompressedAvailable = Integer.MAX_VALUE;
|
myCompressedAvailable = Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
@ -50,17 +50,19 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
myOutBufferOffset = OUT_BUFFER_SIZE;
|
myOutBufferOffset = OUT_BUFFER_SIZE;
|
||||||
myOutBufferLength = 0;
|
myOutBufferLength = 0;
|
||||||
|
|
||||||
startInflating();
|
myInflatorId = startInflating();
|
||||||
myInflatingInProgress = true;
|
if (myInflatorId == -1) {
|
||||||
}
|
throw new IOException("cannot start inflating");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() {
|
public int available() {
|
||||||
return myAvailable;
|
return myAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
if (myAvailable <= 0) {
|
if (myAvailable <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,7 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
fillOutBuffer();
|
fillOutBuffer();
|
||||||
}
|
}
|
||||||
if (myOutBufferLength == 0) {
|
if (myOutBufferLength == 0) {
|
||||||
if (myInflatingInProgress) {
|
if (myInflatorId != -1) {
|
||||||
throw new IOException("cannot read from zip");
|
throw new IOException("cannot read from zip");
|
||||||
} else {
|
} else {
|
||||||
len -= toFill;
|
len -= toFill;
|
||||||
|
@ -94,10 +96,10 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
myAvailable = 0;
|
myAvailable = 0;
|
||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (myAvailable <= 0) {
|
if (myAvailable <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +107,7 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
fillOutBuffer();
|
fillOutBuffer();
|
||||||
}
|
}
|
||||||
if (myOutBufferLength == 0) {
|
if (myOutBufferLength == 0) {
|
||||||
if (myInflatingInProgress) {
|
if (myInflatorId != -1) {
|
||||||
throw new IOException("cannot read from zip");
|
throw new IOException("cannot read from zip");
|
||||||
} else {
|
} else {
|
||||||
myAvailable = 0;
|
myAvailable = 0;
|
||||||
|
@ -115,10 +117,10 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
--myAvailable;
|
--myAvailable;
|
||||||
--myOutBufferLength;
|
--myOutBufferLength;
|
||||||
return myOutBuffer[myOutBufferOffset++];
|
return myOutBuffer[myOutBufferOffset++];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillOutBuffer() throws IOException {
|
private void fillOutBuffer() throws IOException {
|
||||||
if (!myInflatingInProgress) {
|
if (myInflatorId == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +138,7 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
if (myInBufferLength == 0) {
|
if (myInBufferLength == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final long result = inflate(myInBuffer, myInBufferOffset, myInBufferLength, myOutBuffer);
|
final long result = inflate(myInflatorId, myInBuffer, myInBufferOffset, myInBufferLength, myOutBuffer);
|
||||||
if (result <= 0) {
|
if (result <= 0) {
|
||||||
throw new IOException("Cannot inflate zip-compressed block, code = " + result);
|
throw new IOException("Cannot inflate zip-compressed block, code = " + result);
|
||||||
}
|
}
|
||||||
|
@ -147,15 +149,15 @@ class DeflatingDecompressor extends Decompressor {
|
||||||
myOutBufferOffset = 0;
|
myOutBufferOffset = 0;
|
||||||
myOutBufferLength = out;
|
myOutBufferLength = out;
|
||||||
if ((result & (1L << 32)) != 0) {
|
if ((result & (1L << 32)) != 0) {
|
||||||
endInflating();
|
endInflating(myInflatorId);
|
||||||
myInflatingInProgress = false;
|
myInflatorId = -1;
|
||||||
myStream.backSkip(myInBufferLength);
|
myStream.backSkip(myInBufferLength);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private native boolean startInflating();
|
private native int startInflating();
|
||||||
private native void endInflating();
|
private native void endInflating(int inflatorId);
|
||||||
private native long inflate(byte[] in, int inOffset, int inLength, byte[] out);
|
private native long inflate(int inflatorId, byte[] in, int inOffset, int inLength, byte[] out);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,160 +1,161 @@
|
||||||
package org.amse.ys.zip;
|
package org.amse.ys.zip;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class ZipFile {
|
public final class ZipFile {
|
||||||
public static interface InputStreamHolder {
|
public static interface InputStreamHolder {
|
||||||
InputStream getInputStream() throws IOException;
|
InputStream getInputStream() throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FileInputStreamHolder implements InputStreamHolder {
|
private static final class FileInputStreamHolder implements InputStreamHolder {
|
||||||
private final String myFilePath;
|
private final String myFilePath;
|
||||||
|
|
||||||
FileInputStreamHolder(String filePath) {
|
FileInputStreamHolder(String filePath) {
|
||||||
myFilePath = filePath;
|
myFilePath = filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getInputStream() throws IOException {
|
public InputStream getInputStream() throws IOException {
|
||||||
return new FileInputStream(myFilePath);
|
return new FileInputStream(myFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final InputStreamHolder myStreamHolder;
|
private final InputStreamHolder myStreamHolder;
|
||||||
private final LinkedHashMap<String,LocalFileHeader> myFileHeaders = new LinkedHashMap<String,LocalFileHeader>() {
|
private final LinkedHashMap<String,LocalFileHeader> myFileHeaders = new LinkedHashMap<String,LocalFileHeader>() {
|
||||||
private static final long serialVersionUID = -4412796553514902113L;
|
private static final long serialVersionUID = -4412796553514902113L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalFileHeader get(Object key) {
|
public LocalFileHeader get(Object key) {
|
||||||
return super.get(((String)key).toLowerCase());
|
return super.get(((String)key).toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalFileHeader put(String key, LocalFileHeader value) {
|
public LocalFileHeader put(String key, LocalFileHeader value) {
|
||||||
return super.put(key.toLowerCase(), value);
|
return super.put(key.toLowerCase(), value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean myAllFilesAreRead;
|
private boolean myAllFilesAreRead;
|
||||||
|
|
||||||
public ZipFile(String filePath) {
|
public ZipFile(String filePath) {
|
||||||
this(new FileInputStreamHolder(filePath));
|
this(new FileInputStreamHolder(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipFile(InputStreamHolder streamHolder) {
|
public ZipFile(InputStreamHolder streamHolder) {
|
||||||
myStreamHolder = streamHolder;
|
myStreamHolder = streamHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<LocalFileHeader> headers() {
|
public Collection<LocalFileHeader> headers() {
|
||||||
try {
|
try {
|
||||||
readAllHeaders();
|
readAllHeaders();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
}
|
}
|
||||||
return myFileHeaders.values();
|
return myFileHeaders.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean readFileHeader(MyBufferedInputStream baseStream, String fileToFind) throws IOException {
|
private boolean readFileHeader(MyBufferedInputStream baseStream, String fileToFind) throws IOException {
|
||||||
LocalFileHeader header = new LocalFileHeader();
|
LocalFileHeader header = new LocalFileHeader();
|
||||||
header.readFrom(baseStream);
|
header.readFrom(baseStream);
|
||||||
|
|
||||||
if (header.Signature != LocalFileHeader.FILE_HEADER_SIGNATURE) {
|
if (header.Signature != LocalFileHeader.FILE_HEADER_SIGNATURE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (header.FileName != null) {
|
if (header.FileName != null) {
|
||||||
myFileHeaders.put(header.FileName, header);
|
myFileHeaders.put(header.FileName, header);
|
||||||
if (header.FileName.equalsIgnoreCase(fileToFind)) {
|
if (header.FileName.equalsIgnoreCase(fileToFind)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((header.Flags & 0x08) == 0) {
|
if ((header.Flags & 0x08) == 0) {
|
||||||
baseStream.skip(header.CompressedSize);
|
baseStream.skip(header.CompressedSize);
|
||||||
} else {
|
} else {
|
||||||
findAndReadDescriptor(baseStream, header);
|
findAndReadDescriptor(baseStream, header);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readAllHeaders() throws IOException {
|
private void readAllHeaders() throws IOException {
|
||||||
if (myAllFilesAreRead) {
|
if (myAllFilesAreRead) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myAllFilesAreRead = true;
|
myAllFilesAreRead = true;
|
||||||
|
|
||||||
MyBufferedInputStream baseStream = getBaseStream();
|
MyBufferedInputStream baseStream = getBaseStream();
|
||||||
baseStream.setPosition(0);
|
baseStream.setPosition(0);
|
||||||
myFileHeaders.clear();
|
myFileHeaders.clear();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
readFileHeader(baseStream, null);
|
readFileHeader(baseStream, null);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
storeBaseStream(baseStream);
|
storeBaseStream(baseStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds descriptor of the last header and installs sizes of files
|
* Finds descriptor of the last header and installs sizes of files
|
||||||
*/
|
*/
|
||||||
private void findAndReadDescriptor(MyBufferedInputStream baseStream, LocalFileHeader header) throws IOException {
|
private void findAndReadDescriptor(MyBufferedInputStream baseStream, LocalFileHeader header) throws IOException {
|
||||||
Decompressor decompressor = Decompressor.init(baseStream, header);
|
Decompressor decompressor = Decompressor.init(baseStream, header);
|
||||||
int uncompressedSize = 0;
|
int uncompressedSize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
int blockSize = decompressor.read(null, 0, 2048);
|
int blockSize = decompressor.read(null, 0, 2048);
|
||||||
if (blockSize <= 0) {
|
if (blockSize <= 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
uncompressedSize += blockSize;
|
uncompressedSize += blockSize;
|
||||||
}
|
}
|
||||||
header.UncompressedSize = uncompressedSize;
|
header.UncompressedSize = uncompressedSize;
|
||||||
}
|
Decompressor.storeDecompressor(decompressor);
|
||||||
|
}
|
||||||
private final Queue<MyBufferedInputStream> myStoredStreams = new LinkedList<MyBufferedInputStream>();
|
|
||||||
|
private final Queue<MyBufferedInputStream> myStoredStreams = new LinkedList<MyBufferedInputStream>();
|
||||||
synchronized void storeBaseStream(MyBufferedInputStream baseStream) {
|
|
||||||
myStoredStreams.add(baseStream);
|
synchronized void storeBaseStream(MyBufferedInputStream baseStream) {
|
||||||
}
|
myStoredStreams.add(baseStream);
|
||||||
|
}
|
||||||
synchronized MyBufferedInputStream getBaseStream() throws IOException {
|
|
||||||
MyBufferedInputStream baseStream = myStoredStreams.poll();
|
synchronized MyBufferedInputStream getBaseStream() throws IOException {
|
||||||
return (baseStream != null) ? baseStream : new MyBufferedInputStream(myStreamHolder);
|
MyBufferedInputStream baseStream = myStoredStreams.poll();
|
||||||
}
|
return (baseStream != null) ? baseStream : new MyBufferedInputStream(myStreamHolder);
|
||||||
|
}
|
||||||
private ZipInputStream createZipInputStream(LocalFileHeader header) throws IOException {
|
|
||||||
return new ZipInputStream(this, header);
|
private ZipInputStream createZipInputStream(LocalFileHeader header) throws IOException {
|
||||||
}
|
return new ZipInputStream(this, header);
|
||||||
|
}
|
||||||
public int getEntrySize(String entryName) throws IOException {
|
|
||||||
return getHeader(entryName).UncompressedSize;
|
public int getEntrySize(String entryName) throws IOException {
|
||||||
}
|
return getHeader(entryName).UncompressedSize;
|
||||||
|
}
|
||||||
public InputStream getInputStream(String entryName) throws IOException {
|
|
||||||
return createZipInputStream(getHeader(entryName));
|
public InputStream getInputStream(String entryName) throws IOException {
|
||||||
}
|
return createZipInputStream(getHeader(entryName));
|
||||||
|
}
|
||||||
public LocalFileHeader getHeader(String entryName) throws IOException {
|
|
||||||
if (!myFileHeaders.isEmpty()) {
|
public LocalFileHeader getHeader(String entryName) throws IOException {
|
||||||
LocalFileHeader header = myFileHeaders.get(entryName);
|
if (!myFileHeaders.isEmpty()) {
|
||||||
if (header != null) {
|
LocalFileHeader header = myFileHeaders.get(entryName);
|
||||||
return header;
|
if (header != null) {
|
||||||
}
|
return header;
|
||||||
if (myAllFilesAreRead) {
|
}
|
||||||
throw new ZipException("Entry " + entryName + " is not found");
|
if (myAllFilesAreRead) {
|
||||||
}
|
throw new ZipException("Entry " + entryName + " is not found");
|
||||||
}
|
}
|
||||||
// ready to read file header
|
}
|
||||||
MyBufferedInputStream baseStream = getBaseStream();
|
// ready to read file header
|
||||||
baseStream.setPosition(0);
|
MyBufferedInputStream baseStream = getBaseStream();
|
||||||
try {
|
baseStream.setPosition(0);
|
||||||
while (!readFileHeader(baseStream, entryName)) {
|
try {
|
||||||
}
|
while (!readFileHeader(baseStream, entryName)) {
|
||||||
LocalFileHeader header = myFileHeaders.get(entryName);
|
}
|
||||||
if (header != null) {
|
LocalFileHeader header = myFileHeaders.get(entryName);
|
||||||
return header;
|
if (header != null) {
|
||||||
}
|
return header;
|
||||||
} finally {
|
}
|
||||||
storeBaseStream(baseStream);
|
} finally {
|
||||||
}
|
storeBaseStream(baseStream);
|
||||||
throw new ZipException("Entry " + entryName + " is not found");
|
}
|
||||||
}
|
throw new ZipException("Entry " + entryName + " is not found");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue