Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 864699a1 authored by felkachang's avatar felkachang
Browse files

Add feature to support multiple archive

To implement ArchiveHandle, ArchiveEntryInputStream and
ArchiveRegistry to support multiple archive type.

ArchiveHandle is a type of common handler to handle the things just
like ZipFile. To get the inputStream for Proxy and get all of
entries in the archive.

ArchiveEntryInputStream is a type of common input stream comes from
supported archive file. And, different archive file has different
method to generate the input stream.

ArchiveRegistry is to maintain the support list and keep the related
generators.

Here are the supported list: 7z, zip, tar, tgz.

To add Apache2 license in DocumentsUI module.

Fixes: 117912708
Test: atest DocumentsUITests

Change-Id: I4eb91af59b1a120a3df17f0546cd612200eea2d2
parent 9d4b81c7
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

java_defaults {
    name: "documentsui_defaults",

MODULE_LICENSE_APACHE2

0 → 100644
+0 −0

Empty file added.

+5 −1
Original line number Diff line number Diff line
@@ -16,3 +16,7 @@
-keep public class androidx.core.view.accessibility.AccessibilityNodeInfoCompat {
   public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat obtain();
}

# To prevent class not found exception in org.brotli.dec.Dictionary
-keep final class org.brotli.dec.DictionaryData
+21 −13
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;

/**
@@ -69,11 +70,11 @@ public abstract class Archive implements Closeable {

    // The container as well as values are guarded by mEntries.
    @GuardedBy("mEntries")
    final Map<String, ZipArchiveEntry> mEntries;
    final Map<String, ArchiveEntry> mEntries;

    // The container as well as values and elements of values are guarded by mEntries.
    @GuardedBy("mEntries")
    final Map<String, List<ZipArchiveEntry>> mTree;
    final Map<String, List<ArchiveEntry>> mTree;

    Archive(
            Context context,
@@ -92,9 +93,16 @@ public abstract class Archive implements Closeable {
    /**
     * Returns a valid, normalized path for an entry.
     */
    public static String getEntryPath(ZipArchiveEntry entry) {
    public static String getEntryPath(ArchiveEntry entry) {
        if (entry instanceof ZipArchiveEntry) {
            /**
             * Some of archive entry doesn't have the same naming rule.
             * For example: The name of 7 zip directory entry doesn't end with '/'.
             * Only check for Zip archive.
             */
            Preconditions.checkArgument(entry.isDirectory() == entry.getName().endsWith("/"),
                    "Ill-formated ZIP-file.");
        }
        if (entry.getName().startsWith("/")) {
            return entry.getName();
        } else {
@@ -135,11 +143,11 @@ public abstract class Archive implements Closeable {
        }

        synchronized (mEntries) {
            final List<ZipArchiveEntry> parentList = mTree.get(parsedParentId.mPath);
            final List<ArchiveEntry> parentList = mTree.get(parsedParentId.mPath);
            if (parentList == null) {
                throw new FileNotFoundException();
            }
            for (final ZipArchiveEntry entry : parentList) {
            for (final ArchiveEntry entry : parentList) {
                addCursorRow(result, entry);
            }
        }
@@ -157,7 +165,7 @@ public abstract class Archive implements Closeable {
                "Mismatching archive Uri. Expected: %s, actual: %s.");

        synchronized (mEntries) {
            final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
            final ArchiveEntry entry = mEntries.get(parsedId.mPath);
            if (entry == null) {
                throw new FileNotFoundException();
            }
@@ -178,12 +186,12 @@ public abstract class Archive implements Closeable {
                "Mismatching archive Uri. Expected: %s, actual: %s.");

        synchronized (mEntries) {
            final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
            final ArchiveEntry entry = mEntries.get(parsedId.mPath);
            if (entry == null) {
                return false;
            }

            final ZipArchiveEntry parentEntry = mEntries.get(parsedParentId.mPath);
            final ArchiveEntry parentEntry = mEntries.get(parsedParentId.mPath);
            if (parentEntry == null || !parentEntry.isDirectory()) {
                return false;
            }
@@ -210,7 +218,7 @@ public abstract class Archive implements Closeable {
                "Mismatching archive Uri. Expected: %s, actual: %s.");

        synchronized (mEntries) {
            final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
            final ArchiveEntry entry = mEntries.get(parsedId.mPath);
            if (entry == null) {
                throw new FileNotFoundException();
            }
@@ -267,7 +275,7 @@ public abstract class Archive implements Closeable {
    /**
     * Not thread safe.
     */
    void addCursorRow(MatrixCursor cursor, ZipArchiveEntry entry) {
    void addCursorRow(MatrixCursor cursor, ArchiveEntry entry) {
        final MatrixCursor.RowBuilder row = cursor.newRow();
        final ArchiveId parsedId = createArchiveId(getEntryPath(entry));
        row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId());
@@ -286,7 +294,7 @@ public abstract class Archive implements Closeable {
        row.add(Document.COLUMN_FLAGS, flags);
    }

    static String getMimeTypeForEntry(ZipArchiveEntry entry) {
    static String getMimeTypeForEntry(ArchiveEntry entry) {
        if (entry.isDirectory()) {
            return Document.MIME_TYPE_DIR;
        }
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.documentsui.archives;

import android.text.TextUtils;

import androidx.annotation.NonNull;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;

/**
 * To simulate the input stream by using ZipFile, SevenZFile, or ArchiveInputStream.
 */
abstract class ArchiveEntryInputStream extends InputStream {
    private final long mSize;
    private final ReadSource mReadSource;

    private ArchiveEntryInputStream(ReadSource readSource, @NonNull ArchiveEntry archiveEntry) {
        mReadSource = readSource;
        mSize = archiveEntry.getSize();
    }

    @Override
    public int read() throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (mReadSource != null) {
            return mReadSource.read(b, off, len);
        }

        return -1; /* end of input stream */
    }

    @Override
    public int available() throws IOException {
        return mReadSource == null ? 0 : StrictMath.toIntExact(mSize);
    }

    /**
     * The interface describe how to make the ArchiveHandle to next entry.
     */
    private interface NextEntryIterator {
        ArchiveEntry getNextEntry() throws IOException;
    }

    /**
     * The interface provide where to read the data.
     */
    private interface ReadSource {
        int read(byte[] b, int off, int len) throws IOException;
    }

    private static boolean moveToArchiveEntry(
            NextEntryIterator nextEntryIterator, ArchiveEntry archiveEntry) throws IOException {
        ArchiveEntry entry;
        while ((entry = nextEntryIterator.getNextEntry()) != null) {
            if (TextUtils.equals(entry.getName(), archiveEntry.getName())) {
                return true;
            }
        }
        return false;
    }

    private static class WrapArchiveInputStream extends ArchiveEntryInputStream {
        private WrapArchiveInputStream(ReadSource readSource,
                ArchiveEntry archiveEntry, NextEntryIterator iterator) throws IOException {
            super(readSource, archiveEntry);

            moveToArchiveEntry(iterator, archiveEntry);
        }
    }

    private static class WrapZipFileInputStream extends ArchiveEntryInputStream {
        private final Closeable mCloseable;

        private WrapZipFileInputStream(
                ReadSource readSource, @NonNull ArchiveEntry archiveEntry, Closeable closeable)
                throws IOException {
            super(readSource, archiveEntry);
            mCloseable = closeable;
        }

        @Override
        public void close() throws IOException {
            super.close();
            if (mCloseable != null) {
                mCloseable.close();
            }
        }
    }

    static InputStream create(@NonNull ArchiveHandle archiveHandle,
            @NonNull ArchiveEntry archiveEntry) throws IOException {
        if (archiveHandle == null) {
            throw new IllegalArgumentException("archiveHandle is null");
        }

        if (archiveEntry == null) {
            throw new IllegalArgumentException("ArchiveEntry is empty");
        }

        if (archiveEntry.isDirectory() || archiveEntry.getSize() <= 0
                || TextUtils.isEmpty(archiveEntry.getName())) {
            throw new IllegalArgumentException("ArchiveEntry is an invalid file entry");
        }

        Object commonArchive = archiveHandle.getCommonArchive();

        if (commonArchive instanceof SevenZFile) {
            return new WrapArchiveInputStream(
                (b, off, len) -> ((SevenZFile) commonArchive).read(b, off, len),
                archiveEntry,
                () -> ((SevenZFile) commonArchive).getNextEntry());
        } else if (commonArchive instanceof ZipFile) {
            final InputStream inputStream =
                    ((ZipFile) commonArchive).getInputStream((ZipArchiveEntry) archiveEntry);
            return new WrapZipFileInputStream(
                (b, off, len) -> inputStream.read(b, off, len),
                archiveEntry,
                () -> inputStream.close());
        } else if (commonArchive instanceof ArchiveInputStream) {
            return new WrapArchiveInputStream(
                (b, off, len) -> ((ArchiveInputStream) commonArchive).read(b, off, len),
                archiveEntry,
                () -> ((ArchiveInputStream) commonArchive).getNextEntry());
        }

        return null;
    }
}
Loading