/* * ------------------------------------------------------------------------------ * Hermes FTP Server * Copyright (c) 2005-2014 Lars Behnke * ------------------------------------------------------------------------------ * * This file is part of Hermes FTP Server. * * Hermes FTP Server 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. * * Hermes FTP Server 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. * * You should have received a copy of the GNU General Public License * along with Hermes FTP Server; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ------------------------------------------------------------------------------ */ package com.apporiented.hermesftp.streams; import java.io.IOException; import java.io.OutputStream; /** * In the case of a file being sent with file-structure to a record-oriented host, there exists the * question of what criteria the host should use to divide the file into records which can be * processed locally. If this division is necessary, an FTP implementation should use the * end-of-line sequence, CRLF for ASCII, or NewLine (0x25) for EBCDIC text files, as the delimiter. * If an FTP implementation adopts this technique, it must be prepared to reverse the transformation * if the file is retrieved with file-structure. * * @author Lars Behnke */ public class BlockModeOutputStream extends OutputStream implements BlockModeConstants, RecordWriteSupport { /** * The default block size to be used. */ private static final int DEFAULT_BLOCK_SIZE = 1024; private OutputStream os; private byte[] buffer; private int idx; private boolean fileComplete; private boolean recordComplete; /** * Constructor. * * @param os The nested output stream. */ public BlockModeOutputStream(OutputStream os) { this(os, DEFAULT_BLOCK_SIZE); } /** * Constructor. * * @param os The output stream. * @param blockSize The blocksize. */ public BlockModeOutputStream(OutputStream os, int blockSize) { super(); this.os = os; this.buffer = new byte[blockSize]; this.idx = 0; this.fileComplete = false; } /** * Writes a complete record and marks the transfer optionally with EOF. * * @param record The data to be transmitted. * @param eof True, if the transfer is complete. * @throws IOException If writing the data fails or the file was previously finalized. */ public void writeRecord(byte[] record, boolean eof) throws IOException { recordComplete = false; write(record); finalizeRecord(eof); } /** * {@inheritDoc} */ public void write(int b) throws IOException { if (fileComplete) { throw new IOException("EOF marked already."); } buffer[idx] = (byte) b; idx++; if (idx >= buffer.length) { writeBlock(0x00, buffer.length); idx = 0; } recordComplete = false; } private void writeBlock(int code, int len) throws IOException { os.write(code); os.write((len >>> 8) & 0xFF); //noinspection PointlessBitwiseExpression os.write((len >>> 0) & 0xFF); os.write(buffer, 0, len); } /** * {@inheritDoc} */ public void flush() throws IOException { os.flush(); } /** * Flushes the last dataset. Block mode transfer requires setting EOF explicitly. * * @param eof True, if end of file. * @throws IOException When writing fails. */ public void finalizeRecord(boolean eof) throws IOException { if (!fileComplete && !recordComplete) { int descriptor = DESC_CODE_EOR; if (eof) { descriptor |= DESC_CODE_EOF; fileComplete = true; } writeBlock(descriptor, idx); recordComplete = true; idx = 0; } } /** * {@inheritDoc} */ public void close() throws IOException { finalizeRecord(true); os.close(); } }