Package com.alexkasko.delta

Source Code of com.alexkasko.delta.DirDeltaCreator$CreatedIndexer

package com.alexkasko.delta;

import com.google.common.base.Function;
import com.google.common.collect.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nothome.delta.Delta;
import com.nothome.delta.GDiffWriter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
import static com.alexkasko.delta.HashUtils.computeSha1;

/**
* Creates ZIP file (or stream) with GDIFF deltas for all changed files and '.index' text file
* (with '.index_' prefix) with list of unchanged, added, updated and deleted files with SHA1 hash sums
*
* @author alexkasko
* Date: 11/18/11
*/
public class DirDeltaCreator {
    private static final String EMPTY_STRING = "";

    /**
     * Creates patch ZIP file
     *
     * @param oldDir old version of directory
     * @param newDir new version of directory
     * @param outFile file to write patch into
     * @throws IOException on any io or consistency problem
     */
    public void create(File oldDir, File newDir, File outFile) throws IOException {
        create(oldDir, newDir, outFile, IOCase.SYSTEM);
    }

    /**
     * Creates patch ZIP file
     *
     * @param oldDir old version of directory
     * @param newDir new version of directory
     * @param outFile file to write patch into
     * @param caseSensitive case sensivity flag
     * @throws IOException on any io or consistency problem
     */
    public void create(File oldDir, File newDir, File outFile, IOCase caseSensitive) throws IOException {
        OutputStream out = null;
        try {
            out = FileUtils.openOutputStream(outFile);
            IOFileFilter filter = TrueFileFilter.TRUE;
            create(oldDir, newDir, filter, out, caseSensitive);
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    /**
     * Writes zipped patch into provided output stream
     *
     * @param oldDir old version of directory
     * @param newDir new version of directory
     * @param filter IO filter to select files
     * @param patch output stream to write patch into
     * @throws IOException on any io or consistency problem
     */
    public void create(File oldDir, File newDir, IOFileFilter filter, OutputStream patch) throws IOException {
        create(oldDir, newDir, filter, patch, IOCase.SYSTEM);
    }

    /**
     * Writes zipped patch into provided output stream
     *
     * @param oldDir old version of directory
     * @param newDir new version of directory
     * @param filter IO filter to select files
     * @param patch output stream to write patch into
     * @param caseSensitive case sensivity flag
     * @throws IOException on any io or consistency problem
     */
    public void create(File oldDir, File newDir, IOFileFilter filter, OutputStream patch, IOCase caseSensitive) throws IOException {
        DeltaIndex paths = readDeltaPaths(oldDir, newDir, filter, caseSensitive);
        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(patch));
        writeIndex(paths, out);
        writeCreated(paths.created, newDir, out);
        writeUpdated(paths.updated, oldDir, newDir, out);
        out.close();
    }

    private DeltaIndex readDeltaPaths(File oldDir, File newDir, IOFileFilter filter, IOCase caseSensitive) throws IOException {
        if(!(null != oldDir && oldDir.exists() && oldDir.isDirectory())) throw new IOException("Bad oldDir argument");
        if(!(null != newDir && newDir.exists() && newDir.isDirectory())) throw new IOException("Bad newDir argument");
        // read files
        Collection<File> oldFiles = listFiles(oldDir, filter, filter);
        Collection<File> newFiles = listFiles(newDir, filter, filter);
        // want to do comparing on strings, without touching FS
        Set<String> oldSet = ImmutableSet.copyOf(Collections2.transform(oldFiles, new Relativiser(oldDir, caseSensitive)));
        Set<String> newSet = ImmutableSet.copyOf(Collections2.transform(newFiles, new Relativiser(newDir, caseSensitive)));
        // partitioning
        List<String> createdPaths = Ordering.natural().immutableSortedCopy(Sets.difference(newSet, oldSet));
        List<String> existedPaths = Ordering.natural().immutableSortedCopy(Sets.intersection(oldSet, newSet));
        List<String> deletedPaths = Ordering.natural().immutableSortedCopy(Sets.difference(oldSet, newSet));
        // converting
        ImmutableList<IndexEntry.Created> created = ImmutableList.copyOf(Lists.transform(createdPaths, new CreatedIndexer(newDir)));
        ImmutableList<IndexEntry.Deleted> deleted = ImmutableList.copyOf(Lists.transform(deletedPaths, new DeletedIndexer(oldDir)));
        List<IndexEntry> existed = Lists.transform(existedPaths, new ExistedIndexer(oldDir, newDir));
        // partitioning
        ImmutableList<IndexEntry.Updated> updated = ImmutableList.copyOf(Iterables.filter(existed, IndexEntry.Updated.class));
        ImmutableList<IndexEntry.Unchanged> unchanged = ImmutableList.copyOf(Iterables.filter(existed, IndexEntry.Unchanged.class));
        return new DeltaIndex(created, deleted, updated, unchanged);
    }

    private void writeIndex(DeltaIndex paths, ZipOutputStream out) throws IOException {
        // lazy transforms
        out.putNextEntry(new ZipEntry(".index_" + UUID.randomUUID().toString()));
        Gson gson = new GsonBuilder().create();
        Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8"));
        for (IndexEntry ie : paths.getAll()) {
            gson.toJson(ie, IndexEntry.class, writer);
            writer.write("\n");
        }
        writer.flush();
        out.closeEntry();
    }

    private void writeCreated(List<IndexEntry.Created> paths, File newDir, ZipOutputStream out) throws IOException {
        for(IndexEntry.Created en : paths) {
            out.putNextEntry(new ZipEntry(en.path));
            File file = new File(newDir, en.path);
            FileUtils.copyFile(file, out);
            out.closeEntry();
        }
    }

    private void writeUpdated(List<IndexEntry.Updated> paths, File oldDir, File newDir, ZipOutputStream out) throws IOException {
        for(IndexEntry.Updated en : paths) {
            out.putNextEntry(new ZipEntry(en.path + ".gdiff"));
            File source = new File(oldDir, en.path);
            File target = new File(newDir, en.path);
            computeDelta(source, target, out);
            out.closeEntry();
        }
    }

    private void computeDelta(File source, File target, OutputStream out) throws IOException {
        OutputStream guarded = new NoCloseOutputStream(out);
        GDiffWriter writer = new GDiffWriter(guarded);
        new Delta().compute(source, target, writer);
    }

    private static class Relativiser implements Function<File, String> {
        private final String parent;
        private final IOCase caseSensitive;

        private Relativiser(File parent, IOCase caseSensitive) {
            this.parent = separatorsToUnix(parent.getPath());
            this.caseSensitive = caseSensitive;
        }

        @Override
        public String apply(File input) {
            String path = separatorsToUnix(input.getPath());
            // check whether actual child
            checkArgument(parent.equals(path.substring(0, parent.length())));
            String relative = path.substring(parent.length() + 1);
            return caseSensitive.isCaseSensitive() ? relative : relative.toLowerCase();
        }
    }

    private static class CreatedIndexer implements Function<String, IndexEntry.Created> {
        private final File parent;

        private CreatedIndexer(File parent) {
            this.parent = parent;
        }

        @Override
        public IndexEntry.Created apply(String path) {
            String sha1 = computeSha1(new File(parent, path));
            return new IndexEntry.Created(path, EMPTY_STRING, sha1);
        }
    }

    private static class DeletedIndexer implements Function<String, IndexEntry.Deleted> {
        private final File parent;

        private DeletedIndexer(File parent) {
            this.parent = parent;
        }

        @Override
        public IndexEntry.Deleted apply(String path) {
            String sha1 = computeSha1(new File(parent, path));
            return new IndexEntry.Deleted(path, sha1, EMPTY_STRING);
        }
    }

    private static class ExistedIndexer implements Function<String, IndexEntry> {
        private final File oldParent;
        private final File newParent;

        private ExistedIndexer(File oldParent, File newParent) {
            this.oldParent = oldParent;
            this.newParent = newParent;
        }

        @Override
        public IndexEntry apply(String path) {
            String oldSha1 = computeSha1(new File(oldParent, path));
            String newSha1 = computeSha1(new File(newParent, path));
            if(oldSha1.equals(newSha1)) {
                return new IndexEntry.Unchanged(path, oldSha1, newSha1);
            } else {
                return new IndexEntry.Updated(path, oldSha1, newSha1);
            }
        }
    }
}
TOP

Related Classes of com.alexkasko.delta.DirDeltaCreator$CreatedIndexer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.