/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.core.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import net.sf.jazzlib.ZipEntry;
import net.sf.jazzlib.ZipInputStream;
import net.sf.jazzlib.ZipOutputStream;
import org.olat.core.commons.modules.bc.meta.MetaInfoHelper;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.LocalImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.version.Versionable;
import org.olat.core.util.vfs.version.VersionsFileManager;
/**
* Initial Date: 04.12.2002
*
* @author Mike Stock
*
* Comment:
*
*/
public class ZipUtil {
private static final String DIR_NAME__MACOSX = "__MACOSX/";
private static final OLog log = Tracing.createLoggerFor(VersionsFileManager.class);
/**
* Constructor for ZipUtil.
*/
public ZipUtil() {
super();
}
/**
* Unzip a file to a directory
* @param zipFile The zip file to unzip
* @param targetDir The directory to unzip the file to
* @return True if successfull, false otherwise
*/
public static boolean unzip(File zipFile, File targetDir) {
return unzip(new LocalFileImpl(zipFile), new LocalFolderImpl(targetDir));
}
/**
* Unzip a inputstream to a directory
* @param zipInputStream The inputstream to unzip
* @param targetDir The directory to unzip the file to
* @return True if successfull, false otherwise
*/
public static boolean unzip(VFSLeaf zipLeaf, VFSContainer targetDir) {
return unzip(zipLeaf, targetDir, null, false);
}
/**
* Unzip a inputstream to a directory using the versioning system of VFS
* @param zipLeaf The file to unzip
* @param targetDir The directory to unzip the file to
* @param the identity of who unzip the file
* @param versioning enabled or not
* @return True if successfull, false otherwise
*/
public static boolean unzip(VFSLeaf zipLeaf, VFSContainer targetDir, Identity identity, boolean versioning) {
ZipInputStream oZip = new ZipInputStream(zipLeaf.getInputStream());
try {
// unzip files
ZipEntry oEntr = oZip.getNextEntry();
while (oEntr != null) {
if (oEntr.getName() != null && !oEntr.getName().startsWith(DIR_NAME__MACOSX)) {
if (oEntr.isDirectory()) {
// skip MacOSX specific metadata directory
// create directories
getAllSubdirs(targetDir, oEntr.getName(), true);
} else {
// create file
VFSContainer createIn = targetDir;
String name = oEntr.getName();
// check if entry has directories which did not show up as
// directories above
int dirSepIndex = name.lastIndexOf('/');
if (dirSepIndex == -1) {
// try it windows style, backslash is also valid format
dirSepIndex = name.lastIndexOf('\\');
}
if (dirSepIndex > 0) {
// create subdirs
createIn = getAllSubdirs(targetDir, name.substring(0, dirSepIndex), true);
if (createIn == null) {
if (log.isDebug()) log.debug("Error creating directory structure for zip entry: "
+ oEntr.getName());
return false;
}
name = name.substring(dirSepIndex + 1);
}
if(versioning) {
VFSLeaf newEntry = (VFSLeaf)createIn.resolve(name);
if(newEntry == null) {
newEntry = createIn.createChildLeaf(name);
OutputStream out = newEntry.getOutputStream(false);
if (!FileUtils.copy(oZip, out)) return false;
FileUtils.closeSafely(out);
} else if (newEntry instanceof Versionable) {
Versionable versionable = (Versionable)newEntry;
if(versionable.getVersions().isVersioned()) {
versionable.getVersions().addVersion(identity, "", oZip);
}
}
} else {
VFSLeaf newEntry = createIn.createChildLeaf(name);
if (newEntry != null) {
OutputStream out = newEntry.getOutputStream(false);
if (!FileUtils.copy(oZip, out)) return false;
FileUtils.closeSafely(out);
}
}
}
}
oZip.closeEntry();
oEntr = oZip.getNextEntry();
}
} catch (IOException e) {
return false;
} finally {
FileUtils.closeSafely(oZip);
}
return true;
} // unzip
/**
* Check if a file in the zip is already in the path
* @param zipLeaf
* @param targetDir
* @param identity
* @param isAdmin
* @return the list of files which already exist
*/
public static List<String> checkLockedFileBeforeUnzip(VFSLeaf zipLeaf, VFSContainer targetDir, Identity identity, boolean isAdmin) {
List<String> lockedFiles = new ArrayList<String>();
ZipInputStream oZip = new ZipInputStream(zipLeaf.getInputStream());
try {
// unzip files
ZipEntry oEntr = oZip.getNextEntry();
while (oEntr != null) {
if (oEntr.getName() != null && !oEntr.getName().startsWith(DIR_NAME__MACOSX)) {
if (oEntr.isDirectory()) {
// skip MacOSX specific metadata directory
// directories aren't locked
oZip.closeEntry();
oEntr = oZip.getNextEntry();
continue;
} else {
// search file
VFSContainer createIn = targetDir;
String name = oEntr.getName();
// check if entry has directories which did not show up as
// directories above
int dirSepIndex = name.lastIndexOf('/');
if (dirSepIndex == -1) {
// try it windows style, backslash is also valid format
dirSepIndex = name.lastIndexOf('\\');
}
if (dirSepIndex > 0) {
// get subdirs
createIn = getAllSubdirs(targetDir, name.substring(0, dirSepIndex), false);
if (createIn == null) {
//sub directories don't exist, and aren't locked
oZip.closeEntry();
oEntr = oZip.getNextEntry();
continue;
}
name = name.substring(dirSepIndex + 1);
}
VFSLeaf newEntry = (VFSLeaf)createIn.resolve(name);
if(MetaInfoHelper.isLocked(newEntry, identity, isAdmin)) {
lockedFiles.add(name);
}
}
}
oZip.closeEntry();
oEntr = oZip.getNextEntry();
}
} catch (IOException e) {
return null;
} finally {
FileUtils.closeSafely(oZip);
}
return lockedFiles;
}
/**
* Get the whole subpath.
* @param create the missing directories
* @param base
* @param subDirPath
* @return Returns the last container of this subpath.
*/
private static VFSContainer getAllSubdirs(VFSContainer base, String subDirPath, boolean create) {
StringTokenizer st;
if (subDirPath.indexOf("/") != -1) {
st = new StringTokenizer(subDirPath, "/", false);
} else {
// try it windows style, backslash is also valid format
st = new StringTokenizer(subDirPath, "\\", false);
}
VFSContainer currentPath = base;
while (st.hasMoreTokens()) {
String nextSubpath = st.nextToken();
VFSItem vfsSubpath = currentPath.resolve(nextSubpath);
if (vfsSubpath == null && !create) {
return null;
}
if (vfsSubpath == null || (vfsSubpath instanceof VFSLeaf)) {
vfsSubpath = currentPath.createChildContainer(nextSubpath);
if (vfsSubpath == null) return null;
}
currentPath = (VFSContainer)vfsSubpath;
}
return currentPath;
}
/**
* Add the set of files residing in root to the ZIP file named target.
* Files in subfolders will be compressed too.
*
* @param files Filenames to add to ZIP, relative to root
* @param root Base path.
* @param target Target ZIP file.
* @return true if successfull, false otherwise.
*/
public static boolean zip(Set<String> files, File root, File target) {
// Create a buffer for reading the files
if (target.exists()) return false;
List<VFSItem> vfsFiles = new ArrayList<VFSItem>();
LocalFolderImpl vfsRoot = new LocalFolderImpl(root);
for (Iterator<String> iter = files.iterator(); iter.hasNext();) {
String fileName = iter.next();
VFSItem item = vfsRoot.resolve(fileName);
if (item == null) return false;
vfsFiles.add(item);
}
return zip(vfsFiles, new LocalFileImpl(target));
} // zip
public static boolean zip(List<VFSItem> vfsFiles, VFSLeaf target) {
boolean success = true;
OutputStream out = target.getOutputStream(false);
if (out == null) {
String name = target.getName();
if (target instanceof LocalImpl)
name = ((LocalImpl)target).getBasefile().getAbsolutePath();
throw new OLATRuntimeException(ZipUtil.class, "Error getting output stream for file: " + name, null);
}
ZipOutputStream zipOut = new ZipOutputStream(out);
for (Iterator<VFSItem> iter = vfsFiles.iterator(); iter.hasNext();) {
success = success && addToZip(iter.next(), "", zipOut);
}
FileUtils.closeSafely(zipOut);
return success;
}
private static boolean addToZip(VFSItem vfsItem, String currentPath, ZipOutputStream out) {
boolean success = true;
InputStream in = null;
try {
String itemName = currentPath.length() == 0 ?
vfsItem.getName() : currentPath + "/" + vfsItem.getName();
if (vfsItem instanceof VFSContainer) {
// this is a directory
List<VFSItem> items = ((VFSContainer)vfsItem).getItems();
for (Iterator<VFSItem> iter = items.iterator(); iter.hasNext();) {
success = success && addToZip(iter.next(), itemName, out);
}
} else {
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(itemName));
// Transfer bytes from the file to the ZIP file
in = ((VFSLeaf)vfsItem).getInputStream();
if (in != null) FileUtils.copy(in, out);
// Complete the entry
out.closeEntry();
}
} catch (IOException ioe) {
return false;
} finally {
if (in != null) FileUtils.closeSafely(in);
}
return success;
} // addToZip
/**
* Zip all files under a certain root directory.
*
* @param rootFile
* @param targetZipFile
* @return true = success, false = exception/error
*/
public static boolean zipAll(File rootFile, File targetZipFile) {
Set<String> fileSet = new HashSet<String>();
String[] files = rootFile.list();
for (int i = 0; i < files.length; i++) {
fileSet.add(files[i]);
}
return zip(fileSet, rootFile, targetZipFile);
}
}