/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.ut.biolab.medsavant.shared.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
/**
* I/O-related utility methods. Functions for manipulating Savant files are in
* SavantFileUtils.
*
* @author mfiume, tarkvara
*/
public class IOUtils {
private static final Log LOG = LogFactory.getLog(IOUtils.class);
/**
*
* @param inputFile - the file to check
* @param directory - the directory to test for.
* @return true if 'directory' is an ancestor of 'inputFile', false otherwise.
* @throws IOException
*/
public static boolean isInDirectory(File inputFile, File directory) throws IOException {
if (inputFile.isDirectory() && inputFile.getAbsolutePath().equals(directory.getAbsolutePath())) {
return true;
}
File parent = inputFile.getParentFile();
while (parent != null) {
if (parent.getCanonicalPath().equals(directory.getCanonicalPath())) {
return true;
}
parent = parent.getParentFile();
}
return false;
}
/**
* Deletes the directory p, recursing upwards through the directory tree and deleting all empty
* parent directories until it reaches the ancestor directory 'stopAtDir'. Immediately returns
* if 'stopAtDir' is not an ancestor of p.
*
* @param p - The nested directory to delete.
* @param stopAtDir - the ancestor directory to stop at (this directory will NOT be deleted, even if empty).
* @throws IOException
*/
public static void deleteEmptyParents(File p, File stopAtDir) throws IOException{
if(!isInDirectory(p, stopAtDir)){
return;
}
if(p != null && p.isDirectory() && !p.getAbsolutePath().equals(stopAtDir.getAbsolutePath())){
if(p.listFiles().length == 0){
File parent = p.getParentFile();
p.delete();
deleteEmptyParents(parent, stopAtDir);
}
}
}
public static void copyFile(File srcFile, File destFile) throws IOException {
if (srcFile.equals(destFile)) {
return;
}
copyStream(new FileInputStream(srcFile), new FileOutputStream(destFile));
}
public static void copyDir(File srcDir, File destDir) throws IOException {
File[] files = srcDir.listFiles();
if (files != null) {
for (File f : files) {
copyFile(f, new File(destDir, f.getName()));
}
}
}
public static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[8192];
int len;
long tot = 0;
try {
while ((len = input.read(buf)) > 0) {
tot+=len;
output.write(buf, 0, len);
}
LOG.info("Copied/extracted "+tot+" bytes");
} catch (IOException x) {
// There's a bug in BlockCompressedInputStream where it throws an IOException instead of doing a proper EOF.
// Suppress this exception, but throw real ones.
if (!x.getMessage().equals("Unexpected compressed block length: 1")) {
x.printStackTrace();
throw x;
}
} finally {
input.close();
output.close();
}
}
/**
* Cheesy method which lets us read from an InputStream without having to
* instantiate a BufferedReader. Intended to get around some glitches
* reading GZIPInputStreams over an HTTP stream.
*/
public static String readLine(InputStream input) throws IOException {
StringBuilder buf = new StringBuilder();
int c;
while ((c = input.read()) >= 0 && c != '\n') {
buf.append((char) c);
}
return c >= 0 ? buf.toString() : null;
}
/**
* Recursively delete a directory.
*/
public static boolean deleteDirectory(File path) {
if (path.exists()) {
File[] files = path.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
}
return path.delete();
}
/**
* Make sure this directory and all its parents have world execute
* permissions. This is necessary to ensure that MySQL can write files to
* the given directory.
*
* @param base
* @return
*/
public static void checkForWorldExecute(File f) throws IOException {
//File f = base.isDirectory() ? base : base.getParentFile();
if (hasWorldExecute(f)) {
/*f = f.getParentFile();
if (f == null) {
// Reached /
return;
}*/
return;
}
throw new IOException(f + " did not have execute permissions.");
}
/**
* Retrieve the permission string (e.g. "rwxrwxrwx") for the given file.
* TODO: implement this in a less Unix-specific manner.
*
* @param f the file or directory whose permissions we are checking
*/
private static String getPermissionString(File f) throws IOException {
Process proc = Runtime.getRuntime().exec("ls -ld " + f);
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = null;
String result = null;
while ((line = reader.readLine()) != null) {
int spacePos = line.indexOf(' ');
if (spacePos > 0) {
result = line.substring(1, spacePos);
}
}
//LOG.info("Permission of " + f.getAbsolutePath() + " is " + result);
return result;
}
/**
* Does the given directory have world-execute permissions? TODO: implement
* this in a less Unix-specific manner.
*
* @param dir the directory to be checked
*/
private static boolean hasWorldExecute(File f) throws IOException {
String perm = getPermissionString(f);
if (perm != null && perm.length() >= 9) {
return perm.charAt(8) == 'x';
}
return false;
}
private static List<File> unArchive(File dest, ArchiveInputStream ais) throws IOException {
List<File> files = new ArrayList<File>();
ArchiveEntry archiveEntry = ais.getNextEntry();
// tarIn is a TarArchiveInputStream
while (archiveEntry != null) {// create a file with the same name as the tarEntry
File destPath = new File(dest, archiveEntry.getName());
if (archiveEntry.isDirectory()) {
destPath.mkdirs();
} else {
destPath.createNewFile();
byte[] btoRead = new byte[1024];
BufferedOutputStream bout
= new BufferedOutputStream(new FileOutputStream(destPath));
int len = 0;
while ((len = ais.read(btoRead)) != -1) {
bout.write(btoRead, 0, len);
}
bout.close();
btoRead = null;
files.add(destPath);
}
archiveEntry = ais.getNextEntry();
}
ais.close();
return files;
}
/**
* Strips the last extension from the filename corresponding to f,
* and returns a new File for this new filename, or null if
* the file was not found to have an extension.
*/
public static String stripExtension(File f){
String filename = f.getName();
for(int i = filename.length() - 1; i > 0; --i){
if(filename.charAt(i) == '.'){
return filename.substring(0, i);
}
}
return null;
}
/**
* Moves a file from one location to another using apache io library.
* Use this instead of File.renameTo
*
* @param src - the path to the source file
* @param dst - the path to the destination file
* @return
*/
public static boolean moveFile(File src, File dst){
try{
FileUtils.moveFile(src, dst);
}catch(FileExistsException fee){
LOG.error("Error while moving file", fee);
return false;
}catch(IOException ie){
LOG.error("Error while moving File", ie);
return false;
}
return true;
}
/**
* @return true if the File is a compressed file, archive, or compressed archive.
*/
public static boolean isArchive(File f){
String n = f.getName().toLowerCase();
return n.endsWith(".zip") || n.endsWith(".bz2") || n.endsWith(".tgz") || n.endsWith(".gz") || n.endsWith(".tar");
}
/**
* Decompresses/unarchives the file given by 'f' to the destination path
* given by dest, and returns a list of all the files contained within the archive.
* If the input file is compressed, but not archived, the destination filename will be
* "dest/"+stripExtension(f.getName()). If the input file is
* not compressed or archived, the input file will be moved to the new location
* given by dest, and returned in a one-element list.
*
* Note the file given by f will be deleted.
*
* @return A list of files that were decompressed from the input file f, or
* a list containing the input file in its new location if the input file was not compressed/archived.
* @throws IOException
* @see stripExtension
*/
public static List<File> decompressAndDelete(File f, File dest) throws IOException {
String lcfn = f.getName().toLowerCase();
InputStream is = new BufferedInputStream(new FileInputStream(f));
List<File> files;
//Detect compression type.
if (lcfn.endsWith(".gz") || lcfn.endsWith(".tgz")) {
is = new GzipCompressorInputStream(is, true);
} else if (lcfn.endsWith(".bz2")) {
is = new BZip2CompressorInputStream(is, true);
}
//Detect archive type.
if (lcfn.endsWith(".tgz") || lcfn.endsWith(".tar.gz") || lcfn.endsWith(".tar") || lcfn.endsWith(".tar.bz2")) {
is = new TarArchiveInputStream(is);
files = unArchive(dest, (ArchiveInputStream)is);
} else if (lcfn.endsWith(".zip")) {
is = new ZipArchiveInputStream(is);
files = unArchive(dest, (ArchiveInputStream)is);
} else{
String filename = f.getName();
if (!(is instanceof BufferedInputStream)) {
filename = stripExtension(f);
if(filename == null){
filename = f.getName()+".decompressed";
}
}
File outputFile = new File(dest, filename);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile));
if(!(is instanceof BufferedInputStream)){
IOUtils.copyStream(is, bos);
}else{
if(!moveFile(f, outputFile)){
throw new IOException("Couldn't move file "+f.getAbsolutePath()+" to "+outputFile.getAbsolutePath());
}
}
bos.close();
files = new ArrayList<File>(1);
files.add(outputFile);
}
is.close();
if(f.exists()){
f.delete();
}
return files;
}
/**
* Checks if an input stream is gzipped.
*
* @param in
* @return
*/
public static boolean isGZipped(InputStream in) {
if (!in.markSupported()) {
in = new BufferedInputStream(in);
}
in.mark(2);
int magic = 0;
try {
magic = in.read() & 0xff | ((in.read() << 8) & 0xff00);
in.reset();
} catch (IOException e) {
e.printStackTrace(System.err);
return false;
}
return magic == GZIPInputStream.GZIP_MAGIC;
}
/**
* Checks if a file is gzipped.
*
* @param f
* @return
*/
public static boolean isGZipped(File f) {
int magic = 0;
try {
RandomAccessFile raf = new RandomAccessFile(f, "r");
magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00);
raf.close();
} catch (Throwable e) {
e.printStackTrace(System.err);
}
return magic == GZIPInputStream.GZIP_MAGIC;
}
/**
* Checks if a file is zipped.
*
* @param f
* @return
*/
public static boolean isZipped(File f) {
try {
RandomAccessFile raf = new RandomAccessFile(f, "r");
long n = raf.readInt();
raf.close();
if (n == 0x504B0304) {
return true;
} else {
return false;
}
} catch (Throwable e) {
e.printStackTrace(System.err);
return false;
}
}
/**
* Unzip a zip file
*
* @param zipFile Path to the zip file
* @param toPath Destination path
* @throws ZipException
* @throws IOException
*/
public static List<File> unzipFile(File file, String toPath) throws ZipException, IOException {
int BUFFER = 2048;
ZipFile zip = new ZipFile(file);
//new File(newPath).mkdir();
Enumeration zipFileEntries = zip.entries();
List<File> files = new ArrayList<File>();
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
File destFile = new File(toPath, currentEntry);
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
if (!entry.isDirectory()) {
BufferedInputStream is = new BufferedInputStream(zip.getInputStream(entry));
int currentByte;
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream dest = new BufferedOutputStream(fos,
BUFFER);
// read and write until last byte is encountered
while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, currentByte);
}
dest.flush();
dest.close();
is.close();
}
if (currentEntry.toLowerCase().endsWith(".zip") && isZipped(destFile)) {
// found a zip file, try to open
files.addAll(unzipFile(destFile, new File(destFile.getAbsolutePath()).getParent()));
} else {
files.add(destFile);
}
}
return files;
}
public static File zipDirectory(File dir, File outFile) throws IOException {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outFile));
zipRecursively(".", dir, out);
out.close();
return outFile;
}
private static void zipRecursively(String path, File dir, ZipOutputStream out) throws IOException {
for (File f : dir.listFiles()) {
writeFileToZipStream(path, f, out);
if (f.isDirectory()) {
zipRecursively(path + "/" + f.getName() + "/", f, out);
}
}
}
private static void writeFileToZipStream(String path, File f, ZipOutputStream out) throws IOException {
// name the file inside the zip file
if (f.isFile()) {
out.putNextEntry(new ZipEntry(path + "/" + f.getName()));
FileInputStream in = new FileInputStream(f);
byte[] b = new byte[1024];
int count;
while ((count = in.read(b)) > 0) {
out.write(b, 0, count);
}
in.close();
} else if (f.isDirectory()) {
out.putNextEntry(new ZipEntry(f.getName() + "/"));
}
}
public static File zipFile(File file, File outputFile) throws FileNotFoundException, IOException {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outputFile));
writeFileToZipStream(".", file, out);
out.close();
return outputFile;
}
}