Skip to content
Permalink
Browse files

Merge pull request #15 from slide/JENKINS-9018

Implementing attachments (JENKINS-9018)
  • Loading branch information
slide committed Aug 4, 2011
2 parents d13efd0 + 28b7e31 commit 31cf48c1abc1a55ff80f50e369006f9ab1c7e834
@@ -0,0 +1,132 @@
/**
*
*/
package hudson.plugins.emailext;

import hudson.FilePath;
import hudson.Util;
import hudson.FilePath.FileCallable;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.remoting.VirtualChannel;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.MimetypesFileTypeMap;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeBodyPart;

import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;

/**
* @author acearl
*
*/
public class AttachmentUtils implements Serializable {

private static final long serialVersionUID = 1L;

private String attachmentsPattern;

public AttachmentUtils(String attachmentsPattern) {
this.attachmentsPattern = attachmentsPattern;
}

/**
* Provides a datasource wrapped around the FilePath class to
* allow access to remote files (on slaves).
* @author acearl
*
*/
private static class FilePathDataSource implements DataSource {
private FilePath file;

public FilePathDataSource(FilePath file) {
this.file = file;
}

public InputStream getInputStream() throws IOException {
return file.read();
}

public OutputStream getOutputStream() throws IOException {
throw new IOException("Unsupported");
}

public String getContentType() {
return MimetypesFileTypeMap.getDefaultFileTypeMap()
.getContentType(file.getName());
}

public String getName() {
return file.getName();
}
}

private List<MimeBodyPart> getAttachments(final AbstractBuild<?, ?> build, final BuildListener listener)
throws MessagingException, InterruptedException, IOException {
List<MimeBodyPart> attachments = null;
FilePath ws = build.getWorkspace();
long totalAttachmentSize = 0;
long maxAttachmentSize =
ExtendedEmailPublisher.DESCRIPTOR.getMaxAttachmentSize();
if(ws == null) {
listener.error("Error: No workspace found!");
} else if(attachmentsPattern != null && attachmentsPattern.trim().length() > 0) {
attachments = new ArrayList<MimeBodyPart>();
FilePath[] files = ws.list(attachmentsPattern);

for(FilePath file : files) {
if(maxAttachmentSize > 0 &&
(totalAttachmentSize + file.length()) >= maxAttachmentSize) {
listener.getLogger().println("Skipping `" + file.getName()
+ "' ("+ file.length() +
" bytes) - too large for maximum attachments size");
continue;
}

MimeBodyPart attachmentPart = new MimeBodyPart();
FilePathDataSource fileDataSource = new FilePathDataSource(file);

try {
attachmentPart.setDataHandler(new DataHandler(fileDataSource));
attachmentPart.setFileName(file.getName());
attachments.add(attachmentPart);
totalAttachmentSize += file.length();
} catch(MessagingException e) {
listener.getLogger().println("Error adding `" +
file.getName() + "' as attachment - " +
e.getMessage());
}
}
}
return attachments;
}

public void attach(Multipart multipart, AbstractBuild<?,?> build, BuildListener listener) {
try {
List<MimeBodyPart> attachments = getAttachments(build, listener);
if(attachments != null) {
for(MimeBodyPart attachment : attachments) {
multipart.addBodyPart(attachment);
}
}
} catch (IOException e) {
listener.error("Error accessing files to attach: " + e.getMessage());
} catch (MessagingException e) {
listener.error("Error attaching items to message: " + e.getMessage());
} catch (InterruptedException e) {
listener.error("Interrupted in processing attachments: " + e.getMessage());
}
}
}
@@ -120,7 +120,7 @@ public void setRecipientList(String recipientList) {
public Object readResolve() {
if(recipientList != null && recipientList.trim().length() == 0) {
this.recipientList = ExtendedEmailPublisher.PROJECT_DEFAULT_RECIPIENTS_TEXT;
}
return this;
}
return this;
}
}
@@ -2,7 +2,10 @@

import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
@@ -13,6 +16,7 @@
import hudson.plugins.emailext.plugins.ContentBuilder;
import hudson.plugins.emailext.plugins.EmailTrigger;
import hudson.plugins.emailext.plugins.EmailTriggerDescriptor;
import hudson.remoting.VirtualChannel;
import hudson.scm.ChangeLogSet.Entry;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
@@ -21,16 +25,30 @@
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MimetypesFileTypeMap;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeMultipart;

import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -123,6 +141,11 @@ public static EmailTriggerDescriptor getEmailTriggerType(String mailerId) {
* The default body of the emails for this project. ($PROJECT_DEFAULT_BODY)
*/
public String defaultContent;

/**
* The project wide set of attachments.
*/
public String attachmentsPattern;

/**
* Get the list of configured email triggers for this project.
@@ -277,7 +300,11 @@ private MimeMessage createMail(EmailType type, AbstractBuild<?, ?> build, BuildL

setSubject(type, build, msg, charset);

setContent(type, build, msg, charset);
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(getContent(type, build, msg, charset));
AttachmentUtils attachments = new AttachmentUtils(attachmentsPattern);
attachments.attach(multipart, build, listener);
msg.setContent(multipart);

EnvVars env = build.getEnvironment(listener);

@@ -376,7 +403,7 @@ private String getRecipientList(final EmailType type, final AbstractBuild<?, ?>
return recipients;
}

private void setContent(final EmailType type, final AbstractBuild<?, ?> build, MimeMessage msg, String charset)
private MimeBodyPart getContent(final EmailType type, final AbstractBuild<?, ?> build, MimeMessage msg, String charset)
throws MessagingException {
final String text = new ContentBuilder().transformText(type.getBody(), this, type, build);

@@ -392,8 +419,12 @@ private void setContent(final EmailType type, final AbstractBuild<?, ?> build, M
}
messageContentType += "; charset=" + charset;

msg.setContent(text, messageContentType);
}
// set the email message text
// (plain text or HTML depending on the content type)
MimeBodyPart msgPart = new MimeBodyPart();
msgPart.setContent(text, messageContentType);
return msgPart;
}

private static void addAddressesFromRecipientList(Set<InternetAddress> addresses, String recipientList,
EnvVars envVars, BuildListener listener) {
@@ -88,6 +88,11 @@
* This is a global default recipient list for sending emails.
*/
private String recipientList = "";

/**
* The maximum size of all the attachments (in bytes)
*/
private long maxAttachmentSize = -1;

private boolean overrideGlobalSettings;

@@ -206,6 +211,14 @@ public String getDefaultBody() {
public String getDefaultRecipients() {
return recipientList;
}

public long getMaxAttachmentSize() {
return maxAttachmentSize;
}

public long getMaxAttachmentSizeMb() {
return maxAttachmentSize / (1024 * 1024);
}

public boolean getOverrideGlobalSettings() {
return overrideGlobalSettings;
@@ -233,6 +246,7 @@ public Publisher newInstance(StaplerRequest req, JSONObject formData)
m.contentType = formData.getString("project_content_type");
m.defaultSubject = formData.getString("project_default_subject");
m.defaultContent = formData.getString("project_default_content");
m.attachmentsPattern = formData.getString("project_attachments");
m.configuredTriggers = new ArrayList<EmailTrigger>();

// Create a new email trigger for each one that is configured
@@ -314,7 +328,11 @@ public boolean configure(StaplerRequest req, JSONObject formData)
defaultBody = nullify(req.getParameter("ext_mailer_default_body"));
recipientList = nullify(req.getParameter("ext_mailer_default_recipients")) != null ?
req.getParameter("ext_mailer_default_recipients") : "";


// convert the value into megabytes (1024 * 1024 bytes)
maxAttachmentSize = nullify(req.getParameter("ext_mailer_max_attachment_size")) != null ?
(Long.parseLong(req.getParameter("ext_mailer_max_attachment_size")) * 1024 * 1024) : -1;

overrideGlobalSettings = req.getParameter("ext_mailer_override_global_settings") != null;

precedenceBulk = req.getParameter("extmailer.addPrecedenceBulk") != null;
@@ -356,4 +374,19 @@ public FormValidation doRecipientListRecipientsCheck(@QueryParameter final Strin
throws IOException, ServletException {
return new EmailRecepientUtils().validateFormRecipientList(value);
}

public FormValidation doMaxAttachmentSizeCheck(@QueryParameter final String value)
throws IOException, ServletException {
try {
String testValue = value.trim();
// we support an empty value (which means default)
// or a number
if(testValue.length() > 0) {
Long.parseLong(testValue);
}
return FormValidation.ok();
} catch (Exception e) {
return FormValidation.error(e.getMessage());
}
}
}
@@ -83,6 +83,22 @@
</j:choose>
</f:entry>

<!-- This is the default attachments set for the projcet. -->
<f:entry title="${%Attachments}"
help="/plugin/email-ext/help/projectConfig/attachments.html"
description="${%description('http://ant.apache.org/manual/Types/fileset.html')}">
<j:choose>
<j:when test="${instance.configured}">
<input class="setting-input" name="project_attachments"
type="text" value="${instance.attachmentsPattern}"/>
</j:when>
<j:otherwise>
<input class="setting-input" name="project_attachments"
type="text" value=""/>
</j:otherwise>
</j:choose>
</f:entry>

<!-- This is the help section. It displays a bunch of dynamic help for all content tokens. -->
<tr>
<td></td>
@@ -1,2 +1,6 @@
projectContentType.plainText=Plain Text (text/plain)
projectContentType.html=HTML (text/html)
description=Can use wildcards like ''module/dist/**/*.zip''. \
See the <a href="{0}">@includes of Ant fileset</a> for the exact format. \
The base directory is <a href="ws/">the workspace</a>.

@@ -80,6 +80,21 @@
<input class="setting-input" name="ext_mailer_default_subject"
type="text" value="${descriptor.defaultSubject}"/>
</f:entry>
<f:entry title="${%Maximum Attachment Size}"
help="/plugin/email-ext/help/globalConfig/maxAttachmentSize.html">
<j:choose>
<j:when test="${descriptor.maxAttachmentSize>0}">
<input class="setting-input" name="ext_mailer_max_attachment_size"
type="text" value="${descriptor.maxAttachmentSizeMb}"
checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/maxAttachmentSizeCheck?value='+encodeURIComponent(this.value)"/>
</j:when>
<j:otherwise>
<input class="setting-input" name="ext_mailer_max_attachment_size"
type="text" value=""
checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/maxAttachmentSizeCheck?value='+encodeURIComponent(this.value)"/>
</j:otherwise>
</j:choose>
</f:entry>
<f:entry title="${%Default Content}"
help="/plugin/email-ext/help/globalConfig/defaultBody.html">
<f:textarea class="setting-input"
@@ -0,0 +1,4 @@
<div>
The maximum size (in MB) for all attachments. If left blank,
there is no limit to the size of all attachments.
</div>
@@ -0,0 +1,4 @@
<div>
This is the set of attachments that will be used for the email. The format is a
comma separated list of Ant include file syntax.
</div>

0 comments on commit 31cf48c

Please sign in to comment.
You can’t perform that action at this time.