/******************************************************************************* * Copyright (c) 2009 Thales Corporate Services SAS * * Author : Gregory Boissinot * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal* * in the Software without restriction, including without limitation the rights * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included in * * all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * * THE SOFTWARE. * *******************************************************************************/ package com.thalesgroup.hudson.plugins.copyarchiver; import hudson.FilePath; import hudson.FilePath.FileCallable; import hudson.FilePath.TarCompression; import hudson.Functions; import hudson.Util; import hudson.model.Hudson; import hudson.remoting.Future; import hudson.remoting.Pipe; import hudson.remoting.VirtualChannel; import hudson.util.IOException2; import org.apache.commons.io.IOUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.types.FileSet; import org.apache.tools.tar.TarEntry; import org.apache.tools.tar.TarInputStream; import org.apache.tools.tar.TarOutputStream; import java.io.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static hudson.util.jna.GNUCLibrary.LIBC; public class FilePathArchiver implements Serializable { FilePath filePath; public FilePathArchiver(FilePath filePath) { this.filePath = filePath; } class CopyImpl extends Copy { private int copySize; public CopyImpl() { setProject(new org.apache.tools.ant.Project()); } protected void doFileOperations() { copySize = super.fileCopyMap.size(); super.doFileOperations(); } public int getNumCopied() { return copySize; } } public int copyRecursiveTo(final boolean flatten, final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException { if (filePath.getChannel() == target.getChannel()) { // local to local copy. return filePath.act(new FileCallable<Integer>() { public Integer invoke(File base, VirtualChannel channel) throws IOException { if (!base.exists()) return 0; assert target.getChannel() == null; try { CopyImpl copyTask = new CopyImpl(); copyTask.setTodir(new File(target.getRemote())); copyTask.addFileset(Util.createFileSet(base, fileMask, excludes)); copyTask.setIncludeEmptyDirs(false); copyTask.setFlatten(flatten); copyTask.execute(); return copyTask.getNumCopied(); } catch (BuildException e) { throw new IOException2("Failed to copy " + base + "/" + fileMask + " to " + target, e); } } }); } else { // remote -> local copy final Pipe pipe = Pipe.createRemoteToLocal(); //final FilePath targetTemp = new FilePath(Util.createTempDir()); Future<Integer> future = filePath.actAsync(new FileCallable<Integer>() { public Integer invoke(File f, VirtualChannel channel) throws IOException { try { return writeToTar(f, fileMask, excludes, TarCompression.GZIP.compress(pipe.getOut())); } finally { pipe.getOut().close(); } } }); try { readFromTar(flatten, filePath.getRemote() + '/' + fileMask, new File(target.getRemote()), TarCompression.GZIP.extract(pipe.getIn())); } catch (IOException e) {// BuildException or IOException try { future.get(3, TimeUnit.SECONDS); throw e; // the remote side completed successfully, so the error must be local } catch (ExecutionException x) { // report both errors throw new IOException2(Functions.printThrowable(e), x); } catch (TimeoutException _) { // remote is hanging throw e; } } try { return future.get(); /* return target.act(new FileCallable<Integer>() { public Integer invoke(File base, VirtualChannel channel) throws IOException { String fileMaskTemp = "**"; try { CopyImpl copyTask = new CopyImpl(); copyTask.setTodir(new File(target.getRemote())); //copyTask.addFileset(Util.createFileSet(new File(targetTemp.toURI()), "", null)); copyTask.setIncludeEmptyDirs(false); copyTask.setFlatten(flatten); copyTask.execute(); targetTemp.delete(); return copyTask.getNumCopied(); } catch (Exception e) { throw new IOException2("Failed to copy " + targetTemp + "/" + fileMaskTemp + " to " + target, e); } } }); */ } catch (ExecutionException e) { throw new IOException2(e); } } } /** * Writes to a tar stream and stores obtained files to the base dir. * * @return number of files/directories that are written. */ /** * * @param baseDir the base directory * @param fileMask the mask file * @param excludes the excludes pattern * @param out the output stream * @return the number of files * @throws IOException the IOException */ private Integer writeToTar(File baseDir, String fileMask, String excludes, OutputStream out) throws IOException { FileSet fs = Util.createFileSet(baseDir, fileMask, excludes); byte[] buf = new byte[8192]; TarOutputStream tar = new TarOutputStream(new BufferedOutputStream(out)); tar.setLongFileMode(TarOutputStream.LONGFILE_GNU); String[] files; if (baseDir.exists()) { DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project()); files = ds.getIncludedFiles(); } else { files = new String[0]; } for (String f : files) { if (Functions.isWindows()) f = f.replace('\\', '/'); File file = new File(baseDir, f); TarEntry te = new TarEntry(f); te.setModTime(file.lastModified()); if (!file.isDirectory()) te.setSize(file.length()); tar.putNextEntry(te); if (!file.isDirectory()) { FileInputStream in = new FileInputStream(file); int len; while ((len = in.read(buf)) >= 0) tar.write(buf, 0, len); in.close(); } tar.closeEntry(); } tar.close(); return files.length; } private static String getFileNameWithoutLeadingDirectory(String name) { String fileName = name.replace('\\', '/'); if (!fileName.contains("/")) { return fileName; } else if (fileName.endsWith("/")) { return null; } else { return fileName.substring(fileName.lastIndexOf("/") + 1); } } /** * Reads from a tar stream and stores obtained files to the base dir. */ /** * Reads from a tar stream and stores obtained files to the base dir. * @param isFlatten true if flatten * @param name the name * @param baseDir the base directory * @param in the inputstream * @throws IOException the IOException */ @SuppressWarnings("unchecked") private static void readFromTar(boolean isFlatten, String name, File baseDir, InputStream in) throws IOException { TarInputStream t = new TarInputStream(in); try { TarEntry te; while ((te = t.getNextEntry()) != null) { File f = new File(baseDir, te.getName()); if (te.isDirectory()) { if (!isFlatten) f.mkdirs(); } else { if (!isFlatten) { File parent = f.getParentFile(); if (parent != null) parent.mkdirs(); } else { f = new File(baseDir, getFileNameWithoutLeadingDirectory(te.getName())); } OutputStream fos = new FileOutputStream(f); try { IOUtils.copy(t, fos); } finally { fos.close(); } f.setLastModified(te.getModTime().getTime()); int mode = te.getMode() & 0777; if (mode != 0 && !Functions.isWindows()) // be defensive try { LIBC.chmod(f.getPath(), mode); } catch (NoClassDefFoundError e) { // be defensive. see http://www.nabble.com/-3.0.6--Site-copy-problem%3A-hudson.util.IOException2%3A--java.lang.NoClassDefFoundError%3A-Could-not-initialize-class--hudson.util.jna.GNUCLibrary-td23588879.html } } } } catch (IOException e) { throw new IOException2("Failed to extract " + name, e); } finally { t.close(); } } }