package net.hurstfrost.hudson.speaks;
import hudson.Extension;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import net.sf.json.JSONObject;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.xml.sax.InputSource;
import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;
/**
* {@link Notifier} that makes Hudson speak using <a href="http://freetts.sourceforge.net/">FreeTTS</a>.
*
* @author Edward Hurst-Frost
*/
public class HudsonSpeaksNotifier extends Notifier {
private String projectTemplate;
public String getProjectTemplate() {
return StringUtils.isEmpty(projectTemplate)?getDescriptor().getAnnouncementTemplate():projectTemplate;
}
public void setProjectTemplate(String template) {
if (StringUtils.isEmpty(template) || template.trim().equals(getDescriptor().getAnnouncementTemplate())) {
projectTemplate = null;
} else {
projectTemplate = template;
}
}
@DataBoundConstructor
public HudsonSpeaksNotifier() {
// Default constructor
}
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.STEP;
}
@Override
public HudsonSpeaksDescriptor getDescriptor() {
return (HudsonSpeaksDescriptor) super.getDescriptor();
}
@Override
public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher, final BuildListener listener) {
JellyContext jellyContext = new JellyContext();
jellyContext.setVariable("build", build);
jellyContext.setVariable("duration", getDuration(build));
String announcementMessage;
final String template = !StringUtils.isEmpty(projectTemplate)?projectTemplate:getDescriptor().getAnnouncementTemplate();
try {
announcementMessage = getAnnouncementMessage(jellyContext, template);
} catch (Exception e) {
listener.getLogger().println("Hudson failed to interpret your announcement teamplate '" + template + "' : " + e.toString());
return true;
}
if (!StringUtils.isEmpty(announcementMessage)) {
listener.getLogger().println("Hudson speaks '" + announcementMessage + "'");
try {
say(announcementMessage);
} catch (Exception e) {
listener.getLogger().println("Hudson failed to speak '" + announcementMessage + "' : " + e.toString());
}
}
return true;
}
private static void say(String announcementMessage) {
VoiceManager voiceManager = VoiceManager.getInstance();
Voice helloVoice = voiceManager.getVoice("kevin16");
helloVoice.allocate();
helloVoice.speak(announcementMessage);
helloVoice.deallocate();
}
private static String getAnnouncementMessage(JellyContext jellyContext, String template) throws JellyException, IOException {
StringWriter writer = new StringWriter();
final XMLOutput xmlOutput = XMLOutput.createXMLOutput(writer);
final String script = "<?xml version=\"1.0\"?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:x=\"jelly:xml\" xmlns:html=\"jelly:html\">" + template + "</j:jelly>";
byte[] bytes;
try {
bytes = script.getBytes("UTF-8");
} catch (Exception e) {
// Probably never happens
bytes = script.getBytes();
}
jellyContext.runScript(new InputSource(new ByteArrayInputStream(bytes)), xmlOutput);
xmlOutput.flush();
xmlOutput.close();
return writer.toString().trim();
}
private String getDuration(final AbstractBuild<?, ?> build) {
String durationString = build.getTimestampString();
durationString = durationString.replaceAll(" sec", " seconds");
durationString = durationString.replaceAll(" ms", " milli seconds");
durationString = durationString.replaceAll(" min", " minutes");
return durationString;
}
@Extension
public static final class HudsonSpeaksDescriptor extends BuildStepDescriptor<Publisher> {
public static final String DEFAULT_TEMPLATE = "<j:choose>\n"+
"<j:when test=\"${build.result!='SUCCESS' || build.project.lastBuild.result!='SUCCESS'}\">\n"+
"Your attention please. Project ${build.project.name}, build number ${build.number}: ${build.result} in ${duration}.\n"+
"<j:if test=\"${build.result!='SUCCESS'}\"> Get fixing those bugs team!</j:if>\n" +
"</j:when>\n"+
"<j:otherwise><!-- Say nothing --></j:otherwise>\n"+
"</j:choose>";
private String globalTemplate = DEFAULT_TEMPLATE;
public HudsonSpeaksDescriptor() {
load();
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
@Override
public boolean configure(final StaplerRequest req, JSONObject json) {
globalTemplate = json.optString("announcementTemplate");
if (StringUtils.isEmpty(globalTemplate)) {
globalTemplate = DEFAULT_TEMPLATE;
}
save();
return true;
// return super.configure(req, json);
}
@Override
public String getDisplayName() {
return "Hudson Speaks!";
}
@Override
public HudsonSpeaksNotifier newInstance(StaplerRequest req, JSONObject formData) {
HudsonSpeaksNotifier m = new HudsonSpeaksNotifier();
req.bindParameters(m,"speaks_");
return m;
}
public FormValidation doTestSpeech(@QueryParameter String testPhrase) {
if (StringUtils.isEmpty(testPhrase)) {
return FormValidation.errorWithMarkup("<p>Please enter a phrase for Hudson to speak.</p>");
}
try {
say(testPhrase);
} catch (Exception e) {
return FormValidation.errorWithMarkup("<p>Hudson failed to speak</p><pre>" + Util.escape(Functions.printThrowable(e)) + "</pre>");
}
return FormValidation.ok("Hudson said '" + testPhrase + "' successfully.");
}
public String getAnnouncementTemplate() {
return StringUtils.isEmpty(globalTemplate)?DEFAULT_TEMPLATE:globalTemplate;
}
public void setAnnouncementTemplate(String newTemplate) {
this.globalTemplate = StringUtils.isEmpty(newTemplate)?DEFAULT_TEMPLATE:newTemplate;
}
}
}