/* * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved. * * http://izpack.org/ http://izpack.codehaus.org/ * * Copyright 2007 Dennis Reil * * 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.izforge.izpack.io; import com.izforge.izpack.util.Debug; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Date; import java.util.Random; import java.util.zip.GZIPOutputStream; /** * An outputstream which transparently spans over multiple volumes. The size of the volumes and an * additonal space for the first volume can be specified. * * @author Dennis Reil, <Dennis.Reil@reddot.de> */ public class FileSpanningOutputStream extends OutputStream { public static final long KB = 1000; public static final long MB = 1000 * KB; // the default size of a volume public static final long DEFAULT_VOLUME_SIZE = 650 * MB; // free space on first volume // may be used for placing additional files on cd beside the pack files // default is 0, so there's no additional space public static final long DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE = 0; // the default volume name protected static final String DEFAULT_VOLUME_NAME = "rdpack"; protected static final long FILE_NOT_AVAILABLE = -1; // the maximum size of a volume protected long maxvolumesize = DEFAULT_VOLUME_SIZE; // the addition free space of volume 0 protected long firstvolumefreespacesize = DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE; public static final String VOLUMES_INFO = "/volumes.info"; public static final int MAGIC_NUMER_LENGTH = 10; // the current file this stream writes to protected File currentfile; // the name of the volumes protected String volumename; // the current index of the volume, the stream writes to protected int currentvolumeindex; // a normal file outputstream for writting to the current volume private FileOutputStream fileoutputstream; private GZIPOutputStream zippedoutputstream; // private byte[] magicnumber; // the current position in the open file protected long filepointer; protected long totalbytesofpreviousvolumes; /** * Creates a new spanning output stream with specified volume names and a maximum volume size * * @param volumename - the name of the volumes * @param maxvolumesize - the maximum volume size * @throws IOException */ public FileSpanningOutputStream(String volumename, long maxvolumesize) throws IOException { this(new File(volumename), maxvolumesize); } /** * Creates a new spanning output stream with specified volume names and a maximum volume size * * @param volume - the first volume * @param maxvolumesize - the maximum volume size * @throws IOException */ public FileSpanningOutputStream(File volume, long maxvolumesize) throws IOException { this(volume, maxvolumesize, 0); } /** * Creates a new spanning output stream with specified volume names and a maximum volume size * * @param volume - the first volume * @param maxvolumesize - the maximum volume size * @param currentvolume - the current volume * @throws IOException */ protected FileSpanningOutputStream(File volume, long maxvolumesize, int currentvolume) throws IOException { this.generateMagicNumber(); this.createVolumeOutputStream(volume, maxvolumesize, currentvolume); } private void generateMagicNumber() { // only create a magic number, if not already done if (magicnumber == null) { // create empty magic number magicnumber = new byte[MAGIC_NUMER_LENGTH]; Date currenttime = new Date(); long currenttimeseconds = currenttime.getTime(); // create random number generator Random random = new Random(currenttimeseconds); random.nextBytes(magicnumber); Debug.trace("created new magic number for FileOutputstream: " + new String(magicnumber)); for (int i = 0; i < magicnumber.length; i++) { Debug.trace(i + " - " + magicnumber[i]); } } } /** * Actually creates the outputstream for writing a volume with index currentvolume and a maximum * of maxvolumesize * * @param volume - the volume to write to * @param maxvolumesize - the maximum volume size * @param currentvolume - the currentvolume index * @throws IOException */ private void createVolumeOutputStream(File volume, long maxvolumesize, int currentvolume) throws IOException { fileoutputstream = new FileOutputStream(volume); zippedoutputstream = new GZIPOutputStream(fileoutputstream, 256); currentfile = volume; this.currentvolumeindex = currentvolume; this.maxvolumesize = maxvolumesize; // try to get the volumename from the given volume file // the first volume has no suffix, additional volumes have a .INDEX# suffix String volumesuffix = "." + currentvolume; String volabsolutePath = volume.getAbsolutePath(); if (volabsolutePath.endsWith(volumesuffix)) { volumename = volabsolutePath.substring(0, volabsolutePath.indexOf(volumesuffix)); } else { volumename = volabsolutePath; } long oldfilepointer = filepointer; // write magic number into output stream this.write(magicnumber); // reset filepointer filepointer = oldfilepointer; } /** * @param volume * @throws IOException */ public FileSpanningOutputStream(File volume) throws IOException { this(volume.getAbsolutePath(), DEFAULT_VOLUME_SIZE); } /** * @param volumename * @throws IOException */ public FileSpanningOutputStream(String volumename) throws IOException { this(volumename, DEFAULT_VOLUME_SIZE); } /** * @throws IOException */ public FileSpanningOutputStream() throws IOException { this(DEFAULT_VOLUME_NAME, DEFAULT_VOLUME_SIZE); } /** * Returns the size of the current volume * * @return the size of the current volume FILE_NOT_AVAILABLE, if there's no current volume */ protected long getCurrentVolumeSize() { if (currentfile == null) { return FILE_NOT_AVAILABLE; } try { flush(); } catch (IOException e) { e.printStackTrace(); } // create a new instance currentfile = new File(currentfile.getAbsolutePath()); if (currentvolumeindex == 0) { // this is the first volume, add the additional free space // and add a reserve for overhead and not yet written data return currentfile.length() + this.firstvolumefreespacesize + Math.round(0.001 * currentfile.length()); } else { // not the first volume, just return the actual length // and add a reserve for overhead and not yet written data return currentfile.length() + Math.round(0.001 * currentfile.length()); } } /** * Closes the stream to the current volume and reopens to the next volume * * @throws IOException */ protected void createStreamToNextVolume() throws IOException { // close current stream close(); totalbytesofpreviousvolumes = currentfile.length(); currentvolumeindex++; // get the name of the next volume String nextvolumename = volumename + "." + currentvolumeindex; // does the creation this.createVolumeOutputStream(new File(nextvolumename), this.maxvolumesize, this.currentvolumeindex); } /** * @see java.io.OutputStream#close() */ public void close() throws IOException { this.flush(); zippedoutputstream.close(); fileoutputstream.close(); // reset the filepointer // filepointer = 0; } /** * @see java.io.OutputStream#write(byte[], int, int) */ public void write(byte[] b, int off, int len) throws IOException { if (len > maxvolumesize) { throw new IOException( "file can't be written. buffer length exceeded maxvolumesize (" + " > " + maxvolumesize + ")"); } // get the current size of this file long currentsize = getCurrentVolumeSize(); // calculate the available bytes long available = maxvolumesize - currentsize; if (available < len) { Debug.trace("Not enough space left on volume. available: " + available); Debug.trace("current size is: " + currentsize); // there's not enough space available // create the next volume this.createStreamToNextVolume(); } // enough space available, just write to the outputstream zippedoutputstream.write(b, off, len); // increase filepointer by written bytes filepointer += len; } /** * @see java.io.OutputStream#write(byte[]) */ public void write(byte[] b) throws IOException { this.write(b, 0, b.length); } /** * @see java.io.OutputStream#write(int) */ public void write(int b) throws IOException { long availablebytes = maxvolumesize - getCurrentVolumeSize(); if (availablebytes >= 1) { zippedoutputstream.write(b); // increase filepointer by written byte filepointer++; } else { // create next volume this.createStreamToNextVolume(); zippedoutputstream.write(b); // increase filepointer by written byte filepointer++; } } /** * @see java.io.OutputStream#flush() */ public void flush() throws IOException { zippedoutputstream.flush(); fileoutputstream.flush(); } /** * Returns the amount of currently created volumes * * @return the amount of created volumes */ public int getVolumeCount() { return this.currentvolumeindex + 1; } /** * @return */ public long getFirstvolumefreespacesize() { return firstvolumefreespacesize; } /** * @param firstvolumefreespacesize */ public void setFirstvolumefreespacesize(long firstvolumefreespacesize) { this.firstvolumefreespacesize = firstvolumefreespacesize; } /** * Returns the current position in this file * * @return the position in this file * @throws IOException */ public long getCompressedFilepointer() throws IOException { this.flush(); // return filepointer; return totalbytesofpreviousvolumes + currentfile.length(); } public long getFilepointer() { return filepointer; } }