All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reandroid.archive.Archive Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright (C) 2022 github.com/REAndroid
 *
 *  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.reandroid.archive;

import com.reandroid.apk.APKLogger;
import com.reandroid.archive.block.*;
import com.reandroid.archive.io.*;
import com.reandroid.archive.model.CentralFileDirectory;
import com.reandroid.archive.model.LocalFileDirectory;
import com.reandroid.utils.ObjectsUtil;
import com.reandroid.utils.collection.ArrayIterator;
import com.reandroid.utils.collection.CollectionUtil;
import com.reandroid.utils.collection.ComputeIterator;
import com.reandroid.utils.io.FileUtil;
import com.reandroid.utils.io.IOUtil;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

public abstract class Archive implements Closeable {

    private final T zipInput;
    private final ArchiveEntry[] entryList;
    private final EndRecord endRecord;
    private final ApkSignatureBlock apkSignatureBlock;

    public Archive(T zipInput) throws IOException {
        this.zipInput = zipInput;
        CentralFileDirectory cfd = new CentralFileDirectory();
        cfd.visit(zipInput);
        this.endRecord = cfd.getEndRecord();
        LocalFileDirectory lfd = new LocalFileDirectory(cfd);
        lfd.visit(zipInput);
        this.entryList  = lfd.buildArchiveEntryList();
        this.apkSignatureBlock = lfd.getApkSigBlock();
    }

    public ZipEntryMap createZipEntryMap(){
        return new ZipEntryMap(mapEntrySource());
    }

    public InputSource[] getInputSources(){
        // TODO: make InputSource for directory entry
        return getInputSources(ArchiveEntry::isFile);
    }
    public InputSource[] getInputSources(Predicate filter){
        Iterator iterator = ComputeIterator.of(iterator(filter), this::createInputSource);
        List sourceList = CollectionUtil.toList(iterator);
        return sourceList.toArray(new InputSource[sourceList.size()]);
    }

    public PathTree getPathTree(){
        PathTree root = PathTree.newRoot();
        Iterator iterator = getFiles();
        while (iterator.hasNext()){
            ArchiveEntry entry = iterator.next();
            InputSource inputSource = createInputSource(entry);
            root.add(inputSource.getAlias(), inputSource);
        }
        return root;
    }
    public LinkedHashMap mapEntrySource(){
        LinkedHashMap map = new LinkedHashMap<>(size());
        Iterator iterator = getFiles();
        while (iterator.hasNext()){
            ArchiveEntry entry = iterator.next();
            InputSource inputSource = createInputSource(entry);
            map.put(inputSource.getAlias(), inputSource);
        }
        return map;
    }

    public T getZipInput() {
        return zipInput;
    }

    abstract InputSource createInputSource(ArchiveEntry entry);
    public InputSource getEntrySource(String path){
        if(path == null){
            return null;
        }
        ArchiveEntry[] entryList = this.entryList;
        int length = entryList.length;
        for(int i = 0; i < length; i++){
            ArchiveEntry entry = entryList[i];
            if(entry.isDirectory()){
                continue;
            }
            if(path.equals(entry.getName())){
                return createInputSource(entry);
            }
        }
        return null;
    }
    public InputStream openRawInputStream(ArchiveEntry archiveEntry) throws IOException {
        return zipInput.getInputStream(archiveEntry.getFileOffset(), archiveEntry.getDataSize());
    }
    public InputStream openInputStream(ArchiveEntry archiveEntry) throws IOException {
        InputStream rawInputStream = openRawInputStream(archiveEntry);
        if(!archiveEntry.isCompressed()){
            return rawInputStream;
        }
        return new InflaterInputStream(rawInputStream,
                new Inflater(true), 1024*1000);
    }
    public Iterator getFiles() {
        return iterator(ArchiveEntry::isFile);
    }
    public Iterator iterator() {
        return new ArrayIterator<>(entryList);
    }
    public Iterator iterator(Predicate filter) {
        return new ArrayIterator<>(entryList, filter);
    }
    public int size(){
        return entryList.length;
    }
    public ApkSignatureBlock getApkSignatureBlock() {
        return apkSignatureBlock;
    }
    public EndRecord getEndRecord() {
        return endRecord;
    }

    public int extractAll(File dir) throws IOException {
        return extractAll(dir, null, null);
    }
    public int extractAll(File dir, APKLogger logger) throws IOException {
        return extractAll(dir, null, logger);
    }
    public int extractAll(File dir, Predicate filter) throws IOException {
        return extractAll(dir, filter, null);
    }
    public int extractAll(File dir, Predicate filter, APKLogger logger) throws IOException {
        Iterator iterator = iterator(filter);
        int result = 0;
        while (iterator.hasNext()){
            ArchiveEntry archiveEntry = iterator.next();
            extract(toFile(dir, archiveEntry), archiveEntry, logger);
            result ++;
        }
        return result;
    }
    public void extract(File file, ArchiveEntry archiveEntry) throws IOException{
        extract(file, archiveEntry, null);
    }
    public void extract(File file, ArchiveEntry archiveEntry, APKLogger logger) throws IOException {
        if(archiveEntry.isDirectory()) {
            // TODO: make directories considering file collision
            return;
        }
        FileUtil.ensureParentDirectory(file);
        if(logger != null){
            long size = archiveEntry.getDataSize();
            if(size > LOG_LARGE_FILE_SIZE){
                logger.logVerbose("Extracting ["
                        + FileUtil.toReadableFileSize(size) + "] "+ archiveEntry.getName());
            }
        }
        if(archiveEntry.getMethod() != Archive.STORED){
            extractCompressed(file, archiveEntry);
        }else {
            extractStored(file, archiveEntry);
        }
        applyAttributes(archiveEntry, file);
    }
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private void applyAttributes(ArchiveEntry archiveEntry, File file) {
        CentralEntryHeader ceh = archiveEntry.getCentralEntryHeader();
        ceh.getFilePermissions().apply(file);
        long time = Archive.dosToJavaDate(ceh.getDosTime()).getTime();
        file.setLastModified(time);
    }
    abstract void extractStored(File file, ArchiveEntry archiveEntry) throws IOException;
    private void extractCompressed(File file, ArchiveEntry archiveEntry) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(file);
        IOUtil.writeAll(openInputStream(archiveEntry), outputStream);
    }
    private File toFile(File dir, ArchiveEntry archiveEntry){
        String name = archiveEntry.getName().replace('/', File.separatorChar);
        return new File(dir, name);
    }
    @Override
    public void close() throws IOException {
        this.zipInput.close();
    }

    public static PathTree buildPathTree(T1[] inputSources){
        PathTree root = PathTree.newRoot();
        int length = inputSources.length;
        for(int i = 0; i < length; i ++){
            T1 item = inputSources[i];
            root.add(item.getAlias(), item);
        }
        return root;
    }

    public static Date dosToJavaDate(long dosTime) {
        final Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
        cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
    public static long javaToDosTime(long javaTime) {
        return javaToDosTime(new Date(javaTime));
    }
    public static long javaToDosTime(Date date) {
        if(date == null || date.getTime() == 0){
            return 0;
        }
        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);
        int year = cal.get(Calendar.YEAR);
        if(year < 1980){
            return 0;
        }
        int result = cal.get(Calendar.DATE);
        result = (cal.get(Calendar.MONTH) + 1 << 5) | result;
        result = ((cal.get(Calendar.YEAR) - 1980) << 9) | result;
        int time = cal.get(Calendar.SECOND) >> 1;
        time = (cal.get(Calendar.MINUTE) << 5) | time;
        time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
        return ((long) result << 16) | time;
    }

    private static final long LOG_LARGE_FILE_SIZE = 1024 * 1000 * 20;


    public static final int STORED = ObjectsUtil.of(0);
    public static final int DEFLATED = ObjectsUtil.of(8);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy