package hudson.plugins.filesystem_scm;
import java.io.*;
import java.util.*;
import org.kohsuke.stapler.StaplerRequest;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.scm.ChangeLogParser;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
public class FSSCM extends SCM {
/** The source folder
*
*/
private String path;
/** If true, will delete everything in workspace every time before we checkout
*
*/
private boolean clearWorkspace;
/** If we have include/exclude filter, then this is true
*
*/
private boolean filterEnabled;
/** Is this filter a include filter or exclude filter
*
*/
private boolean includeFilter;
/** filters will be passed to org.apache.commons.io.filefilter.WildcardFileFilter
*
*/
private String[] filters;
// Don't use DataBoundConsturctor, it is still not mature enough, many HTML form elements are not binded
// @DataBoundConstructor
public FSSCM(String path, boolean clearWorkspace, boolean filterEnabled, boolean includeFilter, String[] filters) {
this.path = path;
this.clearWorkspace = clearWorkspace;
this.filterEnabled = filterEnabled;
this.includeFilter = includeFilter;
if ( null != filters ) {
Vector<String> v = new Vector<String>();
for(int i=0; i<filters.length; i++) {
// remove empty strings
if ( StringUtils.isNotEmpty(filters[i]) ) {
v.add(filters[i]);
}
}
this.filters = (String[]) v.toArray(new String[1]);
} else {
this.filters = null;
}
}
public String getPath() {
return path;
}
public String[] getFilters() {
return filters;
}
public boolean isFilterEnabled() {
return filterEnabled;
}
public boolean isIncludeFilter() {
return includeFilter;
}
public boolean isClearWorkspace() {
return clearWorkspace;
}
@Override
public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile)
throws IOException, InterruptedException {
long start = System.currentTimeMillis();
PrintStream log = launcher.getListener().getLogger();
log.println("FSSCM.checkout " + path + " to " + workspace);
Boolean b = Boolean.TRUE;
AllowDeleteList allowDeleteList = new AllowDeleteList(build.getProject().getRootDir());
if ( clearWorkspace ) {
log.println("FSSCM.clearWorkspace...");
workspace.deleteRecursive();
}
// we will only delete a file if it is listed in the allowDeleteList
// ie. we will only delete a file if it is copied by us
if ( allowDeleteList.fileExists() ) {
allowDeleteList.load();
} else {
// watch list save file doesn't exist
// we will assuem all existing files are under watch
// ie. everything can be deleted
if ( workspace.exists() ) {
// if we enable clearWorkspace on the 1st jobrun, seems the workspace will be deleted
// running a RemoteListDir() on a not existing folder will throw an exception
// anyway, if the folder doesn't exist, we dont' need to list the files
Set<String> existingFiles = workspace.act(new RemoteListDir());
allowDeleteList.setList(existingFiles);
}
}
RemoteFolderDiff.CheckOut callable = new RemoteFolderDiff.CheckOut();
setupRemoteFolderDiff(callable, build.getProject(), allowDeleteList.getList());
List<FolderDiff.Entry> list = workspace.act(callable);
// maintain the watch list
for(FolderDiff.Entry entry : list) {
if ( FolderDiff.Entry.Type.DELETED.equals(entry.getType()) ) {
allowDeleteList.remove(entry.getFilename());
} else {
// added or modified
allowDeleteList.add(entry.getFilename());
}
}
allowDeleteList.save();
// raw log
String str = callable.getLog();
if ( str.length() > 0 ) log.println(str);
ChangelogSet.XMLSerializer handler = new ChangelogSet.XMLSerializer();
ChangelogSet changeLogSet = new ChangelogSet(build, list);
handler.save(changeLogSet, changelogFile);
log.println("FSSCM.check completed in " + formatDurration(System.currentTimeMillis()-start));
return b;
}
@Override
public ChangeLogParser createChangeLogParser() {
return new ChangelogSet.XMLSerializer();
}
/**
* There are two things we need to check
* <ul>
* <li>files created or modified since last build time, we only need to check the source folder</li>
* <li>file deleted since last build time, we have to compare source and destination folder</li>
* </ul>
*/
@Override
public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener)
throws IOException, InterruptedException {
long start = System.currentTimeMillis();
PrintStream log = launcher.getListener().getLogger();
log.println("FSSCM.pollChange: " + path);
AllowDeleteList allowDeleteList = new AllowDeleteList(project.getRootDir());
// we will only delete a file if it is listed in the allowDeleteList
// ie. we will only delete a file if it is copied by us
if ( allowDeleteList.fileExists() ) {
allowDeleteList.load();
} else {
// watch list save file doesn't exist
// we will assuem all existing files are under watch
// ie. everything can be deleted
Set<String> existingFiles = workspace.act(new RemoteListDir());
allowDeleteList.setList(existingFiles);
}
RemoteFolderDiff.PollChange callable = new RemoteFolderDiff.PollChange();
setupRemoteFolderDiff(callable, project, allowDeleteList.getList());
boolean changed = workspace.act(callable);
String str = callable.getLog();
if ( str.length() > 0 ) log.println(str);
log.println("FSSCM.pollChange return " + changed);
log.println("FSSCM.poolChange completed in " + formatDurration(System.currentTimeMillis()-start));
return changed;
}
private void setupRemoteFolderDiff(RemoteFolderDiff diff, AbstractProject project, Set<String> allowDeleteList) {
Run lastBuild = project.getLastBuild();
if ( null == lastBuild ) {
diff.setLastBuildTime(0);
diff.setLastSuccessfulBuildTime(0);
} else {
diff.setLastBuildTime(lastBuild.getTimestamp().getTimeInMillis());
Run lastSuccessfulBuild = project.getLastSuccessfulBuild();
if ( null == lastSuccessfulBuild ) {
diff.setLastSuccessfulBuildTime(-1);
} else {
diff.setLastSuccessfulBuildTime(lastSuccessfulBuild.getTimestamp().getTimeInMillis());
}
}
diff.setSrcPath(path);
if ( filterEnabled ) {
if ( includeFilter ) diff.setIncludeFilter(filters);
else diff.setExcludeFilter(filters);
}
diff.setAllowDeleteList(allowDeleteList);
}
protected static String formatDurration(long diff) {
if ( diff < 60*1000L ) {
// less than 1 minute
if ( diff <= 1 ) return diff + " millisecond";
else if ( diff < 1000L ) return diff + " milliseconds";
else if ( diff < 2000L ) return ((double)diff/1000.0) + " second";
else return ((double)diff/1000.0) + " seconds";
} else {
return org.apache.commons.lang.time.DurationFormatUtils.formatDurationWords(diff, true, true);
}
}
@Extension
public static final class DescriptorImpl extends SCMDescriptor<FSSCM> {
public DescriptorImpl() {
super(FSSCM.class, null);
load();
}
@Override
public String getDisplayName() {
return "File System";
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
return true;
}
@Override
public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException {
String path = req.getParameter("fs_scm.path");
String[] filters = req.getParameterValues("fs_scm.filters");
Boolean filterEnabled = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("fs_scm.filterEnabled")));
Boolean includeFilter = Boolean.valueOf(req.getParameter("fs_scm.includeFilter"));
Boolean clearWorkspace = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("fs_scm.clearWorkspace")));
return new FSSCM(path, clearWorkspace, filterEnabled, includeFilter, filters);
}
}
}