uk.gov.nationalarchives.droid.profile.ProfileDiskAction Maven / Gradle / Ivy
/**
* Copyright (c) 2016, The National Archives
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the The National Archives nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package uk.gov.nationalarchives.droid.profile;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import org.apache.commons.io.DirectoryWalker;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.schlichtherle.util.zip.BasicZipFile;
import de.schlichtherle.util.zip.ZipEntry;
import de.schlichtherle.util.zip.ZipOutputStream;
import uk.gov.nationalarchives.droid.results.handlers.ProgressObserver;
/**
* @author rflitcroft
*
*/
public class ProfileDiskAction {
private static final int UNITY_PERCENT = 100;
private static final int BUFFER_SIZE = 8192;
private static final char FORWARD_SLASH = '/';
private static final char BACKWARD_SLASH = '\\';
private final Log log = LogFactory.getLog(getClass());
/**
* Saves the profile to disk by zipping it up.
*
* @param baseDir
* the base direcytory of the zip operation
* @param destination
* the ntarget zip file
* @param callback
* a progress observer, notified on progress
* @throws IOException
* if a file IO operation failed
*/
public void saveProfile(String baseDir, java.io.File destination,
ProgressObserver callback) throws IOException {
log.info(String.format("Saving profile [%s] to [%s]", baseDir, destination));
File output = new File(destination.getAbsolutePath() + ".tmp~");
if (!output.delete() && output.exists()) {
String message = String.format("Could not delete original profile file: %s. "
+ "Will try to delete on exit.", output.getAbsolutePath());
log.warn(message);
output.deleteOnExit();
}
if (!output.createNewFile()) {
throw new IOException(String.format("Error creating tmp file [%s]", output));
}
ProfileWalker profileWalker = new ProfileWalker(new File(baseDir), output, callback);
try {
profileWalker.save();
callback.onProgress(UNITY_PERCENT);
if (destination.exists()) {
if (!destination.delete()) {
throw new IOException(String.format("Error removing old file [%s]", destination));
}
}
if (!output.renameTo(destination)) {
throw new IOException(String.format("Error creating saved file [%s]", destination));
}
} finally {
profileWalker.close();
}
}
/**
* Walks directories and files in a profile.
*
* @author rflitcroft
*
*/
private final class ProfileWalker extends DirectoryWalker {
private String sourcePath;
private File destination;
//private ZipArchiveOutputStream out;
private ZipOutputStream out;
private File source;
private ProgressObserver callback;
private final long bytesToProcess;
private long bytesProcessed;
/**
*
*/
public ProfileWalker(File source, File destination,
ProgressObserver callback) {
sourcePath = source.getAbsolutePath() + File.separator;
this.source = source;
this.destination = destination;
this.callback = callback;
bytesToProcess = FileUtils.sizeOfDirectory(source);
}
/**
* @throws IOException
*
*/
public void close() throws IOException {
out.close();
}
@SuppressWarnings("unchecked")
@Override
protected void handleStart(File startDirectory, Collection results)
throws IOException {
//out = new ZipArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(destination)));
out = new ZipOutputStream(new FileOutputStream(destination));
out.setMethod(ZipEntry.DEFLATED);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
protected void handleEnd(Collection results) throws IOException {
out.close();
}
@SuppressWarnings("unchecked")
@Override
protected boolean handleDirectory(File directory, int depth,
Collection results) {
return true;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
protected void handleDirectoryStart(File directory, int depth, Collection results) {
}
@SuppressWarnings("unchecked")
@Override
protected void handleFile(File file, int depth, Collection results)
throws IOException {
String entryPath = getUnixStylePath(StringUtils.substringAfter(file
.getAbsolutePath(), sourcePath));
//ZipArchiveEntry entry = (ZipArchiveEntry) out.createArchiveEntry(file, entryPath);
//out.putArchiveEntry(entry);
ZipEntry entry = new ZipEntry(entryPath);
out.putNextEntry(entry);
final BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
try {
bytesProcessed = writeFile(in, out, callback, bytesProcessed, bytesToProcess);
} finally {
//out.closeArchiveEntry();
out.closeEntry();
in.close();
}
}
protected void save() throws IOException {
walk(source, Collections.EMPTY_LIST);
}
}
/**
* Loads a droid file from disk.
*
* @param source
* the droid file to load
* @param destination
* the target directory where the source should be unpacked
* @param observer
* a progress observer which is notified on progress
* @throws IOException
* if the file operations failed
*/
public void load(File source, File destination, ProgressObserver observer)
throws IOException {
// Delete any remnants of this expanded profile
if (destination.exists()) {
FileUtils.deleteDirectory(destination);
}
BasicZipFile zip = new BasicZipFile(source);
try {
// count the zip entries so we can do progress bar
long totalSize = 0L;
for (Enumeration extends ZipEntry> it = zip.entries(); it
.hasMoreElements();) {
ZipEntry e = it.nextElement();
totalSize += e.getSize();
}
long bytesSoFar = 0L;
for (Enumeration extends ZipEntry> it = zip.entries(); it
.hasMoreElements();) {
ZipEntry entry = it.nextElement();
// zip entries can be created on windows or unix, and can retain
// the path separator for that platform. We must ensure that
// the paths we find inside the zip file will work on the platform
// we are running on. Technically, zip entry paths should be
// created using the unix separator, no matter what platform they
// are created on - but this is not always done correctly.
final String entryName = getPlatformSpecificPath(entry.getName());
File expandedFile = new File(destination + File.separator
+ entryName);
BufferedInputStream in = new BufferedInputStream(zip
.getInputStream(entry));
FileUtils.forceMkdir(expandedFile.getParentFile());
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(expandedFile));
bytesSoFar = readFile(in, out, observer, bytesSoFar, totalSize);
}
observer.onProgress(UNITY_PERCENT);
} finally {
zip.close();
}
}
/**
* Replace all path separators in the path with the correct one for the
* platform we are running on. This enables us to correctly recreate the
* directory structure for zip entries on the platform we are running on,
* even if the zip file was created using path separators from another platform.
*
* @param path the path to be made platform specific.
* @return a platform specific path.
*/
private String getPlatformSpecificPath(final String path) {
// NOTE: there is a "bug" in the Java String replaceAll function, noted as such in the
// the java bug databases.
// If the replacement string is a backslash (platform specific file separator in windows),
// then the replace function thinks it's merely the first character in an escaped string,
// and should be doubled up. Needless to say, we can't simply double up all path separators,
// as the forward slash would replace with //, instead of /, whereas \\ would give \.
// So, the following generates an error when the File separator is a backslash.
//final String platformPath = path.replaceAll("[\\\\/]", File.separator);
String platformPath = null;
switch (File.separatorChar) {
case BACKWARD_SLASH:
platformPath = path.replace(FORWARD_SLASH, BACKWARD_SLASH);
break;
case FORWARD_SLASH:
platformPath = path.replace(BACKWARD_SLASH, FORWARD_SLASH);
break;
default: // file separator is not a Windows OR Unix convention...
platformPath = path.replaceAll("[\\\\/]", File.separator);
}
return platformPath;
}
/**
* Replaces any back slashes with forward slashes, to give a unix style path.
* Zip files should use the / instead of the \ separator (no matter what platform
* they are created on), as this will work on both platforms with zip software.
* @param path of file
* @return String a unix style path (all separators are forward slashes)
*/
private String getUnixStylePath(final String path) {
return path.replace(BACKWARD_SLASH, FORWARD_SLASH);
}
private long readFile(BufferedInputStream in, BufferedOutputStream out,
ProgressObserver observer, long bytesSoFar, long totalSize)
throws IOException {
long totalBytesRead = bytesSoFar;
try {
int bytesIn = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesIn = in.read(buffer)) != -1) {
totalBytesRead += bytesIn;
int progressSoFar = (int) ((UNITY_PERCENT * totalBytesRead) / totalSize);
observer.onProgress(progressSoFar);
out.write(buffer, 0, bytesIn);
}
} finally {
try {
in.close();
} finally {
out.close();
}
}
return totalBytesRead;
}
private long writeFile(BufferedInputStream in, ZipOutputStream out, //ZipArchiveOutputStream out,
ProgressObserver observer, long bytesSoFar, long totalSize)
throws IOException {
long totalBytesWritten = bytesSoFar;
int bytesIn = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesIn = in.read(buffer)) != -1) {
totalBytesWritten += bytesIn;
int progressSoFar = (int) ((UNITY_PERCENT * totalBytesWritten) / totalSize);
observer.onProgress(progressSoFar);
out.write(buffer, 0, bytesIn);
}
return totalBytesWritten;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy