Email notification for LeafWeb jobs

This commit is contained in:
2016-03-09 11:43:44 -05:00
parent 314de4dece
commit e80ddbc4d6
10 changed files with 168 additions and 9 deletions
@@ -39,6 +39,22 @@ namespace LeafWeb.Core.Tests.Utility
Assert.That(result, Is.EqualTo("newline")); Assert.That(result, Is.EqualTo("newline"));
} }
[Test]
public void FilterAlphaNumeric_Apostrophes()
{
var str = "apostrophe's";
var result = str.FilterAlphaNumeric();
Assert.That(result, Is.EqualTo("apostrophes"));
}
[Test]
public void FilterAlphaNumeric_ValidFilename()
{
var str = "my apostrophe's.csv";
var result = str.FilterValidFilename();
Assert.That(result, Is.EqualTo("myapostrophes.csv"));
}
[Test] [Test]
public void FilenameFromPath() public void FilenameFromPath()
{ {
@@ -46,5 +62,13 @@ namespace LeafWeb.Core.Tests.Utility
var result = str.FilenameFromPath(); var result = str.FilenameFromPath();
Assert.That(result, Is.EqualTo("file.ext")); Assert.That(result, Is.EqualTo("file.ext"));
} }
[Test]
public void WhitespaceToUnderscoreTest()
{
var str = " w h i t e s p " + Environment.NewLine + " a c e";
var result = str.WhitespaceToUnderscore();
Assert.That(result, Is.EqualTo("_w_h_i_t_e_s_p_a_c_e"));
}
} }
} }
+5
View File
@@ -69,6 +69,11 @@ namespace LeafWeb.Core.DAL
return _db.LeafInputFiles; return _db.LeafInputFiles;
} }
public LeafInputFile GetLeafInputFile(int id)
{
return _db.LeafInputFiles.Find(id);
}
public IQueryable<LeafInputFile> GetLeafInputFiles(LeafInputStatusType status) public IQueryable<LeafInputFile> GetLeafInputFiles(LeafInputStatusType status)
{ {
return return
+10 -3
View File
@@ -1,5 +1,6 @@
using AutoMapper; using AutoMapper;
using LeafWeb.Core.Entities; using LeafWeb.Core.Entities;
using LeafWeb.Core.Utility;
namespace LeafWeb.Core.Remote namespace LeafWeb.Core.Remote
{ {
@@ -11,6 +12,7 @@ namespace LeafWeb.Core.Remote
public string Filename { get; set; } public string Filename { get; set; }
public byte[] Contents { get; set; } public byte[] Contents { get; set; }
public string DirectoryName { get; set; } public string DirectoryName { get; set; }
public string PhotosyntheticType { get; set; }
static PiscalLeafInputFile() static PiscalLeafInputFile()
{ {
@@ -18,9 +20,14 @@ namespace LeafWeb.Core.Remote
new MapperConfiguration(cfg => new MapperConfiguration(cfg =>
{ {
cfg.CreateMap<LeafInputFile, PiscalLeafInputFile>() cfg.CreateMap<LeafInputFile, PiscalLeafInputFile>()
.ForMember(dest => dest.DirectoryName, opt => opt.MapFrom( .ForMember(dest => dest.DirectoryName,
src => PiscalUtility.GetPiscalDirectoryName(src))) opt => opt.MapFrom(src => PiscalUtility.GetPiscalDirectoryName(src)))
.ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id)); .ForMember(dest => dest.Filename, opt =>
opt.MapFrom(src => src.Filename.WhitespaceToUnderscore().FilterValidFilename()))
.ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.PhotosyntheticType,
opt => opt.MapFrom(src => src.LeafInput.PhotosynthesisType.Id.WhitespaceToUnderscore()));
}); });
Mapper = config.CreateMapper(); Mapper = config.CreateMapper();
} }
+1 -1
View File
@@ -57,7 +57,7 @@ namespace LeafWeb.Core.Remote
using (var ssh = GetSshClient()) using (var ssh = GetSshClient())
{ {
ssh.Connect(); ssh.Connect();
var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -f {file.Filename}"; var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -f {file.Filename} -p {file.PhotosyntheticType}";
var command = ssh.CreateCommand(commandText); var command = ssh.CreateCommand(commandText);
command.Execute(); command.Execute();
ssh.Disconnect(); ssh.Disconnect();
+10
View File
@@ -32,6 +32,11 @@ namespace LeafWeb.Core.Utility
return char.ToLowerInvariant(s[0]) + s.Substring(1); return char.ToLowerInvariant(s[0]) + s.Substring(1);
} }
public static string WhitespaceToUnderscore(this string str)
{
return Regex.Replace(str, @"\s+", "_");
}
public static byte[] GetBytes(this string str) public static byte[] GetBytes(this string str)
{ {
return System.Text.Encoding.Default.GetBytes(str); return System.Text.Encoding.Default.GetBytes(str);
@@ -47,6 +52,11 @@ namespace LeafWeb.Core.Utility
return Regex.Replace(input, @"[^\w_]", ""); return Regex.Replace(input, @"[^\w_]", "");
} }
public static string FilterValidFilename(this string input)
{
return Regex.Replace(input, @"[^\w_\-\.]", "");
}
public static string FilenameFromPath(this string path) public static string FilenameFromPath(this string path)
{ {
return Regex.Replace(path, @".*/([^/]*$)", "$1"); return Regex.Replace(path, @".*/([^/]*$)", "$1");
+1 -1
View File
@@ -17,7 +17,7 @@
<rules> <rules>
<logger name="Hangfire.*" minlevel="Debug" maxlevel="Info" final="true"/> <logger name="Hangfire.*" minlevel="Debug" maxlevel="Info" final="true"/>
<logger name="*" minlevel="Trace" writeTo="debugLogger"/> <logger name="*" minlevel="Debug" writeTo="debugLogger"/>
<logger name="*" minlevel="Error" writeTo="exceptionLogger" /> <logger name="*" minlevel="Error" writeTo="exceptionLogger" />
</rules> </rules>
</nlog> </nlog>
+93
View File
@@ -0,0 +1,93 @@
using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net.Mail;
using LeafWeb.Core.DAL;
using LeafWeb.Core.Entities;
using NLog;
namespace LeafWeb.Web.Services
{
public class EmailNotificationService : IDisposable
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly string _emaialFromAddress;
private readonly SmtpClient _smtpClient;
private readonly DataService _dataService;
public EmailNotificationService(DataService dataService)
{
_dataService = dataService;
_smtpClient = new SmtpClient();
_emaialFromAddress = ConfigurationManager.AppSettings["EmailFromAddress"];
}
public EmailNotificationService() : this(new DataService())
{ }
public void SendLeafWebError(int leafWebFileId, string errorMessage)
{
var file = _dataService.GetLeafInputFile(leafWebFileId);
var body = $"Your LeafWeb analysis job, {file.LeafInput.Identifier} (with filename {file.Filename}), encountered the following errors." + Environment.NewLine
+ "You will need to correct your input and resubmit." + Environment.NewLine + Environment.NewLine
+ errorMessage;
var message = new MailMessage(_emaialFromAddress, file.LeafInput.Email, "LeafWeb processing error", body);
SendMessage(message);
}
public void SendLeafWebSuccess(int leafWebFileId)
{
var file = _dataService.GetLeafInputFile(leafWebFileId);
var body = $"Your LeafWeb analysis job, {file.LeafInput.Identifier} (with filename {file.Filename}), has completed." + Environment.NewLine;
var message = new MailMessage(_emaialFromAddress, file.LeafInput.Email, "LeafWeb results", body);
var fileStreams =
(from outputFile in
file.LeafOutputFiles
select Tuple.Create(outputFile, new MemoryStream(outputFile.Contents))).ToList();
try
{
foreach (var fileStream in fileStreams)
{
var attachment = new Attachment(fileStream.Item2, fileStream.Item1.Filename);
message.Attachments.Add(attachment);
}
SendMessage(message);
}
finally
{
// can't dispose those memory streams until the message is sent
foreach (var stream in fileStreams.Select(f => f.Item2))
{
stream.Dispose();
}
}
}
private void SendMessage(MailMessage mailMessage)
{
try
{
Logger.Debug("Email sending to " + mailMessage.To + ", subject: " + mailMessage.Subject);
_smtpClient.Send(mailMessage);
}
catch (SmtpException ex)
{
Logger.Error(ex, "Failed to send mail: {0}", ex.Message);
}
}
public void Dispose()
{
_dataService.Dispose();
}
}
}
+13 -2
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Hangfire;
using LeafWeb.Core.DAL; using LeafWeb.Core.DAL;
using LeafWeb.Core.Entities; using LeafWeb.Core.Entities;
using LeafWeb.Core.Remote; using LeafWeb.Core.Remote;
@@ -12,14 +13,16 @@ namespace LeafWeb.Web.Services
{ {
private readonly DataService _dataService; private readonly DataService _dataService;
private readonly PiscalService _piscalService; private readonly PiscalService _piscalService;
private readonly EmailNotificationService _emailService;
public PiscalQueueManager(DataService dataService, PiscalService piscalService) public PiscalQueueManager(DataService dataService, PiscalService piscalService, EmailNotificationService emailService)
{ {
_dataService = dataService; _dataService = dataService;
_piscalService = piscalService; _piscalService = piscalService;
_emailService = emailService;
} }
public PiscalQueueManager() : this(new DataService(), new PiscalService()) {} public PiscalQueueManager() : this(new DataService(), new PiscalService(), new EmailNotificationService()) {}
private static readonly object ProcessQueueLock = new object(); private static readonly object ProcessQueueLock = new object();
@@ -109,6 +112,8 @@ namespace LeafWeb.Web.Services
// update db // update db
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete); _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete);
BackgroundJob.Enqueue(() => _emailService.SendLeafWebSuccess(file.Id));
// remove working data from the server // remove working data from the server
logger.Info("LeafInputFile: {0}, Cleanup", file.Id); logger.Info("LeafInputFile: {0}, Cleanup", file.Id);
_piscalService.Cleanup(file); _piscalService.Cleanup(file);
@@ -127,6 +132,12 @@ namespace LeafWeb.Web.Services
logger.Info("LeafInputFile: {0}, Error Message: {1}", file.Id, errorMessage); logger.Info("LeafInputFile: {0}, Error Message: {1}", file.Id, errorMessage);
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, errorMessage); _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, errorMessage);
BackgroundJob.Enqueue(() => _emailService.SendLeafWebError(file.Id, errorMessage));
// remove working data from the server
logger.Info("LeafInputFile: {0}, Cleanup", file.Id);
_piscalService.Cleanup(file);
break; break;
} }
} }
+10 -2
View File
@@ -22,8 +22,16 @@
<add key="webpages:Enabled" value="false" /> <add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" /> <add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="TimeZoneId" value="Pacific Standard Time" /> <add key="TimeZoneId" value="US Eastern Standard Time" />
</appSettings> <add key="EmailFromAddress" value="LeafWeb &lt;noreply@leafweb.org&gt;" />
</appSettings>
<system.net>
<mailSettings>
<smtp deliveryMethod="Network" from="LeafWeb &lt;noreply@leafweb.org&gt;">
<network host="localhost" />
</smtp>
</mailSettings>
</system.net>
<system.web> <system.web>
<compilation debug="true" targetFramework="4.5" /> <compilation debug="true" targetFramework="4.5" />
<!-- max 1GB --> <!-- max 1GB -->
+1
View File
@@ -933,6 +933,7 @@
</Compile> </Compile>
<Compile Include="Attributes\HttpParamActionAttribute.cs" /> <Compile Include="Attributes\HttpParamActionAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\EmailNotificationService.cs" />
<Compile Include="Services\PiscalQueueManager.cs" /> <Compile Include="Services\PiscalQueueManager.cs" />
<Compile Include="Services\PiscalService.cs" /> <Compile Include="Services\PiscalService.cs" />
<Compile Include="HangfireStartup.cs" /> <Compile Include="HangfireStartup.cs" />