/*
* Copyright 2015 Daniel Dittmar
*
* 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 dan.dit.whatsthat.util.general;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* This is an utility class which offers specialized IO methods and file
* system support like unzipping a zip archive to a folder or getting all files
* within a directory.
* @author Daniel
*
*/
public final class IOUtil {
private IOUtil() {
}
/**
* Unzips the given zip archive to the given target location, keeping the folder structure
* of the zip archive.
* @param zipArchive The zip archive to be extracted.
* @param target The target location the archive should be extracted to.
* @return <code>true</code> only if every file and directory was successfully extracted and no
* IOException occured. If this returns <code>false</code> it is possible that several files were
* extracted successfully.
*/
public static boolean unzip(File zipArchive, File target) {
if (zipArchive == null || target == null || !zipArchive.exists() || !target.exists()) {
Log.e("HomeStuff", "Unzip failed at 0: " + zipArchive + " target " + target);
return false;
}
ZipInputStream zipinputstream;
try {
zipinputstream = new ZipInputStream(new FileInputStream(zipArchive));
} catch (FileNotFoundException e) {
Log.e("HomeStuff", "Unzip failed at 1: " + e);
return false;
}
ZipEntry zipentry;
final int bufferSize = 1024;
byte[] buf = new byte[bufferSize];
boolean result = true;
do {
// for each entry to be extracted
try {
zipentry = zipinputstream.getNextEntry();
} catch (IOException ioe) {
// critical, especially if next would be a directory which contains files
Log.e("HomeStuff", "Unzip failed at 2: " + ioe);
return false;
}
if (zipentry != null) {
// entry was successfully opened
String entryName = zipentry.getName();
File tempExtracted = new File(target, entryName);
if (zipentry.isDirectory()) {
if (!tempExtracted.isDirectory() && !tempExtracted.mkdirs()) {
Log.e("HomeStuff", "Unzip problem at 3.5.");
result = false;
}
} else {
// not a directory
try {
// unzip the current entry to the specified directory
int n;
FileOutputStream fileoutputstream = new FileOutputStream(tempExtracted);
while ((n = zipinputstream.read(buf, 0, bufferSize)) > 0) {
fileoutputstream.write(buf, 0, n);
}
if (n == 0) {
// this can happen when zip archive is corrupt, closeEntry will never return
fileoutputstream.close();
Log.e("HomeStuff", "Unzip failed at 3: Zip archive corrupt.");
return false;
}
fileoutputstream.close();
zipinputstream.closeEntry();
} catch (IOException e) {
Log.e("HomeStuff", "Unzip problem at 4: " + e);
result = false;
}
}
}
} while (zipentry != null);
if (!result) {
Log.e("HomeStuff", "Unzip failed at 5.");
}
return result;
}
/**
* Recursivly adds the containing files of the given folder to the given list.
* Helper method for getFiles(folder).
* @param folder The folder that is searched for files or subdirectories.
* @param list The list to add the files to.
*/
private static void addContainingFiles(File folder, LinkedList<File> list) {
if (folder == null || !folder.isDirectory() || list == null) {
return;
}
File[] files = folder.listFiles();
for (File file : files) {
if (file.isDirectory()) {
// recursivly search for sub files, do not add the directory to the list
IOUtil.addContainingFiles(file, list);
} else {
list.add(file);
}
}
}
/**
* Recursivly returns all files that are not a directory within the given directory (so
* this includes files in subdirectories). Does nothing if given folder is <code>null</code> or
* not a directory.
* @param folder The folder the tree search starts.
* @return A list containing all files containted in the given directory and all subdirectories.
* The list will not contain directories, only files. Returned list will never be <code>null</code>.
*/
public static LinkedList<File> getFiles(File folder) {
LinkedList<File> filesInFolder = new LinkedList<>();
IOUtil.addContainingFiles(folder, filesInFolder);
return filesInFolder;
}
/**
* A small interface for zipping files. Allows to keep the file hierarchy
* of the given files. So if all files previously were in a folder "/home/images/"
* and are to be zipped into a file in "/zips/", this method could extract the relative path
* starting after "/home/images/" and return the rest, which is at least the file name.
*/
public interface RelativePathExtractor {
/**
* The relative path of the given file in the the target zip archive. Must be at least
* the file's name.
* @param file The file to get the relative path of.
* @return The relative path, at least the file's name.
*/
@NonNull String getRelativePathOfFile(@NonNull File file);
}
/**
* Zips the given list of files into the given target zip file.
* @param toZip The files to zip into an archive.
* @param targetZip The target zip archive.
* @return If zipping was successful, most likely true, see IOException.
* @throws IOException Thrown on any IO related problems during the zipping process.
*/
public static boolean zip(@NonNull List<File> toZip, @NonNull File targetZip, @NonNull
RelativePathExtractor extractor) throws IOException {
FileOutputStream os = new FileOutputStream(targetZip);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
try {
for (int i = 0; i < toZip.size(); ++i) {
File file = toZip.get(i);
if (file == null) {
continue;
}
String filename = extractor.getRelativePathOfFile(file);
ZipEntry entry = new ZipEntry(filename);
zos.putNextEntry(entry);
int n;
byte[] buf = new byte[1024];
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
while ((n = inputStream.read(buf, 0, buf.length)) > 0) {
zos.write(buf, 0, n);
}
inputStream.close();
zos.closeEntry();
}
return true;
} finally {
zos.close();
}
}
}