ch.cyberduck.core.http.DelayedHttpMultipartEntity.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.http.DelayedHttpMultipartEntity.java

Source

package ch.cyberduck.core.http;

/*
 * Copyright (c) 2002-2017 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

import ch.cyberduck.core.MimeTypeService;
import ch.cyberduck.core.transfer.TransferStatus;

import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.mime.MIME;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

public class DelayedHttpMultipartEntity extends DelayedHttpEntity {
    private static final Logger log = Logger.getLogger(DelayedHttpMultipartEntity.class);

    public static final String DEFAULT_BOUNDARY = "------------------------d8ad73fe428d737a";

    private final TransferStatus status;
    private final CountDownLatch exit = new CountDownLatch(1);

    private static final String CR_LF = "\r\n";
    private static final String TWO_DASHES = "--";

    private final byte[] header;
    private final byte[] footer;

    public DelayedHttpMultipartEntity(final String filename, final TransferStatus status) {
        this(filename, status, DEFAULT_BOUNDARY);
    }

    /**
     * @param status Length
     */
    public DelayedHttpMultipartEntity(final String filename, final TransferStatus status, final String boundary) {
        this.status = status;
        final StringBuilder multipartHeader = new StringBuilder();
        multipartHeader.append(TWO_DASHES);
        multipartHeader.append(boundary);
        multipartHeader.append(CR_LF);
        multipartHeader
                .append(String.format("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"", filename));
        multipartHeader.append(CR_LF);
        multipartHeader.append(String.format("%s: %s", HTTP.CONTENT_TYPE,
                StringUtils.isBlank(status.getMime()) ? MimeTypeService.DEFAULT_CONTENT_TYPE : status.getMime()));
        multipartHeader.append(CR_LF);
        multipartHeader.append(CR_LF);
        header = encode(MIME.DEFAULT_CHARSET, multipartHeader.toString()).buffer();
        final StringBuilder multipartFooter = new StringBuilder();
        multipartFooter.append(CR_LF);
        multipartFooter.append(TWO_DASHES);
        multipartFooter.append(boundary);
        multipartFooter.append(TWO_DASHES);
        multipartFooter.append(CR_LF);
        footer = encode(MIME.DEFAULT_CHARSET, multipartFooter.toString()).buffer();
    }

    private static ByteArrayBuffer encode(final Charset charset, final String input) {
        final ByteBuffer encoded = charset.encode(CharBuffer.wrap(input));
        final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
        bab.append(encoded.array(), encoded.position(), encoded.remaining());
        return bab;
    }

    /**
     * HTTP stream to write to
     */
    private OutputStream stream;
    /**
     *
     */
    private boolean consumed = false;

    public long getContentLength() {
        return header.length + status.getLength() + footer.length;
    }

    /**
     * @return The stream to write to after the entry signal was received.
     */
    public OutputStream getStream() {
        if (null == stream) {
            // Nothing to write
            return NullOutputStream.NULL_OUTPUT_STREAM;
        }
        return stream;
    }

    public void writeTo(final OutputStream out) throws IOException {
        try {
            stream = new OutputStream() {
                private final AtomicBoolean close = new AtomicBoolean();

                @Override
                public void write(final byte[] b, final int off, final int len) throws IOException {
                    out.write(b, off, len);
                }

                @Override
                public void write(final int b) throws IOException {
                    out.write(b);
                }

                @Override
                public void write(final byte[] b) throws IOException {
                    out.write(b);
                }

                @Override
                public void close() throws IOException {
                    if (close.get()) {
                        log.warn(String.format("Skip double close of stream %s", this));
                        return;
                    }
                    try {
                        out.write(footer);
                        super.close();
                    } finally {
                        // Signal finished writing to stream
                        exit.countDown();
                        close.set(true);
                    }
                }
            };
            stream.write(header);
        } finally {
            final CountDownLatch entry = this.getEntry();
            // Signal stream is ready for writing
            entry.countDown();
        }
        // Wait for signal when content has been written to the pipe
        try {
            exit.await();
        } catch (InterruptedException e) {
            log.error(String.format("Error waiting for exit signal %s", e.getMessage()));
            throw new IOException(e);
        }
        // Entity written to server
        consumed = true;
    }

    public boolean isStreaming() {
        return !consumed;
    }
}