Java tutorial
/* * IzPack - Copyright 2001-2012 Julien Ponge, All Rights Reserved. * * http://izpack.org/ * http://izpack.codehaus.org/ * * Copyright 2012 Tim Anderson * * 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.installer.unpacker; import com.izforge.izpack.api.data.Blockable; import com.izforge.izpack.api.data.PackFile; import com.izforge.izpack.api.exception.InstallerException; import com.izforge.izpack.util.os.FileQueue; import com.izforge.izpack.util.os.FileQueueMove; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.*; import java.util.logging.Logger; /** * Unpacks a file from a pack. * <p/> * This manages queueing files that are blocked. * * @author Tim Anderson */ public abstract class FileUnpacker { /** * Determines if unpacking should be cancelled. */ private final Cancellable cancellable; /** * The target. */ private File target; /** * Temporary target, used if the target file is blockable. */ private File tmpTarget; /** * The file queue. */ private final FileQueue queue; /** * Determines if the file was queued. */ private boolean queued; /** * The logger. */ private static final Logger logger = Logger.getLogger(FileUnpacker.class.getName()); /** * Constructs a <tt>FileUnpacker</tt>. * * @param cancellable determines if unpacking should be cancelled * @param queue the file queue. May be {@code null} */ public FileUnpacker(Cancellable cancellable, FileQueue queue) { this.cancellable = cancellable; this.queue = queue; } /** * Unpacks a pack file. * * @param file the pack file meta-data * @param packInputStream the pack input stream * @param target the target * @throws IOException for any I/O error * @throws InstallerException for any installer exception */ public abstract void unpack(PackFile file, InputStream packInputStream, File target) throws IOException, InstallerException; /** * Determines if the file was queued. * * @return <tt>true</tt> if the file was queued */ public boolean isQueued() { return queued; } /** * Copies an input stream to a target, setting its timestamp to that of the pack file. * <p/> * If the target is a blockable file, then a temporary file will be created, and the file queued. * * @param file the pack file * @param in the pack file stream * @param target the file to write to * @return the number of bytes actually copied * @throws InterruptedIOException if the copy operation is cancelled * @throws IOException for any I/O error */ protected long copy(PackFile file, InputStream in, File target) throws IOException { OutputStream out = getTarget(file, target); byte[] buffer = new byte[5120]; long bytesCopied = 0; long bytesToCopy = (file.isBackReference() ? file.getLinkedPackFile().length() : file.length()); logger.fine("|- Copying to file system (size: " + bytesToCopy + " bytes)"); try { while (bytesCopied < bytesToCopy) { if (cancellable.isCancelled()) { // operation cancelled throw new InterruptedIOException("Copy operation cancelled"); } bytesCopied = copy(file, buffer, in, out, bytesCopied); } } finally { IOUtils.closeQuietly(out); } postCopy(file); return bytesCopied; } /** * Invoked after copying is complete to set the last modified timestamp, and queue blockable files. * * @param file the pack file meta-data */ protected void postCopy(PackFile file) { setLastModified(file); if (isBlockable(file)) { queue(); } } /** * Copies from the input stream to the output stream. * * @param file the pack file * @param buffer the buffer to use * @param in the stream to read from * @param out the stream to write to * @param bytesCopied the current no. of bytes copied * @return the number of bytes actually copied * @throws IOException for any I/O error */ protected long copy(PackFile file, byte[] buffer, InputStream in, OutputStream out, long bytesCopied) throws IOException { int maxBytes = (int) Math.min(file.length() - bytesCopied, buffer.length); int read = read(buffer, in, maxBytes); if (read == -1) { throw new IOException("Unexpected end of stream (installer corrupted?)"); } out.write(buffer, 0, read); bytesCopied += read; return bytesCopied; } /** * Reads up to <tt>maxBytes</tt> bytes to the specified buffer. * * @param buffer the buffer * @param in the input stream * @param maxBytes the maximum no. of bytes to read * @return the no. of bytes read * @throws IOException for any I/O error */ protected int read(byte[] buffer, InputStream in, int maxBytes) throws IOException { return in.read(buffer, 0, maxBytes); } /** * Returns a stream to the target file. * <p/> * If the target file is blockable, then a temporary file will be created, and a stream to this returned instead. * * @param file the pack file meta-data * @param target the requested target * @return a stream to the actual target * @throws IOException an I/O error occurred */ protected OutputStream getTarget(PackFile file, File target) throws IOException { this.target = target; OutputStream result; if (isBlockable(file)) { // If target file might be blocked the output file must first refer to a temporary file, because // Windows Setup API doesn't work on streams but only on physical files tmpTarget = File.createTempFile("__FQ__", null, target.getParentFile()); result = FileUtils.openOutputStream(tmpTarget); } else { result = FileUtils.openOutputStream(target); } return result; } /** * Sets the last-modified timestamp of a file from the pack-file meta-data. * * @param file the pack file meta-data */ protected void setLastModified(PackFile file) { // Set file modification time if specified if (file.lastModified() >= 0) { File f = (tmpTarget != null) ? tmpTarget : target; if (!f.setLastModified(file.lastModified())) { logger.warning("Failed to set last modified timestamp for: " + target); } } } /** * Determines if a pack file is blockable. * <p/> * Blockable files must be queued using {@link #queue()}. * * @param file the pack file * @return <tt>true</tt> if the file is blockable, otherwise <tt>false</tt> */ private boolean isBlockable(PackFile file) { return queue != null && (file.blockable() != Blockable.BLOCKABLE_NONE); } /** * Queues the target file. */ private void queue() { FileQueueMove move = new FileQueueMove(tmpTarget, target); move.setForceInUse(true); move.setOverwrite(true); queue.add(move); logger.fine(tmpTarget.getAbsolutePath() + " -> " + target.getAbsolutePath() + " added to file queue for being copied after reboot"); // The temporary file must not be deleted until the file queue will be committed tmpTarget.deleteOnExit(); queued = true; } }