org.eclipse.scada.utils.pkg.deb.DebianPackageWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.scada.utils.pkg.deb.DebianPackageWriter.java

Source

/*******************************************************************************
 * Copyright (c) 2014,2016 IBH SYSTEMS GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.scada.utils.pkg.deb;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.eclipse.scada.utils.pkg.deb.control.BinaryPackageControlFile;
import org.eclipse.scada.utils.pkg.deb.control.ControlFileWriter;
import org.eclipse.scada.utils.pkg.deb.control.GenericControlFile;
import org.eclipse.scada.utils.pkg.deb.stream.ChecksumInputStream;
import org.eclipse.scada.utils.str.StringHelper;

import com.google.common.io.ByteStreams;

public class DebianPackageWriter implements AutoCloseable, BinaryPackageBuilder {
    private static final int AR_ARCHIVE_DEFAULT_MODE = 33188; // unfortunately this is not a public field of ArArchiveEntry

    public static final Charset CHARSET = Charset.forName("UTF-8");

    private final ArArchiveOutputStream ar;

    private final byte[] binaryHeader = "2.0\n".getBytes();

    private final File dataTemp;

    private final TarArchiveOutputStream dataStream;

    private final GenericControlFile packageControlFile;

    private final TimestampProvider timestampProvider;

    private long installedSize = 0;

    private final Map<String, String> checkSums = new TreeMap<>();

    private final Set<String> confFiles = new TreeSet<>();

    private final Set<String> paths = new HashSet<>();

    private ContentProvider preinstScript;

    private ContentProvider postinstScript;

    private ContentProvider prermScript;

    private ContentProvider postrmScript;

    public DebianPackageWriter(final OutputStream stream, final GenericControlFile packageControlFile)
            throws IOException {
        this(stream, packageControlFile, TimestampProvider.DEFAULT_TIMESTAMP_PROVIDER);
    }

    public DebianPackageWriter(final OutputStream stream, final GenericControlFile packageControlFile,
            final TimestampProvider timestampProvider) throws IOException {
        this.packageControlFile = packageControlFile;
        this.timestampProvider = timestampProvider;
        if (getTimestampProvider() == null) {
            throw new IllegalArgumentException("'timestampProvider' must not be null");
        }
        BinaryPackageControlFile.validate(packageControlFile);

        this.ar = new ArArchiveOutputStream(stream);

        this.ar.putArchiveEntry(new ArArchiveEntry("debian-binary", this.binaryHeader.length, 0, 0,
                AR_ARCHIVE_DEFAULT_MODE, getTimestampProvider().getModTime() / 1000));
        this.ar.write(this.binaryHeader);
        this.ar.closeArchiveEntry();

        this.dataTemp = File.createTempFile("data", null);

        this.dataStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(this.dataTemp)));
        this.dataStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
    }

    public void addFile(final File file, final String fileName, final EntryInformation entryInformation)
            throws IOException {
        addFile(new FileContentProvider(file), fileName, entryInformation);
    }

    public void addFile(final byte[] content, final String fileName, final EntryInformation entryInformation)
            throws IOException {
        addFile(new StaticContentProvider(content), fileName, entryInformation);
    }

    public void addFile(final String content, final String fileName, final EntryInformation entryInformation)
            throws IOException {
        addFile(new StaticContentProvider(content), fileName, entryInformation);
    }

    @Override
    public void addFile(final ContentProvider contentProvider, String fileName, EntryInformation entryInformation)
            throws IOException {
        if (entryInformation == null) {
            entryInformation = EntryInformation.DEFAULT_FILE;
        }

        try {
            fileName = cleanupPath(fileName);

            if (entryInformation.isConfigurationFile()) {
                this.confFiles.add(fileName.substring(1)); // without the leading dot
            }

            final TarArchiveEntry entry = new TarArchiveEntry(fileName);
            entry.setSize(contentProvider.getSize());
            applyInfo(entry, entryInformation, this.getTimestampProvider());

            checkCreateParents(fileName);

            this.dataStream.putArchiveEntry(entry);

            final Map<String, byte[]> results = new HashMap<>();
            try (final ChecksumInputStream in = new ChecksumInputStream(contentProvider.createInputStream(),
                    results, MessageDigest.getInstance("MD5"))) {
                this.installedSize += ByteStreams.copy(in, this.dataStream);
            }

            this.dataStream.closeArchiveEntry();

            // record the checksum
            recordChecksum(fileName, results.get("MD5"));
        } catch (final Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * clean up the path so that is looks like "./usr/local/file"
     */
    private String cleanupPath(String fileName) {
        if (fileName == null) {
            return null;
        }

        fileName = fileName.replace("\\", "/"); // just in case we get windows paths
        fileName = fileName.replace("/+", "/");

        if (fileName.startsWith("./")) {
            return fileName;
        }
        if (fileName.startsWith("/")) {
            return "." + fileName;
        }
        return "./" + fileName;
    }

    @Override
    public void addDirectory(String directory, final EntryInformation entryInformation) throws IOException {
        directory = cleanupPath(directory);
        if (!directory.endsWith("/")) {
            directory += '/';
        }
        checkCreateParents(directory);
        internalAddDirectory(directory, entryInformation);
    }

    protected void internalAddDirectory(final String path, final EntryInformation entryInformation)
            throws IOException {
        final TarArchiveEntry entry = new TarArchiveEntry(path);
        applyInfo(entry, entryInformation, this.getTimestampProvider());

        this.dataStream.putArchiveEntry(entry);
        this.dataStream.closeArchiveEntry();

        this.paths.add(path);
    }

    private static void applyInfo(final TarArchiveEntry entry, final EntryInformation entryInformation,
            TimestampProvider timestampProvider) {
        if (entryInformation == null) {
            return;
        }

        if (entryInformation.getUser() != null) {
            entry.setUserName(entryInformation.getUser());
        }
        if (entryInformation.getGroup() != null) {
            entry.setGroupName(entryInformation.getGroup());
        }
        entry.setMode(entryInformation.getMode());
        entry.setModTime(timestampProvider.getModTime());
    }

    private void checkCreateParents(final String fileName) throws IOException {
        final String toks[] = fileName.split("/+");

        String current = "";

        for (int i = 0; i < toks.length - 1; i++) {
            if (toks[i].isEmpty()) {
                continue;
            }

            current += toks[i] + "/";
            if (!this.paths.contains(current)) {
                internalAddDirectory(current, EntryInformation.DEFAULT_DIRECTORY);
            }
        }
    }

    private void recordChecksum(final String fileName, final byte[] bs) {
        this.checkSums.put(fileName, StringHelper.toHex(bs));
    }

    @Override
    public void close() throws IOException {
        try {
            try {
                buildAndAddControlFile();
                this.dataStream.close();
                addArFile(this.dataTemp, "data.tar.gz");
            } finally {
                this.ar.close();
            }
        } finally {
            this.dataTemp.delete();
        }
    }

    private void buildAndAddControlFile() throws IOException, FileNotFoundException {
        final File controlFile = File.createTempFile("control", null);
        try {
            try (GZIPOutputStream gout = new GZIPOutputStream(new FileOutputStream(controlFile));
                    TarArchiveOutputStream tout = new TarArchiveOutputStream(gout)) {
                tout.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);

                addControlContent(tout, "control", createControlContent(), -1);
                addControlContent(tout, "md5sums", createChecksumContent(), -1);
                addControlContent(tout, "conffiles", createConfFilesContent(), -1);
                addControlContent(tout, "preinst", this.preinstScript,
                        EntryInformation.DEFAULT_FILE_EXEC.getMode());
                addControlContent(tout, "prerm", this.prermScript, EntryInformation.DEFAULT_FILE_EXEC.getMode());
                addControlContent(tout, "postinst", this.postinstScript,
                        EntryInformation.DEFAULT_FILE_EXEC.getMode());
                addControlContent(tout, "postrm", this.postrmScript, EntryInformation.DEFAULT_FILE_EXEC.getMode());
            }
            addArFile(controlFile, "control.tar.gz");
        } finally {
            controlFile.delete();
        }
    }

    private void addControlContent(final TarArchiveOutputStream out, final String name,
            final ContentProvider content, final int mode) throws IOException {
        if (content == null || !content.hasContent()) {
            return;
        }

        final TarArchiveEntry entry = new TarArchiveEntry(name);
        if (mode >= 0) {
            entry.setMode(mode);
        }

        entry.setUserName("root");
        entry.setGroupName("root");
        entry.setSize(content.getSize());
        entry.setModTime(this.getTimestampProvider().getModTime());
        out.putArchiveEntry(entry);
        try (InputStream stream = content.createInputStream()) {
            ByteStreams.copy(stream, out);
        }
        out.closeArchiveEntry();
    }

    protected ContentProvider createControlContent() throws IOException {
        this.packageControlFile.set(BinaryPackageControlFile.Fields.INSTALLED_SIZE, "" + this.installedSize);

        final StringWriter sw = new StringWriter();
        try (ControlFileWriter writer = new ControlFileWriter(sw)) {
            this.packageControlFile.write(writer);
        }
        sw.close();

        return new StaticContentProvider(sw.toString());
    }

    protected ContentProvider createChecksumContent() throws IOException {
        if (this.checkSums.isEmpty()) {
            return ContentProvider.NULL_CONTENT;
        }

        final StringWriter sw = new StringWriter();

        for (final Map.Entry<String, String> entry : this.checkSums.entrySet()) {
            final String filename = entry.getKey().substring(2); // without the leading dot and slash

            sw.append(entry.getValue());
            sw.append("  ");
            sw.append(filename);
            sw.append('\n');
        }

        sw.close();

        return new StaticContentProvider(sw.toString());
    }

    protected ContentProvider createConfFilesContent() throws IOException {
        if (this.confFiles.isEmpty()) {
            return ContentProvider.NULL_CONTENT;
        }

        final StringWriter sw = new StringWriter();

        for (final String confFile : this.confFiles) {
            sw.append(confFile).append('\n');
        }

        sw.close();
        return new StaticContentProvider(sw.toString());
    }

    private void addArFile(final File file, final String entryName) throws IOException {
        final ArArchiveEntry entry = new ArArchiveEntry(entryName, file.length(), 0, 0, AR_ARCHIVE_DEFAULT_MODE,
                timestampProvider.getModTime() / 1000);
        this.ar.putArchiveEntry(entry);

        ByteStreams.copy(new FileInputStream(file), this.ar);

        this.ar.closeArchiveEntry();
    }

    public void setPostinstScript(final ContentProvider postinstScript) {
        this.postinstScript = postinstScript;
    }

    public void setPostrmScript(final ContentProvider postrmScript) {
        this.postrmScript = postrmScript;
    }

    public void setPreinstScript(final ContentProvider preinstScript) {
        this.preinstScript = preinstScript;
    }

    public void setPrermScript(final ContentProvider prermScript) {
        this.prermScript = prermScript;
    }

    public TimestampProvider getTimestampProvider() {
        return timestampProvider;
    }
}