bMain
bMain

Reputation: 354

Get an InputStream from a ZipEntry from a ZipInputStream from a DocumentFile

This is for an experimental Android project done in Java. What I need is to read a ZIP file from an external card (as fast as possible, if possible) and return the InputStream corresponding to an entry each time it is needed by an OSMDroid custom tiles provider.

I can do this pretty easily when serving a ZIP from the primary (or whatever it's called within their mangled filesystem) disk but when trying to read from an external card, the only option in newer versions of Android is to use their shiny SAF.

When the path is not a relative one but a URI to an external card, I use a DocumentFile to retrieve the ZIP's InputStream (apparently, the only way to do it).

DocumentFile file = DocumentFile.fromSingleUri(this, uri);
ContentResolver contentResolver = getContentResolver();
InputStream inStream = contentResolver.openInputStream(file.getUri());

Question 1: What would be amazing at this point, where using DocumentFile already took its toll on performance due to how slower it is than the good ol File, is to be able to have that inStream converted into a ZipFile. Or somehow directly get a ZipFile from DocumentFile. Is that possible? Maybe get a File from DocumentFile then a ZipFile from that?

I didn't find any solution so I went on with the InputStream, which I converted to a ZipInputStream. The problem in my code is that I can only run through it once, when what I need is to do it every time a file's stream from the Zip is needed.

public class MyZipFileArchive implements IArchiveFile {
    protected static ZipInputStream mZipInputStream;
}

public void init(InputStream inStream) {
    mZipInputStream = new ZipInputStream(inStream);
}

public InputStream getInputStream(ITileSource pTileSource, long pMapTileIndex) {
    final String path = pTileSource.getTileRelativeFilenameString(pMapTileIndex);
    ZipEntry entry = null;
    while ((entry = mZipInputStream.getNextEntry()) != null) {
        if (path.equals(entry.getName())) {
            return mZipInputStream;
        }
    }
    return null;
}

Question 2: The first time the getInputStream is called it returns what it finds but now the stream is at its end and the second time it does it, null is returned.

So, I need a way to either reset the stream, if possible, each time the function is called, or use another approach. I tried storing each ZipEntry into a List at the beginning, then loop through it. It doesn't work. Also, I did not find a way to convert ZipEntry into the InputStream required to be returned.

How would you tackle this? Basically, I need to use DocumentFile and I need to get a ZipFile from it or some fast way to find its entries.

Upvotes: 1

Views: 486

Answers (1)

johngray1965
johngray1965

Reputation: 652

You need to read the stream from the DocumentFile in one pass:

// the uri to your zip document in SAF
context.contentResolver.openInputStream(uri).use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
   generateSequence { zipInputStream.nextEntry }.forEach { entry ->
       if (entry.name.startsWith("/")) {
           throw SecurityException("Dangerous Zip entry: $entry")
        }
        if (entry.isDirectory) {
            // create the directory for entry.name
         } else {
            // get outputStream for dest file for entry.name
            outputStream.use { zipOutputStream ->
                zipInputStream.copyTo(zipOutputStream)
             }
          }
       }
    }
}

Upvotes: 0

Related Questions