How to extract zip archive with subdirectories using SAF / DocumentFile?

657 Views Asked by At

I'm struggling with extracting a zip file contents using the evil Storage Access Framework, as you may know; I can't use any File objects so I have to use ZipInputStream, ZipOutputStream and DocumentFile, Here's the zip file structure:

Folder 1/ABC 001.jpg
Folder 1/ABC 002.jpg
Folder 2/ABC 003.jpg
Folder 2/ABC 004.jpg
Folder 2/Folder 3/ABC 005.jpg
Folder 2/Folder 3/ABC 006.jpg
Folder 2/Folder 3/Folder 4/ABC 007.jpg
Folder 2/Folder 3/Folder 4/ABC 008.jpg
ABC 009.jpg

Here's my code:

    public void extractZipFile(DocumentFile srcZipFile, DocumentFile destDir) throws IOException
    {
        ZipEntry entry;

        InputStream inputStream = resolver.openInputStream(srcZipFile.getUri());

        try (java.util.zip.ZipInputStream zipInputStream = new java.util.zip.ZipInputStream(inputStream))
        {
            while ((entry = zipInputStream.getNextEntry()) != null)
            {
                DocumentFile currentDestDir = destDir;

                if (!entry.isDirectory())
                {
                    unzipFile(entry, zipInputStream, currentDestDir);
                }
                else
                {
                    String finalFolderName = entry.getName().replace("/", "");
                    currentDestDir = destDir.createDirectory(finalFolderName);
                }
            }
        }

        inputStream.close();
    }

    private void unzipFile(ZipEntry fileEntry, java.util.zip.ZipInputStream zipInputStream, DocumentFile destDir) throws IOException
    {
        int readLen;
        byte[] readBuffer = new byte[BUFFER_SIZE];

        DocumentFile destFile = destDir.createFile("*/*", fileEntry.getName());

        try (OutputStream outputStream = resolver.openOutputStream(destFile.getUri()))
        {
            while ((readLen = zipInputStream.read(readBuffer)) != -1)
            {
                outputStream.write(readBuffer, 0, readLen);
            }
        }
    }

And here's how the out put look like:

output

Thanks

3

There are 3 best solutions below

1
On

Use Unzip(); method form FileUtilsPlus library

1
On

Thanks guys I'm all set, I had to build a method like below which returns DocumentFile and creates necessary directories, Posting it here in case someone needs...

private DocumentFile CreateFileWithDirectories(String path, DocumentFile destDir)
{
    // Like ---> Folder 2/ or Folder 1/Folder 2/Folder 3/
    if (path.endsWith("/"))
    {
        String[] tempStr = path.split("/");

        DocumentFile parentDir = destDir;
        DocumentFile childDir = null;

        for (String st : tempStr)
        {
            childDir = parentDir.findFile(st);

            if (childDir == null)
            {
                childDir = parentDir.createDirectory(st);
            }
            parentDir = childDir;
        }

        // returns null
    }
    // Like ---> 1 Test/Folder 2/Folder 3/ABC 005.jpg
    else if (path.contains("/"))
    {
        String[] tempStr = path.split("/");

        DocumentFile parentDir = destDir;
        DocumentFile childDir = null;

        for (int i = 0; i < tempStr.length - 1; i++)
        {
            childDir = parentDir.findFile(tempStr[i]);

            if (childDir == null) // No file exists
            {
                childDir = parentDir.createDirectory(tempStr[i]);
            }

            //else --> Yes file exists
            parentDir = childDir;
        }

        String finalFileName = path.substring(path.lastIndexOf("/") + 1);

        return parentDir.createFile("*/*", finalFileName);
    }
    // Like ---> /
    else if (path.equals("/"))
    {
        return null;
    }
    // Like ---> ABC 005.jpg or ABC 005
    else
    {
        return destDir.createFile("*/*", path);
    }

    return null;
}
0
On

You can use SimpleStorage to compress/decompress a ZIP file:

val zipFile: DocumentFile = ...
zipFile?.decompressZip(applicationContext, targetFolder, object : ZipDecompressionCallback<DocumentFile>(uiScope) {
    override fun onCompleted(
        zipFile: DocumentFile,
        targetFolder: DocumentFile,
        bytesDecompressed: Long,
        totalFilesDecompressed: Int,
        decompressionRate: Float
    ) {
        Toast.makeText(applicationContext, "Decompressed $totalFilesDecompressed files from ${zipFile.name}", Toast.LENGTH_SHORT).show()
    }
    override fun onFailed(errorCode: ErrorCode) {
        Toast.makeText(applicationContext, "$errorCode", Toast.LENGTH_SHORT).show()
    }
})

And for compressing files, you can use extension function List<DocumentFile>.compressToZip()