Email notification for LeafWeb jobs
This commit is contained in:
@@ -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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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 <noreply@leafweb.org>" />
|
||||||
|
</appSettings>
|
||||||
|
<system.net>
|
||||||
|
<mailSettings>
|
||||||
|
<smtp deliveryMethod="Network" from="LeafWeb <noreply@leafweb.org>">
|
||||||
|
<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 -->
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user