package hudson.plugins.sfee;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BallColor;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskAction;
import hudson.model.TaskListener;
import hudson.model.TaskThread;
import hudson.model.TaskThread.ListenerAndText;
import hudson.plugins.sfee.webservice.ArtifactDetailSoapRow;
import hudson.plugins.sfee.webservice.InvalidFilterFault;
import hudson.plugins.sfee.webservice.InvalidSessionFault;
import hudson.plugins.sfee.webservice.NoSuchObjectFault;
import hudson.plugins.sfee.webservice.PermissionDeniedFault;
import hudson.plugins.sfee.webservice.SystemFault;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.util.Iterators;
import hudson.widgets.HistoryWidget;
import hudson.widgets.HistoryWidget.Adapter;
import java.io.File;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.servlet.ServletException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.framework.io.LargeText;
public class SFEEReleaseTask<T extends AbstractBuild> extends TaskAction
implements AccessControlled {
private final AbstractBuild<?, ?> build;
private final String releasePackageId;
private final String releaseName;
private final String maturity;
private final String releaseToReplace;
private List<TrackerArtifact> resolvedTrackerArtifacts;
private final boolean uploadFiles;
private final boolean replaceFiles;
private boolean uploadBuildLog;
private Map<String, Boolean> downloadingArtifactList = null;
public SFEEReleaseTask(AbstractBuild<?, ?> build, String releasePackageId,
String releaseName, String releaseToReplace, String maturity,
boolean uploadFiles, boolean replaceFiles) {
this.build = build;
this.releasePackageId = releasePackageId;
this.releaseName = releaseName;
this.releaseToReplace = releaseToReplace;
this.maturity = maturity;
this.uploadFiles = uploadFiles;
this.replaceFiles = replaceFiles;
}
@Override
public ACL getACL() {
return build.getACL();
}
@Override
protected Permission getPermission() {
return PluginImpl.PUBLISH;
}
public String getDisplayName() {
if (hasPermission(getPermission())) {
return "SourceForge";
} else {
return null;
}
}
public String getIconFileName() {
if (hasPermission(getPermission())) {
return isCompleted() ? "star-gold.gif" : "star.gif";
} else {
return null;
}
}
public String getUrlName() {
if (hasPermission(getPermission())) {
return "upload";
} else {
return null;
}
}
public boolean isCompleted() {
return records.size() > 0
&& records.get(records.size() - 1).getResult() == Result.SUCCESS;
}
/**
* Records of a deployment.
*/
public final CopyOnWriteArrayList<Record> records = new CopyOnWriteArrayList<Record>();
protected String fileReleaseId;
public HistoryWidgetImpl getHistoryWidget() {
return new HistoryWidgetImpl();
}
public Object getDynamic(String token, StaplerRequest req,
StaplerResponse rsp) {
if ("buildHistory".equals(token)) {
return getHistoryWidget();
}
if ("buildTimeTrend".equals(token)) {
return null;
}
return records.get(Integer.valueOf(token));
}
private final class HistoryWidgetImpl extends
HistoryWidget<SFEEReleaseTask<?>, Record> {
private HistoryWidgetImpl() {
super(SFEEReleaseTask.this, Iterators.reverse(records), ADAPTER);
}
@Override
public String getDisplayName() {
return "Deployment History";
}
}
/**
* Performs an upload.
*/
public final void doUpload(StaplerRequest req, StaplerResponse rsp)
throws ServletException, IOException {
getACL().checkPermission(getPermission());
// ----------------------------------
// Check upload of build log
// ----------------------------------
uploadBuildLog = "on".equals(req.getParameter("uploadBuildLog"));
// ----------------------------------
// Set which artifact to upload
// ----------------------------------
boolean doUpload = false;
List buildArtifacts = build.getArtifacts();
for (Iterator iterator = buildArtifacts.iterator(); iterator.hasNext();) {
Run.Artifact buildArtifact = (Run.Artifact) iterator.next();
doUpload = "on".equals(req
.getParameter(buildArtifact.getFileName()));
getDownloadingArtifactList().put(buildArtifact.getFileName(),
doUpload);
}
startUpload();
rsp.sendRedirect(".");
}
public void startUpload() throws IOException {
File logFile = new File(build.getRootDir(), "sfee-upload."
+ records.size() + ".log");
final Record record = new Record(logFile.getName());
records.add(record);
new TaskThread(this, ListenerAndText.forFile(logFile)) {
protected void perform(TaskListener listener) throws Exception {
try {
long start = System.currentTimeMillis();
final SourceForgeSite site = SourceForgeSite.DESCRIPTOR
.getSite();
if (fileReleaseId == null) {
fileReleaseId = createOrUpdateRelease(listener,
releaseName, releaseToReplace,
releasePackageId, maturity);
if (fileReleaseId == null) {
record.result = Result.FAILURE;
return;
}
if (isUploadFiles()) {
listener.getLogger().println(
"Existing files will "
+ (isReplaceFiles() ? "" : "not")
+ " be overwitten");
if (isUploadBuildLog()) {
String logFileName = releaseName + "-"
+ build.getParent().getDisplayName()
+ "-" + build.getNumber() + "-"
+ build.getId() + ".log";
listener.getLogger().println(
"Uploading build log file "
+ logFileName + "...");
site.uploadFileForRelease(fileReleaseId,
logFileName, new DataHandler(
new FileDataSource(build
.getLogFile())),
isReplaceFiles());
}
List buildArtifacts = build.getArtifacts();
for (Iterator iterator = buildArtifacts.iterator(); iterator
.hasNext();) {
Run.Artifact buildArtifact = (Run.Artifact) iterator
.next();
if (getDownloadingArtifactList().get(
buildArtifact.getFileName())) {
listener.getLogger().print(
"Uploading file "
+ buildArtifact
.getFileName()
+ " ...");
site.uploadFileForRelease(fileReleaseId,
buildArtifact.getFileName(),
buildArtifact.getFile().toURI().toURL(),
isReplaceFiles());
listener.getLogger().println(
" upload successfully completed!");
} else {
listener.getLogger().println(
"Skipping unselected file "
+ buildArtifact
.getFileName());
}
}
}
} else {
listener.getLogger().println(
"Reusing previous release " + fileReleaseId);
}
resolvedTrackerArtifacts = findResolvedArtifacts(build
.getProject(), releaseName);
long duration = System.currentTimeMillis() - start;
listener.getLogger().println(
"Total time: " + Util.getTimeSpanString(duration));
record.result = Result.SUCCESS;
build.addAction(new SFEEReleaseCompletedTask(
SFEEReleaseTask.this));
onComplete();
} finally {
if (record.result == null)
record.result = Result.FAILURE;
// persist the record
build.save();
}
}
}.start();
}
private static final Adapter<SFEEReleaseTask.Record> ADAPTER = new Adapter<SFEEReleaseTask.Record>() {
public int compare(SFEEReleaseTask.Record record, String key) {
return record.getNumber() - Integer.parseInt(key);
}
public String getKey(SFEEReleaseTask.Record record) {
return String.valueOf(record.getNumber());
}
public boolean isBuilding(SFEEReleaseTask.Record record) {
return record.isBuilding();
}
public String getNextKey(String key) {
return String.valueOf(Integer.parseInt(key) + 1);
}
};
public final class Record {
/**
* Log file name. Relative to {@link AbstractBuild#getRootDir()}.
*/
private final String fileName;
/**
* Status of this record.
*/
private Result result;
private final Calendar timeStamp;
private long duration, estimatedDuration;
public Record(String fileName) {
this.fileName = fileName;
timeStamp = new GregorianCalendar();
}
/**
* Returns the log of this deployment record.
*/
public LargeText getLog() {
return new LargeText(new File(build.getRootDir(), fileName), true);
}
/**
* Result of the deployment. During the build, this value is null.
*/
public Result getResult() {
return result;
}
public int getNumber() {
return records.indexOf(this);
}
public boolean isBuilding() {
return result == null;
}
public Calendar getTimestamp() {
return (Calendar) timeStamp.clone();
}
public String getBuildStatusUrl() {
return getIconColor().getImage();
}
public BallColor getIconColor() {
if (result == null)
return BallColor.GREY_ANIME;
else
return result.color;
}
// TODO: Eventually provide a better UI
public final void doIndex(StaplerRequest req, StaplerResponse rsp)
throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
getLog().writeLogTo(0, rsp.getWriter());
}
public void doStop(StaplerRequest req, StaplerResponse rsp)
throws IOException {
rsp.sendRedirect("../stop");
}
public String getTimestampString() {
return Util.getTimeSpanString(duration);
}
public Object getExecutor() {
return new Object() {
/**
* Returns the progress of the current build in the number
* between 0-100.
*
* @return -1 if it's impossible to estimate the progress.
*/
public int getProgress() {
long d = estimatedDuration;
if (d < 0)
return -1;
int num = (int) ((System.currentTimeMillis() - timeStamp
.getTimeInMillis()) * 100 / d);
if (num >= 100)
num = 99;
return num;
}
};
}
}
public static String createOrUpdateRelease(TaskListener listener,
String releaseName, String releaseToReplace, String packageId,
String maturity) throws NoSuchObjectFault, InvalidSessionFault,
SystemFault, PermissionDeniedFault, RemoteException {
final SourceForgeSite site = SourceForgeSite.DESCRIPTOR.getSite();
String releaseId = null;
if (releaseToReplace != null) {
listener.getLogger().printf(
"Update release from '%s' to '%s'\n...", releaseToReplace,
releaseName);
// update old release
releaseId = site.updateReleaseName(packageId, releaseToReplace,
releaseName);
if (releaseId == null) {
listener.fatalError("No release found with name "
+ releaseToReplace);
return null;
}
// creating next release
listener.getLogger().printf("Creating new release '%s'\n",
releaseToReplace);
site.createRelease(packageId, releaseToReplace, "", "active",
maturity);
} else {
// create release
listener.getLogger().printf("Checking for existing release\n",
releaseName);
releaseId = site.getReleaseId(packageId, releaseName);
if (releaseId == null) {
listener.getLogger().printf("Creating new release '%s'\n",
releaseName);
releaseId = site.createRelease(packageId, releaseName, "",
"active", maturity);
}
}
return releaseId;
}
private List<TrackerArtifact> findResolvedArtifacts(
AbstractProject<?, ?> p, String releaseName)
throws InvalidFilterFault, NoSuchObjectFault, InvalidSessionFault,
SystemFault, PermissionDeniedFault, RemoteException {
SourceForgeProject project = SourceForgeProject.getProperty(p);
SourceForgeSite site = SourceForgeSite.DESCRIPTOR.getSite();
List<ArtifactDetailSoapRow> findArtifactsResolvedInRelease = site
.findArtifactsResolvedInRelease(releaseName, project
.getProjectId());
List<TrackerArtifact> trackerArtifacts = new ArrayList<TrackerArtifact>();
for (ArtifactDetailSoapRow r : findArtifactsResolvedInRelease) {
System.out.println("Adding new TrackerArtifact");
trackerArtifacts.add(new TrackerArtifact(r));
}
System.out.println("TrackerArtifact size: " + trackerArtifacts.size());
return trackerArtifacts;
}
public AbstractBuild<?, ?> getBuild() {
return build;
}
public String getReleasePackageId() {
return releasePackageId;
}
public String getReleaseName() {
return releaseName;
}
public String getMaturity() {
return maturity;
}
public String getReleaseToReplace() {
return releaseToReplace;
}
public List<TrackerArtifact> getResolvedTrackerArtifacts() {
try {
resolvedTrackerArtifacts = findResolvedArtifacts(
build.getProject(), releaseName);
} catch (Exception e) {
e.printStackTrace();
}
return resolvedTrackerArtifacts;
}
public boolean isUploadFiles() {
return uploadFiles;
}
public boolean isReplaceFiles() {
return replaceFiles;
}
public CopyOnWriteArrayList<Record> getRecords() {
return records;
}
public Object getFileReleaseId() {
return fileReleaseId;
}
public String getFileReleaseUrl() {
return fileReleaseId != null ? SourceForgeSite.DESCRIPTOR.getSite()
.getURL(fileReleaseId) : null;
}
public boolean isUploadBuildLog() {
return uploadBuildLog;
}
public void setUploadBuildLog(boolean uploadBuildLog) {
this.uploadBuildLog = uploadBuildLog;
}
public void doStop(StaplerRequest req, StaplerResponse rsp)
throws IOException {
if (workerThread != null) {
workerThread.interrupt();
}
rsp.sendRedirect(".");
}
protected void onComplete() {
}
public void setDownloadingArtifactList(
Map<String, Boolean> downloadingArtifactList) {
this.downloadingArtifactList = downloadingArtifactList;
}
public Map<String, Boolean> getDownloadingArtifactList() {
if (downloadingArtifactList == null) {
downloadingArtifactList = new HashMap<String, Boolean>();
List buildArtifacts = build.getArtifacts();
for (Iterator iterator = buildArtifacts.iterator(); iterator
.hasNext();) {
Run.Artifact buildArtifact = (Run.Artifact) iterator.next();
System.out.println("Adding new artifact: "
+ buildArtifact.getFileName());
downloadingArtifactList.put(buildArtifact.getFileName(),
Boolean.TRUE);
}
}
return downloadingArtifactList;
}
public void checkPermission(Permission permission) {
getACL().checkPermission(permission);
}
public boolean hasPermission(Permission permission) {
return getACL().hasPermission(permission);
}
}