/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package install; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.URL; import java.net.URLConnection; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; /** * A class containing all of GetBIRCH's abstract static I/O methods ** * @author Graham Alvare * @author Dale Hamel * @author Brian Fristensky */ public class BIRCHIO { /** * Stores the path of the GetBIRCH JAR file */ public static final File JAR_PATH = getJarPath(); /** * The number of bytes in one kilobyte (used for buffer creation). */ public static final int ONE_KB = 1024; /** * The number of bytes in one kilobyte (used for buffer creation). */ public static final int EIGHT_KB = 8192; /** * The number of bytes in one megabyte (used for buffer creation). */ public static final int ONE_MB = 1048576; /** * The buffer size to use for file transfers * A large buffer size seems to cause downloads to fail * for larger files transferred over long distances * around the world. Performance is still fine with * a smaller buffer size, and is more reliable. */ //public static final int BUFFSIZE = ONE_MB; public static final int BUFFSIZE = EIGHT_KB; /** * The default home directory for the user ** * Switched from System.getProperty("user.home") to * FileSystemView.getFileSystemView().getDefaultDirectory() * in order to handle "My Documents" in Windows. */ public static final File DEFAULT_HOME = javax.swing.filechooser.FileSystemView.getFileSystemView().getDefaultDirectory(); /** * Downloads content from a URL specified by the parameter "location", * and outputs the progress to the output console. ** * @param location the URL to download from * @param fout the file handle to output the downloaded URL data to * @param logger the output log/console to use to display progress information * @param temp whether the file should be deleted after program execution * @throws IOException passes any exceptions to the calling method */ public static boolean wget(URL location, File fout, OutputConsole logger, boolean temp) throws IOException { boolean success = false; long size = -1; FileOutputStream outs; URLConnection connect; byte[] buffer; // get the URL and file size to download connect = location.openConnection(); size = connect.getContentLength(); // print that we will be downloading a file to the logger if (logger != null) { logger.println("Fetching " + fout + " from " + location); } // create the destination file success = fout.createNewFile() || fout.exists(); // schedule the destination file to be deleted when Java // exits if the method parameter "temp" is set to true if (temp) { fout.deleteOnExit(); } // if the new file was created successfully, // proceed with the download process if (success) { // open the output stream to write the file to outs = new FileOutputStream(fout); // create a new buffer for the file transfer buffer = new byte[BUFFSIZE]; // if the size is greater than zero, we can run a progress bar for // the current download process if (size >= 0 && logger != null) { // start the progress bar for the output console logger.setProgress(0); logger.setIndeterminate(false); logger.println("size: " + size); } // copy the stream contents from the source input stream // to the destination output stream try { if (logger != null) { StreamCopier.streamcopy(buffer, location.openStream(), outs, logger, 0, size); } else { StreamCopier.streamcopy(buffer, location.openStream(), outs); } } catch (Exception ex) { success = false; } // flush and close the output stream outs.flush(); outs.close(); } else if (logger != null) { logger.println("DOWNLOAD FAILED!"); } else { System.out.println("DOWNLOAD FAILED!"); } if (! success){ if (logger != null) { logger.println("DOWNLOAD FAILED!"); } else { System.out.println("DOWNLOAD FAILED!"); } } // reset the progress for the output console // for the next step in GetBIRCH if (logger != null) { logger.setProgressTask(""); logger.setIndeterminate(true); } return success; } /** * Copies a file from one location to another. ** * @param source the source file to copy * @param dest the destination file handle to write the copy to * @param logger the output log/console to use to display progress information * @param temp whether the file should be deleted after program execution * @return whether the file was copied successfully * @throws IOException passes any exceptions to the calling method */ public static boolean copy(File source, File dest, OutputConsole console, boolean temp) throws IOException { boolean success = false; long size = source.length(); FileInputStream in; FileOutputStream out; byte[] buffer; // start the progress bar for the output console console.setProgress(0); console.setIndeterminate(false); // create the destination file success = dest.createNewFile(); // if the new file was created successfully, // proceed with the file copy process if (success) { // open the input and output streams in = new FileInputStream(source); out = new FileOutputStream(dest); // create a new buffer for the file transfer buffer = new byte[BUFFSIZE]; // set the progress status of the output console to reflect the // copy process console.setProgressTask("Copying " + source + " to " + dest); // copy the stream contents from the source input stream // to the destination output stream StreamCopier.streamcopy(buffer, in, out, console, 0, size); // flush and close the output stream out.flush(); out.close(); // schedule the destination file to be deleted when Java // exits if the method parameter "temp" is set to true if (temp) { dest.deleteOnExit(); } } // reset the progress for the output console // for the next step in GetBIRCH console.setProgressTask(""); console.setIndeterminate(true); return success; } /** * Decompresses a .tar.gz archive ** * @param todecompress the archive file (must end in .tar.gz) to decompress * @param path the path to run the decompression program from (i.e. where to extract the archive) * @param stdout where to relay standard output * @param err where to relay standard error * @param temp whether to delete the tar file once the jar has halted its execution */ public static void untar(File todecompress, File path, PrintStream stdout, PrintStream err, boolean temp) throws IOException { String basename = todecompress.getAbsolutePath(); /** BIRCH Bugzilla #1212 Call garbage collection before untaring a file. In practice, getbirch hangs when untaring files in the hundreds of megabytes. There is a lot of argument in the blogosphere saying that garbage collection should never be called, because the system always knows better than you when to call gc. They make the point that explicitly calling gc is evidience * of bad code. That may be true, and maybe there are better ways of handling * the processes of running an external command while forking output to two different * places. However, I have seen no convincing arguments that gc can actually hurt. * We will test to see if the frequency of hangs during untaring is improved * asking Java to call gc before we run tar. Note that the Java documentation * says that the only effect of System.gc() is to SUGGEST to Java that it might * be a good time to collect the garbage. Say what? */ System.gc(); if (Main.IS_WINDOWS && Main.WIN_GZ != null && Main.WIN_TAR != null) { // if we are running GetBIRCH on Windows, we must use an extracted // version of tar.exe and gzip.exe. This is because we cannot // guarantee whether Windows has gzip or tar available (by // default, neither program is installed). // BOTH EXE FILES WERE DOWNLOADED FROM: // http://www2.cs.uidaho.edu/~jeffery/win32/ runCommand(new String[]{Main.WIN_GZ.getAbsolutePath(), basename}, null, path, stdout, err); stdout.flush(); err.flush(); runCommand(new String[]{Main.WIN_TAR.getAbsolutePath(), "xf", basename.substring(0, basename.lastIndexOf(".gz"))}, null, path, stdout, err); } else { // run the host Unix operating system's gunzip and tar commands // on the .tar.gz archive runCommand(new String[]{"gunzip", basename}, null, path, stdout, err); stdout.flush(); err.flush(); runCommand(new String[]{"tar", "-xvpf", basename.substring(0, basename.lastIndexOf(".gz"))}, null, path, stdout, err); } if (temp) { File tar = new File(basename.substring(0, basename.lastIndexOf(".gz"))); if (tar.exists()) { tar.deleteOnExit(); } } } /** * Add a file to a .tar archive ** * @param tar_file the archive filename * @param path the path to add to the archive * @param stdout where to relay standard output * @param err where to relay standard error */ public static void add_tar(File tar_file, File path, PrintStream stdout, PrintStream err) { if (Main.IS_WINDOWS && Main.WIN_GZ != null && Main.WIN_TAR != null) { // if we are running GetBIRCH on Windows, we must use an extracted // version of tar.exe and gzip.exe. This is because we cannot // guarantee whether Windows has gzip or tar available (by // default, neither program is installed). // BOTH EXE FILES WERE DOWNLOADED FROM: // http://www2.cs.uidaho.edu/~jeffery/win32/ runCommand(new String[]{ Main.WIN_TAR.getAbsolutePath(), "rf", tar_file.getAbsolutePath(), path.getAbsolutePath() }, null, path, stdout, err); } else { // run the host Unix operating system's gunzip and tar commands // on the .tar.gz archive runCommand(new String[]{"tar", "-rvpf", tar_file.getAbsolutePath(), path.getAbsolutePath() }, null, path, stdout, err); } } /** * Recursively deletes files and directories ** * @param toDelete the file to delete * @return whether all files, directories and sub-directories were deleted */ public static boolean cleanUp(File toDelete) { boolean success = false; // if the current file handle is a directory, delete all of the // files within the directory before removing the directory if (toDelete.isDirectory()) { for (File file : toDelete.listFiles()) { success &= cleanUp(file); } } // delete the current file or directory success = toDelete.delete(); return success; } /** * Extracts a file from the current Jar file as a temporary file ** * @param resource the resource to extract * @param prefix the prefix for the file to extract. * @param suffix the suffix (or extension) for the file to extract. * @return the file handle for the extracted resource. */ public static File extract_temp(String resource, String prefix, String suffix) { File destination = null; try { // create a new temporary destination file // for writing the source file's content destination = File.createTempFile(prefix, suffix); extract(resource, destination, true); } catch (IOException ioe) { ioe.printStackTrace(System.err); } return destination; } /** * Extracts a file from the current Jar file ** * @param resource the resource to extract * @param destination the destination to extract the resource * @param temp whether to delete the file once the jar has halted its execution */ private static void extract(String resource, File destination, boolean temp) { byte[] buffer; System.out.println("EXTRACTING: " + resource + " TO: " + destination); try { if (destination.canWrite()) { // open the input and output streams InputStream is = BIRCHIO.class.getResourceAsStream(resource); OutputStream os = new FileOutputStream(destination); // create the buffer for copying the file buffer = new byte[1024]; // if the input stream is not null, copy the file data to // the destination output stream (file handle) if (is != null) { StreamCopier.streamcopy(buffer, is, os); // schedule the destination file to be deleted when Java // exits if the method parameter "temp" is set to true if (temp) { destination.deleteOnExit(); } } else { System.out.println("ERROR OPENING RESOURCE FROM JAR! - " + resource); System.exit(0); } // flush and close the output stream os.flush(); os.close(); } } catch (IOException ioe) { ioe.printStackTrace(System.err); } } /** * Runs simple commands. * Reroutes all output to the logger PrintStream. ** * @param execute the command string to run * @param envp the environment variables to set for the command * @param path the path to execute the command in * @param stdout where to relay standard output * @param err where to relay standard error */ public static int runCommand (String[] execute, String[] envp, File path, PrintStream stdout, PrintStream err) { // the exit status for the process int status = -1; // the process object obtained on execution Process process = null; // the string builder to make the execution // string for displaying to the user. StringBuilder execString = new StringBuilder(); // build the command string - for display to the user execString.append("executing -"); for (String cmd : execute) { execString.append(" "); execString.append(cmd); } // relay the command string the message system stdout.println(execString.toString()); // obtain the process object for the command try { process = Runtime.getRuntime().exec(execute, envp, path); // ensure the process object is not null if (process != null) { // output the program's stdout and stderr to the command prompt Thread stdoutThread = new Thread(new StreamCopier(StreamCopier.DEFAULT_BUFF_SIZE, process.getInputStream(), stdout)); Thread stderrThread = new Thread(new StreamCopier(StreamCopier.DEFAULT_BUFF_SIZE, process.getErrorStream(), err)); // start the pipe threads stdoutThread.start(); stderrThread.start(); // close the standard input stream for the process // (because we are not sending any input to the process) process.getOutputStream().close(); // wait for the command to finish and store its exit status status = process.waitFor(); // join the pipe threads stdoutThread.join(); stderrThread.join(); // display the command's result if debug is enabled stdout.println("Command finished execution, return status: " + status); } } catch (Throwable e) { // if there are any errors, print them to the error prompt e.printStackTrace(err); } return status; } /** * This method determines the path of the jar file ** * @return the system path where the jar file is located */ private static File getJarPath() { URL url = BIRCHIO.class.getProtectionDomain().getCodeSource().getLocation(); File parent = null; try { parent = new File(url.toURI().getPath().replaceFirst("^file:", "").replaceAll("getbirch\\.jar.*$", "")); while (parent.exists() && !parent.isDirectory()) { parent = parent.getParentFile(); } } catch (Exception ioe) { parent = new File(System.getProperty("user.dir")); } return parent; } /** * Compresses a directory/file recursively into a zip output stream ** * @param zout the zip output stream destination for the file * @param file the file, or directory, to compress */ public static void compress(ZipOutputStream zout, File file) throws IOException { // create a new buffer to compress files with byte[] buffer = new byte[BUFFSIZE]; if (file.exists() && file.isDirectory()) { // if the current file handle is a directory, recursively compress // all of the files and sub-directories within the handle File[] subfiles = file.listFiles(); for (int i = 0; i < subfiles.length; i++) { if (subfiles[i].exists()) { compress(zout, subfiles[i]); } } } else { ZipEntry ze = new ZipEntry(file.getAbsolutePath()); FileInputStream in = new FileInputStream(file.getAbsolutePath()); // set the file comment to the "exec" if the file is executable // on the host system and architecture ze.setComment(file.canExecute() ? "exec" : ""); // synchronize the last modified time of the zip file ze.setTime(file.lastModified()); // add the file to the zip archive System.out.println(" Adding: " + file.getAbsolutePath()); zout.putNextEntry(ze); StreamCopier.streamcopy(buffer, in, zout); // close the current zip entry (compressed file) zout.closeEntry(); in.close(); } } /** * Decompresses a zip archive. ** * @param zf the zip file to decompress. * @param destDir the destination directory for the zip contents. */ public static void unzip (File zf, File destDir) throws IOException { // extracts just sizes only. // extract resources and put them into the hashtable. FileInputStream fis = new FileInputStream(zf); BufferedInputStream bis = new BufferedInputStream(fis); ZipInputStream zis = new ZipInputStream(bis); ZipEntry ze = null; byte[] buffer = new byte[BUFFSIZE]; // iterate through all of the entries in the zip file while ((ze = zis.getNextEntry()) != null) { // if the current file is not a directory, extract it // this is because we do not need to recurse through directories // in a zip file to extract all sub-directories and files from it if (!ze.isDirectory()) { File dest = new File(destDir, ze.getName()); File parentDir = dest.getParentFile(); FileOutputStream fout = new FileOutputStream(dest); // make the parent directories for the zip file parentDir.mkdirs(); // create the destination file dest.createNewFile(); // synchronizes the last modified times dest.setLastModified(ze.getTime()); // ensure that files stored as executable remain so if ("exec".equalsIgnoreCase(ze.getComment())) { dest.setExecutable(true); } // copy the stream contents from the source input stream // to the destination output stream StreamCopier.streamcopy(buffer, zis, fout); // flush and close the output stream fout.flush(); fout.close(); } } // close the zip file zis.close(); } /** * Recursively determines the size (in bytes) of all files in a given path. ** * @param path the path to determine the size of * @return the number of bytes (size) in the path */ public static long getPathSize(File path) { long length = 0; if (path.isDirectory()) { for (File subfile : path.listFiles()) { length += getPathSize(subfile); } } else { length = path.length(); } return length; } /** * A utility function for reading one line from a file. Many files in BIRCH * are one line files (such as BIRCH.version). This function will read * these files, specified by a URL, then return the string contained by the * file. The URL class is used for the path, so the function will be able * to handle both remote and local files. If the file contains more than * one line, only the first line will actually be read. ** * @param path the URL path of the file * @return the string contained within the file * @throws IOException passes any I/O exceptions to the calling class */ public static String oneLineFile (URL path) throws IOException { // the result from the file String result = null; // create a new URL object and open a URL stream BufferedReader breader = new BufferedReader(new InputStreamReader(path.openStream())); // get the string from the URL result = breader.readLine(); // trim the string (unless null, which would indicate a failure) if (result != null) { result.trim(); } // close the reader object breader.close(); // return the result return result; } }