2006-11-06 05:16:01 +08:00
|
|
|
package hudson;
|
|
|
|
|
2007-03-27 13:17:23 +08:00
|
|
|
import hudson.Launcher.LocalLauncher;
|
|
|
|
import hudson.Launcher.RemoteLauncher;
|
|
|
|
import hudson.model.Hudson;
|
|
|
|
import hudson.model.TaskListener;
|
2006-12-30 03:15:53 +08:00
|
|
|
import hudson.remoting.Callable;
|
|
|
|
import hudson.remoting.Channel;
|
2007-03-27 13:17:23 +08:00
|
|
|
import hudson.remoting.DelegatingCallable;
|
2007-04-06 22:19:15 +08:00
|
|
|
import hudson.remoting.Future;
|
2006-12-30 03:15:53 +08:00
|
|
|
import hudson.remoting.Pipe;
|
|
|
|
import hudson.remoting.RemoteOutputStream;
|
|
|
|
import hudson.remoting.VirtualChannel;
|
2007-03-24 09:18:13 +08:00
|
|
|
import hudson.util.FormFieldValidator;
|
2007-03-27 13:17:23 +08:00
|
|
|
import hudson.util.IOException2;
|
2007-04-06 22:19:15 +08:00
|
|
|
import hudson.util.StreamResource;
|
2006-12-30 03:15:53 +08:00
|
|
|
import org.apache.tools.ant.BuildException;
|
|
|
|
import org.apache.tools.ant.DirectoryScanner;
|
2007-04-06 22:19:15 +08:00
|
|
|
import org.apache.tools.ant.Project;
|
2006-12-30 03:15:53 +08:00
|
|
|
import org.apache.tools.ant.taskdefs.Copy;
|
2007-04-06 22:19:15 +08:00
|
|
|
import org.apache.tools.ant.taskdefs.Untar;
|
2006-12-30 03:15:53 +08:00
|
|
|
import org.apache.tools.ant.types.FileSet;
|
2007-04-06 22:19:15 +08:00
|
|
|
import org.apache.tools.tar.TarEntry;
|
|
|
|
import org.apache.tools.tar.TarOutputStream;
|
2006-11-18 09:31:49 +08:00
|
|
|
|
2007-04-06 22:19:15 +08:00
|
|
|
import java.io.BufferedInputStream;
|
|
|
|
import java.io.BufferedOutputStream;
|
2006-11-06 05:16:01 +08:00
|
|
|
import java.io.File;
|
2006-12-30 03:15:53 +08:00
|
|
|
import java.io.FileFilter;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.FileWriter;
|
2006-11-06 05:16:01 +08:00
|
|
|
import java.io.IOException;
|
2006-12-30 03:15:53 +08:00
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.ObjectInputStream;
|
|
|
|
import java.io.ObjectOutputStream;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.Serializable;
|
|
|
|
import java.io.Writer;
|
2007-03-27 13:17:23 +08:00
|
|
|
import java.net.URI;
|
2006-12-30 03:15:53 +08:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2007-04-08 03:54:56 +08:00
|
|
|
import java.util.StringTokenizer;
|
2007-04-06 22:19:15 +08:00
|
|
|
import java.util.concurrent.ExecutionException;
|
2007-03-09 14:57:24 +08:00
|
|
|
import java.util.zip.ZipEntry;
|
2007-03-27 13:17:23 +08:00
|
|
|
import java.util.zip.ZipOutputStream;
|
2007-04-06 22:19:15 +08:00
|
|
|
import java.util.zip.GZIPOutputStream;
|
|
|
|
import java.util.zip.GZIPInputStream;
|
2006-11-06 05:16:01 +08:00
|
|
|
|
|
|
|
/**
|
2006-12-30 03:15:53 +08:00
|
|
|
* {@link File} like object with remoting support.
|
2006-11-06 05:16:01 +08:00
|
|
|
*
|
|
|
|
* <p>
|
2006-12-30 03:15:53 +08:00
|
|
|
* Unlike {@link File}, which always implies a file path on the current computer,
|
|
|
|
* {@link FilePath} represents a file path on a specific slave or the master.
|
|
|
|
*
|
|
|
|
* Despite that, {@link FilePath} can be used much like {@link File}. It exposes
|
|
|
|
* a bunch of operations (and we should add more operations as long as they are
|
|
|
|
* generally useful), and when invoked against a file on a remote node, {@link FilePath}
|
|
|
|
* executes the necessary code remotely, thereby providing semi-transparent file
|
|
|
|
* operations.
|
|
|
|
*
|
|
|
|
* <h2>Using {@link FilePath} smartly</h2>
|
|
|
|
* <p>
|
|
|
|
* The transparency makes it easy to write plugins without worrying too much about
|
|
|
|
* remoting, by making it works like NFS, where remoting happens at the file-system
|
|
|
|
* later.
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* But one should note that such use of remoting may not be optional. Sometimes,
|
|
|
|
* it makes more sense to move some computation closer to the data, as opposed to
|
|
|
|
* move the data to the computation. For example, if you are just computing a MD5
|
|
|
|
* digest of a file, then it would make sense to do the digest on the host where
|
|
|
|
* the file is located, as opposed to send the whole data to the master and do MD5
|
|
|
|
* digesting there.
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* {@link FilePath} supports this "code migration" by in the
|
|
|
|
* {@link #act(FileCallable)} method. One can pass in a custom implementation
|
|
|
|
* of {@link FileCallable}, to be executed on the node where the data is located.
|
|
|
|
* The following code shows the example:
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* FilePath file = ...;
|
|
|
|
*
|
|
|
|
* // make 'file' a fresh empty directory.
|
|
|
|
* file.act(new FileCallable<Void>() {
|
|
|
|
* // if 'file' is on a different node, this FileCallable will
|
|
|
|
* // be transfered to that node and executed there.
|
|
|
|
* public Void invoke(File f,VirtualChannel channel) {
|
|
|
|
* // f and file represents the same thing
|
|
|
|
* f.deleteContents();
|
|
|
|
* f.mkdirs();
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
* </pre>
|
2006-11-06 05:16:01 +08:00
|
|
|
*
|
|
|
|
* <p>
|
2006-12-30 03:15:53 +08:00
|
|
|
* When {@link FileCallable} is transfered to a remote node, it will be done so
|
|
|
|
* by using the same Java serializaiton scheme that the remoting module uses.
|
|
|
|
* See {@link Channel} for more about this.
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* {@link FilePath} itself can be sent over to a remote node as a part of {@link Callable}
|
|
|
|
* serialization. For example, sending a {@link FilePath} of a remote node to that
|
|
|
|
* node causes {@link FilePath} to become "local". Similarly, sending a
|
|
|
|
* {@link FilePath} that represents the local computer causes it to become "remote."
|
2006-11-06 05:16:01 +08:00
|
|
|
*
|
|
|
|
* @author Kohsuke Kawaguchi
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public final class FilePath implements Serializable {
|
|
|
|
/**
|
|
|
|
* When this {@link FilePath} represents the remote path,
|
|
|
|
* this field is always non-null on master (the field represents
|
|
|
|
* the channel to the remote slave.) When transferred to a slave via remoting,
|
|
|
|
* this field reverts back to null, since it's transient.
|
|
|
|
*
|
|
|
|
* When this {@link FilePath} represents a path on the master,
|
|
|
|
* this field is null on master. When transferred to a slave via remoting,
|
|
|
|
* this field becomes non-null, representing the {@link Channel}
|
|
|
|
* back to the master.
|
|
|
|
*
|
|
|
|
* This is used to determine whether we are running on the master or the slave.
|
|
|
|
*/
|
|
|
|
private transient VirtualChannel channel;
|
|
|
|
|
|
|
|
// since the platform of the slave might be different, can't use java.io.File
|
2006-11-06 05:16:01 +08:00
|
|
|
private final String remote;
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
public FilePath(VirtualChannel channel, String remote) {
|
|
|
|
this.channel = channel;
|
2006-11-06 05:16:01 +08:00
|
|
|
this.remote = remote;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-12-30 03:15:53 +08:00
|
|
|
* To create {@link FilePath} on the master computer.
|
2006-11-06 05:16:01 +08:00
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public FilePath(File localPath) {
|
|
|
|
this.channel = null;
|
|
|
|
this.remote = localPath.getPath();
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
public FilePath(FilePath base, String rel) {
|
2006-12-30 03:15:53 +08:00
|
|
|
this.channel = base.channel;
|
2006-11-06 05:16:01 +08:00
|
|
|
if(base.isUnix()) {
|
|
|
|
this.remote = base.remote+'/'+rel;
|
|
|
|
} else {
|
|
|
|
this.remote = base.remote+'\\'+rel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the remote path is Unix.
|
|
|
|
*/
|
|
|
|
private boolean isUnix() {
|
|
|
|
// Windows can handle '/' as a path separator but Unix can't,
|
|
|
|
// so err on Unix side
|
|
|
|
return remote.indexOf("\\")==-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getRemote() {
|
|
|
|
return remote;
|
|
|
|
}
|
|
|
|
|
2007-03-09 14:57:24 +08:00
|
|
|
/**
|
|
|
|
* Creates a zip file from this directory or a file and sends that to the given output stream.
|
|
|
|
*/
|
|
|
|
public void createZipArchive(OutputStream os) throws IOException, InterruptedException {
|
|
|
|
final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
|
|
|
|
act(new FileCallable<Void>() {
|
|
|
|
private transient byte[] buf;
|
|
|
|
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
buf = new byte[8192];
|
|
|
|
|
|
|
|
ZipOutputStream zip = new ZipOutputStream(out);
|
|
|
|
scan(f,zip,"");
|
|
|
|
zip.close();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void scan(File f, ZipOutputStream zip, String path) throws IOException {
|
|
|
|
if(f.isDirectory()) {
|
|
|
|
for( File child : f.listFiles() )
|
|
|
|
scan(child,zip,path+f.getName()+'/');
|
|
|
|
} else {
|
|
|
|
zip.putNextEntry(new ZipEntry(path+f.getName()));
|
|
|
|
FileInputStream in = new FileInputStream(f);
|
|
|
|
int len;
|
|
|
|
while((len=in.read(buf))>0)
|
|
|
|
zip.write(buf,0,len);
|
|
|
|
in.close();
|
|
|
|
zip.closeEntry();
|
|
|
|
}
|
|
|
|
}
|
2007-03-16 03:04:13 +08:00
|
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
2007-03-09 14:57:24 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Code that gets executed on the machine where the {@link FilePath} is local.
|
|
|
|
* Used to act on {@link FilePath}.
|
|
|
|
*
|
|
|
|
* @see FilePath#act(FileCallable)
|
|
|
|
*/
|
|
|
|
public static interface FileCallable<T> extends Serializable {
|
|
|
|
/**
|
|
|
|
* Performs the computational task on the node where the data is located.
|
|
|
|
*
|
|
|
|
* @param f
|
|
|
|
* {@link File} that represents the local file that {@link FilePath} has represented.
|
|
|
|
* @param channel
|
|
|
|
* The "back pointer" of the {@link Channel} that represents the communication
|
|
|
|
* with the node from where the code was sent.
|
|
|
|
*/
|
|
|
|
T invoke(File f, VirtualChannel channel) throws IOException;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes some program on the machine that this {@link FilePath} exists,
|
|
|
|
* so that one can perform local file operations.
|
|
|
|
*/
|
|
|
|
public <T> T act(final FileCallable<T> callable) throws IOException, InterruptedException {
|
|
|
|
if(channel!=null) {
|
|
|
|
// run this on a remote system
|
|
|
|
try {
|
2007-04-06 22:19:15 +08:00
|
|
|
return channel.call(new FileCallableWrapper<T>(callable));
|
2006-12-30 03:15:53 +08:00
|
|
|
} catch (IOException e) {
|
|
|
|
// wrap it into a new IOException so that we get the caller's stack trace as well.
|
|
|
|
throw new IOException2("remote file operation failed",e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// the file is on the local machine.
|
|
|
|
return callable.invoke(new File(remote), Hudson.MasterComputer.localChannel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-06 22:19:15 +08:00
|
|
|
/**
|
|
|
|
* Executes some program on the machine that this {@link FilePath} exists,
|
|
|
|
* so that one can perform local file operations.
|
|
|
|
*/
|
|
|
|
public <T> Future<T> actAsync(final FileCallable<T> callable) throws IOException, InterruptedException {
|
|
|
|
try {
|
|
|
|
return (channel!=null ? channel : Hudson.MasterComputer.localChannel)
|
|
|
|
.callAsync(new FileCallableWrapper<T>(callable));
|
|
|
|
} catch (IOException e) {
|
|
|
|
// wrap it into a new IOException so that we get the caller's stack trace as well.
|
|
|
|
throw new IOException2("remote file operation failed",e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Executes some program on the machine that this {@link FilePath} exists,
|
|
|
|
* so that one can perform local file operations.
|
|
|
|
*/
|
|
|
|
public <V,E extends Throwable> V act(Callable<V,E> callable) throws IOException, InterruptedException, E {
|
|
|
|
if(channel!=null) {
|
|
|
|
// run this on a remote system
|
|
|
|
return channel.call(callable);
|
|
|
|
} else {
|
|
|
|
// the file is on the local machine
|
|
|
|
return callable.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts this file to the URI, relative to the machine
|
|
|
|
* on which this file is available.
|
|
|
|
*/
|
|
|
|
public URI toURI() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<URI>() {
|
|
|
|
public URI invoke(File f, VirtualChannel channel) {
|
|
|
|
return f.toURI();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2006-11-06 05:16:01 +08:00
|
|
|
/**
|
|
|
|
* Creates this directory.
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public void mkdirs() throws IOException, InterruptedException {
|
|
|
|
if(act(new FileCallable<Boolean>() {
|
|
|
|
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return !f.mkdirs() && !f.exists();
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
throw new IOException("Failed to mkdirs: "+remote);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes this directory, including all its contents recursively.
|
|
|
|
*/
|
|
|
|
public void deleteRecursive() throws IOException, InterruptedException {
|
|
|
|
act(new FileCallable<Void>() {
|
|
|
|
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
Util.deleteRecursive(f);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes all the contents of this directory, but not the directory itself
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public void deleteContents() throws IOException, InterruptedException {
|
|
|
|
act(new FileCallable<Void>() {
|
|
|
|
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
Util.deleteContentsRecursive(f);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets just the file name portion.
|
|
|
|
*
|
|
|
|
* This method assumes that the file name is the same between local and remote.
|
|
|
|
*/
|
|
|
|
public String getName() {
|
2007-03-09 14:57:24 +08:00
|
|
|
String r = remote;
|
|
|
|
if(r.endsWith("\\") || r.endsWith("/"))
|
|
|
|
r = r.substring(0,r.length()-1);
|
|
|
|
|
|
|
|
int len = r.length()-1;
|
2006-12-30 03:15:53 +08:00
|
|
|
while(len>=0) {
|
2007-03-09 14:57:24 +08:00
|
|
|
char ch = r.charAt(len);
|
2006-12-30 03:15:53 +08:00
|
|
|
if(ch=='\\' || ch=='/')
|
|
|
|
break;
|
|
|
|
len--;
|
|
|
|
}
|
|
|
|
|
2007-03-09 14:57:24 +08:00
|
|
|
return r.substring(len+1);
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The same as {@code new FilePath(this,rel)} but more OO.
|
|
|
|
*/
|
|
|
|
public FilePath child(String rel) {
|
|
|
|
return new FilePath(this,rel);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the parent file.
|
|
|
|
*/
|
|
|
|
public FilePath getParent() {
|
|
|
|
int len = remote.length()-1;
|
|
|
|
while(len>=0) {
|
|
|
|
char ch = remote.charAt(len);
|
|
|
|
if(ch=='\\' || ch=='/')
|
|
|
|
break;
|
|
|
|
len--;
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
return new FilePath( channel, remote.substring(0,len) );
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a temporary file.
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
|
|
|
|
try {
|
|
|
|
return new FilePath(this,act(new FileCallable<String>() {
|
|
|
|
public String invoke(File dir, VirtualChannel channel) throws IOException {
|
|
|
|
File f = File.createTempFile(prefix, suffix, dir);
|
|
|
|
return f.getName();
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new IOException2("Failed to create a temp file on "+remote,e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a temporary file in this directory and set the contents by the
|
|
|
|
* given text (encoded in the platform default encoding)
|
|
|
|
*/
|
|
|
|
public FilePath createTextTempFile(final String prefix, final String suffix, final String contents) throws IOException, InterruptedException {
|
2007-01-07 13:39:25 +08:00
|
|
|
return createTextTempFile(prefix,suffix,contents,true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a temporary file in this directory and set the contents by the
|
|
|
|
* given text (encoded in the platform default encoding)
|
|
|
|
*/
|
|
|
|
public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
|
2006-11-18 09:31:49 +08:00
|
|
|
try {
|
2007-01-08 07:29:50 +08:00
|
|
|
return new FilePath(channel,act(new FileCallable<String>() {
|
2006-12-30 03:15:53 +08:00
|
|
|
public String invoke(File dir, VirtualChannel channel) throws IOException {
|
2007-01-07 13:39:25 +08:00
|
|
|
if(!inThisDirectory)
|
|
|
|
dir = null;
|
2006-12-30 03:15:53 +08:00
|
|
|
File f = File.createTempFile(prefix, suffix, dir);
|
|
|
|
|
|
|
|
Writer w = new FileWriter(f);
|
|
|
|
w.write(contents);
|
|
|
|
w.close();
|
|
|
|
|
2007-01-08 07:29:50 +08:00
|
|
|
return f.getAbsolutePath();
|
2006-12-30 03:15:53 +08:00
|
|
|
}
|
|
|
|
}));
|
2006-11-18 09:31:49 +08:00
|
|
|
} catch (IOException e) {
|
2006-12-30 03:15:53 +08:00
|
|
|
throw new IOException2("Failed to create a temp file on "+remote,e);
|
2006-11-18 09:31:49 +08:00
|
|
|
}
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes this file.
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public boolean delete() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<Boolean>() {
|
|
|
|
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return f.delete();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the file exists.
|
|
|
|
*/
|
|
|
|
public boolean exists() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<Boolean>() {
|
|
|
|
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return f.exists();
|
|
|
|
}
|
|
|
|
});
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Gets the last modified time stamp of this file, by using the clock
|
|
|
|
* of the machine where this file actually resides.
|
|
|
|
*
|
|
|
|
* @see File#lastModified()
|
|
|
|
*/
|
|
|
|
public long lastModified() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<Long>() {
|
|
|
|
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return f.lastModified();
|
|
|
|
}
|
|
|
|
});
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Checks if the file is a directory.
|
|
|
|
*/
|
|
|
|
public boolean isDirectory() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<Boolean>() {
|
|
|
|
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return f.isDirectory();
|
|
|
|
}
|
|
|
|
});
|
2006-12-17 13:58:52 +08:00
|
|
|
}
|
|
|
|
|
2006-11-06 05:16:01 +08:00
|
|
|
/**
|
2006-12-30 03:15:53 +08:00
|
|
|
* List up files in this directory.
|
|
|
|
*
|
|
|
|
* @param filter
|
|
|
|
* The optional filter used to narrow down the result.
|
|
|
|
* If non-null, must be {@link Serializable}.
|
|
|
|
* If this {@link FilePath} represents a remote path,
|
|
|
|
* the filter object will be executed on the remote machine.
|
2006-11-06 05:16:01 +08:00
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
public List<FilePath> list(final FileFilter filter) throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<List<FilePath>>() {
|
|
|
|
public List<FilePath> invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
File[] children = f.listFiles(filter);
|
|
|
|
if(children ==null) return null;
|
|
|
|
|
|
|
|
ArrayList<FilePath> r = new ArrayList<FilePath>(children.length);
|
|
|
|
for (File child : children)
|
|
|
|
r.add(new FilePath(child));
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2007-01-31 22:14:27 +08:00
|
|
|
/**
|
|
|
|
* List up files in this directory that matches the given Ant-style filter.
|
|
|
|
*
|
|
|
|
* @param includes
|
|
|
|
* See {@link FileSet} for the syntax. String like "foo/*.zip".
|
|
|
|
*/
|
|
|
|
public FilePath[] list(final String includes) throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<FilePath[]>() {
|
|
|
|
public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
FileSet fs = new FileSet();
|
|
|
|
fs.setDir(f);
|
|
|
|
fs.setIncludes(includes);
|
|
|
|
|
|
|
|
DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
|
|
|
|
String[] files = ds.getIncludedFiles();
|
|
|
|
|
|
|
|
FilePath[] r = new FilePath[files.length];
|
|
|
|
for( int i=0; i<r.length; i++ )
|
|
|
|
r[i] = new FilePath(new File(f,files[i]));
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Reads this file.
|
|
|
|
*/
|
|
|
|
public InputStream read() throws IOException {
|
|
|
|
if(channel==null)
|
|
|
|
return new FileInputStream(new File(remote));
|
|
|
|
|
|
|
|
final Pipe p = Pipe.createRemoteToLocal();
|
|
|
|
channel.callAsync(new Callable<Void,IOException>() {
|
|
|
|
public Void call() throws IOException {
|
|
|
|
FileInputStream fis = new FileInputStream(new File(remote));
|
|
|
|
Util.copyStream(fis,p.getOut());
|
|
|
|
fis.close();
|
|
|
|
p.getOut().close();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return p.getIn();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes to this file.
|
|
|
|
* If this file already exists, it will be overwritten.
|
2007-01-08 11:57:47 +08:00
|
|
|
* If the directory doesn't exist, it will be created.
|
2006-12-30 03:15:53 +08:00
|
|
|
*/
|
2007-01-09 01:19:35 +08:00
|
|
|
public OutputStream write() throws IOException, InterruptedException {
|
2007-01-16 07:48:38 +08:00
|
|
|
if(channel==null) {
|
|
|
|
File f = new File(remote);
|
|
|
|
f.getParentFile().mkdirs();
|
|
|
|
return new FileOutputStream(f);
|
|
|
|
}
|
2006-12-30 03:15:53 +08:00
|
|
|
|
2007-01-09 01:19:35 +08:00
|
|
|
return channel.call(new Callable<OutputStream,IOException>() {
|
|
|
|
public OutputStream call() throws IOException {
|
2007-01-08 11:57:47 +08:00
|
|
|
File f = new File(remote);
|
|
|
|
f.getParentFile().mkdirs();
|
|
|
|
FileOutputStream fos = new FileOutputStream(f);
|
2007-01-09 01:19:35 +08:00
|
|
|
return new RemoteOutputStream(fos);
|
2006-12-30 03:15:53 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Computes the MD5 digest of the file in hex string.
|
|
|
|
*/
|
|
|
|
public String digest() throws IOException, InterruptedException {
|
|
|
|
return act(new FileCallable<String>() {
|
|
|
|
public String invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return Util.getDigestOf(new FileInputStream(f));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copies this file to the specified target.
|
|
|
|
*/
|
|
|
|
public void copyTo(FilePath target) throws IOException, InterruptedException {
|
|
|
|
OutputStream out = target.write();
|
|
|
|
try {
|
|
|
|
copyTo(out);
|
|
|
|
} finally {
|
|
|
|
out.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends the contents of this file into the given {@link OutputStream}.
|
|
|
|
*/
|
|
|
|
public void copyTo(OutputStream os) throws IOException, InterruptedException {
|
|
|
|
final OutputStream out = new RemoteOutputStream(os);
|
|
|
|
|
|
|
|
act(new FileCallable<Void>() {
|
|
|
|
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
FileInputStream fis = new FileInputStream(f);
|
|
|
|
Util.copyStream(fis,out);
|
|
|
|
fis.close();
|
|
|
|
out.close();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remoting interface used for {@link FilePath#copyRecursiveTo(String, FilePath)}.
|
|
|
|
*
|
|
|
|
* TODO: this might not be the most efficient way to do the copy.
|
|
|
|
*/
|
|
|
|
interface RemoteCopier {
|
2007-02-27 12:11:35 +08:00
|
|
|
/**
|
|
|
|
* @param fileName
|
|
|
|
* relative path name to the output file. Path separator must be '/'.
|
|
|
|
*/
|
2006-12-30 03:15:53 +08:00
|
|
|
void open(String fileName) throws IOException;
|
|
|
|
void write(byte[] buf, int len) throws IOException;
|
|
|
|
void close() throws IOException;
|
|
|
|
}
|
|
|
|
|
2007-01-30 23:29:44 +08:00
|
|
|
public int copyRecursiveTo(String fileMask, FilePath target) throws IOException, InterruptedException {
|
|
|
|
return copyRecursiveTo(fileMask,null,target);
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
/**
|
|
|
|
* Copies the files that match the given file mask to the specified target node.
|
|
|
|
*
|
2007-01-30 23:29:44 +08:00
|
|
|
* @param excludes
|
|
|
|
* Files to be excluded. Can be null.
|
2006-12-30 03:15:53 +08:00
|
|
|
* @return
|
|
|
|
* the number of files copied.
|
|
|
|
*/
|
2007-01-30 23:29:44 +08:00
|
|
|
public int copyRecursiveTo(final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException {
|
2006-12-30 03:15:53 +08:00
|
|
|
if(this.channel==target.channel) {
|
|
|
|
// local to local copy.
|
|
|
|
return act(new FileCallable<Integer>() {
|
|
|
|
public Integer invoke(File base, VirtualChannel channel) throws IOException {
|
|
|
|
assert target.channel==null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CopyImpl copyTask = new CopyImpl();
|
|
|
|
copyTask.setTodir(new File(target.remote));
|
|
|
|
FileSet src = new FileSet();
|
|
|
|
src.setDir(base);
|
|
|
|
src.setIncludes(fileMask);
|
2007-01-30 23:29:44 +08:00
|
|
|
src.setExcludes(excludes);
|
2006-12-30 03:15:53 +08:00
|
|
|
copyTask.addFileset(src);
|
|
|
|
|
|
|
|
copyTask.execute();
|
|
|
|
return copyTask.getNumCopied();
|
|
|
|
} catch (BuildException e) {
|
|
|
|
throw new IOException2("Failed to copy "+base+"/"+fileMask+" to "+target,e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2007-04-06 22:19:15 +08:00
|
|
|
} else
|
|
|
|
if(this.channel==null) {
|
|
|
|
// local -> remote copy
|
|
|
|
final Pipe pipe = Pipe.createLocalToRemote();
|
|
|
|
|
|
|
|
Future<Void> future = target.actAsync(new FileCallable<Void>() {
|
|
|
|
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
readFromTar(f,pipe.getIn());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
int r = writeToTar(new File(remote),fileMask,excludes,pipe);
|
|
|
|
try {
|
|
|
|
future.get();
|
|
|
|
} catch (ExecutionException e) {
|
|
|
|
throw new IOException2(e);
|
|
|
|
}
|
|
|
|
return r;
|
2006-12-30 03:15:53 +08:00
|
|
|
} else {
|
2007-04-06 22:19:15 +08:00
|
|
|
// remote -> local copy
|
|
|
|
final Pipe pipe = Pipe.createRemoteToLocal();
|
2006-12-30 03:15:53 +08:00
|
|
|
|
2007-04-06 22:19:15 +08:00
|
|
|
Future<Integer> future = actAsync(new FileCallable<Integer>() {
|
|
|
|
public Integer invoke(File f, VirtualChannel channel) throws IOException {
|
|
|
|
return writeToTar(f,fileMask,excludes,pipe);
|
2006-12-30 03:15:53 +08:00
|
|
|
}
|
|
|
|
});
|
2007-04-07 00:59:24 +08:00
|
|
|
readFromTar(new File(target.remote),pipe.getIn());
|
2007-04-06 22:19:15 +08:00
|
|
|
try {
|
|
|
|
return future.get();
|
|
|
|
} 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.
|
|
|
|
*/
|
|
|
|
private Integer writeToTar(File baseDir, String fileMask, String excludes, Pipe pipe) throws IOException {
|
|
|
|
FileSet fs = new FileSet();
|
|
|
|
fs.setDir(baseDir);
|
|
|
|
fs.setIncludes(fileMask);
|
|
|
|
if(excludes!=null)
|
|
|
|
fs.setExcludes(excludes);
|
|
|
|
|
|
|
|
byte[] buf = new byte[8192];
|
|
|
|
|
|
|
|
TarOutputStream tar = new TarOutputStream(new GZIPOutputStream(new BufferedOutputStream(pipe.getOut())));
|
|
|
|
|
|
|
|
DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
|
|
|
|
String[] files = ds.getIncludedFiles();
|
|
|
|
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();
|
2006-12-30 03:15:53 +08:00
|
|
|
}
|
2007-04-06 22:19:15 +08:00
|
|
|
|
|
|
|
tar.close();
|
|
|
|
|
|
|
|
return files.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads from a tar stream and stores obtained files to the base dir.
|
|
|
|
*/
|
|
|
|
private static void readFromTar(File baseDir, InputStream in) throws IOException {
|
|
|
|
Untar untar = new Untar();
|
|
|
|
untar.setProject(new Project());
|
|
|
|
untar.add(new StreamResource(new BufferedInputStream(new GZIPInputStream(in))));
|
|
|
|
untar.setDest(baseDir);
|
|
|
|
untar.execute();
|
2006-12-30 03:15:53 +08:00
|
|
|
}
|
|
|
|
|
2007-03-23 07:30:06 +08:00
|
|
|
/**
|
|
|
|
* Creates a {@link Launcher} for starting processes on the node
|
2007-03-23 07:30:18 +08:00
|
|
|
* that has this file.
|
2007-03-24 09:06:51 +08:00
|
|
|
* @since 1.89
|
2007-03-23 07:30:06 +08:00
|
|
|
*/
|
|
|
|
public Launcher createLauncher(TaskListener listener) {
|
|
|
|
if(channel==null)
|
|
|
|
return new LocalLauncher(listener);
|
|
|
|
else
|
|
|
|
return new RemoteLauncher(listener,channel,isUnix());
|
|
|
|
}
|
|
|
|
|
2007-03-24 09:06:51 +08:00
|
|
|
/**
|
2007-04-08 03:54:56 +08:00
|
|
|
* Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar")
|
2007-03-24 09:06:51 +08:00
|
|
|
* against this directory, and try to point out the problem.
|
|
|
|
*
|
2007-03-24 09:18:13 +08:00
|
|
|
* <p>
|
|
|
|
* This is useful in conjunction with {@link FormFieldValidator}.
|
|
|
|
*
|
2007-03-24 09:06:51 +08:00
|
|
|
* @return
|
|
|
|
* null if no error was found.
|
|
|
|
* @since 1.90
|
2007-03-24 09:18:13 +08:00
|
|
|
* @see FormFieldValidator.WorkspaceFileMask
|
2007-03-24 09:06:51 +08:00
|
|
|
*/
|
2007-04-08 03:54:56 +08:00
|
|
|
public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
|
2007-03-24 09:06:51 +08:00
|
|
|
return act(new FileCallable<String>() {
|
|
|
|
public String invoke(File dir, VirtualChannel channel) throws IOException {
|
2007-04-08 03:54:56 +08:00
|
|
|
StringTokenizer tokens = new StringTokenizer(fileMasks);
|
|
|
|
|
|
|
|
OUTER:
|
|
|
|
while(tokens.hasMoreTokens()) {
|
|
|
|
final String fileMask = tokens.nextToken().trim();
|
|
|
|
String previous = null;
|
|
|
|
String pattern = fileMask;
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
FileSet fs = new FileSet();
|
|
|
|
fs.setDir(dir);
|
|
|
|
fs.setIncludes(pattern);
|
|
|
|
|
|
|
|
DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
|
|
|
|
|
|
|
|
if(ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0) {
|
|
|
|
// found a match
|
|
|
|
if(pattern.equals(fileMask))
|
|
|
|
continue OUTER; // no error
|
|
|
|
if(previous==null)
|
|
|
|
return String.format("'%s' doesn't match anything, although '%s' exists",
|
|
|
|
fileMask, pattern );
|
|
|
|
else
|
|
|
|
return String.format("'%s' doesn't match anything: '%s' exists but not '%s'",
|
|
|
|
fileMask, pattern, previous );
|
|
|
|
}
|
2007-03-24 09:06:51 +08:00
|
|
|
|
2007-04-08 03:54:56 +08:00
|
|
|
int idx = Math.max(pattern.lastIndexOf('\\'),pattern.lastIndexOf('/'));
|
|
|
|
if(idx<0) {
|
|
|
|
if(pattern.equals(fileMask))
|
|
|
|
return String.format("'%s' doesn't match anything", fileMask );
|
|
|
|
else
|
|
|
|
return String.format("'%s' doesn't match anything: even '%s' doesn't exist",
|
|
|
|
fileMask, pattern );
|
|
|
|
}
|
2007-03-24 09:06:51 +08:00
|
|
|
|
2007-04-08 03:54:56 +08:00
|
|
|
// cut off the trailing component and try again
|
|
|
|
previous = pattern;
|
|
|
|
pattern = pattern.substring(0,idx);
|
|
|
|
}
|
2007-03-24 09:06:51 +08:00
|
|
|
}
|
2007-04-08 03:54:56 +08:00
|
|
|
|
|
|
|
return null; // no error
|
2007-03-24 09:06:51 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2006-11-06 05:16:01 +08:00
|
|
|
@Deprecated
|
|
|
|
public String toString() {
|
|
|
|
// to make writing JSPs easily, return local
|
2006-12-30 03:15:53 +08:00
|
|
|
return remote;
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
public VirtualChannel getChannel() {
|
|
|
|
if(channel!=null) return channel;
|
|
|
|
else return Hudson.MasterComputer.localChannel;
|
|
|
|
}
|
|
|
|
|
2007-04-09 03:30:40 +08:00
|
|
|
/**
|
|
|
|
* Returns true if this {@link FilePath} represents a remote file.
|
|
|
|
*/
|
|
|
|
public boolean isRemote() {
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
|
2006-12-30 03:15:53 +08:00
|
|
|
private void writeObject(ObjectOutputStream oos) throws IOException {
|
|
|
|
Channel target = Channel.current();
|
|
|
|
|
|
|
|
if(channel!=null && channel!=target)
|
|
|
|
throw new IllegalStateException("Can't send a remote FilePath to a different remote channel");
|
|
|
|
|
|
|
|
oos.defaultWriteObject();
|
|
|
|
oos.writeBoolean(channel==null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
|
|
|
Channel channel = Channel.current();
|
|
|
|
assert channel!=null;
|
|
|
|
|
|
|
|
ois.defaultReadObject();
|
|
|
|
if(ois.readBoolean()) {
|
|
|
|
this.channel = channel;
|
|
|
|
} else {
|
|
|
|
this.channel = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
2007-04-06 22:19:15 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adapts {@link FileCallable} to {@link Callable}.
|
|
|
|
*/
|
|
|
|
private class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
|
|
|
|
private final FileCallable<T> callable;
|
|
|
|
|
|
|
|
public FileCallableWrapper(FileCallable<T> callable) {
|
|
|
|
this.callable = callable;
|
|
|
|
}
|
|
|
|
|
|
|
|
public T call() throws IOException {
|
|
|
|
return callable.invoke(new File(remote), Channel.current());
|
|
|
|
}
|
|
|
|
|
|
|
|
public ClassLoader getClassLoader() {
|
|
|
|
return callable.getClass().getClassLoader();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
}
|
2006-11-06 05:16:01 +08:00
|
|
|
}
|