001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/ http://izpack.codehaus.org/
005: *
006: * Copyright 2007 Dennis Reil
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
009: * in compliance with the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software distributed under the License
014: * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
015: * or implied. See the License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package com.izforge.izpack.io;
019:
020: import java.io.File;
021: import java.io.FileOutputStream;
022: import java.io.IOException;
023: import java.io.OutputStream;
024: import java.util.Date;
025: import java.util.Random;
026: import java.util.zip.GZIPOutputStream;
027:
028: import com.izforge.izpack.util.Debug;
029:
030: /**
031: * An outputstream which transparently spans over multiple volumes. The size of the volumes and an
032: * additonal space for the first volume can be specified.
033: *
034: * @author Dennis Reil, <Dennis.Reil@reddot.de>
035: */
036: public class FileSpanningOutputStream extends OutputStream {
037:
038: public static final long KB = 1000;
039:
040: public static final long MB = 1000 * KB;
041:
042: // the default size of a volume
043: public static final long DEFAULT_VOLUME_SIZE = 650 * MB;
044:
045: // free space on first volume
046: // may be used for placing additional files on cd beside the pack files
047: // default is 0, so there's no additional space
048: public static final long DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE = 0;
049:
050: // the default volume name
051: protected static final String DEFAULT_VOLUME_NAME = "rdpack";
052:
053: protected static final long FILE_NOT_AVAILABLE = -1;
054:
055: // the maximum size of a volume
056: protected long maxvolumesize = DEFAULT_VOLUME_SIZE;
057:
058: // the addition free space of volume 0
059: protected long firstvolumefreespacesize = DEFAULT_ADDITIONAL_FIRST_VOLUME_FREE_SPACE_SIZE;
060: public static final String VOLUMES_INFO = "/volumes.info";
061:
062: public static final int MAGIC_NUMER_LENGTH = 10;
063:
064: // the current file this stream writes to
065: protected File currentfile;
066:
067: // the name of the volumes
068: protected String volumename;
069:
070: // the current index of the volume, the stream writes to
071: protected int currentvolumeindex;
072:
073: // a normal file outputstream for writting to the current volume
074: private FileOutputStream fileoutputstream;
075:
076: private GZIPOutputStream zippedoutputstream;
077:
078: //
079: private byte[] magicnumber;
080:
081: // the current position in the open file
082: protected long filepointer;
083:
084: protected long totalbytesofpreviousvolumes;
085:
086: /**
087: * Creates a new spanning output stream with specified volume names and a maximum volume size
088: *
089: * @param volumename - the name of the volumes
090: * @param maxvolumesize - the maximum volume size
091: * @throws IOException
092: */
093: public FileSpanningOutputStream(String volumename,
094: long maxvolumesize) throws IOException {
095: this (new File(volumename), maxvolumesize);
096: }
097:
098: /**
099: * Creates a new spanning output stream with specified volume names and a maximum volume size
100: *
101: * @param volume - the first volume
102: * @param maxvolumesize - the maximum volume size
103: * @throws IOException
104: */
105: public FileSpanningOutputStream(File volume, long maxvolumesize)
106: throws IOException {
107: this (volume, maxvolumesize, 0);
108: }
109:
110: /**
111: * Creates a new spanning output stream with specified volume names and a maximum volume size
112: *
113: * @param volume - the first volume
114: * @param maxvolumesize - the maximum volume size
115: * @param currentvolume - the current volume
116: * @throws IOException
117: */
118: protected FileSpanningOutputStream(File volume, long maxvolumesize,
119: int currentvolume) throws IOException {
120: this .generateMagicNumber();
121: this .createVolumeOutputStream(volume, maxvolumesize,
122: currentvolume);
123: }
124:
125: private void generateMagicNumber() {
126: // only create a magic number, if not already done
127: if (magicnumber == null) {
128: // create empty magic number
129: magicnumber = new byte[MAGIC_NUMER_LENGTH];
130: Date currenttime = new Date();
131: long currenttimeseconds = currenttime.getTime();
132: // create random number generator
133: Random random = new Random(currenttimeseconds);
134: random.nextBytes(magicnumber);
135: Debug
136: .trace("created new magic number for FileOutputstream: "
137: + new String(magicnumber));
138: for (int i = 0; i < magicnumber.length; i++) {
139: Debug.trace(i + " - " + magicnumber[i]);
140: }
141: }
142: }
143:
144: /**
145: * Actually creates the outputstream for writing a volume with index currentvolume and a maximum
146: * of maxvolumesize
147: *
148: * @param volume - the volume to write to
149: * @param maxvolumesize - the maximum volume size
150: * @param currentvolume - the currentvolume index
151: * @throws IOException
152: */
153: private void createVolumeOutputStream(File volume,
154: long maxvolumesize, int currentvolume) throws IOException {
155: fileoutputstream = new FileOutputStream(volume);
156: zippedoutputstream = new GZIPOutputStream(fileoutputstream, 256);
157: currentfile = volume;
158: this .currentvolumeindex = currentvolume;
159: this .maxvolumesize = maxvolumesize;
160: // try to get the volumename from the given volume file
161: // the first volume has no suffix, additional volumes have a .INDEX# suffix
162: String volumesuffix = "." + currentvolume;
163: String volabsolutePath = volume.getAbsolutePath();
164: if (volabsolutePath.endsWith(volumesuffix)) {
165: volumename = volabsolutePath.substring(0, volabsolutePath
166: .indexOf(volumesuffix));
167: } else {
168: volumename = volabsolutePath;
169: }
170: long oldfilepointer = filepointer;
171: // write magic number into output stream
172: this .write(magicnumber);
173: // reset filepointer
174: filepointer = oldfilepointer;
175: }
176:
177: /**
178: *
179: * @param volume
180: * @throws IOException
181: */
182: public FileSpanningOutputStream(File volume) throws IOException {
183: this (volume.getAbsolutePath(), DEFAULT_VOLUME_SIZE);
184: }
185:
186: /**
187: *
188: * @param volumename
189: * @throws IOException
190: */
191: public FileSpanningOutputStream(String volumename)
192: throws IOException {
193: this (volumename, DEFAULT_VOLUME_SIZE);
194: }
195:
196: /**
197: *
198: * @throws IOException
199: */
200: public FileSpanningOutputStream() throws IOException {
201: this (DEFAULT_VOLUME_NAME, DEFAULT_VOLUME_SIZE);
202: }
203:
204: /**
205: * Returns the size of the current volume
206: *
207: * @return the size of the current volume FILE_NOT_AVAILABLE, if there's no current volume
208: */
209: protected long getCurrentVolumeSize() {
210: if (currentfile == null) {
211: return FILE_NOT_AVAILABLE;
212: }
213: try {
214: flush();
215: } catch (IOException e) {
216: e.printStackTrace();
217: }
218: // create a new instance
219: currentfile = new File(currentfile.getAbsolutePath());
220: if (currentvolumeindex == 0) {
221: // this is the first volume, add the additional free space
222: // and add a reserve for overhead and not yet written data
223: return currentfile.length() + this .firstvolumefreespacesize
224: + Math.round(0.001 * currentfile.length());
225: } else {
226: // not the first volume, just return the actual length
227: // and add a reserve for overhead and not yet written data
228: return currentfile.length()
229: + Math.round(0.001 * currentfile.length());
230: }
231: }
232:
233: /**
234: * Closes the stream to the current volume and reopens to the next volume
235: *
236: * @throws IOException
237: */
238: protected void createStreamToNextVolume() throws IOException {
239: // close current stream
240: close();
241: totalbytesofpreviousvolumes = currentfile.length();
242: currentvolumeindex++;
243: // get the name of the next volume
244: String nextvolumename = volumename + "." + currentvolumeindex;
245: // does the creation
246: this .createVolumeOutputStream(new File(nextvolumename),
247: this .maxvolumesize, this .currentvolumeindex);
248: }
249:
250: /**
251: * @see java.io.OutputStream#close()
252: */
253: public void close() throws IOException {
254: this .flush();
255: zippedoutputstream.close();
256: fileoutputstream.close();
257: // reset the filepointer
258: // filepointer = 0;
259: }
260:
261: /**
262: * @see java.io.OutputStream#write(byte[], int, int)
263: */
264: public void write(byte[] b, int off, int len) throws IOException {
265: if (len > maxvolumesize) {
266: throw new IOException(
267: "file can't be written. buffer length exceeded maxvolumesize ("
268: + " > " + maxvolumesize + ")");
269: }
270: // get the current size of this file
271: long currentsize = getCurrentVolumeSize();
272: // calculate the available bytes
273: long available = maxvolumesize - currentsize;
274:
275: if (available < len) {
276: Debug.trace("Not enough space left on volume. available: "
277: + available);
278: Debug.trace("current size is: " + currentsize);
279: // there's not enough space available
280: // create the next volume
281: this .createStreamToNextVolume();
282: }
283: // enough space available, just write to the outputstream
284: zippedoutputstream.write(b, off, len);
285: // increase filepointer by written bytes
286: filepointer += len;
287: }
288:
289: /**
290: * @see java.io.OutputStream#write(byte[])
291: */
292: public void write(byte[] b) throws IOException {
293: this .write(b, 0, b.length);
294: }
295:
296: /**
297: * @see java.io.OutputStream#write(int)
298: */
299: public void write(int b) throws IOException {
300: long availablebytes = maxvolumesize - getCurrentVolumeSize();
301: if (availablebytes >= 1) {
302: zippedoutputstream.write(b);
303: // increase filepointer by written byte
304: filepointer++;
305: } else {
306: // create next volume
307: this .createStreamToNextVolume();
308: zippedoutputstream.write(b);
309: // increase filepointer by written byte
310: filepointer++;
311: }
312: }
313:
314: /**
315: * @see java.io.OutputStream#flush()
316: */
317: public void flush() throws IOException {
318: zippedoutputstream.flush();
319: fileoutputstream.flush();
320: }
321:
322: /**
323: * Returns the amount of currently created volumes
324: *
325: * @return the amount of created volumes
326: */
327: public int getVolumeCount() {
328: return this .currentvolumeindex + 1;
329: }
330:
331: /**
332: *
333: * @return
334: */
335: public long getFirstvolumefreespacesize() {
336: return firstvolumefreespacesize;
337: }
338:
339: /**
340: *
341: * @param firstvolumefreespacesize
342: */
343: public void setFirstvolumefreespacesize(
344: long firstvolumefreespacesize) {
345: this .firstvolumefreespacesize = firstvolumefreespacesize;
346: }
347:
348: /**
349: * Returns the current position in this file
350: *
351: * @return the position in this file
352: * @throws IOException
353: */
354: public long getCompressedFilepointer() throws IOException {
355: this .flush();
356: // return filepointer;
357: return totalbytesofpreviousvolumes + currentfile.length();
358: }
359:
360: public long getFilepointer() {
361: return filepointer;
362: }
363: }
|