/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.core.helpers; 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.io.OutputStream; import java.net.URISyntaxException; import java.net.URL; import java.nio.channels.FileChannel; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.mspec.ConflictResolution; import org.eclipse.buckminster.download.DownloadManager; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Plugin; import org.eclipse.ecf.core.security.IConnectContext; import org.eclipse.osgi.util.NLS; import org.osgi.framework.Bundle; /** * @author Thomas Hallgren */ public abstract class FileUtils { public static class CopyOntoSelfException extends LocalizedException { private static final long serialVersionUID = 379621474603864267L; public CopyOntoSelfException(File source, File destination) { super(NLS.bind(Messages.Cannot_copy_0_to_1_since_destination_equal_or_contained_in_source, source, destination)); } } public static class DeleteException extends LocalizedException { private static final long serialVersionUID = -8216112755038789300L; public DeleteException(File file) { this(file, null); } public DeleteException(File file, Throwable e) { super(e, NLS.bind(Messages.Unable_to_delete_0, file)); } } public static class DestinationNotEmptyException extends LocalizedException { private static final long serialVersionUID = -4126568695654461634L; public DestinationNotEmptyException(File destination) { super(NLS.bind(Messages.Unable_to_use_0_destination_for_copy_not_empty, destination)); } } public static class MkdirException extends LocalizedException { private static final long serialVersionUID = -1919074286465177750L; public MkdirException(File directory) { this(directory, null); } public MkdirException(File directory, Throwable e) { super(e, NLS.bind(Messages.Unable_to_create_directory_0, directory)); } } public static final String FILE_SEP = System.getProperty("file.separator"); //$NON-NLS-1$ public static final String PATH_SEP = System.getProperty("path.separator"); //$NON-NLS-1$ public static final boolean CASE_INSENSITIVE_FS = (new File("a").equals(new File("A"))); //$NON-NLS-1$ //$NON-NLS-2$ private static final Pattern[] defaultExcludes; private static final Object THREADLOCK = new Object(); private static HashSet<File> foldersToRemove; static { ArrayList<Pattern> bld = new ArrayList<Pattern>(); // Standard Apache ANT defaultexcludes // addPattern(bld, ".*/[^/]*~$"); //$NON-NLS-1$ addPattern(bld, ".*/#[^/]*#$"); //$NON-NLS-1$ addPattern(bld, ".*/\\.#[^/]*$"); //$NON-NLS-1$ addPattern(bld, ".*/%_[^/]*$"); //$NON-NLS-1$ addPattern(bld, ".*/CVS$"); //$NON-NLS-1$ addPattern(bld, ".*/CVS/.*"); //$NON-NLS-1$ addPattern(bld, ".*/\\.cvsignore$"); //$NON-NLS-1$ addPattern(bld, ".*/SCCS$"); //$NON-NLS-1$ addPattern(bld, ".*/SCCS/.*"); //$NON-NLS-1$ addPattern(bld, ".*/vssver\\.scc$"); //$NON-NLS-1$ addPattern(bld, ".*/\\.svn$"); //$NON-NLS-1$ addPattern(bld, ".*/\\.svn/.*"); //$NON-NLS-1$ addPattern(bld, ".*/\\.DS_Store$"); //$NON-NLS-1$ defaultExcludes = bld.toArray(new Pattern[bld.size()]); } public static void appendRelativeFiles(File directory, File relPath, Map<String, Long> fileNames) { String path = relPath.getPath(); File fileOrDir = relPath.isAbsolute() ? relPath : new File(directory, path); if (fileOrDir.isDirectory()) { StringBuilder builder = new StringBuilder(); builder.append(path); appendRelativeFiles(fileOrDir, fileNames, builder); } else { long ts = fileOrDir.lastModified(); if (ts != 0L) fileNames.put(path, new Long(ts)); } } public static void appendRelativeFiles(File directory, Map<String, Long> fileNames) { appendRelativeFiles(directory, fileNames, new StringBuilder()); } /** * Helper method to calculate a digest for a file or a tree */ static public byte[] calculateDigest(File f, String algorithm, IProgressMonitor monitor) throws NoSuchAlgorithmException, IOException { MessageDigest md = MessageDigest.getInstance(algorithm); DigestOutputStream dos = new DigestOutputStream(NullOutputStream.INSTANCE, md); if (f.isFile()) { FileInputStream fis = new FileInputStream(f); try { copyFile(fis, dos, monitor); } finally { IOUtils.close(fis); } } else deepCalculateDigest(f, dos, f.getCanonicalPath().length() + 1, monitor); dos.close(); return md.digest(); } /** * This method will assert that the <code>source</code> is not present * inside the <code>destination</code> and then call * {@link #prepareDestination(File destination, ConflictResolution strategy, IProgressMonitor monitor)} * . * * @param source * The source. Might be a file or a directory. * @param destination * The destination directory. * @param strategy * how to handle a destination that is not empty * @throws BuckminsterException */ public static void checkCopyConditions(File sourceFile, File destination, ConflictResolution strategy, IProgressMonitor monitor) throws CoreException { // Assert that the destination is different from source and not a // subdirectory inside // of the source. // File tmp = destination; while (tmp != null) { if (tmp.equals(sourceFile)) throw new CopyOntoSelfException(sourceFile, destination); tmp = tmp.getParentFile(); } prepareDestination(destination, strategy, monitor); } public static boolean compareContents(InputStream a, InputStream b) throws IOException { byte[] bufA = new byte[0x400]; byte[] bufB = new byte[0x400]; int countA; while ((countA = a.read(bufA)) > 0) { if (b.read(bufB) != countA) return false; if (!ArrayUtils.equals(bufA, bufB, 0, countA)) return false; } return b.read(bufB) == countA; } /** * Copy the <code>source</code> file to a <code>destDir</code> directory and * give it the name <code>destName</code>. This mehtod assumes that the * source is a common file, that destDir is a directory, and that a file * named destName can be created in destDir. If such a file exists already, * an attempt will be made to overwrite. * * @param source * The source. Cannot be a directory. * @param destDir * The destination directory. * @param destName * The name of the file relative to the destination. * @throws IOException * , MkdirException * @return The total number of bytes copied */ public static long copyFile(File source, File destDir, String destName, IProgressMonitor monitor) throws CoreException { InputStream input = null; try { input = new FileInputStream(source); return copyFile(input, destDir, destName, monitor); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(input); } } public static long copyFile(InputStream input, File destDir, String destName, IProgressMonitor monitor) throws CoreException { OutputStream output = null; MonitorUtils.begin(monitor, 1000); try { if (destDir != null) { File destFile = new File(destDir, destName); destDir = destFile.getParentFile(); // destName might have many // components if (destDir != null && !destDir.exists()) createDirectory(destDir, MonitorUtils.subMonitor(monitor, 100)); else MonitorUtils.worked(monitor, 100); output = new FileOutputStream(destFile); MonitorUtils.worked(monitor, 100); } else output = NullOutputStream.INSTANCE; return copyFile(input, output, MonitorUtils.subMonitor(monitor, 700)); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(output); MonitorUtils.done(monitor); } } public static long copyFile(InputStream input, OutputStream output, byte[] buf, IProgressMonitor monitor) throws IOException { long total = 0; MonitorUtils.begin(monitor, IProgressMonitor.UNKNOWN); try { int count; while ((count = input.read(buf)) > 0) { MonitorUtils.worked(monitor, 1); output.write(buf, 0, count); total += count; } return total; } finally { MonitorUtils.done(monitor); } } public static long copyFile(InputStream input, OutputStream output, IProgressMonitor monitor) throws IOException { if (input instanceof FileInputStream && output instanceof FileOutputStream) { // Use nio style copying with FileChannels FileChannel in = ((FileInputStream) input).getChannel(); FileChannel out = ((FileOutputStream) output).getChannel(); long size = in.size(); if (in.transferTo(0, size, out) != size) throw new IOException("Incomplete copy!"); //$NON-NLS-1$ MonitorUtils.complete(monitor); return size; } return copyFile(input, output, new byte[0x2000], monitor); } public static synchronized void createDirectory(File file, IProgressMonitor monitor) throws MkdirException { MonitorUtils.begin(monitor, 1); try { if (!(file.mkdirs() || file.isDirectory())) throw new MkdirException(file); MonitorUtils.worked(monitor, 1); } catch (SecurityException e) { throw new MkdirException(file, e); } finally { MonitorUtils.done(monitor); } } /** * Creates the given folder and any needed but nonexistent parent folders. * * @param container * @throws CoreException */ public static void createFolder(IContainer container) throws CoreException { IProgressMonitor nullMonitor = new NullProgressMonitor(); if (container instanceof IFolder) { IFolder folder = (IFolder) container; createFolder(folder.getParent()); folder.refreshLocal(IResource.DEPTH_ZERO, nullMonitor); if (!folder.exists()) folder.create(false, true, nullMonitor); } } /** * Creates a folder based on an abstract file handle. The folder and all its * content will be deleted when the process exists. The <code>tmpDir</code> * directory must not exist when this method is called. * * @param tmpDir * The directory to create * @throws MkdirException * If the directory could not be created. */ public static synchronized void createTempFolder(File tmpDir) throws MkdirException { if (!tmpDir.mkdirs()) throw new MkdirException(tmpDir); if (foldersToRemove == null) { foldersToRemove = new HashSet<File>(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // Prevent that foldersToRemove is updated during the // remove // HashSet<File> folders = new HashSet<File>(foldersToRemove); foldersToRemove = null; for (File folder : folders) { try { deleteRecursive(folder, null); } catch (Exception e) { // We're shutting down so this is ignored System.err.println("Failed to remove directory " + folder + " :" + e.toString()); //$NON-NLS-1$ //$NON-NLS-2$ } } } }); } foldersToRemove.add(tmpDir); } /** * Creates a folder based on a generated name. The folder and all its * content will be deleted when the process exists. */ public static synchronized File createTempFolder(String prefix, String suffix) throws CoreException { File tmpFile; try { tmpFile = File.createTempFile(prefix, suffix); } catch (IOException e) { throw BuckminsterException.wrap(e); } if (!tmpFile.delete()) throw new MkdirException(tmpFile); createTempFolder(tmpFile); return tmpFile; } /** * Copy everything found in the <code>sourceDirectory</code> to the * <code>destinationDirectory</code>. The destination is prepared according * to the given <code>strategy</code>. * * @param sourceDirectory * The source directory for the copy * @param destinationDirectory * The destination directory for the copy. * @param strategy * how to handle a destination that is not empty * @throws CoreException */ public static void deepCopy(File sourceDirectory, File destinationDirectory, ConflictResolution strategy, IProgressMonitor monitor) throws CoreException { MonitorUtils.begin(monitor, 1000); try { checkCopyConditions(sourceDirectory, destinationDirectory, strategy, MonitorUtils.subMonitor(monitor, 100)); deepCopyUnchecked(sourceDirectory, destinationDirectory, MonitorUtils.subMonitor(monitor, 900)); } finally { MonitorUtils.done(monitor); } } public static void deepCopyUnchecked(File source, File dest, IProgressMonitor monitor) throws CoreException { deepCopyUnchecked(source, dest, null, defaultExcludes, monitor); } public static void deepCopyUnchecked(File source, File dest, Pattern[] includes, Pattern[] excludes, IProgressMonitor monitor) throws CoreException { String sourceStr = source.toString().replace('\\', '/'); boolean isDir = source.isDirectory(); if (isDir && sourceStr.charAt(sourceStr.length() - 1) != '/') sourceStr += '/'; if (!isMatch(sourceStr, includes, true) || isMatch(sourceStr, excludes, false)) { MonitorUtils.complete(monitor); return; } File[] files = source.listFiles(); if (files == null || files.length == 0) { // Reason might be that the files is in fact a file or // an empty directory. Both are ok. A missing entry is // not OK. // try { if (isDir) MonitorUtils.complete(monitor); else if (source.isFile()) copyFile(source, dest, source.getName(), monitor); else throw new FileNotFoundException(source.getAbsolutePath()); } catch (IOException e) { throw BuckminsterException.wrap(e); } } MonitorUtils.begin(monitor, 10 + files.length * 100); try { createDirectory(dest, MonitorUtils.subMonitor(monitor, 10)); for (File file : files) { IProgressMonitor subMonitor = MonitorUtils.subMonitor(monitor, 100); if (file.isFile()) { sourceStr = source.toString().replace('\\', '/'); if (isMatch(sourceStr, includes, true) && !isMatch(sourceStr, excludes, false)) copyFile(file, dest, file.getName(), subMonitor); else MonitorUtils.complete(subMonitor); } else deepCopyUnchecked(file, new File(dest, file.getName()), includes, excludes, subMonitor); } } finally { MonitorUtils.done(monitor); } } public static void deleteRecursive(File file, IProgressMonitor monitor) throws DeleteException { MonitorUtils.begin(monitor, 1000); try { if (file == null) return; File[] list = file.listFiles(); int count = (list == null) ? 0 : list.length; if (count > 0) { IProgressMonitor subMon = MonitorUtils.subMonitor(monitor, 900); MonitorUtils.begin(subMon, count * 100); try { if (foldersToRemove != null) foldersToRemove.remove(file); while (--count >= 0) deleteRecursive(list[count], MonitorUtils.subMonitor(subMon, 100)); } finally { MonitorUtils.done(subMon); } } else MonitorUtils.worked(monitor, 900); if (!file.delete() && file.exists()) throw new DeleteException(file); MonitorUtils.worked(monitor, 100); } catch (SecurityException e) { throw new DeleteException(file, e); } finally { MonitorUtils.done(monitor); } } public static File findPath(Bundle bundle, String path) throws CoreException { try { return path == null ? null : new File(FileLocator.toFileURL(FileLocator.find(bundle, new Path(path), null)).toURI()); } catch (Exception e) { throw BuckminsterException.wrap(e); } } public static File findPath(Plugin plugin, String path) throws CoreException { return findPath(plugin.getBundle(), path); } public static File findPath(String pluginId, String path) throws CoreException { return findPath(Platform.getBundle(pluginId), path); } public static long getCRC(InputStream input) throws IOException { CRC32 crc32 = new CRC32(); byte[] copyBuffer = new byte[0x2000]; // 8 kilobyte buffer. int count; while ((count = input.read(copyBuffer)) > 0) crc32.update(copyBuffer, 0, count); return crc32.getValue(); } public static long getCRCAndCopy(InputStream input, OutputStream output) throws IOException { CRC32 crc32 = new CRC32(); byte[] copyBuffer = new byte[0x2000]; // 8 kilobyte buffer. int count; while ((count = input.read(copyBuffer)) > 0) { output.write(copyBuffer, 0, count); crc32.update(copyBuffer, 0, count); } return crc32.getValue(); } public static File getFile(URL url) { if (url == null) return null; String proto = url.getProtocol(); if (proto == null) proto = "file"; //$NON-NLS-1$ else { try { URL newUrl = FileLocator.resolve(url); if (newUrl != url) { proto = newUrl.getProtocol(); url = newUrl; } } catch (IOException e1) { } } if ("file".equalsIgnoreCase(proto)) //$NON-NLS-1$ { try { return new File(url.toURI()); } catch (URISyntaxException e) { // This is probably due to spaces in the URL path. If it // is, the URL is per definition corrupt but let's use that // path verbatim anyway // http://bugs.eclipse.org/bugs/show_bug.cgi?id=125967 // String path = url.getPath(); if (path.indexOf(' ') >= 0) return new File(path); // Nope, that was not the problem! // CorePlugin.getLogger().warning(e, e.getMessage()); } catch (IllegalArgumentException e) { // Probably because the URI has a fragement component } } return null; } public static IPath getFileAsPath(URL url) { File file = getFile(url); return file == null ? null : new Path(file.toString()); } /** * <p> * If the <code>fileOrDir</code> is a file, and if the * <code>expectedFileCount <= 1</code>, this method returns the timestamp * for that file. * </p> * <p> * If <code>fileOrDir</code> is a directory the following rules apply: * <ul> * <li>if the <code>expectedFileCount</code> is <code>-1</code>, this method * will return <code>0L</code>.</li> * <li>if the <code>expectedFileCount</code> is <code>0</code>, this method * will return the timestamp of the oldest file found using a recursive scan * of all subdirectories.</li> * <li>if the <code>expectedFileCount</code> is a positive number, this * method will return the timestamp of the oldest file found using a * recursive scan of all subdirectories provided the number of files is * greater or equal to that <code>expectedFileCount</code>.</li> * </ul> * </p> * <p> * for all other cases this method returns <code>0L</code>. * </p> * <p> * The method will return the actual number of files found in the one * element array parameter <code>realFileCount</code>. * * @param fileOrDir * The file or directory to check. * @param expectedFileCount * <code>-1</code>, <code>0</code>, or a minimum expected file * count. * @param realFileCount * A one element array where the actual number of files is * returned. * @return The file modification time according to the rules outlined for * this method. */ public static long getFirstModified(File fileOrDir, int expectedFileCount, int[] realFileCount) { if (fileOrDir.isFile() && expectedFileCount <= 1) { realFileCount[0] = 1; return fileOrDir.lastModified(); } long timestampHolder[] = new long[] { Long.MAX_VALUE }; int count = countFilesAndGetOldest(fileOrDir, 0, timestampHolder); realFileCount[0] = count; if (count == 0 || expectedFileCount < 0 || timestampHolder[0] == Long.MAX_VALUE) return 0L; return timestampHolder[0]; } /** * Returns the timestamp of the last modified file. If fileOrDir is a file, * the timestamp will be equal to the lastModified time of that file. If * fileOrDir is a directory, this method will recurse into itself with each * file or directory found there. The search stops if a file is encountered * that has a timestamp greater or equal to <code>threshold</code>. * * @param The * file or directory to check. * @param threshold * Stop if a file is found that is newer then threshold * @return The last modification time found on a file. If no file is found, * this method returns 0L. */ public static long getLastModified(File fileOrDir, long threshold, int[] realFileCount) { int count = 0; long lastModTime = 0; File[] files = fileOrDir.listFiles(); if (files == null) { if (fileOrDir.isFile()) { lastModTime = fileOrDir.lastModified(); count = 1; } } else { for (File p : files) { int dirFileCount[] = new int[] { 0 }; long modTime = getLastModified(p, threshold, dirFileCount); count += dirFileCount[0]; if (modTime > lastModTime) { lastModTime = modTime; if (modTime > threshold) break; } } } realFileCount[0] = count; return lastModTime; } /** * Creates directories in a synchronized block. * * @param directory * The path for which all directories should be created * @throws CoreException * If the directories cannot be created */ public static void mkdirs(File directory) throws CoreException { synchronized (THREADLOCK) { if (directory == null || directory.exists() && !directory.isDirectory()) throw BuckminsterException.fromMessage(NLS.bind(Messages.Unable_to_create_directory_0_Not_a_directory, directory != null ? directory : "(null)")); //$NON-NLS-1$ if (!directory.exists() && !directory.mkdirs()) throw BuckminsterException.fromMessage(NLS.bind(Messages.Unable_to_create_directory_0, directory)); } } /** * Perform an OS sensitive equality comparison between two paths. This is * very different from the {@link IPath#equals(Object)} since that method is * case sensitive on all platforms. * * @param a * @param b * @return <code>true</code> if both paths are equal or both paths are * <code>null</code>. */ public static boolean pathEquals(IPath a, IPath b) { return (a == null || b == null) ? a == b : a.toFile().equals(b.toFile()); } public static IPath pathForLocation(IPath location) { if (Platform.getLocation().equals(location)) return Path.ROOT; IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (int i = 0; i < projects.length; i++) { IProject project = projects[i]; IPath projectLocation = project.getLocation(); if (projectLocation != null && projectLocation.isPrefixOf(location)) { int segmentsToRemove = projectLocation.segmentCount(); return project.getFullPath().append(location.removeFirstSegments(segmentsToRemove)); } } return null; } /** * This method prepares the <code>destination</code> to receive a file or * files according to the given <code>strategy</code> * * @param source * The source. Might be a file or a directory. * @param destination * The destination directory. * @param strategy * how to handle a destination that is not empty * @throws BuckminsterException */ public static void prepareDestination(File destination, ConflictResolution strategy, IProgressMonitor monitor) throws CoreException { MonitorUtils.begin(monitor, 200); try { File[] list = destination.listFiles(); MonitorUtils.worked(monitor, 30); if (list == null) { if (destination.isFile()) { if (strategy == ConflictResolution.FAIL) throw new DestinationNotEmptyException(destination); if (strategy != ConflictResolution.KEEP) { // Both UPDATE and REPLACE will replace a file // if (!destination.delete()) throw new DeleteException(destination); } MonitorUtils.worked(monitor, 85); } createDirectory(destination, MonitorUtils.subMonitor(monitor, 85)); } else { int numFiles = list.length; if (numFiles > 0) { IProgressMonitor subMonitor = MonitorUtils.subMonitor(monitor, 170); MonitorUtils.begin(subMonitor, numFiles * 100); try { if (strategy == ConflictResolution.FAIL) throw new DestinationNotEmptyException(destination); if (strategy == ConflictResolution.REPLACE) { for (File file : list) deleteRecursive(file, MonitorUtils.subMonitor(subMonitor, 100)); } } finally { MonitorUtils.done(subMonitor); } } else MonitorUtils.worked(monitor, 170); } } finally { MonitorUtils.done(monitor); } } /** * Substitute parameters in the form <dc><paramName><dc> * where <dc> is the <code>delim</code> character and * <paramName> is a parameter that is found in * <code>substitutions</code>. An unmatched parameter substitution string is * replaced with an empty string * * @param input * The input stream to read from * @param output * The output to write to * @param delim * The character that starts and ends a parameter substitution * @param substitutions * The map containing valid substitutions * @throws IOException */ public static void substituteParameters(InputStream input, OutputStream output, char delim, Map<String, String> substitutions) throws IOException { BufferedInputStream bufferedInput = new BufferedInputStream(input); BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); int c; parseName: while ((c = bufferedInput.read()) >= 0) { if (c != delim) { bufferedOutput.write((byte) c); continue; } bufferedInput.mark(Integer.MAX_VALUE); c = bufferedInput.read(); if (c == delim) { bufferedOutput.write(delim); continue; } StringBuilder nameBuilder = new StringBuilder(); nameBuilder.append((char) c); while ((c = bufferedInput.read()) >= 0) { if (c == delim) break; if (Character.isJavaIdentifierPart((char) c)) { nameBuilder.append((char) c); continue; } // Not a valid parameter substitution // bufferedOutput.write(delim); bufferedInput.reset(); continue parseName; } String paramName = nameBuilder.toString(); String param = substitutions.get(paramName); if (param != null) bufferedOutput.write(param.getBytes()); } bufferedOutput.flush(); } public static void unzip(InputStream inputs, String sourceRelPath, File dest, ConflictResolution strategy, IProgressMonitor monitor) throws CoreException { ZipEntry entry; ZipInputStream input = null; MonitorUtils.begin(monitor, 600); if (dest != null) prepareDestination(dest, strategy, MonitorUtils.subMonitor(monitor, 100)); try { int ticksLeft = 500; input = new ZipInputStream(inputs); while ((entry = input.getNextEntry()) != null) { String name = entry.getName(); if (sourceRelPath != null) { if (!name.startsWith(sourceRelPath)) continue; name = name.substring(sourceRelPath.length() + 1); if (name.length() == 0) continue; } IProgressMonitor subMonitor; if (ticksLeft > 0) { subMonitor = MonitorUtils.subMonitor(monitor, 10); ticksLeft -= 10; } else { subMonitor = null; } if (entry.isDirectory()) { if (dest != null) createDirectory(new File(dest, name), subMonitor); continue; } copyFile(input, dest, name, subMonitor); } if (ticksLeft > 0) MonitorUtils.worked(monitor, ticksLeft); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { MonitorUtils.done(monitor); } } /** * Unzip the <code>source</code> contents to a <code>destDir</code> * directory and give it the name <code>destName</code>. This method assumes * that the source is the URL that points to zipped contents, that destDir * is a directory, and that a file named destName can be created in destDir. * * @param source * The source zipped content. * @param sourceRelPath * Relative path to material inside the soruce file. * @param dest * The destination directory. * @param strategy * how to handle a destination that is not empty * @param monitor * The progress monitor used during the operation * @throws BuckminsterException */ public static void unzip(URL source, IConnectContext cctx, String sourceRelPath, File dest, ConflictResolution strategy, IProgressMonitor monitor) throws CoreException { InputStream input = null; try { input = DownloadManager.read(source, cctx); unzip(input, sourceRelPath, dest, strategy, monitor); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(input); } } private static void addPattern(ArrayList<Pattern> bld, String expr) { bld.add(Pattern.compile(expr)); } private static void appendRelativeFiles(File directory, Map<String, Long> fileNames, StringBuilder path) { int pathLen = path.length(); if (pathLen > 0) { path.append(FILE_SEP); pathLen = path.length(); } File[] files = directory.listFiles(); if (files == null) return; int idx = files.length; while (--idx >= 0) { File file = files[idx]; String sourceStr = file.toString().replace('\\', '/'); path.append(file.getName()); if (file.isDirectory()) { if (!isMatch(sourceStr + '/', defaultExcludes, false)) appendRelativeFiles(file, fileNames, path); } else { if (!isMatch(sourceStr, defaultExcludes, false)) fileNames.put(path.toString(), new Long(file.lastModified())); } path.setLength(pathLen); } } private static int countFilesAndGetOldest(File fileOrDir, int count, long[] timestampHolder) { File[] files = fileOrDir.listFiles(); if (files == null) { if (fileOrDir.isFile()) { long timestamp = fileOrDir.lastModified(); if (timestamp < timestampHolder[0]) timestampHolder[0] = timestamp; count++; } } else { for (File file : files) count = countFilesAndGetOldest(file, count, timestampHolder); } return count; } /** * internal helper method to read all files/dirs in a directory tree to a * *single* outstream */ private static void deepCalculateDigest(File from, OutputStream os, int rootOffset, IProgressMonitor monitor) throws IOException { // get the file list, but *always* sort it to ensure we // always process things in the same order as this is important // for always getting the same digest // File names[] = from.listFiles(); MonitorUtils.begin(monitor, names.length * 100); try { Arrays.sort(names); for (int i = 0; i < names.length; i++) { File p = names[i]; // // Make pathnames count for everything we encounter to ensure // changes in dir structure also count (new dirs, empty files, // changed names) // replace the platform specific separator with something // else...we should then // get identical results on any platform. // IProgressMonitor subMonitor = MonitorUtils.subMonitor(monitor, 100); os.write(p.getName().getBytes()); if (p.isDirectory()) { os.write(1); deepCalculateDigest(p, os, rootOffset, subMonitor); } else { FileInputStream fis = new FileInputStream(p); copyFile(fis, os, subMonitor); fis.close(); } } } finally { MonitorUtils.done(monitor); } } private static boolean isMatch(String fileStr, Pattern[] patterns, boolean whenEmpty) { if (patterns != null) { int idx = patterns.length; if (idx > 0) { while (--idx >= 0) if (patterns[idx].matcher(fileStr).matches()) return true; return false; } } return whenEmpty; } }